diff --git a/markdown/bitburner.corporation.getunlockupgradecost.md b/markdown/bitburner.corporation.getunlockcost.md similarity index 65% rename from markdown/bitburner.corporation.getunlockupgradecost.md rename to markdown/bitburner.corporation.getunlockcost.md index 4f838cf7f..d879909b0 100644 --- a/markdown/bitburner.corporation.getunlockupgradecost.md +++ b/markdown/bitburner.corporation.getunlockcost.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Corporation](./bitburner.corporation.md) > [getUnlockUpgradeCost](./bitburner.corporation.getunlockupgradecost.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Corporation](./bitburner.corporation.md) > [getUnlockCost](./bitburner.corporation.getunlockcost.md) -## Corporation.getUnlockUpgradeCost() method +## Corporation.getUnlockCost() method Gets the cost to unlock a one time unlockable upgrade **Signature:** ```typescript -getUnlockUpgradeCost(upgradeName: string): number; +getUnlockCost(upgradeName: string): number; ``` ## Parameters diff --git a/markdown/bitburner.corporation.hasunlockupgrade.md b/markdown/bitburner.corporation.hasunlock.md similarity index 68% rename from markdown/bitburner.corporation.hasunlockupgrade.md rename to markdown/bitburner.corporation.hasunlock.md index b13eeb0b6..ab36b7588 100644 --- a/markdown/bitburner.corporation.hasunlockupgrade.md +++ b/markdown/bitburner.corporation.hasunlock.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Corporation](./bitburner.corporation.md) > [hasUnlockUpgrade](./bitburner.corporation.hasunlockupgrade.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Corporation](./bitburner.corporation.md) > [hasUnlock](./bitburner.corporation.hasunlock.md) -## Corporation.hasUnlockUpgrade() method +## Corporation.hasUnlock() method Check if you have a one time unlockable upgrade **Signature:** ```typescript -hasUnlockUpgrade(upgradeName: string): boolean; +hasUnlock(upgradeName: string): boolean; ``` ## Parameters diff --git a/markdown/bitburner.corporation.md b/markdown/bitburner.corporation.md index 5910d429a..42afe0a27 100644 --- a/markdown/bitburner.corporation.md +++ b/markdown/bitburner.corporation.md @@ -30,15 +30,15 @@ export interface Corporation extends WarehouseAPI, OfficeAPI | [getIndustryData(industryName)](./bitburner.corporation.getindustrydata.md) | Get constant industry definition data for a specific industry | | [getInvestmentOffer()](./bitburner.corporation.getinvestmentoffer.md) | Get an offer for investment based on you companies current valuation | | [getMaterialData(materialName)](./bitburner.corporation.getmaterialdata.md) | Get constant data for a specific material | -| [getUnlockUpgradeCost(upgradeName)](./bitburner.corporation.getunlockupgradecost.md) | Gets the cost to unlock a one time unlockable upgrade | +| [getUnlockCost(upgradeName)](./bitburner.corporation.getunlockcost.md) | Gets the cost to unlock a one time unlockable upgrade | | [getUpgradeLevel(upgradeName)](./bitburner.corporation.getupgradelevel.md) | Get the level of a levelable upgrade | | [getUpgradeLevelCost(upgradeName)](./bitburner.corporation.getupgradelevelcost.md) | Gets the cost to unlock the next level of a levelable upgrade | | [goPublic(numShares)](./bitburner.corporation.gopublic.md) | Go public | | [hasCorporation()](./bitburner.corporation.hascorporation.md) | Returns whether the player has a corporation. Does not require API access. | -| [hasUnlockUpgrade(upgradeName)](./bitburner.corporation.hasunlockupgrade.md) | Check if you have a one time unlockable upgrade | +| [hasUnlock(upgradeName)](./bitburner.corporation.hasunlock.md) | Check if you have a one time unlockable upgrade | | [issueDividends(rate)](./bitburner.corporation.issuedividends.md) | Issue dividends | | [issueNewShares(amount)](./bitburner.corporation.issuenewshares.md) | Issue new shares | | [levelUpgrade(upgradeName)](./bitburner.corporation.levelupgrade.md) | Level an upgrade. | +| [purchaseUnlock(upgradeName)](./bitburner.corporation.purchaseunlock.md) | Unlock an upgrade | | [sellShares(amount)](./bitburner.corporation.sellshares.md) | Sell Shares | -| [unlockUpgrade(upgradeName)](./bitburner.corporation.unlockupgrade.md) | Unlock an upgrade | diff --git a/markdown/bitburner.corporation.unlockupgrade.md b/markdown/bitburner.corporation.purchaseunlock.md similarity index 65% rename from markdown/bitburner.corporation.unlockupgrade.md rename to markdown/bitburner.corporation.purchaseunlock.md index 74a2448e3..d0a0c06b0 100644 --- a/markdown/bitburner.corporation.unlockupgrade.md +++ b/markdown/bitburner.corporation.purchaseunlock.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Corporation](./bitburner.corporation.md) > [unlockUpgrade](./bitburner.corporation.unlockupgrade.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Corporation](./bitburner.corporation.md) > [purchaseUnlock](./bitburner.corporation.purchaseunlock.md) -## Corporation.unlockUpgrade() method +## Corporation.purchaseUnlock() method Unlock an upgrade **Signature:** ```typescript -unlockUpgrade(upgradeName: string): void; +purchaseUnlock(upgradeName: string): void; ``` ## Parameters diff --git a/markdown/bitburner.corpresearchname.md b/markdown/bitburner.corpresearchname.md index 62bb801c7..63174ada8 100644 --- a/markdown/bitburner.corpresearchname.md +++ b/markdown/bitburner.corpresearchname.md @@ -13,7 +13,6 @@ type CorpResearchName = | "AutoBrew" | "AutoPartyManager" | "Automatic Drug Administration" - | "Bulk Purchasing" | "CPH4 Injections" | "Drones" | "Drones - Assembly" @@ -21,7 +20,6 @@ type CorpResearchName = | "Go-Juice" | "HRBuddy-Recruitment" | "HRBuddy-Training" - | "JoyWire" | "Market-TA.I" | "Market-TA.II" | "Overclock" diff --git a/markdown/bitburner.division.md b/markdown/bitburner.division.md index 53ccc1796..d2548e1fd 100644 --- a/markdown/bitburner.division.md +++ b/markdown/bitburner.division.md @@ -22,12 +22,12 @@ interface Division | [lastCycleRevenue](./bitburner.division.lastcyclerevenue.md) | | number | Revenue last cycle | | [makesProducts](./bitburner.division.makesproducts.md) | | boolean | Whether the industry this division is in is capable of making products | | [name](./bitburner.division.name.md) | | string | Name of the division | +| [numAdVerts](./bitburner.division.numadverts.md) | | number | Number of times AdVert has been bought | | [popularity](./bitburner.division.popularity.md) | | number | Popularity of the division | -| [prodMult](./bitburner.division.prodmult.md) | | number | Production multiplier | -| [products](./bitburner.division.products.md) | | string\[\] | Products developed by this division | -| [research](./bitburner.division.research.md) | | number | Amount of research in that division | +| [productionMult](./bitburner.division.productionmult.md) | | number | Production multiplier | +| [products](./bitburner.division.products.md) | | string\[\] | Names of Products developed by this division | +| [researchPoints](./bitburner.division.researchpoints.md) | | number | Amount of research in that division | | [thisCycleExpenses](./bitburner.division.thiscycleexpenses.md) | | number | Expenses this cycle | | [thisCycleRevenue](./bitburner.division.thiscyclerevenue.md) | | number | Revenue this cycle | | [type](./bitburner.division.type.md) | | [CorpIndustryName](./bitburner.corpindustryname.md) | Type of division, like Agriculture | -| [upgrades](./bitburner.division.upgrades.md) | | number\[\] | All research bought | diff --git a/markdown/bitburner.division.upgrades.md b/markdown/bitburner.division.numadverts.md similarity index 51% rename from markdown/bitburner.division.upgrades.md rename to markdown/bitburner.division.numadverts.md index 7a823cc94..57fc92c63 100644 --- a/markdown/bitburner.division.upgrades.md +++ b/markdown/bitburner.division.numadverts.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Division](./bitburner.division.md) > [upgrades](./bitburner.division.upgrades.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Division](./bitburner.division.md) > [numAdVerts](./bitburner.division.numadverts.md) -## Division.upgrades property +## Division.numAdVerts property -All research bought +Number of times AdVert has been bought **Signature:** ```typescript -upgrades: number[]; +numAdVerts: number; ``` diff --git a/markdown/bitburner.division.prodmult.md b/markdown/bitburner.division.productionmult.md similarity index 57% rename from markdown/bitburner.division.prodmult.md rename to markdown/bitburner.division.productionmult.md index 23f284fe6..c837666fa 100644 --- a/markdown/bitburner.division.prodmult.md +++ b/markdown/bitburner.division.productionmult.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Division](./bitburner.division.md) > [prodMult](./bitburner.division.prodmult.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Division](./bitburner.division.md) > [productionMult](./bitburner.division.productionmult.md) -## Division.prodMult property +## Division.productionMult property Production multiplier **Signature:** ```typescript -prodMult: number; +productionMult: number; ``` diff --git a/markdown/bitburner.division.products.md b/markdown/bitburner.division.products.md index a8e1e9b4a..6b9bcbfcf 100644 --- a/markdown/bitburner.division.products.md +++ b/markdown/bitburner.division.products.md @@ -4,7 +4,7 @@ ## Division.products property -Products developed by this division +Names of Products developed by this division **Signature:** diff --git a/markdown/bitburner.division.research.md b/markdown/bitburner.division.researchpoints.md similarity index 59% rename from markdown/bitburner.division.research.md rename to markdown/bitburner.division.researchpoints.md index 63012a4c0..bc270506b 100644 --- a/markdown/bitburner.division.research.md +++ b/markdown/bitburner.division.researchpoints.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Division](./bitburner.division.md) > [research](./bitburner.division.research.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Division](./bitburner.division.md) > [researchPoints](./bitburner.division.researchpoints.md) -## Division.research property +## Division.researchPoints property Amount of research in that division **Signature:** ```typescript -research: number; +researchPoints: number; ``` diff --git a/markdown/bitburner.export.amt.md b/markdown/bitburner.export.amount.md similarity index 65% rename from markdown/bitburner.export.amt.md rename to markdown/bitburner.export.amount.md index 6eef33c4c..df4cd1a6e 100644 --- a/markdown/bitburner.export.amt.md +++ b/markdown/bitburner.export.amount.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Export](./bitburner.export.md) > [amt](./bitburner.export.amt.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Export](./bitburner.export.md) > [amount](./bitburner.export.amount.md) -## Export.amt property +## Export.amount property Amount of material exported **Signature:** ```typescript -amt: string; +amount: string; ``` diff --git a/markdown/bitburner.export.loc.md b/markdown/bitburner.export.city.md similarity index 67% rename from markdown/bitburner.export.loc.md rename to markdown/bitburner.export.city.md index 204c9fcdc..c6311d07e 100644 --- a/markdown/bitburner.export.loc.md +++ b/markdown/bitburner.export.city.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Export](./bitburner.export.md) > [loc](./bitburner.export.loc.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Export](./bitburner.export.md) > [city](./bitburner.export.city.md) -## Export.loc property +## Export.city property City the material is being exported to **Signature:** ```typescript -loc: CityName; +city: CityName; ``` diff --git a/markdown/bitburner.export.div.md b/markdown/bitburner.export.division.md similarity index 65% rename from markdown/bitburner.export.div.md rename to markdown/bitburner.export.division.md index e869705fa..18925b26d 100644 --- a/markdown/bitburner.export.div.md +++ b/markdown/bitburner.export.division.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Export](./bitburner.export.md) > [div](./bitburner.export.div.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Export](./bitburner.export.md) > [division](./bitburner.export.division.md) -## Export.div property +## Export.division property Division the material is being exported to **Signature:** ```typescript -div: string; +division: string; ``` diff --git a/markdown/bitburner.export.md b/markdown/bitburner.export.md index 274594fb3..533ba40fc 100644 --- a/markdown/bitburner.export.md +++ b/markdown/bitburner.export.md @@ -16,7 +16,7 @@ interface Export | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [amt](./bitburner.export.amt.md) | | string | Amount of material exported | -| [div](./bitburner.export.div.md) | | string | Division the material is being exported to | -| [loc](./bitburner.export.loc.md) | | [CityName](./bitburner.cityname.md) | City the material is being exported to | +| [amount](./bitburner.export.amount.md) | | string | Amount of material exported | +| [city](./bitburner.export.city.md) | | [CityName](./bitburner.cityname.md) | City the material is being exported to | +| [division](./bitburner.export.division.md) | | string | Division the material is being exported to | diff --git a/markdown/bitburner.industrydata.cost.md b/markdown/bitburner.industrydata.cost.md index 206b08947..d4ae54071 100644 --- a/markdown/bitburner.industrydata.cost.md +++ b/markdown/bitburner.industrydata.cost.md @@ -4,7 +4,7 @@ ## IndustryData.cost property -Cost to expand to the division +Cost to make a new division of this industry type **Signature:** diff --git a/markdown/bitburner.industrydata.md b/markdown/bitburner.industrydata.md index d6046cc65..caa0ced08 100644 --- a/markdown/bitburner.industrydata.md +++ b/markdown/bitburner.industrydata.md @@ -16,7 +16,7 @@ interface IndustryData | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [cost](./bitburner.industrydata.cost.md) | | number | Cost to expand to the division | +| [cost](./bitburner.industrydata.cost.md) | | number | Cost to make a new division of this industry type | | [makesMaterials](./bitburner.industrydata.makesmaterials.md) | | boolean | Whether the division makes materials | | [makesProducts](./bitburner.industrydata.makesproducts.md) | | boolean | Whether the division makes products | | [producedMaterials?](./bitburner.industrydata.producedmaterials.md) | | string\[\] | _(Optional)_ Materials produced | diff --git a/markdown/bitburner.material.actualsellamount.md b/markdown/bitburner.material.actualsellamount.md new file mode 100644 index 000000000..4776d2a44 --- /dev/null +++ b/markdown/bitburner.material.actualsellamount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [actualSellAmount](./bitburner.material.actualsellamount.md) + +## Material.actualSellAmount property + +Amount of material sold last cycle + +**Signature:** + +```typescript +actualSellAmount: number; +``` diff --git a/markdown/bitburner.material.cmp.md b/markdown/bitburner.material.competition.md similarity index 64% rename from markdown/bitburner.material.cmp.md rename to markdown/bitburner.material.competition.md index 1175f7e19..6d60525bc 100644 --- a/markdown/bitburner.material.cmp.md +++ b/markdown/bitburner.material.competition.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [cmp](./bitburner.material.cmp.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [competition](./bitburner.material.competition.md) -## Material.cmp property +## Material.competition property Competition for the material, only present if "Market Research - Competition" unlocked **Signature:** ```typescript -cmp: number | undefined; +competition: number | undefined; ``` diff --git a/markdown/bitburner.material.dmd.md b/markdown/bitburner.material.demand.md similarity index 66% rename from markdown/bitburner.material.dmd.md rename to markdown/bitburner.material.demand.md index 770022ccb..4ffdb9cfc 100644 --- a/markdown/bitburner.material.dmd.md +++ b/markdown/bitburner.material.demand.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [dmd](./bitburner.material.dmd.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [demand](./bitburner.material.demand.md) -## Material.dmd property +## Material.demand property Demand for the material, only present if "Market Research - Demand" unlocked **Signature:** ```typescript -dmd: number | undefined; +demand: number | undefined; ``` diff --git a/markdown/bitburner.material.sell.md b/markdown/bitburner.material.desiredsellamount.md similarity index 57% rename from markdown/bitburner.material.sell.md rename to markdown/bitburner.material.desiredsellamount.md index 6c4b2042c..f9d010b10 100644 --- a/markdown/bitburner.material.sell.md +++ b/markdown/bitburner.material.desiredsellamount.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [sell](./bitburner.material.sell.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [desiredSellAmount](./bitburner.material.desiredsellamount.md) -## Material.sell property +## Material.desiredSellAmount property -Amount of material sold +Sell amount, can be "PROD/2" **Signature:** ```typescript -sell: number; +desiredSellAmount: string | number; ``` diff --git a/markdown/bitburner.material.scost.md b/markdown/bitburner.material.desiredsellprice.md similarity index 55% rename from markdown/bitburner.material.scost.md rename to markdown/bitburner.material.desiredsellprice.md index 0704f080c..fb7c01693 100644 --- a/markdown/bitburner.material.scost.md +++ b/markdown/bitburner.material.desiredsellprice.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [sCost](./bitburner.material.scost.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [desiredSellPrice](./bitburner.material.desiredsellprice.md) -## Material.sCost property +## Material.desiredSellPrice property Sell cost, can be "MP+5" **Signature:** ```typescript -sCost: string | number; +desiredSellPrice: string | number; ``` diff --git a/markdown/bitburner.material.exp.md b/markdown/bitburner.material.exports.md similarity index 61% rename from markdown/bitburner.material.exp.md rename to markdown/bitburner.material.exports.md index ec26b13c7..3b851a398 100644 --- a/markdown/bitburner.material.exp.md +++ b/markdown/bitburner.material.exports.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [exp](./bitburner.material.exp.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [exports](./bitburner.material.exports.md) -## Material.exp property +## Material.exports property Export orders **Signature:** ```typescript -exp: Export[]; +exports: Export[]; ``` diff --git a/markdown/bitburner.material.cost.md b/markdown/bitburner.material.marketprice.md similarity index 59% rename from markdown/bitburner.material.cost.md rename to markdown/bitburner.material.marketprice.md index 347cd5012..38ab92430 100644 --- a/markdown/bitburner.material.cost.md +++ b/markdown/bitburner.material.marketprice.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [cost](./bitburner.material.cost.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [marketPrice](./bitburner.material.marketprice.md) -## Material.cost property +## Material.marketPrice property Cost to buy material **Signature:** ```typescript -cost: number; +marketPrice: number; ``` diff --git a/markdown/bitburner.material.md b/markdown/bitburner.material.md index 427144b6a..7ac073f69 100644 --- a/markdown/bitburner.material.md +++ b/markdown/bitburner.material.md @@ -16,15 +16,15 @@ interface Material | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [cmp](./bitburner.material.cmp.md) | | number \| undefined | Competition for the material, only present if "Market Research - Competition" unlocked | -| [cost](./bitburner.material.cost.md) | | number | Cost to buy material | -| [dmd](./bitburner.material.dmd.md) | | number \| undefined | Demand for the material, only present if "Market Research - Demand" unlocked | -| [exp](./bitburner.material.exp.md) | | [Export](./bitburner.export.md)\[\] | Export orders | +| [actualSellAmount](./bitburner.material.actualsellamount.md) | | number | Amount of material sold last cycle | +| [competition](./bitburner.material.competition.md) | | number \| undefined | Competition for the material, only present if "Market Research - Competition" unlocked | +| [demand](./bitburner.material.demand.md) | | number \| undefined | Demand for the material, only present if "Market Research - Demand" unlocked | +| [desiredSellAmount](./bitburner.material.desiredsellamount.md) | | string \| number | Sell amount, can be "PROD/2" | +| [desiredSellPrice](./bitburner.material.desiredsellprice.md) | | string \| number | Sell cost, can be "MP+5" | +| [exports](./bitburner.material.exports.md) | | [Export](./bitburner.export.md)\[\] | Export orders | +| [marketPrice](./bitburner.material.marketprice.md) | | number | Cost to buy material | | [name](./bitburner.material.name.md) | | [CorpMaterialName](./bitburner.corpmaterialname.md) | Name of the material | -| [prod](./bitburner.material.prod.md) | | number | Amount of material produced | -| [qlt](./bitburner.material.qlt.md) | | number | Quality of the material | -| [qty](./bitburner.material.qty.md) | | number | Amount of material | -| [sAmt](./bitburner.material.samt.md) | | string \| number | Sell amount, can be "PROD/2" | -| [sCost](./bitburner.material.scost.md) | | string \| number | Sell cost, can be "MP+5" | -| [sell](./bitburner.material.sell.md) | | number | Amount of material sold | +| [productionAmount](./bitburner.material.productionamount.md) | | number | Amount of material produced last cycle | +| [quality](./bitburner.material.quality.md) | | number | Quality of the material | +| [stored](./bitburner.material.stored.md) | | number | Amount of material | diff --git a/markdown/bitburner.material.prod.md b/markdown/bitburner.material.prod.md deleted file mode 100644 index 6f8970953..000000000 --- a/markdown/bitburner.material.prod.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [prod](./bitburner.material.prod.md) - -## Material.prod property - -Amount of material produced - -**Signature:** - -```typescript -prod: number; -``` diff --git a/markdown/bitburner.material.productionamount.md b/markdown/bitburner.material.productionamount.md new file mode 100644 index 000000000..cb8aa01c3 --- /dev/null +++ b/markdown/bitburner.material.productionamount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [productionAmount](./bitburner.material.productionamount.md) + +## Material.productionAmount property + +Amount of material produced last cycle + +**Signature:** + +```typescript +productionAmount: number; +``` diff --git a/markdown/bitburner.material.qlt.md b/markdown/bitburner.material.quality.md similarity index 62% rename from markdown/bitburner.material.qlt.md rename to markdown/bitburner.material.quality.md index 4b9d7bf08..e3fefa430 100644 --- a/markdown/bitburner.material.qlt.md +++ b/markdown/bitburner.material.quality.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [qlt](./bitburner.material.qlt.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [quality](./bitburner.material.quality.md) -## Material.qlt property +## Material.quality property Quality of the material **Signature:** ```typescript -qlt: number; +quality: number; ``` diff --git a/markdown/bitburner.material.samt.md b/markdown/bitburner.material.samt.md deleted file mode 100644 index c34a1ea15..000000000 --- a/markdown/bitburner.material.samt.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [sAmt](./bitburner.material.samt.md) - -## Material.sAmt property - -Sell amount, can be "PROD/2" - -**Signature:** - -```typescript -sAmt: string | number; -``` diff --git a/markdown/bitburner.material.qty.md b/markdown/bitburner.material.stored.md similarity index 62% rename from markdown/bitburner.material.qty.md rename to markdown/bitburner.material.stored.md index b513825ba..c18a1f012 100644 --- a/markdown/bitburner.material.qty.md +++ b/markdown/bitburner.material.stored.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [qty](./bitburner.material.qty.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Material](./bitburner.material.md) > [stored](./bitburner.material.stored.md) -## Material.qty property +## Material.stored property Amount of material **Signature:** ```typescript -qty: number; +stored: number; ``` diff --git a/markdown/bitburner.office.avgene.md b/markdown/bitburner.office.avgenergy.md similarity index 63% rename from markdown/bitburner.office.avgene.md rename to markdown/bitburner.office.avgenergy.md index 0ad56b7d6..a53684692 100644 --- a/markdown/bitburner.office.avgene.md +++ b/markdown/bitburner.office.avgenergy.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [avgEne](./bitburner.office.avgene.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [avgEnergy](./bitburner.office.avgenergy.md) -## Office.avgEne property +## Office.avgEnergy property Average energy of the employees **Signature:** ```typescript -avgEne: number; +avgEnergy: number; ``` diff --git a/markdown/bitburner.office.avgmor.md b/markdown/bitburner.office.avgmorale.md similarity index 63% rename from markdown/bitburner.office.avgmor.md rename to markdown/bitburner.office.avgmorale.md index 0b0c1bf44..801f6b1a8 100644 --- a/markdown/bitburner.office.avgmor.md +++ b/markdown/bitburner.office.avgmorale.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [avgMor](./bitburner.office.avgmor.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [avgMorale](./bitburner.office.avgmorale.md) -## Office.avgMor property +## Office.avgMorale property Average morale of the employees **Signature:** ```typescript -avgMor: number; +avgMorale: number; ``` diff --git a/markdown/bitburner.office.loc.md b/markdown/bitburner.office.city.md similarity index 65% rename from markdown/bitburner.office.loc.md rename to markdown/bitburner.office.city.md index 5002b6357..ab616c1dd 100644 --- a/markdown/bitburner.office.loc.md +++ b/markdown/bitburner.office.city.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [loc](./bitburner.office.loc.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [city](./bitburner.office.city.md) -## Office.loc property +## Office.city property City of the office **Signature:** ```typescript -loc: CityName; +city: CityName; ``` diff --git a/markdown/bitburner.office.employeeprod.md b/markdown/bitburner.office.employeeproductionbyjob.md similarity index 50% rename from markdown/bitburner.office.employeeprod.md rename to markdown/bitburner.office.employeeproductionbyjob.md index 9d30748ab..4ddaaca42 100644 --- a/markdown/bitburner.office.employeeprod.md +++ b/markdown/bitburner.office.employeeproductionbyjob.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [employeeProd](./bitburner.office.employeeprod.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [employeeProductionByJob](./bitburner.office.employeeproductionbyjob.md) -## Office.employeeProd property +## Office.employeeProductionByJob property Production of the employees **Signature:** ```typescript -employeeProd: Record; +employeeProductionByJob: Record; ``` diff --git a/markdown/bitburner.office.maxene.md b/markdown/bitburner.office.maxenergy.md similarity index 64% rename from markdown/bitburner.office.maxene.md rename to markdown/bitburner.office.maxenergy.md index 081ad48a2..ddcb2d488 100644 --- a/markdown/bitburner.office.maxene.md +++ b/markdown/bitburner.office.maxenergy.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [maxEne](./bitburner.office.maxene.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [maxEnergy](./bitburner.office.maxenergy.md) -## Office.maxEne property +## Office.maxEnergy property Maximum amount of energy of the employees **Signature:** ```typescript -maxEne: number; +maxEnergy: number; ``` diff --git a/markdown/bitburner.office.maxmor.md b/markdown/bitburner.office.maxmorale.md similarity index 63% rename from markdown/bitburner.office.maxmor.md rename to markdown/bitburner.office.maxmorale.md index 423a204ae..9108cb490 100644 --- a/markdown/bitburner.office.maxmor.md +++ b/markdown/bitburner.office.maxmorale.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [maxMor](./bitburner.office.maxmor.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [maxMorale](./bitburner.office.maxmorale.md) -## Office.maxMor property +## Office.maxMorale property Maximum morale of the employees **Signature:** ```typescript -maxMor: number; +maxMorale: number; ``` diff --git a/markdown/bitburner.office.md b/markdown/bitburner.office.md index bbcb4d16e..264166b37 100644 --- a/markdown/bitburner.office.md +++ b/markdown/bitburner.office.md @@ -16,14 +16,14 @@ export interface Office | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [avgEne](./bitburner.office.avgene.md) | | number | Average energy of the employees | -| [avgMor](./bitburner.office.avgmor.md) | | number | Average morale of the employees | +| [avgEnergy](./bitburner.office.avgenergy.md) | | number | Average energy of the employees | +| [avgMorale](./bitburner.office.avgmorale.md) | | number | Average morale of the employees | +| [city](./bitburner.office.city.md) | | [CityName](./bitburner.cityname.md) | City of the office | | [employeeJobs](./bitburner.office.employeejobs.md) | | Record<[CorpEmployeePosition](./bitburner.corpemployeeposition.md), number> | Positions of the employees | -| [employeeProd](./bitburner.office.employeeprod.md) | | Record<[CorpEmployeePosition](./bitburner.corpemployeeposition.md), number> | Production of the employees | -| [employees](./bitburner.office.employees.md) | | number | Amount of employees | -| [loc](./bitburner.office.loc.md) | | [CityName](./bitburner.cityname.md) | City of the office | -| [maxEne](./bitburner.office.maxene.md) | | number | Maximum amount of energy of the employees | -| [maxMor](./bitburner.office.maxmor.md) | | number | Maximum morale of the employees | +| [employeeProductionByJob](./bitburner.office.employeeproductionbyjob.md) | | Record<[CorpEmployeePosition](./bitburner.corpemployeeposition.md), number> | Production of the employees | +| [maxEnergy](./bitburner.office.maxenergy.md) | | number | Maximum amount of energy of the employees | +| [maxMorale](./bitburner.office.maxmorale.md) | | number | Maximum morale of the employees | +| [numEmployees](./bitburner.office.numemployees.md) | | number | Amount of employees | | [size](./bitburner.office.size.md) | | number | Maximum number of employee | | [totalExperience](./bitburner.office.totalexperience.md) | | number | Total experience of all employees | diff --git a/markdown/bitburner.office.employees.md b/markdown/bitburner.office.numemployees.md similarity index 60% rename from markdown/bitburner.office.employees.md rename to markdown/bitburner.office.numemployees.md index 28fe01312..e0ef90172 100644 --- a/markdown/bitburner.office.employees.md +++ b/markdown/bitburner.office.numemployees.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [employees](./bitburner.office.employees.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Office](./bitburner.office.md) > [numEmployees](./bitburner.office.numemployees.md) -## Office.employees property +## Office.numEmployees property Amount of employees **Signature:** ```typescript -employees: number; +numEmployees: number; ``` diff --git a/markdown/bitburner.product.actualsellamount.md b/markdown/bitburner.product.actualsellamount.md new file mode 100644 index 000000000..388974aea --- /dev/null +++ b/markdown/bitburner.product.actualsellamount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [actualSellAmount](./bitburner.product.actualsellamount.md) + +## Product.actualSellAmount property + +Amount of product sold last cycle + +**Signature:** + +```typescript +actualSellAmount: number; +``` diff --git a/markdown/bitburner.product.cmp.md b/markdown/bitburner.product.competition.md similarity index 64% rename from markdown/bitburner.product.cmp.md rename to markdown/bitburner.product.competition.md index 12cad48b0..de30e590b 100644 --- a/markdown/bitburner.product.cmp.md +++ b/markdown/bitburner.product.competition.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [cmp](./bitburner.product.cmp.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [competition](./bitburner.product.competition.md) -## Product.cmp property +## Product.competition property Competition for the product, only present if "Market Research - Competition" unlocked **Signature:** ```typescript -cmp: number | undefined; +competition: number | undefined; ``` diff --git a/markdown/bitburner.product.dmd.md b/markdown/bitburner.product.demand.md similarity index 67% rename from markdown/bitburner.product.dmd.md rename to markdown/bitburner.product.demand.md index 435553d48..ce141b718 100644 --- a/markdown/bitburner.product.dmd.md +++ b/markdown/bitburner.product.demand.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [dmd](./bitburner.product.dmd.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [demand](./bitburner.product.demand.md) -## Product.dmd property +## Product.demand property Demand for the product, only present if "Market Research - Demand" unlocked **Signature:** ```typescript -dmd: number | undefined; +demand: number | undefined; ``` diff --git a/markdown/bitburner.product.desiredsellamount.md b/markdown/bitburner.product.desiredsellamount.md new file mode 100644 index 000000000..ebc13f6f6 --- /dev/null +++ b/markdown/bitburner.product.desiredsellamount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [desiredSellAmount](./bitburner.product.desiredsellamount.md) + +## Product.desiredSellAmount property + +Desired sell amount, e.g. "PROD/2" + +**Signature:** + +```typescript +desiredSellAmount: string | number; +``` diff --git a/markdown/bitburner.product.desiredsellprice.md b/markdown/bitburner.product.desiredsellprice.md new file mode 100644 index 000000000..12134278d --- /dev/null +++ b/markdown/bitburner.product.desiredsellprice.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [desiredSellPrice](./bitburner.product.desiredsellprice.md) + +## Product.desiredSellPrice property + +Desired sell price, can be "MP+5" + +**Signature:** + +```typescript +desiredSellPrice: string | number; +``` diff --git a/markdown/bitburner.product.developmentprogress.md b/markdown/bitburner.product.developmentprogress.md index 77394b9ae..0c3d1d787 100644 --- a/markdown/bitburner.product.developmentprogress.md +++ b/markdown/bitburner.product.developmentprogress.md @@ -4,7 +4,7 @@ ## Product.developmentProgress property -Creation progress - A number between 0-100 representing percentage +A number between 0-100 representing percentage completion **Signature:** diff --git a/markdown/bitburner.product.effectiverating.md b/markdown/bitburner.product.effectiverating.md new file mode 100644 index 000000000..f019c90c0 --- /dev/null +++ b/markdown/bitburner.product.effectiverating.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [effectiveRating](./bitburner.product.effectiverating.md) + +## Product.effectiveRating property + +Effective rating in the specific city + +**Signature:** + +```typescript +effectiveRating: number; +``` diff --git a/markdown/bitburner.product.effrat.md b/markdown/bitburner.product.effrat.md deleted file mode 100644 index 8d93ddce2..000000000 --- a/markdown/bitburner.product.effrat.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [effRat](./bitburner.product.effrat.md) - -## Product.effRat property - -Effective rating - -**Signature:** - -```typescript -effRat: number; -``` diff --git a/markdown/bitburner.product.md b/markdown/bitburner.product.md index ea03553e3..ec619687a 100644 --- a/markdown/bitburner.product.md +++ b/markdown/bitburner.product.md @@ -16,17 +16,17 @@ interface Product | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [cmp](./bitburner.product.cmp.md) | | number \| undefined | Competition for the product, only present if "Market Research - Competition" unlocked | -| [developmentProgress](./bitburner.product.developmentprogress.md) | | number | Creation progress - A number between 0-100 representing percentage | -| [dmd](./bitburner.product.dmd.md) | | number \| undefined | Demand for the product, only present if "Market Research - Demand" unlocked | -| [effRat](./bitburner.product.effrat.md) | | number | Effective rating | +| [actualSellAmount](./bitburner.product.actualsellamount.md) | | number | Amount of product sold last cycle | +| [competition](./bitburner.product.competition.md) | | number \| undefined | Competition for the product, only present if "Market Research - Competition" unlocked | +| [demand](./bitburner.product.demand.md) | | number \| undefined | Demand for the product, only present if "Market Research - Demand" unlocked | +| [desiredSellAmount](./bitburner.product.desiredsellamount.md) | | string \| number | Desired sell amount, e.g. "PROD/2" | +| [desiredSellPrice](./bitburner.product.desiredsellprice.md) | | string \| number | Desired sell price, can be "MP+5" | +| [developmentProgress](./bitburner.product.developmentprogress.md) | | number | A number between 0-100 representing percentage completion | +| [effectiveRating](./bitburner.product.effectiverating.md) | | number | Effective rating in the specific city | | [name](./bitburner.product.name.md) | | string | Name of the product | -| [pCost](./bitburner.product.pcost.md) | | number | Production cost | -| [prod](./bitburner.product.prod.md) | | number | Amount of product produced | -| [properties](./bitburner.product.properties.md) | | { \[key: string\]: number } | Product Properties. The data is {qlt, per, dur, rel, aes, fea} | -| [qty](./bitburner.product.qty.md) | | number | Amount of product | -| [rat](./bitburner.product.rat.md) | | number | Product Rating | -| [sAmt](./bitburner.product.samt.md) | | string | Sell amount, can be "PROD/2" | -| [sCost](./bitburner.product.scost.md) | | string | Sell cost, can be "MP+5" | -| [sell](./bitburner.product.sell.md) | | number | Amount of product sold | +| [productionAmount](./bitburner.product.productionamount.md) | | number | Amount of product produced last cycle | +| [productionCost](./bitburner.product.productioncost.md) | | number | Production cost | +| [rating](./bitburner.product.rating.md) | | number | Rating based on stats | +| [stats](./bitburner.product.stats.md) | | { quality: number; performance: number; durability: number; reliability: number; aesthetics: number; features: number; } | Product stats | +| [stored](./bitburner.product.stored.md) | | number | Amount of product stored in warehouse | diff --git a/markdown/bitburner.product.productionamount.md b/markdown/bitburner.product.productionamount.md new file mode 100644 index 000000000..d4cf3b4b4 --- /dev/null +++ b/markdown/bitburner.product.productionamount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [productionAmount](./bitburner.product.productionamount.md) + +## Product.productionAmount property + +Amount of product produced last cycle + +**Signature:** + +```typescript +productionAmount: number; +``` diff --git a/markdown/bitburner.product.pcost.md b/markdown/bitburner.product.productioncost.md similarity index 57% rename from markdown/bitburner.product.pcost.md rename to markdown/bitburner.product.productioncost.md index ba9d50aa2..187b8cbe0 100644 --- a/markdown/bitburner.product.pcost.md +++ b/markdown/bitburner.product.productioncost.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [pCost](./bitburner.product.pcost.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [productionCost](./bitburner.product.productioncost.md) -## Product.pCost property +## Product.productionCost property Production cost **Signature:** ```typescript -pCost: number; +productionCost: number; ``` diff --git a/markdown/bitburner.product.properties.md b/markdown/bitburner.product.properties.md deleted file mode 100644 index 33a0853ab..000000000 --- a/markdown/bitburner.product.properties.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [properties](./bitburner.product.properties.md) - -## Product.properties property - -Product Properties. The data is {qlt, per, dur, rel, aes, fea} - -**Signature:** - -```typescript -properties: { [key: string]: number }; -``` diff --git a/markdown/bitburner.product.rat.md b/markdown/bitburner.product.rat.md deleted file mode 100644 index 20dd8d38f..000000000 --- a/markdown/bitburner.product.rat.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [rat](./bitburner.product.rat.md) - -## Product.rat property - -Product Rating - -**Signature:** - -```typescript -rat: number; -``` diff --git a/markdown/bitburner.product.prod.md b/markdown/bitburner.product.rating.md similarity index 57% rename from markdown/bitburner.product.prod.md rename to markdown/bitburner.product.rating.md index a389700b8..c299c5dd9 100644 --- a/markdown/bitburner.product.prod.md +++ b/markdown/bitburner.product.rating.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [prod](./bitburner.product.prod.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [rating](./bitburner.product.rating.md) -## Product.prod property +## Product.rating property -Amount of product produced +Rating based on stats **Signature:** ```typescript -prod: number; +rating: number; ``` diff --git a/markdown/bitburner.product.samt.md b/markdown/bitburner.product.samt.md deleted file mode 100644 index dd23a4b4f..000000000 --- a/markdown/bitburner.product.samt.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [sAmt](./bitburner.product.samt.md) - -## Product.sAmt property - -Sell amount, can be "PROD/2" - -**Signature:** - -```typescript -sAmt: string; -``` diff --git a/markdown/bitburner.product.scost.md b/markdown/bitburner.product.scost.md deleted file mode 100644 index 5e27849cb..000000000 --- a/markdown/bitburner.product.scost.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [sCost](./bitburner.product.scost.md) - -## Product.sCost property - -Sell cost, can be "MP+5" - -**Signature:** - -```typescript -sCost: string; -``` diff --git a/markdown/bitburner.product.sell.md b/markdown/bitburner.product.sell.md deleted file mode 100644 index 26311ba0e..000000000 --- a/markdown/bitburner.product.sell.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [sell](./bitburner.product.sell.md) - -## Product.sell property - -Amount of product sold - -**Signature:** - -```typescript -sell: number; -``` diff --git a/markdown/bitburner.product.stats.md b/markdown/bitburner.product.stats.md new file mode 100644 index 000000000..e9c485f62 --- /dev/null +++ b/markdown/bitburner.product.stats.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [stats](./bitburner.product.stats.md) + +## Product.stats property + +Product stats + +**Signature:** + +```typescript +stats: { + quality: number; + performance: number; + durability: number; + reliability: number; + aesthetics: number; + features: number; + }; +``` diff --git a/markdown/bitburner.product.qty.md b/markdown/bitburner.product.stored.md similarity index 54% rename from markdown/bitburner.product.qty.md rename to markdown/bitburner.product.stored.md index 7aa538e9d..a1b63d6e3 100644 --- a/markdown/bitburner.product.qty.md +++ b/markdown/bitburner.product.stored.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [qty](./bitburner.product.qty.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Product](./bitburner.product.md) > [stored](./bitburner.product.stored.md) -## Product.qty property +## Product.stored property -Amount of product +Amount of product stored in warehouse **Signature:** ```typescript -qty: number; +stored: number; ``` diff --git a/markdown/bitburner.warehouse.loc.md b/markdown/bitburner.warehouse.city.md similarity index 65% rename from markdown/bitburner.warehouse.loc.md rename to markdown/bitburner.warehouse.city.md index ebe775ab8..bd42bea33 100644 --- a/markdown/bitburner.warehouse.loc.md +++ b/markdown/bitburner.warehouse.city.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [bitburner](./bitburner.md) > [Warehouse](./bitburner.warehouse.md) > [loc](./bitburner.warehouse.loc.md) +[Home](./index.md) > [bitburner](./bitburner.md) > [Warehouse](./bitburner.warehouse.md) > [city](./bitburner.warehouse.city.md) -## Warehouse.loc property +## Warehouse.city property City in which the warehouse is located **Signature:** ```typescript -loc: CityName; +city: CityName; ``` diff --git a/markdown/bitburner.warehouse.md b/markdown/bitburner.warehouse.md index 21c56a306..b71067ca6 100644 --- a/markdown/bitburner.warehouse.md +++ b/markdown/bitburner.warehouse.md @@ -16,8 +16,8 @@ interface Warehouse | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [city](./bitburner.warehouse.city.md) | | [CityName](./bitburner.cityname.md) | City in which the warehouse is located | | [level](./bitburner.warehouse.level.md) | | number | Amount of size upgrade bought | -| [loc](./bitburner.warehouse.loc.md) | | [CityName](./bitburner.cityname.md) | City in which the warehouse is located | | [size](./bitburner.warehouse.size.md) | | number | Total space in the warehouse | | [sizeUsed](./bitburner.warehouse.sizeused.md) | | number | Used space in the warehouse | | [smartSupplyEnabled](./bitburner.warehouse.smartsupplyenabled.md) | | boolean | Smart Supply status in the warehouse | diff --git a/markdown/bitburner.warehouseapi.getproduct.md b/markdown/bitburner.warehouseapi.getproduct.md index 2c307c23b..a8cf56991 100644 --- a/markdown/bitburner.warehouseapi.getproduct.md +++ b/markdown/bitburner.warehouseapi.getproduct.md @@ -9,7 +9,7 @@ Get product data **Signature:** ```typescript -getProduct(divisionName: string, city: CityName | `${CityName}`, productName: string): Product; +getProduct(divisionName: string, cityName: CityName | `${CityName}`, productName: string): Product; ``` ## Parameters @@ -17,7 +17,7 @@ getProduct(divisionName: string, city: CityName | `${CityName}`, productName: st | Parameter | Type | Description | | --- | --- | --- | | divisionName | string | Name of the division | -| city | [CityName](./bitburner.cityname.md) \| \`${[CityName](./bitburner.cityname.md)}\` | Name of the city | +| cityName | [CityName](./bitburner.cityname.md) \| \`${[CityName](./bitburner.cityname.md)}\` | | | productName | string | Name of the product | **Returns:** diff --git a/markdown/bitburner.warehouseapi.md b/markdown/bitburner.warehouseapi.md index 6e23cb1f7..12c460505 100644 --- a/markdown/bitburner.warehouseapi.md +++ b/markdown/bitburner.warehouseapi.md @@ -26,7 +26,7 @@ Requires the Warehouse API upgrade from your corporation. | [discontinueProduct(divisionName, productName)](./bitburner.warehouseapi.discontinueproduct.md) | Discontinue a product. | | [exportMaterial(sourceDivision, sourceCity, targetDivision, targetCity, materialName, amt)](./bitburner.warehouseapi.exportmaterial.md) | Set material export data | | [getMaterial(divisionName, city, materialName)](./bitburner.warehouseapi.getmaterial.md) | Get material data | -| [getProduct(divisionName, city, productName)](./bitburner.warehouseapi.getproduct.md) | Get product data | +| [getProduct(divisionName, cityName, productName)](./bitburner.warehouseapi.getproduct.md) | Get product data | | [getUpgradeWarehouseCost(divisionName, city, amt)](./bitburner.warehouseapi.getupgradewarehousecost.md) | Gets the cost to upgrade a warehouse to the next level | | [getWarehouse(divisionName, city)](./bitburner.warehouseapi.getwarehouse.md) | Get warehouse data | | [hasWarehouse(divisionName, city)](./bitburner.warehouseapi.haswarehouse.md) | Check if you have a warehouse in city | @@ -38,8 +38,8 @@ Requires the Warehouse API upgrade from your corporation. | [sellProduct(divisionName, city, productName, amt, price, all)](./bitburner.warehouseapi.sellproduct.md) | Set product sell data. | | [setMaterialMarketTA1(divisionName, city, materialName, on)](./bitburner.warehouseapi.setmaterialmarketta1.md) | Set market TA 1 for a material. | | [setMaterialMarketTA2(divisionName, city, materialName, on)](./bitburner.warehouseapi.setmaterialmarketta2.md) | Set market TA 2 for a material. | -| [setProductMarketTA1(divisionName, city, productName, on)](./bitburner.warehouseapi.setproductmarketta1.md) | Set market TA 1 for a product. | -| [setProductMarketTA2(divisionName, city, productName, on)](./bitburner.warehouseapi.setproductmarketta2.md) | Set market TA 2 for a product. | +| [setProductMarketTA1(divisionName, productName, on)](./bitburner.warehouseapi.setproductmarketta1.md) | \* Set market TA 1 for a product. | +| [setProductMarketTA2(divisionName, productName, on)](./bitburner.warehouseapi.setproductmarketta2.md) | Set market TA 2 for a product. | | [setSmartSupply(divisionName, city, enabled)](./bitburner.warehouseapi.setsmartsupply.md) | Set smart supply | | [setSmartSupplyOption(divisionName, city, materialName, option)](./bitburner.warehouseapi.setsmartsupplyoption.md) | Set whether smart supply uses leftovers before buying | | [upgradeWarehouse(divisionName, city, amt)](./bitburner.warehouseapi.upgradewarehouse.md) | Upgrade warehouse | diff --git a/markdown/bitburner.warehouseapi.setproductmarketta1.md b/markdown/bitburner.warehouseapi.setproductmarketta1.md index 92cdcec07..e8cb53e50 100644 --- a/markdown/bitburner.warehouseapi.setproductmarketta1.md +++ b/markdown/bitburner.warehouseapi.setproductmarketta1.md @@ -4,12 +4,12 @@ ## WarehouseAPI.setProductMarketTA1() method -Set market TA 1 for a product. +\* Set market TA 1 for a product. **Signature:** ```typescript -setProductMarketTA1(divisionName: string, city: CityName | `${CityName}`, productName: string, on: boolean): void; +setProductMarketTA1(divisionName: string, productName: string, on: boolean): void; ``` ## Parameters @@ -17,7 +17,6 @@ setProductMarketTA1(divisionName: string, city: CityName | `${CityName}`, produc | Parameter | Type | Description | | --- | --- | --- | | divisionName | string | Name of the division | -| city | [CityName](./bitburner.cityname.md) \| \`${[CityName](./bitburner.cityname.md)}\` | Name of the city | | productName | string | Name of the product | | on | boolean | market ta enabled | diff --git a/markdown/bitburner.warehouseapi.setproductmarketta2.md b/markdown/bitburner.warehouseapi.setproductmarketta2.md index 0d10ee665..d2c7c07ea 100644 --- a/markdown/bitburner.warehouseapi.setproductmarketta2.md +++ b/markdown/bitburner.warehouseapi.setproductmarketta2.md @@ -9,7 +9,7 @@ Set market TA 2 for a product. **Signature:** ```typescript -setProductMarketTA2(divisionName: string, city: CityName | `${CityName}`, productName: string, on: boolean): void; +setProductMarketTA2(divisionName: string, productName: string, on: boolean): void; ``` ## Parameters @@ -17,7 +17,6 @@ setProductMarketTA2(divisionName: string, city: CityName | `${CityName}`, produc | Parameter | Type | Description | | --- | --- | --- | | divisionName | string | Name of the division | -| city | [CityName](./bitburner.cityname.md) \| \`${[CityName](./bitburner.cityname.md)}\` | Name of the city | | productName | string | Name of the product | | on | boolean | market ta enabled | diff --git a/src/Achievements/Achievements.ts b/src/Achievements/Achievements.ts index a114467b9..39e5af588 100644 --- a/src/Achievements/Achievements.ts +++ b/src/Achievements/Achievements.ts @@ -2,7 +2,7 @@ import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { SkillNames } from "../Bladeburner/data/SkillNames"; import { Skills } from "../Bladeburner/Skills"; import { CONSTANTS } from "../Constants"; -import { IndustryType } from "../Corporation/data/Enums"; +import { CorpUnlockName, IndustryType } from "../Corporation/data/Enums"; import { Exploit } from "../Exploits/Exploit"; import { Factions } from "../Faction/Factions"; import { AllGangs } from "../Gang/AllGangs"; @@ -27,6 +27,7 @@ import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { workerScripts } from "../Netscript/WorkerScripts"; import type { PlayerObject } from "../PersonObjects/Player/PlayerObject"; +import { getRecordValues } from "../Types/Record"; // Unable to correctly cast the JSON data into AchievementDataJson type otherwise... const achievementData = ((data)).achievements; @@ -441,23 +442,28 @@ export const achievements: Record = { ...achievementData.CORPORATION_BRIBE, Icon: "CORPLOBBY", Visible: () => hasAccessToSF(Player, 3), - Condition: () => Player.corporation !== null && Player.corporation.unlockUpgrades[6] === 1, + Condition: () => !!Player.corporation && Player.corporation.unlocks.has(CorpUnlockName.GovernmentPartnership), }, CORPORATION_PROD_1000: { ...achievementData.CORPORATION_PROD_1000, Icon: "CORP1000", Visible: () => hasAccessToSF(Player, 3), - Condition: () => Player.corporation !== null && Player.corporation.divisions.some((d) => d.prodMult >= 1000), + Condition: () => { + if (!Player.corporation) return false; + for (const division of Player.corporation.divisions.values()) { + if (division.productionMult >= 1000) return true; + } + return false; + }, }, CORPORATION_EMPLOYEE_3000: { ...achievementData.CORPORATION_EMPLOYEE_3000, Icon: "CORPCITY", Visible: () => hasAccessToSF(Player, 3), Condition: (): boolean => { - if (Player.corporation === null) return false; - for (const d of Player.corporation.divisions) { - let totalEmployees = 0; - for (const o of Object.values(d.offices)) if (o && o.totalEmployees) totalEmployees += o.totalEmployees; + if (!Player.corporation) return false; + for (const division of Player.corporation.divisions.values()) { + const totalEmployees = getRecordValues(division.offices).reduce((a, b) => a + b.numEmployees, 0); if (totalEmployees >= 3000) return true; } return false; @@ -469,8 +475,13 @@ export const achievements: Record = { Name: "Own the land", Description: "Expand to the Real Estate division.", Visible: () => hasAccessToSF(Player, 3), - Condition: () => - Player.corporation !== null && Player.corporation.divisions.some((d) => d.type === IndustryType.RealEstate), + Condition: () => { + if (!Player.corporation) return false; + for (const division of Player.corporation.divisions.values()) { + if (division.type === IndustryType.RealEstate) return true; + } + return false; + }, }, INTELLIGENCE_255: { ...achievementData.INTELLIGENCE_255, diff --git a/src/Augmentation/Augmentation.tsx b/src/Augmentation/Augmentation.tsx index a961c6b8d..4be7f1ea8 100644 --- a/src/Augmentation/Augmentation.tsx +++ b/src/Augmentation/Augmentation.tsx @@ -1,7 +1,7 @@ // Class definition for a single Augmentation object import * as React from "react"; -import type { CompletedProgramName } from "src/Programs/Programs"; +import type { CompletedProgramName } from "../Programs/Programs"; import { Faction } from "../Faction/Faction"; import { Factions } from "../Faction/Factions"; import { formatPercent } from "../ui/formatNumber"; diff --git a/src/Bladeburner/Bladeburner.tsx b/src/Bladeburner/Bladeburner.tsx index b2cacafa0..53318516d 100644 --- a/src/Bladeburner/Bladeburner.tsx +++ b/src/Bladeburner/Bladeburner.tsx @@ -37,6 +37,7 @@ import { isSleeveSupportWork } from "../PersonObjects/Sleeve/Work/SleeveSupportW import { WorkStats, newWorkStats } from "../Work/WorkStats"; import { CityName } from "../Enums"; import { getRandomMember } from "../utils/helpers/enum"; +import { createEnumKeyedRecord } from "../Types/Record"; export interface BlackOpsAttempt { error?: string; @@ -70,7 +71,7 @@ export class Bladeburner { type: ActionTypes.Idle, }); - cities: Record; + cities = createEnumKeyedRecord(CityName, (name) => new City(name)); city = CityName.Sector12; // Todo: better types for all these Record types. Will need custom types or enums for the named string categories (e.g. skills). skills: Record = {}; @@ -101,10 +102,6 @@ export class Bladeburner { consoleLogs: string[] = ["Bladeburner Console", "Type 'help' to see console commands"]; constructor() { - this.cities = {} as Record; - // This for loop ensures the above type is met for this.cities. - for (const city of Object.values(CityName)) this.cities[city] = new City(city); - this.updateSkillMultipliers(); // Calls resetSkillMultipliers() // Max Stamina is based on stats and Bladeburner-specific bonuses diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts index 770c62365..b3cef06d5 100644 --- a/src/Corporation/Actions.ts +++ b/src/Corporation/Actions.ts @@ -2,33 +2,28 @@ import { Player } from "@player"; import { MaterialInfo } from "./MaterialInfo"; import { Corporation } from "./Corporation"; import { IndustryResearchTrees, IndustriesData } from "./IndustryData"; -import { Industry } from "./Industry"; +import { Division } from "./Division"; import * as corpConstants from "./data/Constants"; import { OfficeSpace } from "./OfficeSpace"; import { Material } from "./Material"; import { Product } from "./Product"; import { Warehouse } from "./Warehouse"; -import { CorporationUnlockUpgrade } from "./data/CorporationUnlockUpgrades"; -import { CorporationUpgrade } from "./data/CorporationUpgrades"; -import { Cities } from "../Locations/Cities"; import { IndustryType } from "./data/Enums"; import { ResearchMap } from "./ResearchMap"; import { isRelevantMaterial } from "./ui/Helpers"; import { CityName } from "../Enums"; import { getRandomInt } from "../utils/helpers/getRandomInt"; import { CorpResearchName } from "@nsdefs"; -import { calculateUpgradeCost } from "./helpers"; import { isInteger } from "lodash"; +import { getRecordValues } from "../Types/Record"; export function NewIndustry(corporation: Corporation, industry: IndustryType, name: string): void { - if (corporation.divisions.length >= corporation.maxDivisions) + if (corporation.divisions.size >= corporation.maxDivisions) throw new Error(`Cannot expand into ${industry} industry, too many divisions!`); - for (let i = 0; i < corporation.divisions.length; ++i) { - if (corporation.divisions[i].name === name) { - throw new Error("This division name is already in use!"); - } - } + if (corporation.divisions.has(name)) throw new Error(`Division name ${name} is already in use!`); + // "Overview" is forbidden as a division name, see CorporationRoot.tsx for why this would cause issues. + if (name === "Overview") throw new Error(`"Overview" is a forbidden division name.`); const data = IndustriesData[industry]; if (!data) throw new Error(`Invalid industry: '${industry}'`); @@ -39,8 +34,9 @@ export function NewIndustry(corporation: Corporation, industry: IndustryType, na throw new Error("New division must have a name!"); } else { corporation.funds = corporation.funds - cost; - corporation.divisions.push( - new Industry({ + corporation.divisions.set( + name, + new Division({ corp: corporation, name: name, type: industry, @@ -50,13 +46,11 @@ export function NewIndustry(corporation: Corporation, industry: IndustryType, na } export function removeIndustry(corporation: Corporation, name: string) { - const divIndex = corporation.divisions.findIndex((div) => div.name === name); - if (divIndex === -1) throw new Error("There is no division called " + name); - - corporation.divisions.splice(divIndex, 1); + if (!corporation.divisions.has(name)) throw new Error("There is no division called " + name); + corporation.divisions.delete(name); } -export function NewCity(corporation: Corporation, division: Industry, city: CityName): void { +export function purchaseOffice(corporation: Corporation, division: Division, city: CityName): void { if (corporation.funds < corpConstants.officeInitialCost) { throw new Error("You don't have enough company funds to open a new office!"); } @@ -65,30 +59,11 @@ export function NewCity(corporation: Corporation, division: Industry, city: City } corporation.funds = corporation.funds - corpConstants.officeInitialCost; division.offices[city] = new OfficeSpace({ - loc: city, + city: city, size: corpConstants.officeInitialSize, }); } -export function UnlockUpgrade(corporation: Corporation, upgrade: CorporationUnlockUpgrade): void { - if (corporation.funds < upgrade.price) { - throw new Error("Insufficient funds"); - } - if (corporation.unlockUpgrades[upgrade.index] === 1) { - throw new Error(`You have already unlocked the ${upgrade.name} upgrade!`); - } - corporation.unlock(upgrade); -} - -export function LevelUpgrade(corporation: Corporation, upgrade: CorporationUpgrade, amount: number): void { - const cost = calculateUpgradeCost(corporation, upgrade, amount); - if (corporation.funds < cost) { - throw new Error("Insufficient funds"); - } else { - corporation.upgrade(upgrade, amount); - } -} - export function IssueDividends(corporation: Corporation, rate: number): void { if (isNaN(rate) || rate < 0 || rate > corpConstants.dividendMaxRate) { throw new Error(`Invalid value. Must be an number between 0 and ${corpConstants.dividendMaxRate}`); @@ -124,9 +99,9 @@ export function IssueNewShares(corporation: Corporation, amount: number): [numbe return [profit, amount, privateShares]; } -export function SellMaterial(mat: Material, amt: string, price: string): void { +export function SellMaterial(material: Material, amount: string, price: string): void { if (price === "") price = "0"; - if (amt === "") amt = "0"; + if (amount === "") amount = "0"; let cost = price.replace(/\s+/g, ""); cost = cost.replace(/[^-()\d/*+.MPe]/g, ""); //Sanitize cost let temp = cost.replace(/MP/, "1.234e5"); @@ -142,19 +117,19 @@ export function SellMaterial(mat: Material, amt: string, price: string): void { } if (cost.includes("MP")) { - mat.sCost = cost; //Dynamically evaluated + material.desiredSellPrice = cost; //Dynamically evaluated } else { - mat.sCost = temp; + material.desiredSellPrice = temp; } //Parse quantity - amt = amt.toUpperCase(); - if (amt.includes("MAX") || amt.includes("PROD") || amt.includes("INV")) { - let q = amt.replace(/\s+/g, ""); + amount = amount.toUpperCase(); + if (amount.includes("MAX") || amount.includes("PROD") || amount.includes("INV")) { + let q = amount.replace(/\s+/g, ""); q = q.replace(/[^-()\d/*+.MAXPRODINV]/g, ""); - let tempQty = q.replace(/MAX/g, mat.maxsll.toString()); - tempQty = tempQty.replace(/PROD/g, mat.prd.toString()); - tempQty = tempQty.replace(/INV/g, mat.prd.toString()); + let tempQty = q.replace(/MAX/g, material.maxSellPerCycle.toString()); + tempQty = tempQty.replace(/PROD/g, material.productionAmount.toString()); + tempQty = tempQty.replace(/INV/g, material.productionAmount.toString()); try { tempQty = eval(tempQty); } catch (e) { @@ -164,26 +139,19 @@ export function SellMaterial(mat: Material, amt: string, price: string): void { if (tempQty == null || isNaN(parseFloat(tempQty))) { throw new Error("Invalid value or expression for sell quantity field"); } - mat.sllman[0] = true; - mat.sllman[1] = q; //Use sanitized input - } else if (isNaN(parseFloat(amt)) || parseFloat(amt) < 0) { + material.desiredSellAmount = q; //Use sanitized input + } else if (isNaN(parseFloat(amount)) || parseFloat(amount) < 0) { throw new Error("Invalid value for sell quantity field! Must be numeric or 'PROD' or 'MAX'"); } else { - let q = parseFloat(amt); + let q = parseFloat(amount); if (isNaN(q)) { q = 0; } - if (q === 0) { - mat.sllman[0] = false; - mat.sllman[1] = 0; - } else { - mat.sllman[0] = true; - mat.sllman[1] = q; - } + material.desiredSellAmount = q; } } -export function SellProduct(product: Product, city: string, amt: string, price: string, all: boolean): void { +export function SellProduct(product: Product, city: CityName, amt: string, price: string, all: boolean): void { //Parse price if (price.includes("MP")) { //Dynamically evaluated quantity. First test to make sure its valid @@ -200,27 +168,24 @@ export function SellProduct(product: Product, city: string, amt: string, price: if (temp == null || isNaN(parseFloat(temp))) { throw new Error("Invalid value or expression for sell price field."); } - product.sCost[city] = price; //Use sanitized price + product.cityData[city].desiredSellPrice = price; //Use sanitized price } else { const cost = parseFloat(price); if (isNaN(cost)) { throw new Error("Invalid value for sell price field"); } - product.sCost[city] = cost; + product.cityData[city].desiredSellPrice = cost; } - // Array of all cities. Used later - const cities = Object.keys(Cities); - // Parse quantity amt = amt.toUpperCase(); if (amt.includes("MAX") || amt.includes("PROD") || amt.includes("INV")) { //Dynamically evaluated quantity. First test to make sure its valid let qty = amt.replace(/\s+/g, ""); qty = qty.replace(/[^-()\d/*+.MAXPRODINV]/g, ""); - let temp = qty.replace(/MAX/g, product.maxsll.toString()); - temp = temp.replace(/PROD/g, product.data[city][1].toString()); - temp = temp.replace(/INV/g, product.data[city][0].toString()); + let temp = qty.replace(/MAX/g, product.maxSellAmount.toString()); + temp = temp.replace(/PROD/g, product.cityData[city].productionAmount.toString()); + temp = temp.replace(/INV/g, product.cityData[city].stored.toString()); try { temp = eval(temp); } catch (e) { @@ -231,14 +196,11 @@ export function SellProduct(product: Product, city: string, amt: string, price: throw new Error("Invalid value or expression for sell quantity field"); } if (all) { - for (let i = 0; i < cities.length; ++i) { - const tempCity = cities[i]; - product.sllman[tempCity][0] = true; - product.sllman[tempCity][1] = qty; //Use sanitized input + for (const cityName of Object.values(CityName)) { + product.cityData[cityName].desiredSellAmount = qty; //Use sanitized input } } else { - product.sllman[city][0] = true; - product.sllman[city][1] = qty; //Use sanitized input + product.cityData[city].desiredSellAmount = qty; //Use sanitized input } } else if (isNaN(parseFloat(amt)) || parseFloat(amt) < 0) { throw new Error("Invalid value for sell quantity field! Must be numeric or 'PROD' or 'MAX'"); @@ -249,24 +211,18 @@ export function SellProduct(product: Product, city: string, amt: string, price: } if (qty === 0) { if (all) { - for (let i = 0; i < cities.length; ++i) { - const tempCity = cities[i]; - product.sllman[tempCity][0] = false; - product.sllman[tempCity][1] = ""; + for (const cityName of Object.values(CityName)) { + product.cityData[cityName].desiredSellAmount = 0; } } else { - product.sllman[city][0] = false; - product.sllman[city][1] = ""; + product.cityData[city].desiredSellAmount = 0; } } else if (all) { - for (let i = 0; i < cities.length; ++i) { - const tempCity = cities[i]; - product.sllman[tempCity][0] = true; - product.sllman[tempCity][1] = qty; + for (const cityName of Object.values(CityName)) { + product.cityData[cityName].desiredSellAmount = qty; } } else { - product.sllman[city][0] = true; - product.sllman[city][1] = qty; + product.cityData[city].desiredSellAmount = qty; } } } @@ -286,7 +242,7 @@ export function BuyMaterial(material: Material, amt: number): void { if (isNaN(amt) || amt < 0) { throw new Error(`Invalid amount '${amt}' to buy material '${material.name}'`); } - material.buy = amt; + material.buyAmount = amt; } export function BulkPurchase(corp: Corporation, warehouse: Warehouse, material: Material, amt: number): void { @@ -298,10 +254,10 @@ export function BulkPurchase(corp: Corporation, warehouse: Warehouse, material: if (amt > maxAmount) { throw new Error(`You do not have enough warehouse size to fit this purchase`); } - const cost = amt * material.bCost; + const cost = amt * material.marketPrice; if (corp.funds >= cost) { corp.funds = corp.funds - cost; - material.qty += amt; + material.stored += amt; } else { throw new Error(`You cannot afford this purchase.`); } @@ -365,7 +321,7 @@ export function BuyTea(corp: Corporation, office: OfficeSpace): boolean { export function ThrowParty(corp: Corporation, office: OfficeSpace, costPerEmployee: number): number { const mult = 1 + costPerEmployee / 10e6; - const cost = costPerEmployee * office.totalEmployees; + const cost = costPerEmployee * office.numEmployees; if (corp.funds < cost) { return 0; } @@ -378,16 +334,15 @@ export function ThrowParty(corp: Corporation, office: OfficeSpace, costPerEmploy return mult; } -export function PurchaseWarehouse(corp: Corporation, division: Industry, city: CityName): void { +export function purchaseWarehouse(corp: Corporation, division: Division, city: CityName): void { if (corp.funds < corpConstants.warehouseInitialCost) return; if (division.warehouses[city]) return; + corp.funds = corp.funds - corpConstants.warehouseInitialCost; division.warehouses[city] = new Warehouse({ - corp: corp, - industry: division, + division: division, loc: city, size: corpConstants.warehouseInitialSize, }); - corp.funds = corp.funds - corpConstants.warehouseInitialCost; } export function UpgradeWarehouseCost(warehouse: Warehouse, amt: number): number { @@ -397,7 +352,7 @@ export function UpgradeWarehouseCost(warehouse: Warehouse, amt: number): number ); } -export function UpgradeWarehouse(corp: Corporation, division: Industry, warehouse: Warehouse, amt = 1): void { +export function UpgradeWarehouse(corp: Corporation, division: Division, warehouse: Warehouse, amt = 1): void { const sizeUpgradeCost = UpgradeWarehouseCost(warehouse, amt); if (corp.funds < sizeUpgradeCost) return; warehouse.level += amt; @@ -405,7 +360,7 @@ export function UpgradeWarehouse(corp: Corporation, division: Industry, warehous corp.funds = corp.funds - sizeUpgradeCost; } -export function HireAdVert(corp: Corporation, division: Industry): void { +export function HireAdVert(corp: Corporation, division: Division): void { const cost = division.getAdVertCost(); if (corp.funds < cost) return; corp.funds = corp.funds - cost; @@ -414,7 +369,7 @@ export function HireAdVert(corp: Corporation, division: Industry): void { export function MakeProduct( corp: Corporation, - division: Industry, + division: Division, city: CityName, productName: string, designInvest: number, @@ -447,53 +402,46 @@ export function MakeProduct( } else if (division.hasResearch("uPgrade: Capacity.I")) { maxProducts = 4; } - const products = division.products; - if (Object.keys(products).length >= maxProducts) { + if (division.products.size >= maxProducts) { throw new Error(`You are already at the max products (${maxProducts}) for division: ${division.name}!`); } const product = new Product({ name: productName.replace(/[<>]/g, "").trim(), //Sanitize for HTMl elements createCity: city, - designCost: designInvest, - advCost: marketingInvest, + designInvestment: designInvest, + advertisingInvestment: marketingInvest, }); - if (products[product.name]) { + if (division.products.has(product.name)) { throw new Error(`You already have a product with this name!`); } corp.funds = corp.funds - (designInvest + marketingInvest); - products[product.name] = product; + division.products.set(product.name, product); } -export function Research(division: Industry, researchName: CorpResearchName): void { +export function Research(division: Division, researchName: CorpResearchName): void { + const corp = Player.corporation; + if (!corp) return; const researchTree = IndustryResearchTrees[division.type]; if (researchTree === undefined) throw new Error(`No research tree for industry '${division.type}'`); - const allResearch = researchTree.getAllNodes(); - if (!allResearch.includes(researchName)) throw new Error(`No research named '${researchName}'`); const research = ResearchMap[researchName]; - if (division.researched[researchName]) return; - if (division.sciResearch < research.cost) + if (division.researched.has(researchName)) return; + if (division.researchPoints < research.cost) throw new Error(`You do not have enough Scientific Research for ${research.name}`); - division.sciResearch -= research.cost; + division.researchPoints -= research.cost; // Get the Node from the Research Tree and set its 'researched' property researchTree.research(researchName); - division.researched[researchName] = true; + division.researched.add(researchName); // I couldn't figure out where else to put this so that warehouse size would get updated instantly // whether research is done by script or UI. All other stats gets calculated in every cycle // Warehouse size gets updated only when something increases it. if (researchName == "Drones - Transport") { - for (const city of Object.values(CityName)) { - const warehouse = division.warehouses[city]; - if (!warehouse) continue; - - if (Player.corporation) { - // Stores cycles in a "buffer". Processed separately using Engine Counters - warehouse.updateSize(Player.corporation, division); - } + for (const warehouse of getRecordValues(division.warehouses)) { + warehouse.updateSize(corp, division); } } } @@ -503,7 +451,7 @@ export function ExportMaterial( cityName: CityName, material: Material, amt: string, - division?: Industry, + division?: Division, ): void { // Sanitize amt let sanitizedAmt = amt.replace(/\s+/g, "").toUpperCase(); @@ -529,36 +477,36 @@ export function ExportMaterial( throw new Error(`You cannot export material: ${material.name} to division: ${divisionName}!`); } - const exportObj = { ind: divisionName, city: cityName, amt: sanitizedAmt }; - material.exp.push(exportObj); + const exportObj = { division: divisionName, city: cityName, amount: sanitizedAmt }; + material.exports.push(exportObj); } export function CancelExportMaterial(divisionName: string, cityName: string, material: Material, amt: string): void { - for (let i = 0; i < material.exp.length; ++i) { - if (material.exp[i].ind !== divisionName || material.exp[i].city !== cityName || material.exp[i].amt !== amt) + for (let i = 0; i < material.exports.length; ++i) { + if ( + material.exports[i].division !== divisionName || + material.exports[i].city !== cityName || + material.exports[i].amount !== amt + ) continue; - material.exp.splice(i, 1); + material.exports.splice(i, 1); break; } } -export function LimitProductProduction(product: Product, cityName: string, qty: number): void { - if (qty < 0 || isNaN(qty)) { - product.prdman[cityName][0] = false; - product.prdman[cityName][1] = 0; +export function LimitProductProduction(product: Product, cityName: CityName, quantity: number): void { + if (quantity < 0 || isNaN(quantity)) { + product.cityData[cityName].productionLimit = null; } else { - product.prdman[cityName][0] = true; - product.prdman[cityName][1] = qty; + product.cityData[cityName].productionLimit = quantity; } } -export function LimitMaterialProduction(material: Material, qty: number): void { - if (qty < 0 || isNaN(qty)) { - material.prdman[0] = false; - material.prdman[1] = 0; +export function LimitMaterialProduction(material: Material, quantity: number): void { + if (quantity < 0 || isNaN(quantity)) { + material.productionLimit = null; } else { - material.prdman[0] = true; - material.prdman[1] = qty; + material.productionLimit = quantity; } } diff --git a/src/Corporation/Corporation.tsx b/src/Corporation/Corporation.tsx index 7b955411f..383fc34b5 100644 --- a/src/Corporation/Corporation.tsx +++ b/src/Corporation/Corporation.tsx @@ -1,8 +1,8 @@ import { CorporationState } from "./CorporationState"; -import { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades"; -import { CorporationUpgrade, CorporationUpgrades } from "./data/CorporationUpgrades"; +import { CorpUnlocks } from "./data/CorporationUnlocks"; +import { CorpUpgrades } from "./data/CorporationUpgrades"; import * as corpConstants from "./data/Constants"; -import { Industry } from "./Industry"; +import { Division } from "./Division"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { showLiterature } from "../Literature/LiteratureHelpers"; @@ -11,9 +11,13 @@ import { Player } from "@player"; import { dialogBoxCreate } from "../ui/React/DialogBox"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; -import { CityName } from "../Enums"; import { CorpStateName } from "@nsdefs"; import { calculateUpgradeCost } from "./helpers"; +import { JSONMap, JSONSet } from "../Types/Jsonable"; +import { CorpUnlockName, CorpUpgradeName } from "./data/Enums"; +import { formatMoney } from "../ui/formatNumber"; +import { isPositiveInteger } from "../types"; +import { createEnumKeyedRecord, getRecordValues } from "../Types/Record"; interface IParams { name?: string; @@ -23,8 +27,8 @@ interface IParams { export class Corporation { name = "The Corporation"; - //A division/business sector is represented by the object: - divisions: Industry[] = []; + /** Map keyed by division name */ + divisions = new JSONMap(); maxDivisions = 20 * BitNodeMultipliers.CorporationDivisions; //Financial stats @@ -44,9 +48,12 @@ export class Corporation { sharePrice = 0; storedCycles = 0; - unlockUpgrades: number[]; - upgrades: number[]; - upgradeMultipliers: number[]; + unlocks = new JSONSet(); + upgrades = createEnumKeyedRecord(CorpUpgradeName, (name) => ({ + level: 0, + // For dreamsense, value is not a multiplier so it starts at 0 + value: name === CorpUpgradeName.DreamSense ? 0 : 1, + })); cycleValuation = 0; valuationsList = [0]; @@ -58,11 +65,6 @@ export class Corporation { constructor(params: IParams = {}) { this.name = params.name ? params.name : "The Corporation"; - const numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length; - const numUpgrades = Object.keys(CorporationUpgrades).length; - this.unlockUpgrades = Array(numUnlockUpgrades).fill(0); - this.upgrades = Array(numUpgrades).fill(0); - this.upgradeMultipliers = Array(numUpgrades).fill(1); this.seedFunded = params.seedFunded ?? false; } @@ -93,9 +95,6 @@ export class Corporation { this.divisions.forEach((ind) => { ind.resetImports(state); - }); - - this.divisions.forEach((ind) => { ind.process(marketCycles, state, this); }); @@ -134,8 +133,6 @@ export class Corporation { this.funds = 150e9; } - // Process dividends - this.updateDividendTax(); if (this.dividendRate > 0 && cycleProfit > 0) { // Validate input again, just to be safe if (isNaN(this.dividendRate) || this.dividendRate < 0 || this.dividendRate > corpConstants.dividendMaxRate) { @@ -157,16 +154,6 @@ export class Corporation { } } - updateDividendTax(): void { - this.dividendTax = 1 - BitNodeMultipliers.CorporationSoftcap + 0.15; - if (this.unlockUpgrades[5] === 1) { - this.dividendTax -= 0.05; - } - if (this.unlockUpgrades[6] === 1) { - this.dividendTax -= 0.1; - } - } - getCycleDividends(): number { const profit = this.revenue - this.expenses; const cycleProfit = profit * corpConstants.secondsPerMarketCycle; @@ -186,14 +173,14 @@ export class Corporation { } val = this.funds + profit * 85e3; - val *= Math.pow(1.1, this.divisions.length); + val *= Math.pow(1.1, this.divisions.size); val = Math.max(val, 0); } else { val = 10e9 + Math.max(this.funds, 0) / 3; //Base valuation if (profit > 0) { val += profit * 315e3; } - val *= Math.pow(1.1, this.divisions.length); + val *= Math.pow(1.1, this.divisions.size); val -= val % 1e6; //Round down to nearest millionth } return val * BitNodeMultipliers.CorporationValuation; @@ -293,145 +280,81 @@ export class Corporation { } } - //One time upgrades that unlock new features - unlock(upgrade: CorporationUnlockUpgrade): void { - const upgN = upgrade.index, - price = upgrade.price; - while (this.unlockUpgrades.length <= upgN) { - this.unlockUpgrades.push(0); - } - if (this.funds < price) { - dialogBoxCreate("You don't have enough funds to unlock this!"); - return; - } - this.unlockUpgrades[upgN] = 1; - this.funds = this.funds - price; + /** Purchasing a one-time unlock + * @returns A string on failure, indicating the reason for failure. */ + purchaseUnlock(unlockName: CorpUnlockName): string | void { + if (this.unlocks.has(unlockName)) return `The corporation has already unlocked ${unlockName}`; + const price = CorpUnlocks[unlockName].price; + if (this.funds < price) return `Insufficient funds to purchase ${unlockName}, requires ${formatMoney(price)}`; + this.funds -= price; + this.unlocks.add(unlockName); - // Apply effects for one-time upgrades - this.updateDividendTax(); + // Apply effects for one-time unlocks + if (unlockName === CorpUnlockName.ShadyAccounting) this.dividendTax -= 0.05; + if (unlockName === CorpUnlockName.GovernmentPartnership) this.dividendTax -= 0.1; } - //Levelable upgrades - upgrade(upgrade: CorporationUpgrade, amount: number): void { - if (amount < 1) amount = 1; - const upgN = upgrade.index, - upgradeAmt = upgrade.benefit; //Amount by which the upgrade multiplier gets increased (additive) - while (this.upgrades.length <= upgN) { - this.upgrades.push(0); - } - while (this.upgradeMultipliers.length <= upgN) { - this.upgradeMultipliers.push(1); + /** Purchasing a levelable upgrade + * @returns A string on failure, indicating the reason for failure. */ + purchaseUpgrade(upgradeName: CorpUpgradeName, amount = 1): string | void { + if (!isPositiveInteger(amount)) { + return `Number of upgrade levels purchased must be a positive integer (attempted: ${amount}).`; } + const upgrade = CorpUpgrades[upgradeName]; const totalCost = calculateUpgradeCost(this, upgrade, amount); - if (this.funds < totalCost) { - dialogBoxCreate("You don't have enough funds to purchase this!"); - return; - } - this.upgrades[upgN] += amount; - this.funds = this.funds - totalCost; + if (this.funds < totalCost) return `Not enough funds to purchase ${amount} of upgrade ${upgradeName}.`; + this.funds -= totalCost; + this.upgrades[upgradeName].level += amount; + this.upgrades[upgradeName].value += upgrade.benefit; - //Increase upgrade multiplier - this.upgradeMultipliers[upgN] = 1 + this.upgrades[upgN] * upgradeAmt; - - //If storage size is being updated, update values in Warehouse objects - if (upgN === 1) { - for (let i = 0; i < this.divisions.length; ++i) { - const industry = this.divisions[i]; - for (const city of Object.keys(industry.warehouses) as CityName[]) { - const warehouse = industry.warehouses[city]; - if (warehouse === 0) continue; - if (Object.hasOwn(industry.warehouses, city) && warehouse) { - warehouse.updateSize(this, industry); - } + // Apply effects for upgrades + if (upgradeName === CorpUpgradeName.SmartStorage) { + for (const division of this.divisions.values()) { + for (const warehouse of getRecordValues(division.warehouses)) { + warehouse.updateSize(this, division); } } } } getProductionMultiplier(): number { - const mult = this.upgradeMultipliers[0]; - if (isNaN(mult) || mult < 1) { - return 1; - } else { - return mult; - } + return this.upgrades[CorpUpgradeName.SmartFactories].value; } getStorageMultiplier(): number { - const mult = this.upgradeMultipliers[1]; - if (isNaN(mult) || mult < 1) { - return 1; - } else { - return mult; - } + return this.upgrades[CorpUpgradeName.SmartStorage].value; } getDreamSenseGain(): number { - const gain = this.upgradeMultipliers[2] - 1; - return gain <= 0 ? 0 : gain; + return this.upgrades[CorpUpgradeName.DreamSense].value; } getAdvertisingMultiplier(): number { - const mult = this.upgradeMultipliers[3]; - if (isNaN(mult) || mult < 1) { - return 1; - } else { - return mult; - } + return this.upgrades[CorpUpgradeName.WilsonAnalytics].value; } getEmployeeCreMultiplier(): number { - const mult = this.upgradeMultipliers[4]; - if (isNaN(mult) || mult < 1) { - return 1; - } else { - return mult; - } + return this.upgrades[CorpUpgradeName.NuoptimalNootropicInjectorImplants].value; } - getEmployeeChaMultiplier(): number { - const mult = this.upgradeMultipliers[5]; - if (isNaN(mult) || mult < 1) { - return 1; - } else { - return mult; - } + getEmployeeChaMult(): number { + return this.upgrades[CorpUpgradeName.SpeechProcessorImplants].value; } - getEmployeeIntMultiplier(): number { - const mult = this.upgradeMultipliers[6]; - if (isNaN(mult) || mult < 1) { - return 1; - } else { - return mult; - } + getEmployeeIntMult(): number { + return this.upgrades[CorpUpgradeName.NeuralAccelerators].value; } - getEmployeeEffMultiplier(): number { - const mult = this.upgradeMultipliers[7]; - if (isNaN(mult) || mult < 1) { - return 1; - } else { - return mult; - } + getEmployeeEffMult(): number { + return this.upgrades[CorpUpgradeName.FocusWires].value; } - getSalesMultiplier(): number { - const mult = this.upgradeMultipliers[8]; - if (isNaN(mult) || mult < 1) { - return 1; - } else { - return mult; - } + getSalesMult(): number { + return this.upgrades[CorpUpgradeName.ABCSalesBots].value; } - getScientificResearchMultiplier(): number { - const mult = this.upgradeMultipliers[9]; - if (isNaN(mult) || mult < 1) { - return 1; - } else { - return mult; - } + getScientificResearchMult(): number { + return this.upgrades[CorpUpgradeName.ProjectInsight].value; } // Adds the Corporation Handbook (Starter Guide) to the player's home computer. diff --git a/src/Corporation/CorporationState.ts b/src/Corporation/CorporationState.ts index 2056881cf..c2b527c52 100644 --- a/src/Corporation/CorporationState.ts +++ b/src/Corporation/CorporationState.ts @@ -15,14 +15,7 @@ export class CorporationState { // Transition to the next state nextState(): void { - if (this.state < 0 || this.state >= stateNames.length) { - this.state = 0; - } - - ++this.state; - if (this.state >= stateNames.length) { - this.state = 0; - } + this.state = (this.state + 1) % stateNames.length; } // Serialize the current object to a JSON save state. diff --git a/src/Corporation/Division.ts b/src/Corporation/Division.ts new file mode 100644 index 000000000..426dfbc0d --- /dev/null +++ b/src/Corporation/Division.ts @@ -0,0 +1,1111 @@ +import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; +import { CityName } from "../Enums"; +import { IndustryResearchTrees, IndustriesData } from "./IndustryData"; +import * as corpConstants from "./data/Constants"; +import { CorpEmployeeJob, IndustryType } from "./data/Enums"; +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 { MaterialInfo } from "./MaterialInfo"; +import { Warehouse } from "./Warehouse"; +import { Corporation } from "./Corporation"; +import { CorpMaterialName, CorpResearchName, CorpStateName } from "@nsdefs"; +import { JSONMap, JSONSet } from "../Types/Jsonable"; +import { PartialRecord, getRecordEntries, getRecordKeys, getRecordValues } from "../Types/Record"; +import { Material } from "./Material"; + +interface DivisionParams { + name: string; + corp: Corporation; + type: IndustryType; +} + +export class Division { + name = "DefaultDivisionName"; + type = IndustryType.Agriculture; + researchPoints = 0; + researched = new JSONSet(); + requiredMaterials: PartialRecord = {}; + + //An array of the name of materials being produced + producedMaterials: CorpMaterialName[] = []; + + products = new JSONMap(); + makesProducts = false; + + awareness = 0; + popularity = 0; + 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 represented 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 */ + realEstateFactor = 0; + researchFactor = 0; + hardwareFactor = 0; + robotFactor = 0; + aiCoreFactor = 0; + advertisingFactor = 0; + + productionMult = 0; //Production multiplier + + //Financials + lastCycleRevenue = 0; + lastCycleExpenses = 0; + thisCycleRevenue = 0; + thisCycleExpenses = 0; + + state: CorpStateName = "START"; + newInd = true; + + // Sector 12 office and warehouse are added by default, these entries are added in the constructor. + warehouses: PartialRecord = {}; + offices: PartialRecord = {}; + + numAdVerts = 0; + + constructor(params: DivisionParams | null = null) { + if (!params) return; + // Must be initialized inside the constructor because it references the industry + this.type = params.type; + this.name = params.name; + // Add default starting + this.warehouses[CityName.Sector12] = new Warehouse({ + loc: CityName.Sector12, + division: this, + size: corpConstants.warehouseInitialSize, + }); + this.offices[CityName.Sector12] = new OfficeSpace({ + city: CityName.Sector12, + size: corpConstants.officeInitialSize, + }); + + // Loading data based on this division's industry type + const data = IndustriesData[this.type]; + this.startingCost = data.startingCost; + this.makesProducts = data.product ? true : false; + this.realEstateFactor = data.realEstateFactor ?? 0; + this.researchFactor = data.scienceFactor ?? 0; + this.hardwareFactor = data.hardwareFactor ?? 0; + this.robotFactor = data.robotFactor ?? 0; + this.aiCoreFactor = data.aiCoreFactor ?? 0; + this.advertisingFactor = data.advertisingFactor ?? 0; + this.requiredMaterials = data.requiredMaterials; + this.producedMaterials = data.producedMaterials ?? []; + } + + 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 corpConstants.maxProductsBase + additional; + } + + hasMaximumNumberProducts(): boolean { + return this.products.size >= 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 (const warehouse of getRecordValues(this.warehouses)) { + const materials = warehouse.materials; + + const cityMult = + Math.pow(0.002 * materials["Real Estate"].stored + 1, this.realEstateFactor) * + Math.pow(0.002 * materials.Hardware.stored + 1, this.hardwareFactor) * + Math.pow(0.002 * materials.Robots.stored + 1, this.robotFactor) * + Math.pow(0.002 * materials["AI Cores"].stored + 1, this.aiCoreFactor); + multSum += Math.pow(cityMult, 0.73); + } + + multSum < 1 ? (this.productionMult = 1) : (this.productionMult = multSum); + } + + updateWarehouseSizeUsed(warehouse: Warehouse): void { + warehouse.updateMaterialSizeUsed(); + + for (const prod of this.products.values()) { + warehouse.sizeUsed += prod.cityData[warehouse.city].stored * prod.size; + } + } + + process(marketCycles = 1, state: CorpStateName, corporation: Corporation): void { + this.state = state; + + //At the start of a cycle, store and reset revenue/expenses + //Then calculate salaries and process 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 * corpConstants.secondsPerMarketCycle); + this.lastCycleExpenses = this.thisCycleExpenses / (marketCycles * corpConstants.secondsPerMarketCycle); + 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.values(CityName)) { + const office = this.offices[officeLoc]; + if (office) 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.requiredMaterials, + prodMats = this.producedMaterials; + + //Only 'process the market' for materials that this industry deals with + for (const city of Object.values(CityName)) { + //If this industry has a warehouse in this city, process the market + //for every material this industry requires or produces + 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[]) { + if (Object.hasOwn(reqMats, name)) { + wh.materials[name].processMarket(); + } + } + + //Produced materials are stored in an array + for (const matName of prodMats) wh.materials[matName].processMarket(); + + //Process these twice because these boost production ?????? + wh.materials.Hardware.processMarket(); + wh.materials.Robots.processMarket(); + wh.materials["AI Cores"].processMarket(); + wh.materials["Real Estate"].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 product of this.products.values()) { + let change = getRandomInt(0, 3) * 0.0004; + if (change === 0) continue; + + if ( + this.type === IndustryType.Pharmaceutical || + this.type === IndustryType.Software || + this.type === IndustryType.Robotics + ) { + change *= 3; + } + change *= marketCycles; + product.demand -= change; + product.competition += change; + product.competition = Math.min(product.competition, 99.99); + product.demand = Math.max(product.demand, 0.001); + } + } + + //Process production, purchase, and import/export of materials + processMaterials(marketCycles = 1, corporation: Corporation): [number, number] { + let revenue = 0; + let expenses = 0; + this.calculateProductionFactors(); + + for (const [city, office] of getRecordEntries(this.offices)) { + // Research points can be created even without a warehouse + this.researchPoints += + // Todo: add constant for magic number + 0.004 * + Math.pow(office.employeeProductionByJob[CorpEmployeeJob.RandD], 0.5) * + corporation.getScientificResearchMult() * + this.getScientificResearchMultiplier(); + + // Employee pay is an expense even with no warehouse. + expenses += office.totalSalary; + + const warehouse = this.warehouses[city]; + if (!warehouse) continue; + + switch (this.state) { + case "PURCHASE": { + const smartBuy: PartialRecord = {}; + + /* Process purchase of materials, not from smart supply */ + for (const [matName, mat] of getRecordEntries(warehouse.materials)) { + const reqMat = this.requiredMaterials[matName]; + if (warehouse.smartSupplyEnabled && reqMat) { + // Smart supply + mat.buyAmount = reqMat * warehouse.smartSupplyStore; + let buyAmt = mat.buyAmount * corpConstants.secondsPerMarketCycle * marketCycles; + const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialInfo[matName].size); + buyAmt = Math.min(buyAmt, maxAmt); + if (buyAmt > 0) smartBuy[matName] = [buyAmt, reqMat]; + } else { + // Not smart supply + let buyAmt = 0; + let maxAmt = 0; + + buyAmt = mat.buyAmount * corpConstants.secondsPerMarketCycle * marketCycles; + + maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialInfo[matName].size); + + buyAmt = Math.min(buyAmt, maxAmt); + if (buyAmt > 0) { + mat.quality = Math.max(0.1, (mat.quality * mat.stored + 1 * buyAmt) / (mat.stored + buyAmt)); + mat.stored += buyAmt; + expenses += buyAmt * mat.marketPrice; + } + this.updateWarehouseSizeUsed(warehouse); + } + } //End process purchase of materials + + // Find which material were trying to create the least amount of product with. + let worseAmt = 1e99; + for (const [buyAmt, reqMat] of Object.values(smartBuy)) { + const amt = buyAmt / reqMat; + if (amt < worseAmt) worseAmt = amt; + } + + // Align all the materials to the smallest amount. + for (const buyArray of Object.values(smartBuy)) { + buyArray[0] = worseAmt * buyArray[1]; + } + + // Calculate the total size of all things were trying to buy + let totalSize = 0; + for (const [matName, [buyAmt]] of getRecordEntries(smartBuy)) { + if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`); + totalSize += buyAmt * MaterialInfo[matName].size; + } + + // Shrink to the size of available space. + const freeSpace = warehouse.size - warehouse.sizeUsed; + if (totalSize > freeSpace) { + // Multiplier applied to buy amounts to not overfill warehouse + const buyMult = freeSpace / totalSize; + for (const buyArray of Object.values(smartBuy)) { + buyArray[0] = Math.floor(buyArray[0] * buyMult); + } + } + + // Use the materials already in the warehouse if the option is on. + for (const [matName, buyArray] of getRecordEntries(smartBuy)) { + if (warehouse.smartSupplyOptions[matName] === "none") continue; + const mat = warehouse.materials[matName]; + if (warehouse.smartSupplyOptions[matName] === "leftovers") { + buyArray[0] = Math.max(0, buyArray[0] - mat.stored); + } else { + buyArray[0] = Math.max(0, buyArray[0] - mat.importAmount); + } + } + + // buy them + for (const [matName, [buyAmt]] of getRecordEntries(smartBuy)) { + const mat = warehouse.materials[matName]; + if (mat.stored + buyAmt != 0) mat.quality = (mat.quality * mat.stored + 1 * buyAmt) / (mat.stored + buyAmt); + else mat.quality = 1; + mat.stored += buyAmt; + mat.buyAmount = buyAmt / 10; + expenses += buyAmt * mat.marketPrice; + } + break; + } + case "PRODUCTION": + warehouse.smartSupplyStore = 0; //Reset smart supply amount + + /* Process production of materials */ + if (this.producedMaterials.length > 0) { + const mat = warehouse.materials[this.producedMaterials[0]]; + //Calculate the maximum production of this material based + //on the office's productivity + const maxProd = + this.getOfficeProductivity(office) * + this.productionMult * // Multiplier from materials + corporation.getProductionMultiplier() * + this.getProductionMultiplier(); // Multiplier from Research + let prod; + + // If there is a limit set on production, apply the limit + prod = mat.productionLimit === null ? maxProd : Math.min(maxProd, mat.productionLimit); + + prod *= corpConstants.secondsPerMarketCycle * 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.producedMaterials.length; ++tmp) { + totalMatSize += MaterialInfo[this.producedMaterials[tmp]].size; + } + for (const [reqMatName, reqQty] of getRecordEntries(this.requiredMaterials)) { + totalMatSize -= MaterialInfo[reqMatName].size * reqQty; + } + // 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 / (corpConstants.secondsPerMarketCycle * marketCycles); + + // Make sure we have enough resource to make our materials + let producableFrac = 1; + for (const [reqMatName, reqMat] of getRecordEntries(this.requiredMaterials)) { + const req = reqMat * prod; + if (warehouse.materials[reqMatName].stored < req) { + producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].stored / req); + } + } + if (producableFrac <= 0) { + producableFrac = 0; + prod = 0; + } + + // Make our materials if they are producable + if (producableFrac > 0 && prod > 0) { + let avgQlt = 0; + let divider = 0; + for (const [reqMatName, reqMat] of getRecordEntries(this.requiredMaterials)) { + const reqMatQtyNeeded = reqMat * prod * producableFrac; + warehouse.materials[reqMatName].stored -= reqMatQtyNeeded; + warehouse.materials[reqMatName].productionAmount = 0; + warehouse.materials[reqMatName].productionAmount -= + reqMatQtyNeeded / (corpConstants.secondsPerMarketCycle * marketCycles); + + avgQlt += warehouse.materials[reqMatName].quality; + divider++; + } + avgQlt /= divider; + avgQlt = Math.max(avgQlt, 1); + for (let j = 0; j < this.producedMaterials.length; ++j) { + let tempQlt = + office.employeeProductionByJob[CorpEmployeeJob.Engineer] / 90 + + Math.pow(this.researchPoints, this.researchFactor) + + Math.pow(warehouse.materials["AI Cores"].stored, this.aiCoreFactor) / 10e3; + const logQlt = Math.max(Math.pow(tempQlt, 0.5), 1); + tempQlt = Math.min(tempQlt, avgQlt * logQlt); + warehouse.materials[this.producedMaterials[j]].quality = Math.max( + 1, + (warehouse.materials[this.producedMaterials[j]].quality * + warehouse.materials[this.producedMaterials[j]].stored + + tempQlt * prod * producableFrac) / + (warehouse.materials[this.producedMaterials[j]].stored + prod * producableFrac), + ); + warehouse.materials[this.producedMaterials[j]].stored += prod * producableFrac; + } + } else { + for (const reqMatName of getRecordKeys(this.requiredMaterials)) { + warehouse.materials[reqMatName].productionAmount = 0; + } + } + + //Per second + const materialProduction = (prod * producableFrac) / (corpConstants.secondsPerMarketCycle * marketCycles); + for (const prodMatName of this.producedMaterials) { + warehouse.materials[prodMatName].productionAmount = materialProduction; + } + } 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 getRecordKeys(this.requiredMaterials)) { + warehouse.materials[reqMatName].productionAmount = 0; + } + } + break; + + case "SALE": + /* Process sale of materials */ + for (const [matName, mat] of getRecordEntries(warehouse.materials)) { + if ((typeof mat.desiredSellPrice === "number" && mat.desiredSellPrice < 0) || mat.desiredSellAmount === 0) { + mat.actualSellAmount = 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 + + // Parse player sell-amount input (needed for TA.II and selling) + let sellAmt: number; + // The amount gets re-multiplied later, so this is the correct + // amount to calculate with for "MAX". + const adjustedQty = mat.stored / (corpConstants.secondsPerMarketCycle * marketCycles); + if (isString(mat.desiredSellAmount)) { + //Dynamically evaluated + let tmp = mat.desiredSellAmount.replace(/MAX/g, (adjustedQty + "").toUpperCase()); + tmp = tmp.replace(/PROD/g, mat.productionAmount + ""); + 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 { + sellAmt = mat.desiredSellAmount; + } + + // Determine the cost that the material will be sold at + const markupLimit = mat.getMarkupLimit(); + let sCost; + if (mat.marketTa2) { + // Reverse engineer the 'maxSell' formula + // 1. Set 'maxSell' = sellAmt + // 2. Substitute formula for 'markup' + // 3. Solve for 'sCost' + const numerator = markupLimit; + const sqrtNumerator = sellAmt; + const sqrtDenominator = + (mat.quality + 0.001) * + marketFactor * + businessFactor * + corporation.getSalesMult() * + advertisingFactor * + this.getSalesMultiplier(); + const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); + let optimalPrice; + if (sqrtDenominator === 0 || denominator === 0) { + if (sqrtNumerator === 0) { + optimalPrice = 0; // Nothing to sell + } else { + optimalPrice = mat.marketPrice + markupLimit; + console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`); + } + } else { + optimalPrice = numerator / denominator + mat.marketPrice; + } + + // 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.marketPrice + markupLimit; + } else if (isString(mat.desiredSellPrice)) { + sCost = mat.desiredSellPrice.replace(/MP/g, mat.marketPrice + ""); + sCost = eval(sCost); + } else { + sCost = mat.desiredSellPrice; + } + + // Calculate how much of the material sells (per second) + let markup = 1; + if (sCost > mat.marketPrice) { + //Penalty if difference between sCost and bCost is greater than markup limit + if (sCost - mat.marketPrice > markupLimit) { + markup = Math.pow(markupLimit / (sCost - mat.marketPrice), 2); + } + } else if (sCost < mat.marketPrice) { + if (sCost <= 0) { + markup = 1e12; //Sell everything, essentially discard + } else { + //Lower prices than market increases sales + markup = mat.marketPrice / sCost; + } + } + + mat.maxSellPerCycle = + (mat.quality + 0.001) * + marketFactor * + markup * + businessFactor * + corporation.getSalesMult() * + advertisingFactor * + this.getSalesMultiplier(); + if (isString(mat.desiredSellAmount)) { + //Dynamically evaluated + let tmp = mat.desiredSellAmount.replace(/MAX/g, (mat.maxSellPerCycle + "").toUpperCase()); + tmp = tmp.replace(/PROD/g, mat.productionAmount + ""); + + 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.maxSellPerCycle, sellAmt); + sellAmt = Math.max(sellAmt, 0); + } else { + //Player's input value is just a number + sellAmt = Math.min(mat.maxSellPerCycle, mat.desiredSellAmount); + } + sellAmt = Math.min(mat.maxSellPerCycle, sellAmt); + sellAmt = sellAmt * corpConstants.secondsPerMarketCycle * marketCycles; + sellAmt = Math.min(mat.stored, sellAmt); + if (sellAmt < 0) { + console.warn(`sellAmt calculated to be negative for ${matName} in ${city}`); + mat.actualSellAmount = 0; + continue; + } + if (sellAmt && sCost >= 0) { + mat.stored -= sellAmt; + revenue += sellAmt * sCost; + mat.actualSellAmount = sellAmt / (corpConstants.secondsPerMarketCycle * marketCycles); + } else { + mat.actualSellAmount = 0; + } + } //End processing of sale of materials + break; + + case "EXPORT": + for (const matName of Object.values(corpConstants.materialNames)) { + if (Object.hasOwn(warehouse.materials, matName)) { + const mat = warehouse.materials[matName]; + mat.exportedLastCycle = 0; //Reset export + for (let expI = 0; expI < mat.exports.length; ++expI) { + const exp = mat.exports[expI]; + + const expIndustry = corporation.divisions.get(exp.division); + if (!expIndustry) { + console.error(`Invalid export! ${exp.division}`); + 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.amount.replace( + /MAX/g, + (mat.stored / (corpConstants.secondsPerMarketCycle * marketCycles) + "").toUpperCase(), + ); + amtStr = amtStr.replace(/EPROD/g, mat.productionAmount.toString()); + amtStr = amtStr.replace(/IPROD/g, tempMaterial.productionAmount.toString()); + amtStr = amtStr.replace(/EINV/g, mat.stored.toString()); + amtStr = amtStr.replace(/IINV/g, tempMaterial.stored.toString()); + 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 * corpConstants.secondsPerMarketCycle * marketCycles; + + if (mat.stored < amt) { + amt = mat.stored; + } + + // 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); + } + if (amt <= 0) { + continue; + } + expWarehouse.materials[matName].importAmount += + 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].quality = Math.max( + 0.1, + (expWarehouse.materials[matName].quality * expWarehouse.materials[matName].stored + + amt * mat.quality) / + (expWarehouse.materials[matName].stored + amt), + ); + + expWarehouse.materials[matName].stored += amt; + mat.stored -= amt; + mat.exportedLastCycle += amt; + expIndustry.updateWarehouseSizeUsed(expWarehouse); + } + //totalExp should be per second + mat.exportedLastCycle /= corpConstants.secondsPerMarketCycle * marketCycles; + } + } + + break; + + case "START": + break; + default: + console.error(`Invalid state: ${this.state}`); + break; + } //End switch(this.state) + this.updateWarehouseSizeUsed(warehouse); + } + return [revenue, expenses]; + } + + /** Process product development and production/sale */ + processProducts(marketCycles = 1, corporation: Corporation): [number, number] { + let revenue = 0; + const expenses = 0; + + //Create products + for (const [name, product] of this.products) { + if (!product.finished) { + // Product still under development + if (this.state !== "PRODUCTION") continue; + const city = product.creationCity; + const office = this.offices[city]; + if (!office) throw new Error(`Product ${name} being created in a city without an office. This is a bug.`); + + product.createProduct(marketCycles, office.employeeProductionByJob); + if (product.developmentProgress >= 100) { + product.finishProduct(this); + } + break; + } else { + revenue += this.processProduct(marketCycles, product, corporation); + } + } + return [revenue, expenses]; + } + + //Processes FINISHED products + processProduct(marketCycles = 1, product: Product, corporation: Corporation): number { + let totalProfit = 0; + for (const [city, office] of getRecordEntries(this.offices)) { + const warehouse = this.warehouses[city]; + if (!warehouse) continue; + 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.productionMult * // Multiplier from materials + this.getProductionMultiplier() * // Multiplier from research + this.getProductProductionMultiplier(); // Multiplier from research + let prod; + + const productionLimit = product.cityData[city].productionLimit; + //Account for whether production is manually limited + if (productionLimit !== null) { + prod = Math.min(maxProd, productionLimit); + } else { + prod = maxProd; + } + prod *= corpConstants.secondsPerMarketCycle * marketCycles; + + //Calculate net change in warehouse storage making the Products will cost + let netStorageSize = product.size; + for (const [reqMatName, reqQty] of getRecordEntries(product.requiredMaterials)) { + netStorageSize -= MaterialInfo[reqMatName].size * reqQty; + } + + //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 / (corpConstants.secondsPerMarketCycle * marketCycles); + + //Make sure we have enough resources to make our Products + let producableFrac = 1; + for (const [reqMatName, reqQty] of getRecordEntries(product.requiredMaterials)) { + const req = reqQty * prod; + if (warehouse.materials[reqMatName].stored < req) { + producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].stored / req); + } + } + + //Make our Products if they are producable + if (producableFrac > 0 && prod > 0) { + let avgQlt = 1; + for (const [reqMatName, reqQty] of getRecordEntries(product.requiredMaterials)) { + const reqMatQtyNeeded = reqQty * prod * producableFrac; + warehouse.materials[reqMatName].stored -= reqMatQtyNeeded; + warehouse.materials[reqMatName].productionAmount -= + reqMatQtyNeeded / (corpConstants.secondsPerMarketCycle * marketCycles); + avgQlt += warehouse.materials[reqMatName].quality; + } + avgQlt /= Object.keys(product.requiredMaterials).length; + const tempEffRat = Math.min(product.rating, avgQlt * Math.pow(product.rating, 0.5)); + //Effective Rating + product.cityData[city].effectiveRating = + (product.cityData[city].effectiveRating * product.cityData[city].stored + + tempEffRat * prod * producableFrac) / + (product.cityData[city].stored + prod * producableFrac); + //Quantity + product.cityData[city].stored += prod * producableFrac; + } + + //Keep track of production Per second + product.cityData[city].productionAmount = + (prod * producableFrac) / (corpConstants.secondsPerMarketCycle * marketCycles); + break; + } + case "SALE": { + //Process sale of Products + product.productionCost = 0; //Estimated production cost + for (const [reqMatName, reqQty] of getRecordEntries(product.requiredMaterials)) { + product.productionCost += reqQty * warehouse.materials[reqMatName].marketPrice; + } + + // Since its a product, its production cost is increased for labor + product.productionCost *= corpConstants.baseProductProfitMult; + + // Sale multipliers + const businessFactor = this.getBusinessFactor(office); //Business employee productivity + const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity + const marketFactor = this.getMarketFactor(product); //Competition + demand + + // Parse player sell-amount input (needed for TA.II and selling) + let sellAmt: number | string; + // The amount gets re-multiplied later, so this is the correct + // amount to calculate with for "MAX". + const adjustedQty = product.cityData[city].stored / (corpConstants.secondsPerMarketCycle * marketCycles); + const desiredSellAmount = product.cityData[city].desiredSellAmount; + if (isString(desiredSellAmount)) { + //Sell amount is dynamically evaluated + let tmp: number | string = desiredSellAmount.replace(/MAX/g, (adjustedQty + "").toUpperCase()); + tmp = tmp.replace(/PROD/g, product.cityData[city].productionAmount.toString()); + try { + tmp = eval(tmp); + if (typeof tmp !== "number") throw ""; + } 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.maxSellAmount; + } + sellAmt = tmp; + } else if (desiredSellAmount && desiredSellAmount > 0) { + sellAmt = desiredSellAmount; + } else sellAmt = adjustedQty; + + if (sellAmt < 0) sellAmt = 0; + + // Calculate Sale Cost (sCost), which could be dynamically evaluated + const markupLimit = Math.max(product.cityData[city].effectiveRating, 0.001) / product.markup; + let sCost: number; + const sellPrice = product.cityData[city].desiredSellPrice; + if (product.marketTa2) { + // Reverse engineer the 'maxSell' formula + // 1. Set 'maxSell' = sellAmt + // 2. Substitute formula for 'markup' + // 3. Solve for 'sCost', product.pCost = sCost + const numerator = markupLimit; + const sqrtNumerator = sellAmt; + const sqrtDenominator = + 0.5 * + Math.pow(product.cityData[city].effectiveRating, 0.65) * + marketFactor * + corporation.getSalesMult() * + businessFactor * + advertisingFactor * + this.getSalesMultiplier(); + const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); + let optimalPrice; + if (sqrtDenominator === 0 || denominator === 0) { + if (sqrtNumerator === 0) { + optimalPrice = 0; // Nothing to sell + } else { + optimalPrice = product.productionCost + markupLimit; + console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`); + } + } else { + optimalPrice = numerator / denominator + product.productionCost; + } + + // 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.productionCost + markupLimit; + } else if (isString(sellPrice)) { + let sCostString = sellPrice; + if (product.markup === 0) { + console.error(`mku is zero, reverting to 1 to avoid Infinity`); + product.markup = 1; + } + sCostString = sCostString.replace(/MP/g, product.productionCost + ""); + sCost = Math.max(product.productionCost, eval(sCostString)); + } else { + sCost = sellPrice; + } + + let markup = 1; + if (sCost > product.productionCost) { + if (sCost - product.productionCost > markupLimit) { + markup = markupLimit / (sCost - product.productionCost); + } + } + + product.maxSellAmount = + 0.5 * + Math.pow(product.cityData[city].effectiveRating, 0.65) * + marketFactor * + corporation.getSalesMult() * + Math.pow(markup, 2) * + businessFactor * + advertisingFactor * + this.getSalesMultiplier(); + sellAmt = Math.min(product.maxSellAmount, sellAmt); + sellAmt = sellAmt * corpConstants.secondsPerMarketCycle * marketCycles; + sellAmt = Math.min(product.cityData[city].stored, sellAmt); //data[0] is qty + if (sellAmt && sCost) { + product.cityData[city].stored -= sellAmt; //data[0] is qty + totalProfit += sellAmt * sCost; + product.cityData[city].actualSellAmount = sellAmt / (corpConstants.secondsPerMarketCycle * marketCycles); //data[2] is sell property + } else { + product.cityData[city].actualSellAmount = 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; + } + + resetImports(state: string): void { + //At the start of the export state, set the imports of everything to 0 + if (state === "EXPORT") { + for (const warehouse of getRecordValues(this.warehouses)) { + for (const material of getRecordValues(warehouse.materials)) { + material.importAmount = 0; + } + } + } + } + + discontinueProduct(productName: string): void { + this.products.delete(productName); + } + + getAdVertCost(): number { + return 1e9 * Math.pow(1.06, this.numAdVerts); + } + + applyAdVert(corporation: Corporation): void { + const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier(); + const awareness = (this.awareness + 3 * advMult) * (1.005 * advMult); + this.awareness = Math.min(awareness, Number.MAX_VALUE); + + const popularity = (this.popularity + 1 * advMult) * ((1 + getRandomInt(1, 3) / 200) * advMult); + this.popularity = Math.min(popularity, Number.MAX_VALUE); + + ++this.numAdVerts; + } + + // 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.employeeProductionByJob[CorpEmployeeJob.Operations]; + const engrProd = office.employeeProductionByJob[CorpEmployeeJob.Engineer]; + const mgmtProd = office.employeeProductionByJob[CorpEmployeeJob.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.employeeProductionByJob[CorpEmployeeJob.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(): [ + totalFactor: number, + awarenessFactor: number, + popularityFactor: number, + ratioFactor: number, + ] { + const awarenessFac = Math.pow(this.awareness + 1, this.advertisingFactor); + const popularityFac = Math.pow(this.popularity + 1, this.advertisingFactor); + 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(item: Material | Product): number { + return Math.max(0.1, (item.demand * (100 - item.competition)) / 100); + } + + // Returns a boolean indicating whether this Industry has the specified Research + hasResearch(name: CorpResearchName): boolean { + return this.researched.has(name); + } + + updateResearchTree(): void { + const researchTree = IndustryResearchTrees[this.type]; + for (const research of this.researched) researchTree.research(research); + } + + // Get multipliers from Research + getAdvertisingMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getAdvertisingMultiplier(); + } + + getEmployeeChaMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getEmployeeChaMultiplier(); + } + + getEmployeeCreMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getEmployeeCreMultiplier(); + } + + getEmployeeEffMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getEmployeeEffMultiplier(); + } + + getEmployeeIntMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getEmployeeIntMultiplier(); + } + + getProductionMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getProductionMultiplier(); + } + + getProductProductionMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getProductProductionMultiplier(); + } + + getSalesMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getSalesMultiplier(); + } + + getScientificResearchMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getScientificResearchMultiplier(); + } + + getStorageMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + this.updateResearchTree(); + return researchTree.getStorageMultiplier(); + } + + /** Serialize the current object to a JSON save state. */ + toJSON(): IReviverValue { + return Generic_toJSON("Division", this); + } + + /** Initializes a Industry object from a JSON save state. */ + static fromJSON(value: IReviverValue): Division { + return Generic_fromJSON(Division, value.data); + } +} + +constructorsForReviver.Division = Division; diff --git a/src/Corporation/Export.ts b/src/Corporation/Export.ts index 2a83d28a2..63717d64e 100644 --- a/src/Corporation/Export.ts +++ b/src/Corporation/Export.ts @@ -1,7 +1,7 @@ import { CityName } from "../Enums"; export interface Export { - ind: string; + division: string; city: CityName; - amt: string; + amount: string; } diff --git a/src/Corporation/Industry.ts b/src/Corporation/Industry.ts deleted file mode 100644 index b35a86fc3..000000000 --- a/src/Corporation/Industry.ts +++ /dev/null @@ -1,1238 +0,0 @@ -import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; -import { CityName } from "../Enums"; -import { IndustryResearchTrees, IndustriesData } from "./IndustryData"; -import * as corpConstants from "./data/Constants"; -import { EmployeePositions, IndustryType } from "./data/Enums"; -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 { MaterialInfo } from "./MaterialInfo"; -import { Warehouse } from "./Warehouse"; -import { Corporation } from "./Corporation"; -import { CorpMaterialName, CorpResearchName, CorpStateName } from "@nsdefs"; - -interface IParams { - name?: string; - corp?: Corporation; - type?: IndustryType; -} - -export class Industry { - name: string; - type: IndustryType; - sciResearch = 0; - researched: Partial> = {}; - reqMats: Partial> = {}; - - //An array of the name of materials being produced - prodMats: CorpMaterialName[]; - - products: Partial> = {}; - makesProducts: boolean; - - awareness = 0; - popularity = 0; - 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 represented 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: 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 - - prodMult = 0; //Production multiplier - - //Financials - lastCycleRevenue: number; - lastCycleExpenses: number; - thisCycleRevenue: number; - thisCycleExpenses: number; - - state: CorpStateName = "START"; - newInd = true; - - //Maps locations to warehouses. 0 if no warehouse at that location - warehouses: Record; - - //Maps locations to offices. 0 if no office at that location - offices: Record = { - [CityName.Aevum]: 0, - [CityName.Chongqing]: 0, - [CityName.Sector12]: new OfficeSpace({ - loc: CityName.Sector12, - size: corpConstants.officeInitialSize, - }), - [CityName.NewTokyo]: 0, - [CityName.Ishima]: 0, - [CityName.Volhaven]: 0, - }; - - numAdVerts = 0; - - constructor(params: IParams = {}) { - this.type = params.type || IndustryType.Agriculture; - this.name = params.name ? params.name : ""; - - //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: corpConstants.warehouseInitialSize, - }), - [CityName.NewTokyo]: 0, - [CityName.Ishima]: 0, - [CityName.Volhaven]: 0, - }; - - const data = IndustriesData[this.type]; - if (!data) throw new Error(`Invalid industry: "${this.type}"`); - this.startingCost = data.startingCost; - this.makesProducts = data.product ? true : false; - 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 ?? []; - } - - 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 corpConstants.maxProductsBase + 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 (const city of Object.values(CityName)) { - const warehouse = this.warehouses[city]; - if (!warehouse) continue; - - const materials = warehouse.materials; - - const cityMult = - Math.pow(0.002 * materials["Real Estate"].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["AI Cores"].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 (Object.hasOwn(this.products, prodName)) { - const prod = this.products[prodName]; - if (prod === undefined) continue; - warehouse.sizeUsed += prod.data[warehouse.loc][0] * prod.siz; - } - } - } - - process(marketCycles = 1, state: CorpStateName, corporation: Corporation): void { - this.state = state; - - //At the start of a cycle, store and reset revenue/expenses - //Then calculate salaries and process 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 * corpConstants.secondsPerMarketCycle); - this.lastCycleExpenses = this.thisCycleExpenses / (marketCycles * corpConstants.secondsPerMarketCycle); - 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.values(CityName)) { - const office = this.offices[officeLoc]; - if (office) 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 (const city of Object.values(CityName)) { - //If this industry has a warehouse in this city, process the market - //for every material this industry requires or produces - 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[]) { - if (Object.hasOwn(reqMats, name)) { - wh.materials[name].processMarket(); - } - } - - //Produced materials are stored in an array - for (const matName of prodMats) wh.materials[matName].processMarket(); - - //Process these twice because these boost production - wh.materials.Hardware.processMarket(); - wh.materials.Robots.processMarket(); - wh.materials["AI Cores"].processMarket(); - wh.materials["Real Estate"].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 (Object.hasOwn(this.products, name)) { - const product = this.products[name]; - if (product === undefined) continue; - let change = getRandomInt(0, 3) * 0.0004; - if (change === 0) continue; - - if ( - this.type === IndustryType.Pharmaceutical || - this.type === IndustryType.Software || - this.type === IndustryType.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: Corporation): [number, number] { - let revenue = 0, - expenses = 0; - this.calculateProductionFactors(); - - for (const city of Object.values(CityName)) { - const office = this.offices[city]; - if (office === 0) continue; - - if (this.warehouses[city]) { - const warehouse = this.warehouses[city]; - if (warehouse === 0) continue; - - switch (this.state) { - case "PURCHASE": { - /* Process purchase of materials */ - for (const matName of Object.values(corpConstants.materialNames)) { - if (!Object.hasOwn(warehouse.materials, 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 * corpConstants.secondsPerMarketCycle * marketCycles; - - maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialInfo[matName].size); - - buyAmt = Math.min(buyAmt, maxAmt); - if (buyAmt > 0) { - mat.qlt = Math.max(0.1, (mat.qlt * mat.qty + 1 * buyAmt) / (mat.qty + buyAmt)); - mat.qty += buyAmt; - expenses += buyAmt * mat.bCost; - } - this.updateWarehouseSizeUsed(warehouse); - } //End process purchase of materials - - // smart supply - const smartBuy: Partial> = {}; - for (const matName of Object.values(corpConstants.materialNames)) { - if (!Object.hasOwn(warehouse.materials, 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 * corpConstants.secondsPerMarketCycle * marketCycles; - const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialInfo[matName].size); - 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) as CorpMaterialName[]) { - 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) as CorpMaterialName[]) { - 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) as CorpMaterialName[]) { - const buyAmt = smartBuy[matName]; - if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`); - totalSize += buyAmt * MaterialInfo[matName].size; - } - - // Shrink to the size of available space. - const freeSpace = warehouse.size - warehouse.sizeUsed; - if (totalSize > freeSpace) { - for (const matName of Object.keys(smartBuy) as CorpMaterialName[]) { - 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) as CorpMaterialName[]) { - if (warehouse.smartSupplyOptions[matName] === "none") continue; - const mat = warehouse.materials[matName]; - const buyAmt = smartBuy[matName]; - if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`); - if (warehouse.smartSupplyOptions[matName] === "leftovers") { - smartBuy[matName] = Math.max(0, buyAmt - mat.qty); - } else { - smartBuy[matName] = Math.max(0, buyAmt - mat.imp); - } - } - - // buy them - for (const [matName, buyAmt] of Object.entries(smartBuy) as [CorpMaterialName, number][]) { - const mat = warehouse.materials[matName]; - if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`); - if (mat.qty + buyAmt != 0) mat.qlt = (mat.qlt * mat.qty + 1 * buyAmt) / (mat.qty + buyAmt); - else mat.qlt = 1; - mat.qty += buyAmt; - mat.buy = buyAmt / 10; - 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 *= corpConstants.secondsPerMarketCycle * 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 += MaterialInfo[this.prodMats[tmp]].size; - } - for (const reqMatName of Object.keys(this.reqMats) as CorpMaterialName[]) { - const normQty = this.reqMats[reqMatName]; - if (normQty === undefined) continue; - totalMatSize -= MaterialInfo[reqMatName].size * 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 / (corpConstants.secondsPerMarketCycle * marketCycles); - - // Make sure we have enough resource to make our materials - let producableFrac = 1; - for (const reqMatName of Object.keys(this.reqMats) as CorpMaterialName[]) { - if (Object.hasOwn(this.reqMats, 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) { - let avgQlt = 0; - let divider = 0; - for (const reqMatName of Object.keys(this.reqMats) as CorpMaterialName[]) { - 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 / (corpConstants.secondsPerMarketCycle * marketCycles); - - avgQlt += warehouse.materials[reqMatName].qlt; - divider++; - } - avgQlt /= divider; - avgQlt = Math.max(avgQlt, 1); - for (let j = 0; j < this.prodMats.length; ++j) { - let tempQlt = - office.employeeProd[EmployeePositions.Engineer] / 90 + - Math.pow(this.sciResearch, this.sciFac) + - Math.pow(warehouse.materials["AI Cores"].qty, this.aiFac) / 10e3; - 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; - } - } else { - for (const reqMatName of Object.keys(this.reqMats) as CorpMaterialName[]) { - if (Object.hasOwn(this.reqMats, reqMatName)) { - warehouse.materials[reqMatName].prd = 0; - } - } - } - - //Per second - const fooProd = (prod * producableFrac) / (corpConstants.secondsPerMarketCycle * 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) as CorpMaterialName[]) { - warehouse.materials[reqMatName].prd = 0; - } - } - break; - - case "SALE": - /* Process sale of materials */ - for (const matName of Object.values(corpConstants.materialNames)) { - if (Object.hasOwn(warehouse.materials, matName)) { - const mat = warehouse.materials[matName]; - if ((typeof mat.sCost === "number" && mat.sCost < 0) || !mat.sllman[0]) { - 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 - - // 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].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]; - } - - // Determine the cost that the material will be sold at - const markupLimit = mat.getMarkupLimit(); - let sCost; - if (mat.marketTa2) { - // Reverse engineer the 'maxSell' formula - // 1. Set 'maxSell' = sellAmt - // 2. Substitute formula for 'markup' - // 3. Solve for 'sCost' - const numerator = markupLimit; - const sqrtNumerator = sellAmt; - 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; // Nothing to sell - } 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.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(); - if (isString(mat.sllman[1])) { - //Dynamically evaluated - let tmp = mat.sllman[1].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]); - } - sellAmt = Math.min(mat.maxsll, sellAmt); - sellAmt = sellAmt * corpConstants.secondsPerMarketCycle * 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 / (corpConstants.secondsPerMarketCycle * marketCycles); - } else { - mat.sll = 0; - } - } - } //End processing of sale of materials - break; - - case "EXPORT": - for (const matName of Object.values(corpConstants.materialNames)) { - if (Object.hasOwn(warehouse.materials, 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 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( - /MAX/g, - (mat.qty / (corpConstants.secondsPerMarketCycle * marketCycles) + "").toUpperCase(), - ); - 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()); - 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 * corpConstants.secondsPerMarketCycle * marketCycles; - - if (mat.qty < amt) { - amt = mat.qty; - } - - // 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); - } - if (amt <= 0) { - continue; - } - 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); - } - //totalExp should be per second - mat.totalExp /= corpConstants.secondsPerMarketCycle * 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) { - this.sciResearch += - 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: Corporation): [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; - - prod.createProduct(marketCycles, office.employeeProd); - if (prod.prog >= 100) { - prod.finishProduct(this); - } - break; - } - } - } - - //Produce Products - for (const prodName of Object.keys(this.products)) { - if (Object.hasOwn(this.products, prodName)) { - const prod = this.products[prodName]; - if (prod && prod.fin) { - revenue += this.processProduct(marketCycles, prod, corporation); - } - } - } - return [revenue, expenses]; - } - - //Processes FINISHED products - processProduct(marketCycles = 1, product: Product, corporation: Corporation): number { - let totalProfit = 0; - for (const city of Object.values(CityName)) { - const office = this.offices[city]; - if (office === 0) continue; - const warehouse = this.warehouses[city]; - if (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 *= corpConstants.secondsPerMarketCycle * marketCycles; - - //Calculate net change in warehouse storage making the Products will cost - let netStorageSize = product.siz; - for (const [reqMatName, reqQty] of Object.entries(product.reqMats) as [CorpMaterialName, number][]) { - netStorageSize -= MaterialInfo[reqMatName].size * reqQty; - } - - //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 / (corpConstants.secondsPerMarketCycle * marketCycles); - - //Make sure we have enough resources to make our Products - let producableFrac = 1; - 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); - } - } - - //Make our Products if they are producable - if (producableFrac > 0 && prod > 0) { - let avgQlt = 1; - 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); - avgQlt += warehouse.materials[reqMatName].qlt; - } - 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); - //Quantity - product.data[city][0] += prod * producableFrac; - } - - //Keep track of production Per second - product.data[city][1] = (prod * producableFrac) / (corpConstants.secondsPerMarketCycle * marketCycles); - break; - } - case "SALE": { - //Process sale of Products - product.pCost = 0; //Estimated production cost - for (const [reqMatName, reqQty] of Object.entries(product.reqMats) as [CorpMaterialName, number][]) { - product.pCost += reqQty * warehouse.materials[reqMatName].bCost; - } - - // Since its a product, its production cost is increased for labor - product.pCost *= corpConstants.baseProductProfitMult; - - // Sale multipliers - const businessFactor = this.getBusinessFactor(office); //Business employee productivity - const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity - const marketFactor = this.getMarketFactor(product); //Competition + demand - - // 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; - } - - // Calculate Sale Cost (sCost), which could be dynamically evaluated - const markupLimit = Math.max(product.data[city][3], 0.001) / product.mku; - let sCost; - if (product.marketTa2) { - // Reverse engineer the 'maxSell' formula - // 1. Set 'maxSell' = sellAmt - // 2. Substitute formula for 'markup' - // 3. Solve for 'sCost', product.pCost = sCost - const numerator = markupLimit; - const sqrtNumerator = sellAmt; - const sqrtDenominator = - 0.5 * - Math.pow(product.data[city][3], 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; // Nothing to sell - } 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[city])) { - const sCostString = product.sCost[city] 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 + ""); - sCost = Math.max(product.pCost, eval(sCost)); - } else { - sCost = product.sCost[city]; - } - - let markup = 1; - if (sCost > product.pCost) { - if (sCost - product.pCost > markupLimit) { - markup = markupLimit / (sCost - product.pCost); - } - } - - product.maxsll = - 0.5 * - Math.pow(product.data[city][3], 0.65) * - marketFactor * - corporation.getSalesMultiplier() * - Math.pow(markup, 2) * - businessFactor * - advertisingFactor * - this.getSalesMultiplier(); - sellAmt = Math.min(product.maxsll, sellAmt); - sellAmt = sellAmt * corpConstants.secondsPerMarketCycle * 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 / (corpConstants.secondsPerMarketCycle * 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; - } - - resetImports(state: string): void { - //At the start of the export state, set the imports of everything to 0 - if (state === "EXPORT") { - for (const city of Object.values(CityName)) { - if (!this.warehouses[city]) continue; - - const warehouse = this.warehouses[city]; - if (warehouse === 0) continue; - for (const matName of Object.values(corpConstants.materialNames)) { - if (Object.hasOwn(warehouse.materials, matName)) { - const mat = warehouse.materials[matName]; - mat.imp = 0; - } - } - } - } - } - - discontinueProduct(product: Product): void { - for (const productName of Object.keys(this.products)) { - if (Object.hasOwn(this.products, productName)) { - if (product === this.products[productName]) { - delete this.products[productName]; - } - } - } - } - - getAdVertCost(): number { - return 1e9 * Math.pow(1.06, this.numAdVerts); - } - - applyAdVert(corporation: Corporation): void { - const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier(); - const awareness = (this.awareness + 3 * advMult) * (1.005 * advMult); - this.awareness = Math.min(awareness, Number.MAX_VALUE); - - const popularity = (this.popularity + 1 * advMult) * ((1 + getRandomInt(1, 3) / 200) * advMult); - this.popularity = Math.min(popularity, Number.MAX_VALUE); - - ++this.numAdVerts; - } - - // 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: CorpResearchName): 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 isn't 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) as CorpResearchName[]) { - 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(): IReviverValue { - return Generic_toJSON("Industry", this); - } - - /** Initializes a Industry object from a JSON save state. */ - static fromJSON(value: IReviverValue): Industry { - const matNameMap = { AICores: "AI Cores", RealEstate: "Real Estate" }; - const indNameMap = { - RealEstate: IndustryType.RealEstate, - Water: IndustryType.Water, - 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]; - } - } - return Generic_fromJSON(Industry, value.data); - } -} - -constructorsForReviver.Industry = Industry; diff --git a/src/Corporation/IndustryData.tsx b/src/Corporation/IndustryData.tsx index 3a95dc3ee..9456e8ef7 100644 --- a/src/Corporation/IndustryData.tsx +++ b/src/Corporation/IndustryData.tsx @@ -1,12 +1,12 @@ import React from "react"; -import { ResearchTree } from "./ResearchTree"; import { Corporation } from "./Corporation"; import { getBaseResearchTreeCopy, getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree"; import { MoneyCost } from "./ui/MoneyCost"; -import { CorpIndustryData, CorpIndustryName } from "@nsdefs"; +import { CorpIndustryData } from "@nsdefs"; import { IndustryType } from "./data/Enums"; +import { createFullRecordFromEntries } from "../Types/Record"; -export const IndustriesData: Record = { +export const IndustriesData: Record = { [IndustryType.Agriculture]: { startingCost: 40e9, description: "Cultivate crops and breed livestock to produce food.", @@ -315,9 +315,14 @@ export const IndustryDescriptions = (industry: IndustryType, corp: Corporation) ); }; -export const IndustryResearchTrees = {} as Record; -resetIndustryResearchTrees(); - +export const IndustryResearchTrees = createFullRecordFromEntries( + Object.values(IndustryType).map((industryType) => { + return [ + industryType, + IndustriesData[industryType].product ? getProductIndustryResearchTreeCopy() : getBaseResearchTreeCopy(), + ]; + }), +); export function resetIndustryResearchTrees() { Object.values(IndustryType).forEach( (ind) => diff --git a/src/Corporation/Material.ts b/src/Corporation/Material.ts index 7dc26aa0a..032ce3cab 100644 --- a/src/Corporation/Material.ts +++ b/src/Corporation/Material.ts @@ -13,52 +13,51 @@ export class Material { name: CorpMaterialName; // Amount of material owned - qty = 0; + stored = 0; // Material's "quality". Unbounded - qlt = 1; + quality = 1; // How much demand the Material has in the market, and the range of possible // values for this "demand" - dmd = 0; - dmdR: number[] = [0, 0]; + demand = 0; + demandRange: number[] = [0, 0]; // How much competition there is for this Material in the market, and the range // of possible values for this "competition" - cmp = 0; - cmpR: number[] = [0, 0]; + competition = 0; + competitionRange: number[] = [0, 0]; // Maximum volatility of this Materials stats - mv = 0; + maxVolatility = 0; // Markup. Determines how high of a price you can charge on the material // compared to the market price without suffering loss in # of sales // Quality is divided by this to determine markup limits // e,g, If mku is 10 and quality is 100 then you can markup prices by 100/10 = 10 - mku = 0; + markup = 0; // How much of this material is being bought, sold, imported and produced every second - buy = 0; - sll = 0; - prd = 0; - imp = 0; + buyAmount = 0; + actualSellAmount = 0; + productionAmount = 0; + importAmount = 0; // Exports of this material to another warehouse/industry - exp: Export[] = []; + exports: Export[] = []; // Total amount of this material exported in the last cycle - totalExp = 0; + exportedLastCycle = 0; // Cost / sec to buy this material. AKA Market Price - bCost = 0; + marketPrice = 0; - // Cost / sec to sell this material - sCost: string | number = 0; + /** null if there is no limit set on production. 0 actually limits production to 0. */ + productionLimit: number | null = null; - // Flags to keep track of whether production and/or sale of this material is limited - // [Whether production/sale is limited, limit amount] - prdman: [boolean, number] = [false, 0]; // Production - sllman: [boolean, string | number] = [false, 0]; // Sale + // Player inputs for sell price and amount. + desiredSellAmount: string | number = 0; + desiredSellPrice: string | number = 0; // Flags that signal whether automatic sale pricing through Market TA is enabled marketTa1 = false; @@ -66,62 +65,62 @@ export class Material { marketTa2Price = 0; // Determines the maximum amount of this material that can be sold in one market cycle - maxsll = 0; + maxSellPerCycle = 0; constructor(params?: IConstructorParams) { this.name = params?.name ?? materialNames[0]; - this.dmd = MaterialInfo[this.name].demandBase; - this.dmdR = MaterialInfo[this.name].demandRange; - this.cmp = MaterialInfo[this.name].competitionBase; - this.cmpR = MaterialInfo[this.name].competitionRange; - this.bCost = MaterialInfo[this.name].baseCost; - this.mv = MaterialInfo[this.name].maxVolatility; - this.mku = MaterialInfo[this.name].baseMarkup; + this.demand = MaterialInfo[this.name].demandBase; + this.demandRange = MaterialInfo[this.name].demandRange; + this.competition = MaterialInfo[this.name].competitionBase; + this.competitionRange = MaterialInfo[this.name].competitionRange; + this.marketPrice = MaterialInfo[this.name].baseCost; + this.maxVolatility = MaterialInfo[this.name].maxVolatility; + this.markup = MaterialInfo[this.name].baseMarkup; } getMarkupLimit(): number { - return this.qlt / this.mku; + return this.quality / this.markup; } // Process change in demand, competition, and buy cost of this material processMarket(): void { // The price will change in accordance with demand and competition. // e.g. If demand goes up, then so does price. If competition goes up, price goes down - const priceVolatility: number = (Math.random() * this.mv) / 300; + const priceVolatility: number = (Math.random() * this.maxVolatility) / 300; const priceChange: number = 1 + priceVolatility; //This 1st random check determines whether competition increases or decreases - const compVolatility: number = (Math.random() * this.mv) / 100; + const compVolatility: number = (Math.random() * this.maxVolatility) / 100; const compChange: number = 1 + compVolatility; if (Math.random() < 0.5) { - this.cmp *= compChange; - if (this.cmp > this.cmpR[1]) { - this.cmp = this.cmpR[1]; + this.competition *= compChange; + if (this.competition > this.competitionRange[1]) { + this.competition = this.competitionRange[1]; } - this.bCost *= 1 / priceChange; // Competition increases, so price goes down + this.marketPrice *= 1 / priceChange; // Competition increases, so price goes down } else { - this.cmp *= 1 / compChange; - if (this.cmp < this.cmpR[0]) { - this.cmp = this.cmpR[0]; + this.competition *= 1 / compChange; + if (this.competition < this.competitionRange[0]) { + this.competition = this.competitionRange[0]; } - this.bCost *= priceChange; // Competition decreases, so price goes up + this.marketPrice *= priceChange; // Competition decreases, so price goes up } // This 2nd random check determines whether demand increases or decreases - const dmdVolatility: number = (Math.random() * this.mv) / 100; + const dmdVolatility: number = (Math.random() * this.maxVolatility) / 100; const dmdChange: number = 1 + dmdVolatility; if (Math.random() < 0.5) { - this.dmd *= dmdChange; - if (this.dmd > this.dmdR[1]) { - this.dmd = this.dmdR[1]; + this.demand *= dmdChange; + if (this.demand > this.demandRange[1]) { + this.demand = this.demandRange[1]; } - this.bCost *= priceChange; // Demand increases, so price goes up + this.marketPrice *= priceChange; // Demand increases, so price goes up } else { - this.dmd *= 1 / dmdChange; - if (this.dmd < this.dmdR[0]) { - this.dmd = this.dmdR[0]; + this.demand *= 1 / dmdChange; + if (this.demand < this.demandRange[0]) { + this.demand = this.demandRange[0]; } - this.bCost *= 1 / priceChange; + this.marketPrice *= 1 / priceChange; } } diff --git a/src/Corporation/OfficeSpace.ts b/src/Corporation/OfficeSpace.ts index f2c256929..89e017548 100644 --- a/src/Corporation/OfficeSpace.ts +++ b/src/Corporation/OfficeSpace.ts @@ -1,33 +1,34 @@ -import { EmployeePositions } from "./data/Enums"; +import { CorpEmployeeJob } from "./data/Enums"; import * as corpConstants from "./data/Constants"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver"; -import { Industry } from "./Industry"; +import { Division } from "./Division"; import { Corporation } from "./Corporation"; import { getRandomInt } from "../utils/helpers/getRandomInt"; import { CityName } from "../Enums"; +import { createEnumKeyedRecord, getRecordKeys } from "../Types/Record"; interface IParams { - loc?: CityName; - size?: number; + city: CityName; + size: number; } export class OfficeSpace { - loc: CityName; - size: number; + city = CityName.Sector12; + size = 1; - maxEne = 100; - maxMor = 100; + maxEnergy = 100; + maxMorale = 100; - avgEne = 75; - avgMor = 75; + avgEnergy = 75; + avgMorale = 75; - avgInt = 75; - avgCha = 75; - totalExp = 0; - avgCre = 75; - avgEff = 75; + avgIntelligence = 75; + avgCharisma = 75; + avgCreativity = 75; + avgEfficiency = 75; - totalEmployees = 0; + totalExperience = 0; + numEmployees = 0; totalSalary = 0; autoTea = false; @@ -35,73 +36,49 @@ export class OfficeSpace { teaPending = false; partyMult = 1; - employeeProd: Record = { - [EmployeePositions.Operations]: 0, - [EmployeePositions.Engineer]: 0, - [EmployeePositions.Business]: 0, - [EmployeePositions.Management]: 0, - [EmployeePositions.RandD]: 0, - [EmployeePositions.Intern]: 0, - [EmployeePositions.Unassigned]: 0, - total: 0, - }; - employeeJobs: Record = { - [EmployeePositions.Operations]: 0, - [EmployeePositions.Engineer]: 0, - [EmployeePositions.Business]: 0, - [EmployeePositions.Management]: 0, - [EmployeePositions.RandD]: 0, - [EmployeePositions.Intern]: 0, - [EmployeePositions.Unassigned]: 0, - }; - employeeNextJobs: Record = { - [EmployeePositions.Operations]: 0, - [EmployeePositions.Engineer]: 0, - [EmployeePositions.Business]: 0, - [EmployeePositions.Management]: 0, - [EmployeePositions.RandD]: 0, - [EmployeePositions.Intern]: 0, - [EmployeePositions.Unassigned]: 0, - }; + employeeProductionByJob = { total: 0, ...createEnumKeyedRecord(CorpEmployeeJob, () => 0) }; + employeeJobs = createEnumKeyedRecord(CorpEmployeeJob, () => 0); + employeeNextJobs = createEnumKeyedRecord(CorpEmployeeJob, () => 0); - constructor(params: IParams = {}) { - this.loc = params.loc ? params.loc : CityName.Sector12; - this.size = params.size ? params.size : 1; + constructor(params: IParams | null = null) { + if (!params) return; + this.city = params.city; + this.size = params.size; } atCapacity(): boolean { - return this.totalEmployees >= this.size; + return this.numEmployees >= this.size; } - process(marketCycles = 1, corporation: Corporation, industry: Industry): number { + process(marketCycles = 1, corporation: Corporation, industry: Division): number { // HRBuddy AutoRecruitment and Interning if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) { this.hireRandomEmployee( - industry.hasResearch("HRBuddy-Training") ? EmployeePositions.Intern : EmployeePositions.Unassigned, + industry.hasResearch("HRBuddy-Training") ? CorpEmployeeJob.Intern : CorpEmployeeJob.Unassigned, ); } // Update employee jobs and job counts - for (const [pos, jobCount] of Object.entries(this.employeeNextJobs) as [EmployeePositions, number][]) { + for (const [pos, jobCount] of Object.entries(this.employeeNextJobs) as [CorpEmployeeJob, number][]) { this.employeeJobs[pos] = jobCount; } // Process Office properties - this.maxEne = 100; - this.maxMor = 100; + this.maxEnergy = 100; + this.maxMorale = 100; - if (industry.hasResearch("Go-Juice")) this.maxEne += 10; - if (industry.hasResearch("Sti.mu")) this.maxMor += 10; + if (industry.hasResearch("Go-Juice")) this.maxEnergy += 10; + if (industry.hasResearch("Sti.mu")) this.maxMorale += 10; if (industry.hasResearch("AutoBrew")) this.autoTea = true; if (industry.hasResearch("AutoPartyManager")) this.autoParty = true; - if (this.totalEmployees > 0) { + if (this.numEmployees > 0) { /** Multiplier for employee morale/energy based on company performance */ let perfMult = 1.002; - if (this.totalEmployees >= 9) { + if (this.numEmployees >= 9) { perfMult = Math.pow( 1 + - 0.002 * Math.min(1 / 9, this.employeeJobs.Intern / this.totalEmployees - 1 / 9) * 9 - + 0.002 * Math.min(1 / 9, this.employeeJobs.Intern / this.numEmployees - 1 / 9) * 9 - (corporation.funds < 0 && industry.lastCycleRevenue < industry.lastCycleExpenses ? 0.001 : 0), marketCycles, ); @@ -111,121 +88,125 @@ export class OfficeSpace { const reduction = 0.002 * marketCycles; if (this.autoTea) { - this.avgEne = this.maxEne; + this.avgEnergy = this.maxEnergy; } else { // Tea gives a flat +2 to energy - this.avgEne = (this.avgEne - reduction * Math.random()) * perfMult + (this.teaPending ? 2 : 0); + this.avgEnergy = (this.avgEnergy - reduction * Math.random()) * perfMult + (this.teaPending ? 2 : 0); } if (this.autoParty) { - this.avgMor = this.maxMor; + this.avgMorale = this.maxMorale; } else { // Each 5% multiplier gives an extra flat +1 to morale to make recovering from low morale easier. const increase = this.partyMult > 1 ? (this.partyMult - 1) * 10 : 0; - this.avgMor = ((this.avgMor - reduction * Math.random()) * perfMult + increase) * this.partyMult; + this.avgMorale = ((this.avgMorale - reduction * Math.random()) * perfMult + increase) * this.partyMult; } - this.avgEne = Math.max(Math.min(this.avgEne, this.maxEne), corpConstants.minEmployeeDecay); - this.avgMor = Math.max(Math.min(this.avgMor, this.maxMor), corpConstants.minEmployeeDecay); + this.avgEnergy = Math.max(Math.min(this.avgEnergy, this.maxEnergy), corpConstants.minEmployeeDecay); + this.avgMorale = Math.max(Math.min(this.avgMorale, this.maxMorale), corpConstants.minEmployeeDecay); this.teaPending = false; this.partyMult = 1; } // Get experience increase; unassigned employees do not contribute, interning employees contribute 10x - this.totalExp += + this.totalExperience += 0.0015 * marketCycles * - (this.totalEmployees - - this.employeeJobs[EmployeePositions.Unassigned] + - this.employeeJobs[EmployeePositions.Intern] * 9); + (this.numEmployees - + this.employeeJobs[CorpEmployeeJob.Unassigned] + + this.employeeJobs[CorpEmployeeJob.Intern] * 9); this.calculateEmployeeProductivity(corporation, industry); - if (this.totalEmployees === 0) { + if (this.numEmployees === 0) { this.totalSalary = 0; } else { this.totalSalary = corpConstants.employeeSalaryMultiplier * marketCycles * - this.totalEmployees * - (this.avgInt + this.avgCha + this.totalExp / this.totalEmployees + this.avgCre + this.avgEff); + this.numEmployees * + (this.avgIntelligence + + this.avgCharisma + + this.totalExperience / this.numEmployees + + this.avgCreativity + + this.avgEfficiency); } return this.totalSalary; } - calculateEmployeeProductivity(corporation: Corporation, industry: Industry): void { - const effCre = this.avgCre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), - effCha = this.avgCha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), - effInt = this.avgInt * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), - effEff = this.avgEff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); - const prodBase = this.avgMor * this.avgEne * 1e-4; + calculateEmployeeProductivity(corporation: Corporation, industry: Division): void { + const effCre = this.avgCreativity * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), + effCha = this.avgCharisma * corporation.getEmployeeChaMult() * industry.getEmployeeChaMultiplier(), + effInt = this.avgIntelligence * corporation.getEmployeeIntMult() * industry.getEmployeeIntMultiplier(), + effEff = this.avgEfficiency * corporation.getEmployeeEffMult() * industry.getEmployeeEffMultiplier(); + const prodBase = this.avgMorale * this.avgEnergy * 1e-4; let total = 0; - const exp = this.totalExp / this.totalEmployees || 0; - for (const name of Object.keys(this.employeeProd) as (EmployeePositions | "total")[]) { + const exp = this.totalExperience / this.numEmployees || 0; + for (const name of getRecordKeys(this.employeeProductionByJob)) { let prodMult = 0; switch (name) { - case EmployeePositions.Operations: + case CorpEmployeeJob.Operations: prodMult = 0.6 * effInt + 0.1 * effCha + exp + 0.5 * effCre + effEff; break; - case EmployeePositions.Engineer: + case CorpEmployeeJob.Engineer: prodMult = effInt + 0.1 * effCha + 1.5 * exp + effEff; break; - case EmployeePositions.Business: + case CorpEmployeeJob.Business: prodMult = 0.4 * effInt + effCha + 0.5 * exp; break; - case EmployeePositions.Management: + case CorpEmployeeJob.Management: prodMult = 2 * effCha + exp + 0.2 * effCre + 0.7 * effEff; break; - case EmployeePositions.RandD: + case CorpEmployeeJob.RandD: prodMult = 1.5 * effInt + 0.8 * exp + effCre + 0.5 * effEff; break; - case EmployeePositions.Unassigned: - case EmployeePositions.Intern: + case CorpEmployeeJob.Unassigned: + case CorpEmployeeJob.Intern: case "total": continue; default: console.error(`Invalid employee position: ${name}`); break; } - this.employeeProd[name] = this.employeeJobs[name] * prodMult * prodBase; - total += this.employeeProd[name]; + this.employeeProductionByJob[name] = this.employeeJobs[name] * prodMult * prodBase; + total += this.employeeProductionByJob[name]; } - this.employeeProd.total = total; + this.employeeProductionByJob.total = total; } - hireRandomEmployee(position: EmployeePositions): boolean { + hireRandomEmployee(position: CorpEmployeeJob): boolean { if (this.atCapacity()) return false; if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) return false; - ++this.totalEmployees; + ++this.numEmployees; ++this.employeeJobs[position]; ++this.employeeNextJobs[position]; - this.totalExp += getRandomInt(50, 100); + this.totalExperience += getRandomInt(50, 100); - this.avgMor = (this.avgMor * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1); - this.avgEne = (this.avgEne * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1); + this.avgMorale = (this.avgMorale * this.numEmployees + getRandomInt(50, 100)) / (this.numEmployees + 1); + this.avgEnergy = (this.avgEnergy * this.numEmployees + getRandomInt(50, 100)) / (this.numEmployees + 1); - this.avgInt = (this.avgInt * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1); - this.avgCha = (this.avgCha * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1); - this.avgCre = (this.avgCre * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1); - this.avgEff = (this.avgEff * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1); + this.avgIntelligence = (this.avgIntelligence * this.numEmployees + getRandomInt(50, 100)) / (this.numEmployees + 1); + this.avgCharisma = (this.avgCharisma * this.numEmployees + getRandomInt(50, 100)) / (this.numEmployees + 1); + this.avgCreativity = (this.avgCreativity * this.numEmployees + getRandomInt(50, 100)) / (this.numEmployees + 1); + this.avgEfficiency = (this.avgEfficiency * this.numEmployees + getRandomInt(50, 100)) / (this.numEmployees + 1); return true; } - autoAssignJob(job: EmployeePositions, target: number): boolean { - if (job === EmployeePositions.Unassigned) { + autoAssignJob(job: CorpEmployeeJob, target: number): boolean { + if (job === CorpEmployeeJob.Unassigned) { throw new Error("internal autoAssignJob function called with EmployeePositions.Unassigned"); } const diff = target - this.employeeNextJobs[job]; if (diff === 0) return true; // We are already at the desired number - else if (diff <= this.employeeNextJobs[EmployeePositions.Unassigned]) { + else if (diff <= this.employeeNextJobs[CorpEmployeeJob.Unassigned]) { // This covers both a negative diff (reducing the amount of employees in position) and a positive (increasing and using up unassigned employees) - this.employeeNextJobs[EmployeePositions.Unassigned] -= diff; + this.employeeNextJobs[CorpEmployeeJob.Unassigned] -= diff; this.employeeNextJobs[job] = target; return true; } @@ -233,11 +214,11 @@ export class OfficeSpace { } getTeaCost(): number { - return corpConstants.teaCostPerEmployee * this.totalEmployees; + return corpConstants.teaCostPerEmployee * this.numEmployees; } setTea(): boolean { - if (!this.teaPending && !this.autoTea && this.totalEmployees > 0) { + if (!this.teaPending && !this.autoTea && this.numEmployees > 0) { this.teaPending = true; return true; } @@ -245,7 +226,7 @@ export class OfficeSpace { } setParty(mult: number): boolean { - if (mult > 1 && this.partyMult === 1 && !this.autoParty && this.totalEmployees > 0) { + if (mult > 1 && this.partyMult === 1 && !this.autoParty && this.numEmployees > 0) { this.partyMult = mult; return true; } @@ -262,10 +243,10 @@ export class OfficeSpace { const empCopy: [{ data: { mor: number; ene: number; exp: number } }] = value.data.employees; delete value.data.employees; const ret = Generic_fromJSON(OfficeSpace, value.data); - ret.totalEmployees = empCopy.length; - ret.avgMor = empCopy.reduce((a, b) => a + b.data.mor, 0) / ret.totalEmployees || 75; - ret.avgEne = empCopy.reduce((a, b) => a + b.data.ene, 0) / ret.totalEmployees || 75; - ret.totalExp = empCopy.reduce((a, b) => a + b.data.exp, 0); + ret.numEmployees = empCopy.length; + ret.avgMorale = empCopy.reduce((a, b) => a + b.data.mor, 0) / ret.numEmployees || 75; + ret.avgEnergy = empCopy.reduce((a, b) => a + b.data.ene, 0) / ret.numEmployees || 75; + ret.totalExperience = empCopy.reduce((a, b) => a + b.data.exp, 0); return ret; } return Generic_fromJSON(OfficeSpace, value.data); diff --git a/src/Corporation/Product.ts b/src/Corporation/Product.ts index 6124b518c..54bdeca0f 100644 --- a/src/Corporation/Product.ts +++ b/src/Corporation/Product.ts @@ -1,144 +1,119 @@ -import { EmployeePositions } from "./data/Enums"; +import { CorpEmployeeJob } from "./data/Enums"; import { MaterialInfo } from "./MaterialInfo"; -import { Industry } from "./Industry"; +import { Division } from "./Division"; import { IndustriesData } from "./IndustryData"; -import { createCityMap } from "../Locations/createCityMap"; - import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver"; import { getRandomInt } from "../utils/helpers/getRandomInt"; import { CityName } from "../Enums"; -import { materialNames } from "./data/Constants"; import { CorpMaterialName } from "@nsdefs"; +import { PartialRecord, createEnumKeyedRecord, getRecordEntries, getRecordKeys } from "../Types/Record"; interface IConstructorParams { - name?: string; - demand?: number; - competition?: number; - markup?: number; - createCity?: CityName; - designCost?: number; - advCost?: number; - quality?: number; - performance?: number; - durability?: number; - reliability?: number; - aesthetics?: number; - features?: number; - loc?: string; - size?: number; - req?: Partial>; + name: string; + createCity: CityName; + designInvestment: number; + advertisingInvestment: number; } +/** A corporation product. Products are shared across the entire division, unlike materials which are per-warehouse */ export class Product { - // Product name - name = ""; + /** Name of the product */ + name = "DefaultProductName"; - // The demand for this Product in the market. Gradually decreases - dmd = 0; + /** Demand for this product, which goes down over time. */ + demand = 0; - // How much competition there is in the market for this Product - cmp = 0; + /** Competition for this product */ + competition = 0; - // Markup. Affects how high of a price you can charge for this Product - // without suffering a loss in the # of sales - mku = 0; + /** Markup. Affects how high of a price you can charge for this Product + without suffering a loss in the # of sales */ + markup = 0; - // Production cost - estimation of how much money it costs to make this Product - pCost = 0; + /** Cost of producing this product if buying its component materials at market price */ + productionCost = 0; - // Sell costs - sCost: Record = createCityMap(0); - - // Variables for handling the creation process of this Product - fin = false; // Whether this Product has finished being created - prog = 0; // Creation progress - A number between 0-100 representing percentage - createCity = CityName.Sector12; // City in which the product is/was being created - designCost = 0; // How much money was invested into designing this Product - advCost = 0; // How much money was invested into advertising this Product + /** Whether the development for this product is finished yet */ + finished = false; + developmentProgress = 0; // Creation progress - A number between 0-100 representing percentage + creationCity = CityName.Sector12; // City in which the product is/was being created + designInvestment = 0; // How much money was invested into designing this Product + advertisingInvestment = 0; // How much money was invested into advertising this Product // The average employee productivity and scientific research across the creation of the Product - creationProd: Record = { - [EmployeePositions.Operations]: 0, - [EmployeePositions.Engineer]: 0, - [EmployeePositions.Business]: 0, - [EmployeePositions.Management]: 0, - [EmployeePositions.RandD]: 0, + creationJobFactors = { + [CorpEmployeeJob.Operations]: 0, + [CorpEmployeeJob.Engineer]: 0, + [CorpEmployeeJob.Business]: 0, + [CorpEmployeeJob.Management]: 0, + [CorpEmployeeJob.RandD]: 0, total: 0, }; // Aggregate score for this Product's 'rating' // This is based on the stats/properties below. The weighting of the // stats/properties below differs between different industries - rat = 0; + rating = 0; - // Stats/properties of this Product - qlt = 0; - per = 0; - dur = 0; - rel = 0; - aes = 0; - fea = 0; + /** Stats of the product */ + stats = { + quality: 0, + performance: 0, + durability: 0, + reliability: 0, + aesthetics: 0, + features: 0, + }; - // Data refers to the production, sale, and quantity of the products - // These values are specific to a city - // For each city, the data is [qty, prod, sell, effRat] - data: Record = createCityMap([0, 0, 0, 0]); + // data that is stored per city + cityData = createEnumKeyedRecord(CityName, () => ({ + /** Amount of product stored in warehouse */ + stored: 0, + /** Amount of this product produced per cycle in this city */ + productionAmount: 0, + /** Amount of this product that was sold last cycle in this city */ + actualSellAmount: 0, + /** Total effective rating of the product in this city */ + effectiveRating: 0, + /** Manual limit on production amount for the product in this city*/ + productionLimit: null as number | null, + /** Player input sell amount e.g. "MAX" */ + desiredSellAmount: 0 as number | string, + /** Player input sell price e.g. "MP * 5" */ + desiredSellPrice: 0 as number | string, + })); - // Location of this Product - // Only applies for location-based products like restaurants/hospitals - loc = ""; + /** How much warehouse space is occupied per unit of this product */ + size = 0; - // How much space 1 unit of the Product takes (in the warehouse) - // Not applicable for all Products - siz = 0; - - // Material requirements. An object that maps the name of a material to how much it requires - // to make 1 unit of the product. - reqMats: Partial> = {}; - - // Data to keep track of whether production/sale of this Product is - // manually limited. These values are specific to a city - // [Whether production/sale is limited, limit amount] - prdman: Record = createCityMap([false, 0]); - sllman: Record = createCityMap([false, 0]); + /** Required materials per unit of this product */ + requiredMaterials: PartialRecord = {}; // Flags that signal whether automatic sale pricing through Market TA is enabled marketTa1 = false; marketTa2 = false; - marketTa2Price: Record = createCityMap(0); + marketTa2Price = createEnumKeyedRecord(CityName, () => 0); - // Determines the maximum amount of this product that can be sold in one market cycle - maxsll = 0; - constructor(params: IConstructorParams = {}) { - this.name = params.name ? params.name : ""; - this.dmd = params.demand ? params.demand : 0; - this.cmp = params.competition ? params.competition : 0; - this.mku = params.markup ? params.markup : 0; - this.createCity = params.createCity ? params.createCity : CityName.Sector12; - this.designCost = params.designCost ? params.designCost : 0; - this.advCost = params.advCost ? params.advCost : 0; - this.qlt = params.quality ? params.quality : 0; - this.per = params.performance ? params.performance : 0; - this.dur = params.durability ? params.durability : 0; - this.rel = params.reliability ? params.reliability : 0; - this.aes = params.aesthetics ? params.aesthetics : 0; - this.fea = params.features ? params.features : 0; - this.loc = params.loc ? params.loc : ""; - this.siz = params.size ? params.size : 0; - this.reqMats = params.req ? params.req : {}; + /** Effective number that "MAX" represents in a sell amount */ + maxSellAmount = 0; + + constructor(params: IConstructorParams | null = null) { + if (!params) return; + this.name = params.name; + this.creationCity = params.createCity; + this.designInvestment = params.designInvestment; + this.advertisingInvestment = params.advertisingInvestment; } // Make progress on this product based on current employee productivity - createProduct(marketCycles: number, employeeProd: typeof this.creationProd): void { - if (this.fin) { - return; - } + createProduct(marketCycles: number, employeeProd: typeof Product.prototype.creationJobFactors): void { + if (this.finished) return; // Designing/Creating a Product is based mostly off Engineers - const opProd = employeeProd[EmployeePositions.Operations]; - const engrProd = employeeProd[EmployeePositions.Engineer]; - const mgmtProd = employeeProd[EmployeePositions.Management]; + const opProd = employeeProd[CorpEmployeeJob.Operations]; + const engrProd = employeeProd[CorpEmployeeJob.Engineer]; + const mgmtProd = employeeProd[CorpEmployeeJob.Management]; const total = opProd + engrProd + mgmtProd; if (total <= 0) { return; @@ -147,119 +122,104 @@ export class Product { // Management is a multiplier for the production from Engineers const mgmtFactor = 1 + mgmtProd / (1.2 * total); const prodMult = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor; - const progress = Math.min(marketCycles * 0.01 * prodMult, 100 - this.prog); + const progress = Math.min(marketCycles * 0.01 * prodMult, 100 - this.developmentProgress); if (progress <= 0) { return; } - this.prog += progress; - for (const pos of Object.keys(employeeProd)) { - this.creationProd[pos] += (employeeProd[pos] * progress) / 100; + this.developmentProgress += progress; + for (const pos of getRecordKeys(employeeProd)) { + this.creationJobFactors[pos] += (employeeProd[pos] * progress) / 100; } } // @param industry - Industry object. Reference to industry that makes this Product - finishProduct(industry: Industry): void { - this.fin = true; + finishProduct(industry: Division): void { + this.finished = true; // Calculate properties - const totalProd = this.creationProd.total; - const engrRatio = this.creationProd[EmployeePositions.Engineer] / totalProd; - const mgmtRatio = this.creationProd[EmployeePositions.Management] / totalProd; - const rndRatio = this.creationProd[EmployeePositions.RandD] / totalProd; - const opsRatio = this.creationProd[EmployeePositions.Operations] / totalProd; - const busRatio = this.creationProd[EmployeePositions.Business] / totalProd; + const totalProd = this.creationJobFactors.total; + const engrRatio = this.creationJobFactors[CorpEmployeeJob.Engineer] / totalProd; + const mgmtRatio = this.creationJobFactors[CorpEmployeeJob.Management] / totalProd; + const rndRatio = this.creationJobFactors[CorpEmployeeJob.RandD] / totalProd; + const opsRatio = this.creationJobFactors[CorpEmployeeJob.Operations] / totalProd; + const busRatio = this.creationJobFactors[CorpEmployeeJob.Business] / totalProd; - const designMult = 1 + Math.pow(this.designCost, 0.1) / 100; + const designMult = 1 + Math.pow(this.designInvestment, 0.1) / 100; const balanceMult = 1.2 * engrRatio + 0.9 * mgmtRatio + 1.3 * rndRatio + 1.5 * opsRatio + busRatio; - const sciMult = 1 + Math.pow(industry.sciResearch, industry.sciFac) / 800; + const sciMult = 1 + Math.pow(industry.researchPoints, industry.researchFactor) / 800; const totalMult = balanceMult * designMult * sciMult; - this.qlt = + this.stats.quality = totalMult * - (0.1 * this.creationProd[EmployeePositions.Engineer] + - 0.05 * this.creationProd[EmployeePositions.Management] + - 0.05 * this.creationProd[EmployeePositions.RandD] + - 0.02 * this.creationProd[EmployeePositions.Operations] + - 0.02 * this.creationProd[EmployeePositions.Business]); - this.per = + (0.1 * this.creationJobFactors[CorpEmployeeJob.Engineer] + + 0.05 * this.creationJobFactors[CorpEmployeeJob.Management] + + 0.05 * this.creationJobFactors[CorpEmployeeJob.RandD] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.Operations] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.Business]); + this.stats.performance = totalMult * - (0.15 * this.creationProd[EmployeePositions.Engineer] + - 0.02 * this.creationProd[EmployeePositions.Management] + - 0.02 * this.creationProd[EmployeePositions.RandD] + - 0.02 * this.creationProd[EmployeePositions.Operations] + - 0.02 * this.creationProd[EmployeePositions.Business]); - this.dur = + (0.15 * this.creationJobFactors[CorpEmployeeJob.Engineer] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.Management] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.RandD] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.Operations] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.Business]); + this.stats.durability = totalMult * - (0.05 * this.creationProd[EmployeePositions.Engineer] + - 0.02 * this.creationProd[EmployeePositions.Management] + - 0.08 * this.creationProd[EmployeePositions.RandD] + - 0.05 * this.creationProd[EmployeePositions.Operations] + - 0.05 * this.creationProd[EmployeePositions.Business]); - this.rel = + (0.05 * this.creationJobFactors[CorpEmployeeJob.Engineer] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.Management] + + 0.08 * this.creationJobFactors[CorpEmployeeJob.RandD] + + 0.05 * this.creationJobFactors[CorpEmployeeJob.Operations] + + 0.05 * this.creationJobFactors[CorpEmployeeJob.Business]); + this.stats.reliability = totalMult * - (0.02 * this.creationProd[EmployeePositions.Engineer] + - 0.08 * this.creationProd[EmployeePositions.Management] + - 0.02 * this.creationProd[EmployeePositions.RandD] + - 0.05 * this.creationProd[EmployeePositions.Operations] + - 0.08 * this.creationProd[EmployeePositions.Business]); - this.aes = + (0.02 * this.creationJobFactors[CorpEmployeeJob.Engineer] + + 0.08 * this.creationJobFactors[CorpEmployeeJob.Management] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.RandD] + + 0.05 * this.creationJobFactors[CorpEmployeeJob.Operations] + + 0.08 * this.creationJobFactors[CorpEmployeeJob.Business]); + this.stats.aesthetics = totalMult * - (0.0 * this.creationProd[EmployeePositions.Engineer] + - 0.08 * this.creationProd[EmployeePositions.Management] + - 0.05 * this.creationProd[EmployeePositions.RandD] + - 0.02 * this.creationProd[EmployeePositions.Operations] + - 0.1 * this.creationProd[EmployeePositions.Business]); - this.fea = + (0.0 * this.creationJobFactors[CorpEmployeeJob.Engineer] + + 0.08 * this.creationJobFactors[CorpEmployeeJob.Management] + + 0.05 * this.creationJobFactors[CorpEmployeeJob.RandD] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.Operations] + + 0.1 * this.creationJobFactors[CorpEmployeeJob.Business]); + this.stats.features = totalMult * - (0.08 * this.creationProd[EmployeePositions.Engineer] + - 0.05 * this.creationProd[EmployeePositions.Management] + - 0.02 * this.creationProd[EmployeePositions.RandD] + - 0.05 * this.creationProd[EmployeePositions.Operations] + - 0.05 * this.creationProd[EmployeePositions.Business]); + (0.08 * this.creationJobFactors[CorpEmployeeJob.Engineer] + + 0.05 * this.creationJobFactors[CorpEmployeeJob.Management] + + 0.02 * this.creationJobFactors[CorpEmployeeJob.RandD] + + 0.05 * this.creationJobFactors[CorpEmployeeJob.Operations] + + 0.05 * this.creationJobFactors[CorpEmployeeJob.Business]); this.calculateRating(industry); - const advMult = 1 + Math.pow(this.advCost, 0.1) / 100; + const advMult = 1 + Math.pow(this.advertisingInvestment, 0.1) / 100; const busmgtgRatio = Math.max(busRatio + mgmtRatio, 1 / totalProd); - this.mku = 100 / (advMult * Math.pow(this.qlt + 0.001, 0.65) * busmgtgRatio); + this.markup = 100 / (advMult * Math.pow(this.stats.quality + 0.001, 0.65) * busmgtgRatio); // I actually don't understand well enough to know if this is right. // I'm adding this to prevent a crash. - if (this.mku === 0 || !isFinite(this.mku)) this.mku = 1; + if (this.markup === 0 || !isFinite(this.markup)) this.markup = 1; - this.dmd = + this.demand = industry.awareness === 0 ? 20 : Math.min(100, advMult * (100 * (industry.popularity / industry.awareness))); - this.cmp = getRandomInt(0, 70); + this.competition = getRandomInt(0, 70); - //Calculate the product's required materials - //For now, just set it to be the same as the requirements to make materials - for (const matName of Object.keys(industry.reqMats) as CorpMaterialName[]) { - if (Object.hasOwn(industry.reqMats, matName)) { - const reqMat = industry.reqMats[matName]; - if (reqMat === undefined) continue; - this.reqMats[matName] = reqMat; - } - } - - //Calculate the product's size - //For now, just set it to be the same size as the requirements to make materials - this.siz = 0; - for (const matName of Object.values(materialNames)) { - const reqMat = industry.reqMats[matName]; - if (reqMat === undefined) continue; - this.siz += MaterialInfo[matName].size * reqMat; + //Calculate the product's required materials and size + this.size = 0; + for (const [matName, reqQty] of getRecordEntries(industry.requiredMaterials)) { + this.requiredMaterials[matName] = reqQty; + this.size += MaterialInfo[matName].size * reqQty; } } - calculateRating(industry: Industry): void { + calculateRating(industry: Division): void { const weights = IndustriesData[industry.type].product?.ratingWeights; if (!weights) return console.error(`Could not find product rating weights for: ${industry}`); - this.rat = 0; - this.rat += weights.quality ? this.qlt * weights.quality : 0; - this.rat += weights.performance ? this.per * weights.performance : 0; - this.rat += weights.durability ? this.dur * weights.durability : 0; - this.rat += weights.reliability ? this.rel * weights.reliability : 0; - this.rat += weights.aesthetics ? this.aes * weights.aesthetics : 0; - this.rat += weights.features ? this.fea * weights.features : 0; + this.rating = getRecordEntries(weights).reduce( + (total, [statName, weight]) => total + this.stats[statName] * weight, + 0, + ); } // Serialize the current object to a JSON save state. @@ -269,18 +229,6 @@ export class Product { // Initializes a Product object from a JSON save state. static fromJSON(value: IReviverValue): Product { - // TODO: Remove all corp graceful loading measures during major corp rebalance / rework. - // For that version, Player.corporation will just get reset to null when loading from older version. - - // Gracefully load saves from when RealEstate and AICores didn't have spaces - if (value.data.reqMats?.RealEstate) { - value.data.reqMats["Real Estate"] = value.data.reqMats.RealEstate; - delete value.data.reqMats.RealEstate; - } - if (value.data.reqMats?.AICores) { - value.data.reqMats["AI Cores"] = value.data.reqMats.AICores; - delete value.data.reqMats.AICores; - } return Generic_fromJSON(Product, value.data); } } diff --git a/src/Corporation/Research.ts b/src/Corporation/Research.ts index 3e6faeff3..a8ca9b5b6 100644 --- a/src/Corporation/Research.ts +++ b/src/Corporation/Research.ts @@ -1,7 +1,6 @@ import { CorpResearchName } from "@nsdefs"; -import { researchNames } from "./data/Constants"; -export interface IConstructorParams { +export interface ResearchParams { name: CorpResearchName; cost: number; desc: string; @@ -19,13 +18,13 @@ export interface IConstructorParams { export class Research { // Name of research. This will be used to identify researches in the Research Tree - name: CorpResearchName; + name: CorpResearchName = "AutoBrew"; // How much scientific research it costs to unlock this cost = 0; // Description of what the Research does - desc = ""; + description = ""; // All possible generic upgrades for the company, in the form of multipliers advertisingMult = 1; @@ -39,39 +38,20 @@ export class Research { sciResearchMult = 1; storageMult = 1; - constructor(p: IConstructorParams = { name: researchNames[0], cost: 0, desc: "" }) { + constructor(p: ResearchParams | null = null) { + if (!p) return; this.name = p.name; this.cost = p.cost; - this.desc = p.desc; - if (p.advertisingMult) { - this.advertisingMult = p.advertisingMult; - } - if (p.employeeChaMult) { - this.employeeChaMult = p.employeeChaMult; - } - if (p.employeeCreMult) { - this.employeeCreMult = p.employeeCreMult; - } - if (p.employeeEffMult) { - this.employeeEffMult = p.employeeEffMult; - } - if (p.employeeIntMult) { - this.employeeIntMult = p.employeeIntMult; - } - if (p.productionMult) { - this.productionMult = p.productionMult; - } - if (p.productProductionMult) { - this.productProductionMult = p.productProductionMult; - } - if (p.salesMult) { - this.salesMult = p.salesMult; - } - if (p.sciResearchMult) { - this.sciResearchMult = p.sciResearchMult; - } - if (p.storageMult) { - this.storageMult = p.storageMult; - } + this.description = p.desc; + this.advertisingMult = p.advertisingMult ?? 1; + this.employeeChaMult = p.employeeChaMult ?? 1; + this.employeeCreMult = p.employeeCreMult ?? 1; + this.employeeEffMult = p.employeeEffMult ?? 1; + this.employeeIntMult = p.employeeIntMult ?? 1; + this.productionMult = p.productionMult ?? 1; + this.productProductionMult = p.productProductionMult ?? 1; + this.salesMult = p.salesMult ?? 1; + this.sciResearchMult = p.sciResearchMult ?? 1; + this.storageMult = p.storageMult ?? 1; } } diff --git a/src/Corporation/ResearchMap.ts b/src/Corporation/ResearchMap.ts index 606faf3d7..a41ec8a05 100644 --- a/src/Corporation/ResearchMap.ts +++ b/src/Corporation/ResearchMap.ts @@ -1,17 +1,189 @@ -// The Research Map is an object that holds all Corporation Research objects -// as values. They are identified by their names -import { Research, IConstructorParams } from "./Research"; -import { researchMetadata } from "./data/ResearchMetadata"; +import { Research } from "./Research"; +import { CorpResearchName } from "@nsdefs"; -export const ResearchMap: Record = {}; +// A full record ensures that every research name is present +/** A record for looking up a research object from the name */ +export const ResearchMap: Record = { + AutoBrew: new Research({ + name: "AutoBrew", + cost: 12e3, + desc: + "Automatically keep your employees fully caffeinated with " + + "tea injections. This research will keep the energy of all " + + "employees at its maximum possible value, for no cost. " + + "This will also disable the Tea upgrade.", + }), + AutoPartyManager: new Research({ + name: "AutoPartyManager", + cost: 15e3, + desc: + "Automatically analyzes your employees' morale " + + "and boosts them whenever it detects a decrease. This research will " + + "keep the morale of all employees at their maximum possible " + + "values, for no cost. " + + "This will also disable the 'Throw Party' feature.", + }), + "Automatic Drug Administration": new Research({ + name: "Automatic Drug Administration", + cost: 10e3, + desc: + "Research how to automatically administer performance-enhancing drugs to all of " + + "your employees. This unlocks Drug-related Research.", + }), + "CPH4 Injections": new Research({ + name: "CPH4 Injections", + cost: 25e3, + desc: + "Develop an advanced and harmless synthetic drug that is administered to " + + "employees to increase all of their stats, except experience, by 10%.", + employeeCreMult: 1.1, + employeeChaMult: 1.1, + employeeEffMult: 1.1, + employeeIntMult: 1.1, + }), + Drones: new Research({ + name: "Drones", + cost: 5e3, + desc: + "Acquire the knowledge needed to create advanced drones. This research does nothing " + + "by itself, but unlocks other Drone-related research.", + }), + "Drones - Assembly": new Research({ + name: "Drones - Assembly", + cost: 25e3, + desc: + "Manufacture and use Assembly Drones to improve the efficiency of " + + "your production lines. This increases all production by 20%.", + productionMult: 1.2, + }), + "Drones - Transport": new Research({ + name: "Drones - Transport", + cost: 30e3, + desc: + "Manufacture and use intelligent Transport Drones to optimize " + + "your warehouses. This increases the storage space of all warehouses " + + "by 50%.", + storageMult: 1.5, + }), + "Go-Juice": new Research({ + name: "Go-Juice", + cost: 25e3, + desc: + "Provide employees with Go-Juice, a tea-derivative that further enhances " + + "the brain's dopamine production. This increases the maximum energy of all " + + "employees by 10.", + }), + "HRBuddy-Recruitment": new Research({ + name: "HRBuddy-Recruitment", + cost: 15e3, + desc: + "Use automated software to handle the hiring of employees. With this " + + "research, each office will automatically hire one employee per " + + "market cycle if there is available space.", + }), + "HRBuddy-Training": new Research({ + name: "HRBuddy-Training", + cost: 20e3, + desc: + "Use automated software to handle the training of employees. With this " + + "research, each employee hired with HRBuddy-Recruitment will automatically " + + "be assigned to 'Intern', rather than being unassigned.", + }), + "Hi-Tech R&D Laboratory": new Research({ + name: "Hi-Tech R&D Laboratory", + cost: 5e3, + desc: + "Construct a cutting-edge facility dedicated to advanced research and " + + "development. This allows you to spend Scientific Research " + + "on powerful upgrades. It also globally increases Scientific Research " + + "production by 10%.", + sciResearchMult: 1.1, + }), -function addResearch(p: IConstructorParams): void { - if (ResearchMap[p.name] != null) { - console.warn(`Duplicate Research being defined: ${p.name}`); - } - ResearchMap[p.name] = new Research(p); -} - -for (const metadata of researchMetadata) { - addResearch(metadata); -} + "Market-TA.I": new Research({ + name: "Market-TA.I", + cost: 20e3, + desc: + "Develop advanced AI software that uses technical analysis to " + + "help you understand and exploit the market. This research " + + "allows you to know what price to sell your Materials/Products " + + "at in order to avoid losing sales due to having too high of a mark-up. " + + "It also lets you automatically use that sale price.", + }), + "Market-TA.II": new Research({ + name: "Market-TA.II", + cost: 50e3, + desc: + "Develop double-advanced AI software that uses technical analysis to " + + "help you understand and exploit the market. This research " + + "allows you to know how many sales of a Material/Product you lose or gain " + + "from having too high or too low of a sale price. It also lets you automatically " + + "set the sale price of your Materials/Products at the optimal price such that " + + "the amount sold matches the amount produced.", + }), + Overclock: new Research({ + name: "Overclock", + cost: 15e3, + desc: + "Equip employees with a headset that uses transcranial direct current " + + "stimulation (tDCS) to increase the speed of their neurotransmitters. " + + "This research increases the intelligence and efficiency of all " + + "employees by 25%.", + employeeEffMult: 1.25, + employeeIntMult: 1.25, + }), + "Self-Correcting Assemblers": new Research({ + name: "Self-Correcting Assemblers", + cost: 25e3, + desc: + "Create assemblers that can be used for universal production. " + + "These assemblers use deep learning to improve their efficiency " + + "at their tasks. This research increases all production by 10%.", + productionMult: 1.1, + }), + "Sti.mu": new Research({ + name: "Sti.mu", + cost: 30e3, + desc: + "Upgrade the tDCS headset to stimulate regions of the brain that " + + "control confidence and enthusiasm. This research increases the maximum " + + "morale of all employees by 10.", + }), + "sudo.Assist": new Research({ + name: "sudo.Assist", + cost: 15e3, + desc: "Develop a virtual assistant AI to handle and manage administrative issues for your corporation.", + }), + "uPgrade: Capacity.I": new Research({ + name: "uPgrade: Capacity.I", + cost: 20e3, + desc: + "Expand the industry's capacity for designing and manufacturing its " + + "various products. This increases the industry's maximum number of products " + + "by 1 (from 3 to 4).", + }), + "uPgrade: Capacity.II": new Research({ + name: "uPgrade: Capacity.II", + cost: 30e3, + desc: + "Expand the industry's capacity for designing and manufacturing its " + + "various products. This increases the industry's maximum number of products " + + "by 1 (from 4 to 5).", + }), + "uPgrade: Dashboard": new Research({ + name: "uPgrade: Dashboard", + cost: 5e3, + desc: + "Improve the software used to manage the industry's production line " + + "for its various products. This allows you to manage the production and " + + "sale of a product before it's finished being designed.", + }), + "uPgrade: Fulcrum": new Research({ + name: "uPgrade: Fulcrum", + cost: 10e3, + desc: + "Streamline the manufacturing of this industry's various products. " + + "This research increases the production of your products by 5%.", + productProductionMult: 1.05, + }), +}; diff --git a/src/Corporation/ResearchTree.ts b/src/Corporation/ResearchTree.ts index b915b738d..11d830fbb 100644 --- a/src/Corporation/ResearchTree.ts +++ b/src/Corporation/ResearchTree.ts @@ -4,14 +4,13 @@ // not an actual Research object. The name can be used to obtain a reference // to the corresponding Research object using the ResearchMap import { CorpResearchName } from "@nsdefs"; -import { researchNames } from "./data/Constants"; import { Research } from "./Research"; import { ResearchMap } from "./ResearchMap"; interface IConstructorParams { children?: Node[]; cost: number; - text: CorpResearchName; + researchName: CorpResearchName; parent?: Node | null; } @@ -34,14 +33,10 @@ export class Node { parent: Node | null = null; // Name of the Research held in this Node - text: CorpResearchName; + researchName: CorpResearchName; - constructor(p: IConstructorParams = { cost: 0, text: researchNames[0] }) { - if (ResearchMap[p.text] == null) { - throw new Error(`Invalid Research name used when constructing ResearchTree Node: ${p.text}`); - } - - this.text = p.text; + constructor(p: IConstructorParams) { + this.researchName = p.researchName; this.cost = p.cost; if (p.children && p.children.length > 0) { @@ -59,16 +54,16 @@ export class Node { } // Recursive function for finding a Node with the specified text - findNode(text: string): Node | null { + findNode(name: CorpResearchName): Node | null { // Is this the Node? - if (this.text === text) { + if (this.researchName === name) { return this; } // Recursively search children let res = null; for (let i = 0; i < this.children.length; ++i) { - res = this.children[i].findNode(text); + res = this.children[i].findNode(name); if (res != null) { return res; } @@ -86,7 +81,7 @@ export class Node { // The root node in a Research Tree must always be the "Hi-Tech R&D Laboratory" export class ResearchTree { // Object containing names of all acquired Research by name - researched: Record = {}; + researched = new Set(); // Root Node root: Node | null = null; @@ -107,7 +102,7 @@ export class ResearchTree { continue; } - res.push(node.text); + res.push(node.researchName); for (let i = 0; i < node.children.length; ++i) { queue.push(node.children[i]); } @@ -175,11 +170,11 @@ export class ResearchTree { continue; } - const research: Research | null = ResearchMap[node.text]; + const research: Research | null = ResearchMap[node.researchName]; // Safety checks if (research == null) { - console.warn(`Invalid Research name in node: ${node.text}`); + console.warn(`Invalid Research name in node: ${node.researchName}`); continue; } @@ -197,7 +192,7 @@ export class ResearchTree { storageMult: research.storageMult, }[propName] ?? null; - if (mult == null) { + if (mult === null) { console.warn(`Invalid propName specified in ResearchTree.getMultiplierHelper: ${propName}`); continue; } @@ -230,13 +225,11 @@ export class ResearchTree { queue.push(this.root); while (queue.length !== 0) { const node: Node | undefined = queue.shift(); - if (node == null) { - continue; - } + if (!node) continue; - if (node.text === name) { + if (node.researchName === name) { node.researched = true; - this.researched[name] = true; + this.researched.add(name); return; } diff --git a/src/Corporation/Warehouse.ts b/src/Corporation/Warehouse.ts index e20fe1f6e..f34bbe489 100644 --- a/src/Corporation/Warehouse.ts +++ b/src/Corporation/Warehouse.ts @@ -1,18 +1,18 @@ import { Material } from "./Material"; import { Corporation } from "./Corporation"; -import { Industry } from "./Industry"; +import { Division } from "./Division"; import { MaterialInfo } from "./MaterialInfo"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver"; -import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { CityName } from "../Enums"; -import { CorpMaterialName } from "@nsdefs"; import { materialNames } from "./data/Constants"; +import { createFullRecordFromEntries, getRecordEntries } from "../Types/Record"; +import { CorpUnlockName } from "./data/Enums"; +import { Player } from "@player"; interface IConstructorParams { - corp?: Corporation; - industry?: Industry; - loc?: CityName; - size?: number; + division: Division; + loc: CityName; + size: number; } export class Warehouse { @@ -20,13 +20,13 @@ export class Warehouse { level = 1; // City that this Warehouse is in - loc: CityName; + city = CityName.Sector12; // Map of Materials held by this Warehouse - materials: Record; + materials = createFullRecordFromEntries(materialNames.map((matName) => [matName, new Material({ name: matName })])); // Maximum amount warehouse can hold - size: number; + size = 0; // Amount of space currently used by warehouse sizeUsed = 0; @@ -35,30 +35,22 @@ export class Warehouse { smartSupplyEnabled = false; // Decide if smart supply should use the amount of materials imported into account when deciding on the amount to buy. - smartSupplyOptions: Record; + smartSupplyOptions = createFullRecordFromEntries(materialNames.map((matName) => [matName, "leftovers"])); // Stores the amount of product to be produced. Used for Smart Supply unlock. // The production tracked by smart supply is always based on the previous cycle, // so it will always trail the "true" production by 1 cycle smartSupplyStore = 0; - constructor(params: IConstructorParams = {}) { - this.loc = params.loc ? params.loc : CityName.Sector12; - this.size = params.size ? params.size : 0; - - this.materials = {} as Record; - this.smartSupplyOptions = {} as Record; - for (const matName of materialNames) { - this.materials[matName] = new Material({ name: matName }); - this.smartSupplyOptions[matName] = "leftovers"; - } - - if (params.corp && params.industry) { - this.updateSize(params.corp, params.industry); - } + constructor(params: IConstructorParams | null = null) { + const corp = Player.corporation; + if (!corp || params === null) return; + this.city = params.loc; + this.size = params.size; + this.updateSize(corp, params.division); // Default smart supply to being enabled if the upgrade is unlocked - if (params.corp?.unlockUpgrades[1]) { + if (corp.unlocks.has(CorpUnlockName.SmartSupply)) { this.smartSupplyEnabled = true; } } @@ -66,21 +58,16 @@ export class Warehouse { // Re-calculate how much space is being used by this Warehouse updateMaterialSizeUsed(): void { this.sizeUsed = 0; - for (const matName of Object.values(materialNames)) { - const mat = this.materials[matName]; - this.sizeUsed += mat.qty * MaterialInfo[matName].size; + for (const [matName, mat] of getRecordEntries(this.materials)) { + this.sizeUsed += mat.stored * MaterialInfo[matName].size; } if (this.sizeUsed > this.size) { console.warn("Warehouse size used greater than capacity, something went wrong"); } } - updateSize(corporation: Corporation, industry: Industry): void { - try { - this.size = this.level * 100 * corporation.getStorageMultiplier() * industry.getStorageMultiplier(); - } catch (e: unknown) { - exceptionAlert(e); - } + updateSize(corporation: Corporation, division: Division): void { + this.size = this.level * 100 * corporation.getStorageMultiplier() * division.getStorageMultiplier(); } // Serialize the current object to a JSON save state. @@ -90,19 +77,6 @@ export class Warehouse { // Initializes a Warehouse object from a JSON save state. static fromJSON(value: IReviverValue): Warehouse { - //Gracefully load saves where AICores and RealEstate material names sometimes did not use spaces - if (value.data?.materials?.AICores) { - value.data.materials["AI Cores"] = value.data.materials.AICores; - value.data.smartSupplyUseLeftovers["AI Cores"] = value.data.smartSupplyUseLeftovers.AICores; - delete value.data.materials.AICores; - delete value.data.smartSupplyUseLeftovers.AICores; - } - if (value.data?.materials?.RealEstate) { - value.data.materials["Real Estate"] = value.data.materials.RealEstate; - value.data.smartSupplyUseLeftovers["Real Estate"] = value.data.smartSupplyUseLeftovers.RealEstate; - delete value.data.materials.RealEstate; - delete value.data.smartSupplyUseLeftovers.RealEstate; - } return Generic_fromJSON(Warehouse, value.data); } } diff --git a/src/Corporation/data/BaseResearchTree.ts b/src/Corporation/data/BaseResearchTree.ts index f839431ee..cf8cb4fbf 100644 --- a/src/Corporation/data/BaseResearchTree.ts +++ b/src/Corporation/data/BaseResearchTree.ts @@ -1,16 +1,13 @@ // Defines the ResearchTree that is common to all Corporation Industries // i.e. all Industries have these types of Research available to unlock +import { CorpResearchName } from "@nsdefs"; import { Research } from "../Research"; import { ResearchMap } from "../ResearchMap"; import { ResearchTree, Node } from "../ResearchTree"; -function makeNode(name: string): Node { - const research: Research | null = ResearchMap[name]; - if (research == null) { - throw new Error(`Invalid research name: ${name}`); - } - - return new Node({ text: research.name, cost: research.cost }); +function makeNode(name: CorpResearchName): Node { + const research: Research = ResearchMap[name]; + return new Node({ researchName: research.name, cost: research.cost }); } // Creates the Nodes for the BaseResearchTree. diff --git a/src/Corporation/data/Constants.ts b/src/Corporation/data/Constants.ts index b5823560c..ccc1f9864 100644 --- a/src/Corporation/data/Constants.ts +++ b/src/Corporation/data/Constants.ts @@ -8,7 +8,8 @@ import { CorpUpgradeName, } from "@nsdefs"; import { CONSTANTS } from "../../Constants"; -import { IndustryType, EmployeePositions } from "./Enums"; +import { IndustryType, CorpEmployeeJob } from "./Enums"; +import { PositiveInteger } from "../../types"; // For typed strings, we need runtime objects to do API typechecking against. @@ -18,7 +19,7 @@ import { IndustryType, EmployeePositions } from "./Enums"; export const stateNames: CorpStateName[] = ["START", "PURCHASE", "PRODUCTION", "EXPORT", "SALE"], // TODO: remove IndustryType and EmployeePositions enums and just use the typed strings. /** Names of all corporation employee positions */ - employeePositions: CorpEmployeePosition[] = Object.values(EmployeePositions), + employeePositions: CorpEmployeePosition[] = Object.values(CorpEmployeeJob), /** Names of all industries. */ industryNames: CorpIndustryName[] = Object.values(IndustryType), /** Names of all materials */ @@ -66,7 +67,6 @@ export const stateNames: CorpStateName[] = ["START", "PURCHASE", "PRODUCTION", " "AutoBrew", "AutoPartyManager", "Automatic Drug Administration", - "Bulk Purchasing", "CPH4 Injections", "Drones", "Drones - Assembly", @@ -123,19 +123,11 @@ export const stateNames: CorpStateName[] = ["START", "PURCHASE", "PRODUCTION", " minEmployeeDecay = 10, /**smart supply ot */ smartSupplyUseOptions = ["leftovers", "imports", "none"], - PurchaseMultipliers: { - [key: string]: number | "MAX" | undefined; - x1: number; - x5: number; - x10: number; - x50: number; - x100: number; - MAX: "MAX"; - } = { - x1: 1, - x5: 5, - x10: 10, - x50: 50, - x100: 100, - MAX: "MAX", + PurchaseMultipliers = { + x1: 1 as PositiveInteger, + x5: 5 as PositiveInteger, + x10: 10 as PositiveInteger, + x50: 50 as PositiveInteger, + x100: 100 as PositiveInteger, + MAX: "MAX" as const, }; diff --git a/src/Corporation/data/CorporationUnlockUpgrades.ts b/src/Corporation/data/CorporationUnlocks.ts similarity index 60% rename from src/Corporation/data/CorporationUnlockUpgrades.ts rename to src/Corporation/data/CorporationUnlocks.ts index 6a647b417..db79f55dc 100644 --- a/src/Corporation/data/CorporationUnlockUpgrades.ts +++ b/src/Corporation/data/CorporationUnlocks.ts @@ -1,100 +1,85 @@ -export interface CorporationUnlockUpgrade { - index: number; - price: number; - name: string; - desc: string; -} +import { CorpUnlockName } from "./Enums"; -export enum CorporationUnlockUpgradeIndex { - Export = 0, - SmartSupply = 1, - MarketResearchDemand = 2, - MarketDataCompetition = 3, - VeChain = 4, - ShadyAccounting = 5, - GovernmentPartnership = 6, - WarehouseAPI = 7, - OfficeAPI = 8, +export interface CorpUnlock { + name: CorpUnlockName; + price: number; + desc: string; } // Corporation Unlock Upgrades // Upgrades for entire corporation, unlocks features, either you have it or you don't. -export const CorporationUnlockUpgrades: Record = { +export const CorpUnlocks: Record = { //Lets you export goods - [CorporationUnlockUpgradeIndex.Export]: { - index: 0, + [CorpUnlockName.Export]: { + name: CorpUnlockName.Export, price: 20e9, - name: "Export", desc: "Develop infrastructure to export your materials to your other facilities. " + "This allows you to move materials around between different divisions and cities.", }, //Lets you buy exactly however many required materials you need for production - [CorporationUnlockUpgradeIndex.SmartSupply]: { - index: 1, + [CorpUnlockName.SmartSupply]: { + name: CorpUnlockName.SmartSupply, price: 25e9, - name: "Smart Supply", desc: "Use advanced AI to anticipate your supply needs. " + "This allows you to purchase exactly however many materials you need for production.", }, //Displays each material/product's demand - [CorporationUnlockUpgradeIndex.MarketResearchDemand]: { - index: 2, + [CorpUnlockName.MarketResearchDemand]: { + name: CorpUnlockName.MarketResearchDemand, price: 5e9, - name: "Market Research - Demand", desc: "Mine and analyze market data to determine the demand of all resources. " + "The demand attribute, which affects sales, will be displayed for every material and product.", }, //Display's each material/product's competition - [CorporationUnlockUpgradeIndex.MarketDataCompetition]: { - index: 3, + [CorpUnlockName.MarketDataCompetition]: { + name: CorpUnlockName.MarketDataCompetition, price: 5e9, - name: "Market Data - Competition", desc: "Mine and analyze market data to determine how much competition there is on the market " + "for all resources. The competition attribute, which affects sales, will be displayed for " + "every material and product.", }, - [CorporationUnlockUpgradeIndex.VeChain]: { - index: 4, + + [CorpUnlockName.VeChain]: { + name: CorpUnlockName.VeChain, price: 10e9, - name: "VeChain", desc: "Use AI and blockchain technology to identify where you can improve your supply chain systems. " + "This upgrade will allow you to view a wide array of useful statistics about your " + "Corporation.", }, - [CorporationUnlockUpgradeIndex.ShadyAccounting]: { - index: 5, + + [CorpUnlockName.ShadyAccounting]: { + name: CorpUnlockName.ShadyAccounting, price: 500e12, - name: "Shady Accounting", desc: "Utilize unscrupulous accounting practices and pay off government officials to save money " + "on taxes. This reduces the dividend tax rate by 5%.", }, - [CorporationUnlockUpgradeIndex.GovernmentPartnership]: { - index: 6, + + [CorpUnlockName.GovernmentPartnership]: { + name: CorpUnlockName.GovernmentPartnership, price: 2e15, - name: "Government Partnership", desc: "Help national governments further their agendas in exchange for lowered taxes. " + "This reduces the dividend tax rate by 10%", }, - [CorporationUnlockUpgradeIndex.WarehouseAPI]: { - index: 7, + + [CorpUnlockName.WarehouseAPI]: { + name: CorpUnlockName.WarehouseAPI, price: 50e9, - name: "Warehouse API", desc: "Enables the warehouse API.", }, - [CorporationUnlockUpgradeIndex.OfficeAPI]: { - index: 8, + + [CorpUnlockName.OfficeAPI]: { + name: CorpUnlockName.OfficeAPI, price: 50e9, - name: "Office API", desc: "Enables the office API.", }, }; diff --git a/src/Corporation/data/CorporationUpgrades.ts b/src/Corporation/data/CorporationUpgrades.ts index 15cd5f582..e9583361a 100644 --- a/src/Corporation/data/CorporationUpgrades.ts +++ b/src/Corporation/data/CorporationUpgrades.ts @@ -1,59 +1,43 @@ -export interface CorporationUpgrade { - index: number; +import { CorpUpgradeName } from "./Enums"; + +export interface CorpUpgrade { + name: CorpUpgradeName; basePrice: number; priceMult: number; benefit: number; - name: string; desc: string; } -export enum CorporationUpgradeIndex { - SmartFactories = 0, - SmartStorage = 1, - DreamSense = 2, - WilsonAnalytics = 3, - NuoptimalNootropicInjectorImplants = 4, - SpeechProcessorImplants = 5, - NeuralAccelerators = 6, - FocusWires = 7, - ABCSalesBots = 8, - ProjectInsight = 9, -} - -// Corporation Upgrades -// Upgrades for entire corporation, levelable upgrades -export const CorporationUpgrades: Record = { +/** Levelable upgrades that affect the entire corporation */ +export const CorpUpgrades: Record = { //Smart factories, increases production - [CorporationUpgradeIndex.SmartFactories]: { - index: CorporationUpgradeIndex.SmartFactories, + [CorpUpgradeName.SmartFactories]: { + name: CorpUpgradeName.SmartFactories, basePrice: 2e9, priceMult: 1.06, benefit: 0.03, - name: "Smart Factories", desc: "Advanced AI automatically optimizes the operation and productivity " + "of factories. Each level of this upgrade increases your global production by 3% (additive).", }, //Smart warehouses, increases storage size - [CorporationUpgradeIndex.SmartStorage]: { - index: CorporationUpgradeIndex.SmartStorage, + [CorpUpgradeName.SmartStorage]: { + name: CorpUpgradeName.SmartStorage, basePrice: 2e9, priceMult: 1.06, benefit: 0.1, - name: "Smart Storage", desc: "Advanced AI automatically optimizes your warehouse storage methods. " + "Each level of this upgrade increases your global warehouse storage size by 10% (additive).", }, //Advertise through dreams, passive popularity/ awareness gain - [CorporationUpgradeIndex.DreamSense]: { - index: CorporationUpgradeIndex.DreamSense, + [CorpUpgradeName.DreamSense]: { + name: CorpUpgradeName.DreamSense, basePrice: 4e9, priceMult: 1.1, benefit: 0.001, - name: "DreamSense", desc: "Use DreamSense LCC Technologies to advertise your corporation " + "to consumers through their dreams. Each level of this upgrade provides a passive " + @@ -63,12 +47,11 @@ export const CorporationUpgrades: Record corp.funds) return 0; + // We can definitely afford 1 of the upgrade let n = 1; - while ( - calculateUpgradeCost(corporation, upgrade, n * 2) < corporation.funds && - (amount != "MAX" ? n < amount : true) - ) { - n *= 2; + // Multiply by 2 until we can't afford it anymore + while (calculateUpgradeCost(corp, upgrade, (n * 2) as PositiveInteger) <= corp.funds) n *= 2; + let tooHigh = n * 2; + while (tooHigh - n > 1) { + const nextCheck = (Math.ceil((tooHigh - n) / 2) + n) as PositiveInteger; + if (calculateUpgradeCost(corp, upgrade, nextCheck) <= corp.funds) n = nextCheck; + else tooHigh = nextCheck; } - for (let i = n / 2; i >= 1; i /= 2) { - if (calculateUpgradeCost(corporation, upgrade, n + i) < corporation.funds) n += i; - } - - return amount === "MAX" ? n : Math.min(n, amount); + return n as PositiveInteger; } diff --git a/src/Corporation/ui/CityTabs.tsx b/src/Corporation/ui/CityTabs.tsx index e72cd5ed6..c7e4fbe65 100644 --- a/src/Corporation/ui/CityTabs.tsx +++ b/src/Corporation/ui/CityTabs.tsx @@ -8,6 +8,7 @@ import { useDivision } from "./Context"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; import { CityName } from "../../Enums"; +import { getRecordKeys } from "../../Types/Record"; interface IProps { city: CityName | "Expand"; @@ -23,7 +24,7 @@ export function CityTabs(props: IProps): React.ReactElement { mainContent = ; } else { const office = division.offices[city]; - if (office === 0) { + if (!office) { setCity(CityName.Sector12); return <>; } @@ -31,7 +32,7 @@ export function CityTabs(props: IProps): React.ReactElement { ); } - const canExpand = Object.values(CityName).filter((cityName) => division.offices[cityName] === 0).length > 0; + const canExpand = Object.values(CityName).length > getRecordKeys(division.offices).length; function handleChange(event: React.SyntheticEvent, tab: CityName | "Expand"): void { setCity(tab); } @@ -40,7 +41,8 @@ export function CityTabs(props: IProps): React.ReactElement { <> {Object.values(division.offices).map( - (office: OfficeSpace | 0) => office !== 0 && , + (office: OfficeSpace | 0) => + office !== 0 && , )} {canExpand && } diff --git a/src/Corporation/ui/Context.ts b/src/Corporation/ui/Context.ts index 5563110d5..b38f1f072 100644 --- a/src/Corporation/ui/Context.ts +++ b/src/Corporation/ui/Context.ts @@ -1,11 +1,11 @@ import React, { useContext } from "react"; import { Corporation } from "../Corporation"; -import { Industry } from "../Industry"; +import { Division } from "../Division"; export const Context = { Corporation: React.createContext({} as Corporation), - Division: React.createContext({} as Industry), + Division: React.createContext({} as Division), }; export const useCorporation = (): Corporation => useContext(Context.Corporation); -export const useDivision = (): Industry => useContext(Context.Division); +export const useDivision = (): Division => useContext(Context.Division); diff --git a/src/Corporation/ui/CorporationRoot.tsx b/src/Corporation/ui/CorporationRoot.tsx index c87775145..212b4bb33 100644 --- a/src/Corporation/ui/CorporationRoot.tsx +++ b/src/Corporation/ui/CorporationRoot.tsx @@ -3,7 +3,6 @@ // divisions, see an overview of your corporation, or create a new industry import React, { useState } from "react"; import { MainPanel } from "./MainPanel"; -import { IndustryType } from "../data/Enums"; import { ExpandIndustryTab } from "./ExpandIndustryTab"; import { Player } from "@player"; import { Context } from "./Context"; @@ -22,16 +21,13 @@ export function CorporationRoot(): React.ReactElement { setDivisionName(tab); } - const canExpand = - Object.values(IndustryType).filter( - (industryType) => corporation.divisions.find((division) => division.type === industryType) === undefined, - ).length > 0; + const canExpand = corporation.divisions.size < corporation.maxDivisions; return ( - {corporation.divisions.map((div) => ( + {[...corporation.divisions.values()].map((div) => ( ))} {canExpand && } diff --git a/src/Corporation/ui/ExpandIndustryTab.tsx b/src/Corporation/ui/ExpandIndustryTab.tsx index 3bf5a907a..9230d74aa 100644 --- a/src/Corporation/ui/ExpandIndustryTab.tsx +++ b/src/Corporation/ui/ExpandIndustryTab.tsx @@ -6,13 +6,12 @@ import { useCorporation } from "./Context"; import { NewIndustry } from "../Actions"; import Typography from "@mui/material/Typography"; -import Button from "@mui/material/Button"; +import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip"; import TextField from "@mui/material/TextField"; import MenuItem from "@mui/material/MenuItem"; import Box from "@mui/material/Box"; import Select, { SelectChangeEvent } from "@mui/material/Select"; import { KEY } from "../../utils/helpers/keyCodes"; - interface IProps { setDivisionName: (name: string) => void; } @@ -26,10 +25,15 @@ export function ExpandIndustryTab(props: IProps): React.ReactElement { const data = IndustriesData[industry]; if (!data) return <>; - const disabled = corp.funds < data.startingCost && corp.divisions.length < corp.maxDivisions; + const disabledText = + corp.divisions.size >= corp.maxDivisions + ? "Corporation already has the maximum number of divisions" + : corp.funds < data.startingCost + ? "Insufficient corporation funds" + : ""; function newIndustry(): void { - if (disabled) return; + if (disabledText) return; try { NewIndustry(corp, industry, name); } catch (err) { @@ -60,7 +64,7 @@ export function ExpandIndustryTab(props: IProps): React.ReactElement { return ( <> - {corp.name} has {corp.divisions.length}/{corp.maxDivisions} divisions. + {corp.name} has {corp.divisions.size} of {corp.maxDivisions} divisions. Create a new division to expand into a new industry: - Confirm - - } - value={city} - onChange={onCityChange} - > + + + Confirm + ); } diff --git a/src/Corporation/ui/Helpers.tsx b/src/Corporation/ui/Helpers.tsx index 1583c4161..56d7b273d 100644 --- a/src/Corporation/ui/Helpers.tsx +++ b/src/Corporation/ui/Helpers.tsx @@ -1,14 +1,14 @@ import { CorpMaterialName } from "@nsdefs"; -import { Industry } from "../Industry"; +import { Division } from "../Division"; // Returns a boolean indicating whether the given material is relevant for the // current industry. -export function isRelevantMaterial(matName: CorpMaterialName, division: Industry): boolean { +export function isRelevantMaterial(matName: CorpMaterialName, division: Division): boolean { // Materials that affect Production multiplier const prodMultiplierMats: CorpMaterialName[] = ["Hardware", "Robots", "AI Cores", "Real Estate"]; - if (Object.keys(division.reqMats).includes(matName)) return true; - if (division.prodMats.includes(matName)) return true; + if (Object.keys(division.requiredMaterials).includes(matName)) return true; + if (division.producedMaterials.includes(matName)) return true; if (prodMultiplierMats.includes(matName)) return true; return false; diff --git a/src/Corporation/ui/Industry.tsx b/src/Corporation/ui/Industry.tsx index 3f30310de..323729d59 100644 --- a/src/Corporation/ui/Industry.tsx +++ b/src/Corporation/ui/Industry.tsx @@ -13,7 +13,7 @@ import { CityName } from "../../Enums"; interface IProps { city: CityName; - warehouse: Warehouse | 0; + warehouse?: Warehouse; office: OfficeSpace; rerender: () => void; } diff --git a/src/Corporation/ui/IndustryOffice.tsx b/src/Corporation/ui/IndustryOffice.tsx index 29f3f13e5..c07efe09e 100644 --- a/src/Corporation/ui/IndustryOffice.tsx +++ b/src/Corporation/ui/IndustryOffice.tsx @@ -3,7 +3,7 @@ import React, { useState } from "react"; import { OfficeSpace } from "../OfficeSpace"; -import { EmployeePositions } from "../data/Enums"; +import { CorpUnlockName, CorpEmployeeJob } from "../data/Enums"; import { BuyTea } from "../Actions"; import { MoneyCost } from "./MoneyCost"; @@ -15,7 +15,7 @@ import { Money } from "../../ui/React/Money"; import { useCorporation, useDivision } from "./Context"; import Typography from "@mui/material/Typography"; -import Button from "@mui/material/Button"; +import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip"; import IconButton from "@mui/material/IconButton"; import Paper from "@mui/material/Paper"; import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; @@ -35,7 +35,7 @@ interface IProps { interface IAutoAssignProps { office: OfficeSpace; - job: EmployeePositions; + job: CorpEmployeeJob; desc: string; rerender: () => void; } @@ -53,7 +53,7 @@ function EmployeeCount(props: { num: number; next: number }): React.ReactElement function AutoAssignJob(props: IAutoAssignProps): React.ReactElement { const currJob = props.office.employeeJobs[props.job]; const nextJob = props.office.employeeNextJobs[props.job]; - const nextUna = props.office.employeeNextJobs[EmployeePositions.Unassigned]; + const nextUna = props.office.employeeNextJobs[CorpEmployeeJob.Unassigned]; function assignEmployee(): void { if (nextUna <= 0) return console.warn("Cannot assign employee. No unassigned employees available"); @@ -94,10 +94,9 @@ function AutoAssignJob(props: IAutoAssignProps): React.ReactElement { function AutoManagement(props: IProps): React.ReactElement { const corp = useCorporation(); const division = useDivision(); - const vechain = corp.unlockUpgrades[4] === 1; // Has Vechain upgrade - const currUna = props.office.employeeJobs[EmployeePositions.Unassigned]; - const nextUna = props.office.employeeNextJobs[EmployeePositions.Unassigned]; + const currUna = props.office.employeeJobs[CorpEmployeeJob.Unassigned]; + const nextUna = props.office.employeeNextJobs[CorpEmployeeJob.Unassigned]; return ( @@ -115,7 +114,7 @@ function AutoManagement(props: IProps): React.ReactElement { Avg Employee Morale: - {formatCorpStat(props.office.avgMor)} + {formatCorpStat(props.office.avgMorale)} @@ -123,7 +122,7 @@ function AutoManagement(props: IProps): React.ReactElement { Avg Employee Energy: - {formatCorpStat(props.office.avgEne)} + {formatCorpStat(props.office.avgEnergy)} @@ -131,7 +130,7 @@ function AutoManagement(props: IProps): React.ReactElement { Avg Employee Experience: - {formatCorpStat(props.office.totalExp / props.office.totalEmployees || 0)} + {formatCorpStat(props.office.totalExperience / props.office.numEmployees || 0)} @@ -144,7 +143,7 @@ function AutoManagement(props: IProps): React.ReactElement { - {vechain && ( + {corp.unlocks.has(CorpUnlockName.VeChain) && ( <> @@ -201,14 +200,14 @@ function AutoManagement(props: IProps): React.ReactElement { 1; + const partyDisabledText = + corp.funds < 0 ? "Insufficient corporation funds" : partyPending ? "A party is already pending for this cycle" : ""; + return ( Office Space - Size: {props.office.totalEmployees} / {props.office.size} employees + Size: {props.office.numEmployees} / {props.office.size} employees - Hires an employee}> - - - - - Upgrade the office's size so that it can hold more employees!}> - - - - + + Hire Employee + + setUpgradeOfficeSizeOpen(true)} + > + Upgrade size + {!division.hasResearch("AutoBrew") && ( - <> - - Provide your employees with tea, increasing their energy by half the difference to 100%, plus 1.5% - - } - > - - - - - + BuyTea(corp, props.office)} + > + {props.office.teaPending ? ( + "Buying Tea" + ) : ( + <> + Buy Tea - + + )} + )} {!division.hasResearch("AutoPartyManager") && ( <> - Throw an office party to increase your employee's morale}> - - - - + setThrowPartyOpen(true)} + > + {props.office.partyMult > 1 ? "Throwing Party..." : "Throw Party"} + ; } + const disabledText = hasMaxProducts + ? `${division.name} already has the maximum number of products (${division.getMaximumNumberProducts()})` + : corp.funds < 0 + ? "Insufficient corporation funds" + : ""; + return ( <> - - You have reached the maximum number of products: {division.getMaximumNumberProducts()} - - ) : ( - "" - ) - } + - - + {createProductButtonText} + setMakeOpen(false)} /> ); } -interface IProps { +interface IndustryOverviewProps { rerender: () => void; } -export function IndustryOverview(props: IProps): React.ReactElement { +export function IndustryOverview(props: IndustryOverviewProps): React.ReactElement { const corp = useCorporation(); const division = useDivision(); const [helpOpen, setHelpOpen] = useState(false); const [researchOpen, setResearchOpen] = useState(false); - const vechain = corp.unlockUpgrades[4] === 1; const profit = division.lastCycleRevenue - division.lastCycleExpenses; let advertisingInfo = false; @@ -109,7 +107,7 @@ export function IndustryOverview(props: IProps): React.ReactElement { const popularityFac = advertisingFactors[2]; const ratioFac = advertisingFactors[3]; const totalAdvertisingFac = advertisingFactors[0]; - if (vechain) { + if (corp.unlocks.has(CorpUnlockName.VeChain)) { advertisingInfo = true; } @@ -162,13 +160,13 @@ export function IndustryOverview(props: IProps): React.ReactElement { + <> Production gain from owning production-boosting materials such as hardware, Robots, AI Cores, and Real Estate. - + } > - Production Multiplier: {formatBigNumber(division.prodMult)} + Production Multiplier: {formatBigNumber(division.productionMult)} setHelpOpen(true)}> @@ -189,25 +187,19 @@ export function IndustryOverview(props: IProps): React.ReactElement { multiplier (Bigger bars = more effective):

- Hardware:    {convertEffectFacToGraphic(division.hwFac)} + Hardware:    {convertEffectFacToGraphic(division.hardwareFactor)}
- Robots:      {convertEffectFacToGraphic(division.robFac)} + Robots:      {convertEffectFacToGraphic(division.robotFactor)}
- AI Cores:    {convertEffectFacToGraphic(division.aiFac)} + AI Cores:    {convertEffectFacToGraphic(division.aiCoreFactor)}
- Real Estate: {convertEffectFacToGraphic(division.reFac)} + Real Estate: {convertEffectFacToGraphic(division.realEstateFactor)}
- - Scientific Research increases the quality of the materials and products that you produce. - - } - > - Scientific Research: {formatBigNumber(division.sciResearch)} + + Scientific Research: {formatBigNumber(division.researchPoints)} - + Hire AdVert -  + {division.makesProducts && }
diff --git a/src/Corporation/ui/IndustryProductEquation.tsx b/src/Corporation/ui/IndustryProductEquation.tsx index bcc324ffc..1e552d5b2 100644 --- a/src/Corporation/ui/IndustryProductEquation.tsx +++ b/src/Corporation/ui/IndustryProductEquation.tsx @@ -1,20 +1,20 @@ import React from "react"; -import { Industry } from "../Industry"; +import { Division } from "../Division"; import { MathJax } from "better-react-mathjax"; import { CorpMaterialName } from "@nsdefs"; interface IProps { - division: Industry; + division: Division; } export function IndustryProductEquation(props: IProps): React.ReactElement { const reqs = []; - for (const reqMat of Object.keys(props.division.reqMats) as CorpMaterialName[]) { - const reqAmt = props.division.reqMats[reqMat]; + for (const reqMat of Object.keys(props.division.requiredMaterials) as CorpMaterialName[]) { + const reqAmt = props.division.requiredMaterials[reqMat]; if (reqAmt === undefined) continue; reqs.push(String.raw`${reqAmt}\text{ }${reqMat}`); } - const prod = props.division.prodMats.map((p) => `1\\text{ }${p}`); + const prod = props.division.producedMaterials.map((p) => `1\\text{ }${p}`); if (props.division.makesProducts) { prod.push("Products"); } diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx index faad93e32..0a0131fb1 100644 --- a/src/Corporation/ui/IndustryWarehouse.tsx +++ b/src/Corporation/ui/IndustryWarehouse.tsx @@ -12,26 +12,28 @@ import { MaterialInfo } from "../MaterialInfo"; import { formatBigNumber, formatMaterialSize } from "../../ui/formatNumber"; import { Corporation } from "../Corporation"; -import { Industry } from "../Industry"; +import { Division } from "../Division"; import { MoneyCost } from "./MoneyCost"; import { isRelevantMaterial } from "./Helpers"; import { IndustryProductEquation } from "./IndustryProductEquation"; -import { PurchaseWarehouse } from "../Actions"; +import { purchaseWarehouse } from "../Actions"; import { useCorporation, useDivision } from "./Context"; import Typography from "@mui/material/Typography"; import Tooltip from "@mui/material/Tooltip"; import Paper from "@mui/material/Paper"; +import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import makeStyles from "@mui/styles/makeStyles"; import createStyles from "@mui/styles/createStyles"; import { CityName } from "../../Enums"; +import { CorpUnlockName } from "../data/Enums"; interface IProps { corp: Corporation; - division: Industry; - warehouse: Warehouse | 0; + division: Division; + warehouse?: Warehouse; currentCity: CityName; rerender: () => void; } @@ -48,14 +50,13 @@ function WarehouseRoot(props: IProps): React.ReactElement { const corp = useCorporation(); const division = useDivision(); const [smartSupplyOpen, setSmartSupplyOpen] = useState(false); - if (props.warehouse === 0) return <>; + if (!props.warehouse) return <>; // Upgrade Warehouse size button const sizeUpgradeCost = corpConstants.warehouseSizeUpgradeCostBase * Math.pow(1.07, props.warehouse.level + 1); const canAffordUpgrade = corp.funds > sizeUpgradeCost; function upgradeWarehouseOnClick(): void { - if (division === null) return; - if (props.warehouse === 0) return; + if (!props.warehouse) return; if (!canAffordUpgrade) return; ++props.warehouse.level; props.warehouse.updateSize(corp, division); @@ -93,7 +94,7 @@ function WarehouseRoot(props: IProps): React.ReactElement { for (const matName of Object.values(corpConstants.materialNames)) { if (!props.warehouse.materials[matName]) continue; // Only create UI for materials that are relevant for the industry or in stock - const isInStock = props.warehouse.materials[matName].qty > 0; + const isInStock = props.warehouse.materials[matName].stored > 0; const isRelevant = isRelevantMaterial(matName, division); if (!isInStock && !isRelevant) continue; mats.push( @@ -108,51 +109,31 @@ function WarehouseRoot(props: IProps): React.ReactElement { } // Create React components for products - const products = []; - if (division.makesProducts && Object.keys(division.products).length > 0) { - for (const productName of Object.keys(division.products)) { - const product = division.products[productName]; - if (!product) continue; - products.push( + const productElements = []; + if (division.makesProducts && division.products.size > 0) { + for (const [productName, product] of division.products) { + productElements.push( , ); } } - const breakdownItems: JSX.Element[] = []; - for (const matName of Object.values(corpConstants.materialNames)) { + const breakdownItems: string[] = []; + for (const matName of corpConstants.materialNames) { const mat = props.warehouse.materials[matName]; - if (!Object.hasOwn(MaterialInfo, matName)) continue; - if (mat.qty === 0) continue; - breakdownItems.push( - <> - {matName}: {formatMaterialSize(mat.qty * MaterialInfo[matName].size)} - , - ); + if (mat.stored === 0) continue; + breakdownItems.push(`${matName}: ${formatMaterialSize(mat.stored * MaterialInfo[matName].size)}`); } - for (const prodName of Object.keys(division.products)) { - const prod = division.products[prodName]; - if (prod === undefined) continue; + for (const [prodName, product] of division.products) { breakdownItems.push( - <> - {prodName}: {formatMaterialSize(prod.data[props.warehouse.loc][0] * prod.siz)} - , + `${prodName}: ${formatMaterialSize(product.cityData[props.currentCity].stored * product.size)}`, ); } let breakdown; - if (breakdownItems && breakdownItems.length > 0) { - breakdown = breakdownItems.reduce( - (previous: JSX.Element, current: JSX.Element): JSX.Element => - (previous && ( - <> - {previous} -
- {current} - - )) || <>{current}, - ); + if (breakdownItems.length > 0) { + breakdown = breakdownItems.map((item, i) =>

{item}

); } else { breakdown = <>No items in storage.; } @@ -160,27 +141,20 @@ function WarehouseRoot(props: IProps): React.ReactElement { return ( - - <>{breakdown} - - ) : ( - "" - ) - } - > + = props.warehouse.size ? "error" : "primary"}> Storage: {formatBigNumber(props.warehouse.sizeUsed)} / {formatBigNumber(props.warehouse.size)} - + This industry uses the following equation for its production:
@@ -196,7 +170,7 @@ function WarehouseRoot(props: IProps): React.ReactElement { {stateText} - {corp.unlockUpgrades[1] && ( + {corp.unlocks.has(CorpUnlockName.SmartSupply) && ( <> ); } @@ -230,18 +204,18 @@ interface IEmptyProps { function EmptyWarehouse(props: IEmptyProps): React.ReactElement { const corp = useCorporation(); const division = useDivision(); - const disabled = corp.funds < corpConstants.warehouseInitialCost; - function purchaseWarehouse(): void { - if (disabled) return; - PurchaseWarehouse(corp, division, props.city); + const disabledText = corp.funds < corpConstants.warehouseInitialCost ? "Insufficient corporation funds" : ""; + function newWarehouse(): void { + if (disabledText) return; + purchaseWarehouse(corp, division, props.city); props.rerender(); } return ( - + ); } diff --git a/src/Corporation/ui/LevelableUpgrade.tsx b/src/Corporation/ui/LevelableUpgrade.tsx index effadb0ca..b7176a6f5 100644 --- a/src/Corporation/ui/LevelableUpgrade.tsx +++ b/src/Corporation/ui/LevelableUpgrade.tsx @@ -2,49 +2,48 @@ import React from "react"; import { dialogBoxCreate } from "../../ui/React/DialogBox"; -import { CorporationUpgrade } from "../data/CorporationUpgrades"; -import { LevelUpgrade } from "../Actions"; +import { CorpUpgrades } from "../data/CorporationUpgrades"; import { MoneyCost } from "./MoneyCost"; import { useCorporation } from "./Context"; import Typography from "@mui/material/Typography"; import Tooltip from "@mui/material/Tooltip"; -import Button from "@mui/material/Button"; +import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip"; import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; import { calculateMaxAffordableUpgrade, calculateUpgradeCost } from "../helpers"; +import { CorpUpgradeName } from "../data/Enums"; +import { PositiveInteger } from "../../types"; interface IProps { - upgrade: CorporationUpgrade; - amount: number | "MAX"; + upgradeName: CorpUpgradeName; + mult: PositiveInteger | "MAX"; rerender: () => void; } -export function LevelableUpgrade(props: IProps): React.ReactElement { +export function LevelableUpgrade({ upgradeName, mult, rerender }: IProps): React.ReactElement { const corp = useCorporation(); - const data = props.upgrade; - const level = corp.upgrades[data.index]; - const amount = props.amount; + const data = CorpUpgrades[upgradeName]; + const level = corp.upgrades[upgradeName].level; - const maxUpgrades = amount === "MAX" ? calculateMaxAffordableUpgrade(corp, data, amount) : amount; - const cost = calculateUpgradeCost(corp, data, maxUpgrades); + const amount = mult === "MAX" ? calculateMaxAffordableUpgrade(corp, data, mult) : mult; + const cost = amount === 0 ? 0 : calculateUpgradeCost(corp, data, amount); const tooltip = data.desc; function onClick(): void { if (corp.funds < cost) return; - try { - LevelUpgrade(corp, props.upgrade, maxUpgrades); - } catch (err) { - dialogBoxCreate(err + ""); - } - props.rerender(); + const message = corp.purchaseUpgrade(upgradeName, amount); + if (message) dialogBoxCreate(`Could not upgrade ${upgradeName} ${amount} times:\n${message}`); + rerender(); } return ( - + + +{amount} -  + {data.name} - lvl {level} diff --git a/src/Corporation/ui/MainPanel.tsx b/src/Corporation/ui/MainPanel.tsx index b57def809..3a610c279 100644 --- a/src/Corporation/ui/MainPanel.tsx +++ b/src/Corporation/ui/MainPanel.tsx @@ -4,7 +4,6 @@ import React from "react"; import { CityTabs } from "./CityTabs"; -import { Industry } from "../Industry"; import { Context, useCorporation } from "./Context"; import { CityName } from "../../Enums"; @@ -16,12 +15,8 @@ interface IProps { export function MainPanel(props: IProps): React.ReactElement { const corp = useCorporation(); - const division = - props.divisionName !== "Overview" - ? corp.divisions.find((division: Industry) => division.name === props.divisionName) - : undefined; // use undefined because find returns undefined - - if (division === undefined) throw new Error("Cannot find division"); + const division = corp.divisions.get(props.divisionName); + if (!division) throw new Error("Cannot find division"); return ( diff --git a/src/Corporation/ui/MaterialElem.tsx b/src/Corporation/ui/MaterialElem.tsx index 4a53976cf..d7202cef1 100644 --- a/src/Corporation/ui/MaterialElem.tsx +++ b/src/Corporation/ui/MaterialElem.tsx @@ -22,6 +22,7 @@ import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import { LimitMaterialProductionModal } from "./modals/LimitMaterialProductionModal"; import { CityName } from "../../Enums"; +import { CorpUnlockName } from "../data/Enums"; interface IMaterialProps { warehouse: Warehouse; @@ -50,29 +51,33 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement { } // Total gain or loss of this material (per second) - const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp; + const totalGain = + mat.buyAmount + mat.productionAmount + mat.importAmount - mat.actualSellAmount - mat.exportedLastCycle; // Flag that determines whether this industry is "new" and the current material should be // marked with flashing-red lights const tutorial = - division.newInd && Object.keys(division.reqMats).includes(mat.name) && mat.buy === 0 && mat.imp === 0; + division.newInd && + Object.keys(division.requiredMaterials).includes(mat.name) && + mat.buyAmount === 0 && + mat.importAmount === 0; // Purchase material button - const purchaseButtonText = `Buy (${formatBigNumber(mat.buy)})`; + const purchaseButtonText = `Buy (${formatBigNumber(mat.buyAmount)})`; // Sell material button let sellButtonText: JSX.Element; - if (mat.sllman[0]) { - if (isString(mat.sllman[1])) { + if (mat.desiredSellAmount) { + if (isString(mat.desiredSellAmount)) { sellButtonText = ( <> - Sell ({formatBigNumber(mat.sll)}/{mat.sllman[1]}) + Sell ({formatBigNumber(mat.actualSellAmount)}/{mat.desiredSellAmount[1]}) ); } else { sellButtonText = ( <> - Sell ({formatBigNumber(mat.sll)}/{formatBigNumber(mat.sllman[1])}) + Sell ({formatBigNumber(mat.actualSellAmount)}/{formatBigNumber(mat.desiredSellAmount)}) ); } @@ -86,12 +91,12 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement { } else if (mat.marketTa1) { sellButtonText = ( <> - {sellButtonText} @ + {sellButtonText} @ ); - } else if (mat.sCost) { - if (isString(mat.sCost)) { - const sCost = mat.sCost.replace(/MP/g, mat.bCost + ""); + } else if (mat.desiredSellPrice) { + if (isString(mat.desiredSellPrice)) { + const sCost = mat.desiredSellPrice.replace(/MP/g, mat.marketPrice + ""); sellButtonText = ( <> {sellButtonText} @ @@ -100,7 +105,7 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement { } else { sellButtonText = ( <> - {sellButtonText} @ + {sellButtonText} @ ); } @@ -111,8 +116,8 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement { // Limit Production button let limitMaterialButtonText = "Limit Material"; - if (mat.prdman[0]) { - limitMaterialButtonText += " (" + formatCorpStat(mat.prdman[1]) + ")"; + if (mat.productionLimit !== null) { + limitMaterialButtonText += " (" + formatCorpStat(mat.productionLimit) + ")"; } return ( @@ -122,20 +127,28 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement { - Buy: {mat.buy >= 1e33 ? mat.buy.toExponential(3) : formatBigNumber(mat.buy)}
- Prod: {formatBigNumber(mat.prd)}
- Sell: {formatBigNumber(mat.sll)}
- Export: {formatBigNumber(mat.totalExp)}
- Import: {formatBigNumber(mat.imp)} - {corp.unlockUpgrades[2] === 1 &&
} - {corp.unlockUpgrades[2] === 1 && "Demand: " + formatCorpStat(mat.dmd)} - {corp.unlockUpgrades[3] === 1 &&
} - {corp.unlockUpgrades[3] === 1 && "Competition: " + formatCorpStat(mat.cmp)} + Buy: {mat.buyAmount >= 1e33 ? mat.buyAmount.toExponential(3) : formatBigNumber(mat.buyAmount)}
+ Prod: {formatBigNumber(mat.productionAmount)}
+ Sell: {formatBigNumber(mat.actualSellAmount)}
+ Export: {formatBigNumber(mat.exportedLastCycle)}
+ Import: {formatBigNumber(mat.importAmount)} + {corp.unlocks.has(CorpUnlockName.MarketResearchDemand) && ( + <> +
+ Demand: {formatCorpStat(mat.demand)} + + )} + {corp.unlocks.has(CorpUnlockName.MarketDataCompetition) && ( + <> +
+ Competition: {formatCorpStat(mat.competition)} + + )}
} > - {mat.name}: {formatBigNumber(mat.qty)} ( + {mat.name}: {formatBigNumber(mat.stored)} ( {totalGain >= 1e33 ? totalGain.toExponential(3) : formatBigNumber(totalGain)}/s)
@@ -146,12 +159,12 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement { } > - MP: {formatMoney(mat.bCost)} + MP: {formatMoney(mat.marketPrice)} The quality of your material. Higher quality will lead to more sales} > - Quality: {formatQuality(mat.qlt)} + Quality: {formatQuality(mat.quality)}
@@ -167,13 +180,11 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement { mat={mat} warehouse={warehouse} open={purchaseMaterialOpen} - disablePurchaseLimit={ - props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name) - } + disablePurchaseLimit={props.warehouse.smartSupplyEnabled && props.mat.name in division.requiredMaterials} onClose={() => setPurchaseMaterialOpen(false)} /> - {corp.unlockUpgrades[0] === 1 && ( + {corp.unlocks.has(CorpUnlockName.Export) && ( <> @@ -182,7 +193,7 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement { )} - ); -} +import { PositiveInteger } from "../../types"; +import { getRecordEntries } from "../../Types/Record"; interface IProps { - purchaseMultiplier: number | string; - onClicks: (() => void)[]; + selectedMultiplier: PositiveInteger | "MAX"; + setMultiplier: (mult: PositiveInteger | "MAX") => void; } -export function MultiplierButtons(props: IProps): React.ReactElement { - if (props.purchaseMultiplier == null) { - throw new Error(`MultiplierButtons constructed without required props`); - } - - const mults = ["x1", "x5", "x10", "x50", "x100", "MAX"]; - const onClicks = props.onClicks; - const buttons = []; - for (let i = 0; i < mults.length; ++i) { - const mult = mults[i]; - const btnProps = { - disabled: props.purchaseMultiplier === PurchaseMultipliers[mult], - onClick: onClicks[i], - text: mult, - }; - - buttons.push(); - } - - return <>{buttons}; +export function MultiplierButtons({ selectedMultiplier, setMultiplier }: IProps): JSX.Element { + return ( + <> + {getRecordEntries(PurchaseMultipliers).map(([text, mult]) => ( + + ))} + + ); } diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx index 4257b23ba..b1ed885f2 100644 --- a/src/Corporation/ui/Overview.tsx +++ b/src/Corporation/ui/Overview.tsx @@ -1,7 +1,7 @@ // React Component for displaying Corporation Overview info import React, { useState } from "react"; import { LevelableUpgrade } from "./LevelableUpgrade"; -import { UnlockUpgrade } from "./UnlockUpgrade"; +import { Unlock } from "./Unlock"; import { BribeFactionModal } from "./modals/BribeFactionModal"; import { SellSharesModal } from "./modals/SellSharesModal"; import { BuybackSharesModal } from "./modals/BuybackSharesModal"; @@ -12,8 +12,7 @@ import { GoPublicModal } from "./modals/GoPublicModal"; import { Factions } from "../../Faction/Factions"; import * as corpConstants from "../data/Constants"; -import { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades"; -import { CorporationUpgrade, CorporationUpgradeIndex, CorporationUpgrades } from "../data/CorporationUpgrades"; +import { CorpUnlocks } from "../data/CorporationUnlocks"; import { CONSTANTS } from "../../Constants"; import { formatCorpStat, formatPercent, formatShares } from "../../ui/formatNumber"; @@ -25,13 +24,15 @@ import { Player } from "@player"; import { useCorporation } from "./Context"; import Typography from "@mui/material/Typography"; import Tooltip from "@mui/material/Tooltip"; -import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import Paper from "@mui/material/Paper"; import Grid from "@mui/material/Grid"; import { MultiplierButtons } from "./MultiplierButtons"; import { SellCorporationModal } from "./modals/SellCorporationModal"; import { SellDivisionModal } from "./modals/SellDivisionModal"; +import { getRecordKeys } from "../../Types/Record"; +import { PositiveInteger } from "../../types"; +import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip"; interface IProps { rerender: () => void; @@ -50,11 +51,11 @@ export function Overview({ rerender }: IProps): React.ReactElement { appendMult("Storage Multiplier: ", corp.getStorageMultiplier()); appendMult("Advertising Multiplier: ", corp.getAdvertisingMultiplier()); appendMult("Empl. Creativity Multiplier: ", corp.getEmployeeCreMultiplier()); - appendMult("Empl. Charisma Multiplier: ", corp.getEmployeeChaMultiplier()); - appendMult("Empl. Intelligence Multiplier: ", corp.getEmployeeIntMultiplier()); - appendMult("Empl. Efficiency Multiplier: ", corp.getEmployeeEffMultiplier()); - appendMult("Sales Multiplier: ", corp.getSalesMultiplier()); - appendMult("Scientific Research Multiplier: ", corp.getScientificResearchMultiplier()); + appendMult("Empl. Charisma Multiplier: ", corp.getEmployeeChaMult()); + appendMult("Empl. Intelligence Multiplier: ", corp.getEmployeeIntMult()); + appendMult("Empl. Efficiency Multiplier: ", corp.getEmployeeEffMult()); + appendMult("Sales Multiplier: ", corp.getSalesMult()); + appendMult("Scientific Research Multiplier: ", corp.getScientificResearchMult()); return ( <> @@ -90,23 +91,24 @@ export function Overview({ rerender }: IProps): React.ReactElement {
- - +
+ Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' This is a .lit file that guides you through the beginning of setting up a Corporation and provides some tips/pointers for helping you get started with managing it. - + } + onClick={() => corp.getStarterGuide()} > - - - {corp.public ? : } + Getting Started Guide + - {corp.divisions.length != 0 ? : <>} + {corp.divisions.size > 0 && } - +
+
{corp.public ? : }

@@ -129,21 +131,24 @@ function PrivateButtons({ rerender }: IPrivateButtonsProps): React.ReactElement return ( <> - {findInvestorsTooltip}}> - - - + setFindInvestorsopen(true)} + > + Find Investors + + Become a publicly traded and owned entity. Going public involves issuing shares for an IPO. Once you are a public company, your shares will be traded on the stock market. - + } + onClick={() => setGoPublicopen(true)} > - - + Go Public + setFindInvestorsopen(false)} rerender={rerender} /> setGoPublicopen(false)} rerender={rerender} /> @@ -157,45 +162,37 @@ interface IUpgradeProps { function Upgrades({ rerender }: IUpgradeProps): React.ReactElement { const corp = useCorporation(); // Don't show upgrades - if (corp.divisions.length <= 0) { + if (corp.divisions.size === 0) { return Upgrades are unlocked once you create an industry.; } - const [purchaseMultiplier, setPurchaseMultiplier] = useState(corpConstants.PurchaseMultipliers.x1); + const [purchaseMultiplier, setPurchaseMultiplier] = useState( + corpConstants.PurchaseMultipliers.x1, + ); - // onClick event handlers for purchase multiplier buttons - const purchaseMultiplierOnClicks = [ - () => setPurchaseMultiplier(corpConstants.PurchaseMultipliers.x1), - () => setPurchaseMultiplier(corpConstants.PurchaseMultipliers.x5), - () => setPurchaseMultiplier(corpConstants.PurchaseMultipliers.x10), - () => setPurchaseMultiplier(corpConstants.PurchaseMultipliers.x50), - () => setPurchaseMultiplier(corpConstants.PurchaseMultipliers.x100), - () => setPurchaseMultiplier(corpConstants.PurchaseMultipliers.MAX), - ]; + const unlocksNotOwned = Object.values(CorpUnlocks) + .filter((unlock) => !corp.unlocks.has(unlock.name)) + .map(({ name }) => ); return ( <> Unlocks - {Object.values(CorporationUnlockUpgrades).map((upgrade: CorporationUnlockUpgrade) => ( - - ))} + {unlocksNotOwned.length ? unlocksNotOwned : All unlocks are owned.} Upgrades - + - {corp.upgrades - .map((level: number, i: number) => CorporationUpgrades[i as CorporationUpgradeIndex]) - .map((upgrade: CorporationUpgrade) => ( - - ))} + {getRecordKeys(corp.upgrades).map((name) => ( + + ))} @@ -228,29 +225,36 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement { return ( <> - {sellSharesTooltip}}> - - - setSellSharesOpen(false)} rerender={rerender} /> - Buy back shares you that previously issued or sold at market price.}> - - - setBuybackSharesOpen(false)} rerender={rerender} /> - {issueNewSharesTooltip}}> - - - setIssueNewSharesOpen(false)} /> - Manage the dividends that are paid out to shareholders (including yourself)} + setSellSharesOpen(true)} > - - + Sell Shares + + setSellSharesOpen(false)} rerender={rerender} /> + setBuybackSharesOpen(true)} + > + Buyback shares + + setBuybackSharesOpen(false)} rerender={rerender} /> + setIssueNewSharesOpen(true)} + > + Issue New Shares + + setIssueNewSharesOpen(false)} /> + setIssueDividendsOpen(true)} + > + Issue Dividends + setIssueDividendsOpen(false)} /> ); @@ -270,17 +274,13 @@ function BribeButton(): React.ReactElement { return ( <> - - - + Bribe Factions + setOpen(false)} /> ); @@ -294,9 +294,9 @@ function SellDivisionButton(): React.ReactElement { } return ( <> - - - + + Sell division + setOpen(false)} /> ); @@ -311,9 +311,9 @@ function RestartButton(): React.ReactElement { return ( <> - - - + + Sell CEO position + setOpen(false)} /> ); diff --git a/src/Corporation/ui/ProductElem.tsx b/src/Corporation/ui/ProductElem.tsx index cc31ff43a..76b0c9896 100644 --- a/src/Corporation/ui/ProductElem.tsx +++ b/src/Corporation/ui/ProductElem.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; +import type { CityName } from "src/Enums"; import * as corpConstants from "../data/Constants"; import { Product } from "../Product"; import { DiscontinueProductModal } from "./modals/DiscontinueProductModal"; @@ -19,9 +20,10 @@ import Tooltip from "@mui/material/Tooltip"; import Paper from "@mui/material/Paper"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; +import { CorpUnlockName } from "../data/Enums"; interface IProductProps { - city: string; + city: CityName; product: Product; rerender: () => void; } @@ -41,21 +43,22 @@ export function ProductElem(props: IProductProps): React.ReactElement { const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard"); // Total product gain = production - sale - const totalGain = product.data[city][1] - product.data[city][2]; + const totalGain = product.cityData[city].productionAmount - product.cityData[city].actualSellAmount; // Sell button let sellButtonText: JSX.Element; - if (product.sllman[city][0]) { - if (isString(product.sllman[city][1])) { + const desiredSellAmount = product.cityData[city].desiredSellAmount; + if (desiredSellAmount !== null) { + if (isString(desiredSellAmount)) { sellButtonText = ( <> - Sell ({formatBigNumber(product.data[city][2])}/{product.sllman[city][1]}) + Sell ({formatBigNumber(product.cityData[city].actualSellAmount)}/{desiredSellAmount}) ); } else { sellButtonText = ( <> - Sell ({formatBigNumber(product.data[city][2])}/{formatBigNumber(product.sllman[city][1])}) + Sell ({formatBigNumber(product.cityData[city].actualSellAmount)}/{formatBigNumber(desiredSellAmount)}) ); } @@ -70,15 +73,16 @@ export function ProductElem(props: IProductProps): React.ReactElement { ); } else if (product.marketTa1) { - const markupLimit = product.rat / product.mku; + const markupLimit = product.rating / product.markup; sellButtonText = ( <> - {sellButtonText} @ + {sellButtonText} @ ); - } else if (product.sCost[city]) { - if (isString(product.sCost[city])) { - const sCost = (product.sCost[city] as string).replace(/MP/g, product.pCost + product.rat / product.mku + ""); + } else if (product.cityData[city].desiredSellPrice) { + const desiredSellPrice = product.cityData[city].desiredSellPrice; + if (isString(desiredSellPrice)) { + const sCost = desiredSellPrice.replace(/MP/g, product.productionCost + product.rating / product.markup + ""); sellButtonText = ( <> {sellButtonText} @ @@ -87,27 +91,26 @@ export function ProductElem(props: IProductProps): React.ReactElement { } else { sellButtonText = ( <> - {sellButtonText} @ + {sellButtonText} @ ); } } // Limit Production button - let limitProductionButtonText = "Limit Production"; - if (product.prdman[city][0]) { - limitProductionButtonText += " (" + formatCorpStat(product.prdman[city][1]) + ")"; - } + const productionLimit = product.cityData[city].productionLimit; + const limitProductionButtonText = + "Limit Production" + (productionLimit !== null ? " (" + formatCorpStat(productionLimit) + ")" : ""); return ( - {!product.fin ? ( + {!product.finished ? ( <> - Designing {product.name} (req. Operations/Engineers in {product.createCity})... + Designing {product.name} (req. Operations/Engineers in {product.creationCity})...
- {formatPercent(product.prog / 100, 2)} complete + {formatPercent(product.developmentProgress / 100, 2)} complete - Prod: {formatBigNumber(product.data[city][1])}/s + Prod: {formatBigNumber(product.cityData[city].productionAmount)}/s
- Sell: {formatBigNumber(product.data[city][2])} /s + Sell: {formatBigNumber(product.cityData[city].actualSellAmount)} /s } > - {product.name}: {formatBigNumber(product.data[city][0])} ({formatBigNumber(totalGain)} + {product.name}: {formatBigNumber(product.cityData[city].stored)} ({formatBigNumber(totalGain)} /s)
@@ -139,27 +142,35 @@ export function ProductElem(props: IProductProps): React.ReactElement { title={ Effective rating is calculated from product rating and the quality of materials used
- Rating: {formatCorpStat(product.rat)}

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

+ Quality: {formatCorpStat(product.stats.quality)}
+ Performance: {formatCorpStat(product.stats.performance)}
+ Durability: {formatCorpStat(product.stats.durability)}
+ Reliability: {formatCorpStat(product.stats.reliability)}
+ Aesthetics: {formatCorpStat(product.stats.aesthetics)}
+ Features: {formatCorpStat(product.stats.features)} + {corp.unlocks.has(CorpUnlockName.MarketResearchDemand) && ( + <> +
+ {"Demand: " + formatCorpStat(product.demand)} + + )} + {corp.unlocks.has(CorpUnlockName.MarketDataCompetition) && ( + <> +
+ {"Competition: " + formatCorpStat(product.competition)} + + )}
} > - Effective rating: {formatCorpStat(product.data[city][3])} + Effective rating: {formatCorpStat(product.cityData[city].effectiveRating)}
An estimate of the material cost it takes to create this Product.}> - Est. Production Cost: {formatMoney(product.pCost / corpConstants.baseProductProfitMult)} + Est. Production Cost: {formatMoney(product.productionCost / corpConstants.baseProductProfitMult)} @@ -172,7 +183,7 @@ export function ProductElem(props: IProductProps): React.ReactElement { } > - Est. Market Price: {formatMoney(product.pCost)} + Est. Market Price: {formatMoney(product.productionCost)} @@ -185,7 +196,7 @@ export function ProductElem(props: IProductProps): React.ReactElement { )} - {(hasUpgradeDashboard || product.fin) && ( + {(hasUpgradeDashboard || product.finished) && ( <> setSellOpen(false)} /> diff --git a/src/Corporation/ui/UnlockUpgrade.tsx b/src/Corporation/ui/Unlock.tsx similarity index 55% rename from src/Corporation/ui/UnlockUpgrade.tsx rename to src/Corporation/ui/Unlock.tsx index c42796164..5ffd3909e 100644 --- a/src/Corporation/ui/UnlockUpgrade.tsx +++ b/src/Corporation/ui/Unlock.tsx @@ -1,47 +1,40 @@ // React Components for the Unlock upgrade buttons on the overview page import React from "react"; -import { dialogBoxCreate } from "../../ui/React/DialogBox"; -import { CorporationUnlockUpgrade } from "../data/CorporationUnlockUpgrades"; +import { CorpUnlocks } from "../data/CorporationUnlocks"; import { useCorporation } from "./Context"; -import { UnlockUpgrade as UU } from "../Actions"; import { MoneyCost } from "./MoneyCost"; import Typography from "@mui/material/Typography"; import Tooltip from "@mui/material/Tooltip"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; +import { CorpUnlockName } from "../data/Enums"; +import { dialogBoxCreate } from "../../ui/React/DialogBox"; -interface IProps { - upgradeData: CorporationUnlockUpgrade; +interface UnlockProps { + name: CorpUnlockName; rerender: () => void; } -export function UnlockUpgrade(props: IProps): React.ReactElement { +export function Unlock(props: UnlockProps): React.ReactElement { const corp = useCorporation(); - const data = props.upgradeData; + const data = CorpUnlocks[props.name]; const tooltip = data.desc; - const price = corp.unlockUpgrades[data.index] === 0 ? data.price : 0; + const price = data.price; function onClick(): void { - if (corp.unlockUpgrades[data.index] === 1) return; - if (corp.funds < data.price) return; - try { - UU(corp, props.upgradeData); - } catch (err) { - dialogBoxCreate(err + ""); - } + // corp.unlock handles displaying a dialog on failure + const message = corp.purchaseUnlock(props.name); + if (message) dialogBoxCreate(`Error while attempting to purchase ${props.name}:\n${message}`); + // Rerenders the parent, which should remove this item if the purchase was successful props.rerender(); } return ( - diff --git a/src/Corporation/ui/modals/BuybackSharesModal.tsx b/src/Corporation/ui/modals/BuybackSharesModal.tsx index 6333c3862..d1e70356f 100644 --- a/src/Corporation/ui/modals/BuybackSharesModal.tsx +++ b/src/Corporation/ui/modals/BuybackSharesModal.tsx @@ -4,11 +4,12 @@ import { formatBigNumber, formatMoney } from "../../../ui/formatNumber"; import { Player } from "@player"; import { useCorporation } from "../Context"; import Typography from "@mui/material/Typography"; -import Button from "@mui/material/Button"; +import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip"; import { NumberInput } from "../../../ui/React/NumberInput"; import { BuyBackShares } from "../../Actions"; import { dialogBoxCreate } from "../../../ui/React/DialogBox"; import { KEY } from "../../../utils/helpers/keyCodes"; +import { isPositiveInteger } from "../../../types"; interface IProps { open: boolean; @@ -24,15 +25,16 @@ export function BuybackSharesModal(props: IProps): React.ReactElement { const currentStockPrice = corp.sharePrice; const buybackPrice = currentStockPrice * 1.1; - const disabled = - shares === null || - isNaN(shares) || - shares <= 0 || - shares > corp.issuedShares || - shares * buybackPrice > Player.money; + const disabledText = !isPositiveInteger(shares) + ? "Number of shares must be a positive integer" + : shares > corp.issuedShares + ? "There are not enough shares available to buyback this many" + : shares * buybackPrice > Player.money + ? "Insufficient player funds" + : ""; function buy(): void { - if (disabled) return; + if (disabledText) return; try { BuyBackShares(corp, shares); } catch (err) { @@ -82,9 +84,9 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
- + ); } diff --git a/src/Corporation/ui/modals/CancelProductModal.tsx b/src/Corporation/ui/modals/CancelProductModal.tsx index 58e6d18b3..59147a1de 100644 --- a/src/Corporation/ui/modals/CancelProductModal.tsx +++ b/src/Corporation/ui/modals/CancelProductModal.tsx @@ -17,7 +17,7 @@ interface IProps { export function CancelProductModal(props: IProps): React.ReactElement { const division = useDivision(); function cancel(): void { - division.discontinueProduct(props.product); + division.discontinueProduct(props.product.name); props.onClose(); props.rerender(); } diff --git a/src/Corporation/ui/modals/CreateCorporationModal.tsx b/src/Corporation/ui/modals/CreateCorporationModal.tsx index 4596917cc..756c137b0 100644 --- a/src/Corporation/ui/modals/CreateCorporationModal.tsx +++ b/src/Corporation/ui/modals/CreateCorporationModal.tsx @@ -6,7 +6,7 @@ import { Router } from "../../../ui/GameRoot"; import { Page } from "../../../ui/Router"; import { Player } from "@player"; import Typography from "@mui/material/Typography"; -import Button from "@mui/material/Button"; +import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip"; import TextField from "@mui/material/TextField"; interface IProps { @@ -16,12 +16,15 @@ interface IProps { export function CreateCorporationModal(props: IProps): React.ReactElement { const canSelfFund = Player.canAfford(150e9); + const [name, setName] = useState(""); + if (!Player.canAccessCorporation() || Player.corporation) { props.onClose(); return <>; } - const [name, setName] = useState(""); + const disabledTextForNoName = name === "" ? "Enter a name for the corporation" : ""; + function onChange(event: React.ChangeEvent): void { setName(event.target.value); } @@ -62,13 +65,16 @@ export function CreateCorporationModal(props: IProps): React.ReactElement { {Player.bitNodeN === 3 && ( - + )} - + ); } diff --git a/src/Corporation/ui/modals/DiscontinueProductModal.tsx b/src/Corporation/ui/modals/DiscontinueProductModal.tsx index 4992879ad..ee9bad333 100644 --- a/src/Corporation/ui/modals/DiscontinueProductModal.tsx +++ b/src/Corporation/ui/modals/DiscontinueProductModal.tsx @@ -17,7 +17,7 @@ interface IProps { export function DiscontinueProductModal(props: IProps): React.ReactElement { const division = useDivision(); function discontinue(): void { - division.discontinueProduct(props.product); + division.discontinueProduct(props.product.name); props.onClose(); props.rerender(); } diff --git a/src/Corporation/ui/modals/ExportModal.tsx b/src/Corporation/ui/modals/ExportModal.tsx index 1efd8539d..530e9031c 100644 --- a/src/Corporation/ui/modals/ExportModal.tsx +++ b/src/Corporation/ui/modals/ExportModal.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { dialogBoxCreate } from "../../../ui/React/DialogBox"; import { Material } from "../../Material"; import { Export } from "../../Export"; -import { Industry } from "../../Industry"; +import { Division } from "../../Division"; import { ExportMaterial } from "../../Actions"; import { Modal } from "../../../ui/React/Modal"; import { useCorporation } from "../Context"; @@ -15,6 +15,7 @@ import MenuItem from "@mui/material/MenuItem"; import Select, { SelectChangeEvent } from "@mui/material/Select"; import { CityName } from "../../../Enums"; import { useRerender } from "../../../ui/React/hooks"; +import { getRecordKeys } from "../../../Types/Record"; interface IProps { open: boolean; @@ -25,33 +26,35 @@ interface IProps { // Create a popup that lets the player manage exports export function ExportModal(props: IProps): React.ReactElement { const corp = useCorporation(); - const possibleDivisions = corp.divisions.filter((division: Industry) => isRelevantMaterial(props.mat.name, division)); + const possibleDivisions = [...corp.divisions.values()].filter((division: Division) => { + return isRelevantMaterial(props.mat.name, division); + }); if (possibleDivisions.length === 0) throw new Error("Export popup created with no divisions."); const defaultDivision = possibleDivisions[0]; if (Object.keys(defaultDivision.warehouses).length === 0) throw new Error("Export popup created in a division with no warehouses."); - const [industry, setIndustry] = useState(defaultDivision.name); - const [city, setCity] = useState(Object.keys(defaultDivision.warehouses)[0] as CityName); - const [amt, setAmt] = useState(""); + const [targetDivision, setTargetDivision] = useState(defaultDivision); + const [targetCity, setTargetCity] = useState(CityName.Sector12); + const [exportAmount, setExportAmount] = useState(""); const rerender = useRerender(); - function onCityChange(event: SelectChangeEvent): void { - setCity(event.target.value as CityName); + function onCityChange(event: SelectChangeEvent): void { + setTargetCity(event.target.value as CityName); } - function onIndustryChange(event: SelectChangeEvent): void { - const div = event.target.value; - setIndustry(div); - setCity(Object.keys(corp.divisions[0].warehouses)[0] as CityName); + function onTargetDivisionChange(event: SelectChangeEvent): void { + const division = corp.divisions.get(event.target.value); + if (!division) return; + setTargetDivision(division); } function onAmtChange(event: React.ChangeEvent): void { - setAmt(event.target.value); + setExportAmount(event.target.value); } function exportMaterial(): void { try { - ExportMaterial(industry, city, props.mat, amt, currentDivision); + ExportMaterial(targetDivision.name, targetCity, props.mat, exportAmount, targetDivision); } catch (err) { dialogBoxCreate(err + ""); } @@ -59,23 +62,22 @@ export function ExportModal(props: IProps): React.ReactElement { } function removeExport(exp: Export): void { - for (let i = 0; i < props.mat.exp.length; ++i) { - if (props.mat.exp[i].ind !== exp.ind || props.mat.exp[i].city !== exp.city || props.mat.exp[i].amt !== exp.amt) + for (let i = 0; i < props.mat.exports.length; ++i) { + if ( + props.mat.exports[i].division !== exp.division || + props.mat.exports[i].city !== exp.city || + props.mat.exports[i].amount !== exp.amount + ) continue; - props.mat.exp.splice(i, 1); + props.mat.exports.splice(i, 1); break; } rerender(); } - const currentDivision = corp.divisions.find((division: Industry) => division.name === industry); - if (currentDivision === undefined) - throw new Error(`Export popup somehow ended up with undefined division '${currentDivision}'`); - const possibleCities = (Object.keys(currentDivision.warehouses) as CityName[]).filter( - (city) => currentDivision.warehouses[city] !== 0, - ); - if (possibleCities.length > 0 && !possibleCities.includes(city)) { - setCity(possibleCities[0]); + const possibleCities = getRecordKeys(targetDivision.warehouses); + if (possibleCities.length > 0 && !possibleCities.includes(targetCity)) { + setTargetCity(possibleCities[0]); } return ( @@ -102,42 +104,39 @@ export function ExportModal(props: IProps): React.ReactElement {
For example: setting the amount "(EINV-20)/10" would try to export all except 20 of the material. - + {[...corp.divisions.values()] + .filter((division) => isRelevantMaterial(props.mat.name, division)) + .map((division) => ( {division.name} ))} - + {possibleCities.map((cityName) => ( + + {cityName} + + ))} - + Below is a list of all current exports of this material from this warehouse. Clicking on one of the exports below will REMOVE that export. - {props.mat.exp.map((exp: Export, index: number) => ( + {props.mat.exports.map((exp: Export, index: number) => ( - Industry: {exp.ind} + Industry: {exp.division}
City: {exp.city}
- Amount/s: {exp.amt} + Amount/s: {exp.amount}
))} diff --git a/src/Corporation/ui/modals/GoPublicModal.tsx b/src/Corporation/ui/modals/GoPublicModal.tsx index 2652c073d..b3872c1a2 100644 --- a/src/Corporation/ui/modals/GoPublicModal.tsx +++ b/src/Corporation/ui/modals/GoPublicModal.tsx @@ -4,10 +4,11 @@ import { Modal } from "../../../ui/React/Modal"; import { formatMoney, formatShares } from "../../../ui/formatNumber"; import { useCorporation } from "../Context"; import Typography from "@mui/material/Typography"; -import Button from "@mui/material/Button"; +import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip"; import { NumberInput } from "../../../ui/React/NumberInput"; import Box from "@mui/material/Box"; import { KEY } from "../../../utils/helpers/keyCodes"; +import { isPositiveInteger } from "../../../types"; interface IProps { open: boolean; @@ -23,14 +24,7 @@ export function GoPublicModal(props: IProps): React.ReactElement { function goPublic(): void { const initialSharePrice = corp.valuation / corp.totalShares; - if (isNaN(shares)) { - dialogBoxCreate("Invalid value for number of issued shares"); - return; - } - if (shares > corp.numShares) { - dialogBoxCreate("Error: You don't have that many shares to issue!"); - return; - } + if (shares >= corp.numShares || (shares !== 0 && !isPositiveInteger(shares))) return; corp.public = true; corp.sharePrice = initialSharePrice; corp.issuedShares = shares; @@ -47,6 +41,13 @@ export function GoPublicModal(props: IProps): React.ReactElement { if (event.key === KEY.ENTER) goPublic(); } + const disabledText = + shares >= corp.numShares + ? "Cannot issue this many shares" + : shares !== 0 && !isPositiveInteger(shares) + ? "Must issue an non-negative integer number of shares" + : ""; + return ( @@ -55,13 +56,13 @@ export function GoPublicModal(props: IProps): React.ReactElement { be deposited directly into your Corporation's funds).

- You have a total of {formatShares(corp.numShares)}-1 shares that you can issue. You cannot sell all your shares. + You can issue some, but not all, of your {formatShares(corp.numShares)} shares.
- +
); diff --git a/src/Corporation/ui/modals/LimitProductProductionModal.tsx b/src/Corporation/ui/modals/LimitProductProductionModal.tsx index db9c55c74..9689f7e1b 100644 --- a/src/Corporation/ui/modals/LimitProductProductionModal.tsx +++ b/src/Corporation/ui/modals/LimitProductProductionModal.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; -import { Product } from "../../Product"; +import type { CityName } from "../../../Enums"; +import type { Product } from "../../Product"; import { LimitProductProduction } from "../../Actions"; import { Modal } from "../../../ui/React/Modal"; import Typography from "@mui/material/Typography"; @@ -11,7 +12,7 @@ interface IProps { open: boolean; onClose: () => void; product: Product; - city: string; + city: CityName; } // Create a popup that lets the player limit the production of a product diff --git a/src/Corporation/ui/modals/MakeProductModal.tsx b/src/Corporation/ui/modals/MakeProductModal.tsx index b84f9b3bf..410d88ba3 100644 --- a/src/Corporation/ui/modals/MakeProductModal.tsx +++ b/src/Corporation/ui/modals/MakeProductModal.tsx @@ -13,6 +13,7 @@ import Select, { SelectChangeEvent } from "@mui/material/Select"; import { KEY } from "../../../utils/helpers/keyCodes"; import { NumberInput } from "../../../ui/React/NumberInput"; import { CityName } from "../../../Enums"; +import { getRecordKeys } from "../../../Types/Record"; interface IProps { open: boolean; @@ -34,8 +35,8 @@ function productPlaceholder(type: string): string { export function MakeProductModal(props: IProps): React.ReactElement { const corp = useCorporation(); const division = useDivision(); - const allCities = Object.values(CityName).filter((cityName) => division.offices[cityName] !== 0); - const [city, setCity] = useState(allCities.length > 0 ? allCities[0] : CityName.Sector12); + const availableCities = getRecordKeys(division.offices); + const [city, setCity] = useState(availableCities.length > 0 ? availableCities[0] : CityName.Sector12); const [name, setName] = useState(""); const [design, setDesign] = useState(NaN); const [marketing, setMarketing] = useState(NaN); @@ -80,7 +81,7 @@ export function MakeProductModal(props: IProps): React.ReactElement { will result in a superior product. Investing money in marketing the product will help the product's sales. - {allIndustries.map((industry) => ( - - {industry.name} - - ))} - - - Division {industry.name} has: -

- Profit: ${formatNumber((industry.lastCycleRevenue - industry.lastCycleExpenses) / 10)} / sec -

- Cities:{" "} - {Object.keys(industry.offices) - .map((city) => (industry.offices[city as CityName] ? 1 : 0)) - .reduce(sum, 0)} -

- Warehouses:{" "} - {Object.keys(industry.warehouses) - .map((city) => (industry.warehouses[city as CityName] ? 1 : 0)) - .reduce(sum, 0)} - {industry.makesProducts ?? ( - - {" "} -

- Products: {industry.products.length}{" "} -
- )} -

-

- Sell price: {formatNumber(price)} -
- + <> + + Would you like to sell a division? +

+ You'll get back half the money you've spent on starting the division and expanding to offices and warehouses. +
+ + Division {divisionToSell.name} has: + + Profit: {formatMoney((divisionToSell.lastCycleRevenue - divisionToSell.lastCycleExpenses) / 10)} / sec{" "} + + Cities:{getRecordKeys(divisionToSell.offices).length} + Warehouses:{getRecordKeys(divisionToSell.warehouses).length} + {divisionToSell.makesProducts ?? Products: {divisionToSell.products.size}} +
+ Sell price: {formatMoney(price)} + + ); } diff --git a/src/Corporation/ui/modals/SellMaterialModal.tsx b/src/Corporation/ui/modals/SellMaterialModal.tsx index 3726c1bce..b7f1a37a0 100644 --- a/src/Corporation/ui/modals/SellMaterialModal.tsx +++ b/src/Corporation/ui/modals/SellMaterialModal.tsx @@ -9,7 +9,7 @@ import Button from "@mui/material/Button"; import { KEY } from "../../../utils/helpers/keyCodes"; function initialPrice(mat: Material): string { - let val = mat.sCost ? mat.sCost + "" : ""; + let val = mat.desiredSellPrice ? mat.desiredSellPrice + "" : ""; if (mat.marketTa2) { val += " (Market-TA.II)"; } else if (mat.marketTa1) { @@ -26,7 +26,7 @@ interface IProps { // Create a popup that let the player manage sales of a material export function SellMaterialModal(props: IProps): React.ReactElement { - const [amt, setAmt] = useState(props.mat.sllman[1] ? props.mat.sllman[1] + "" : ""); + const [amt, setAmt] = useState(props.mat.desiredSellAmount + ""); const [price, setPrice] = useState(initialPrice(props.mat)); function sellMaterial(): void { diff --git a/src/Corporation/ui/modals/SellProductModal.tsx b/src/Corporation/ui/modals/SellProductModal.tsx index 4dcd3ccc6..5c250a974 100644 --- a/src/Corporation/ui/modals/SellProductModal.tsx +++ b/src/Corporation/ui/modals/SellProductModal.tsx @@ -10,9 +10,10 @@ import Button from "@mui/material/Button"; import FormControlLabel from "@mui/material/FormControlLabel"; import Switch from "@mui/material/Switch"; import { KEY } from "../../../utils/helpers/keyCodes"; +import { CityName } from "../../../Enums"; -function initialPrice(product: Product, city: string): string { - let val = product.sCost[city] ? product.sCost[city] + "" : ""; +function initialPrice(product: Product, city: CityName): string { + let val = String(product.cityData[city].desiredSellPrice || ""); if (product.marketTa2) { val += " (Market-TA.II)"; } else if (product.marketTa1) { @@ -25,15 +26,13 @@ interface IProps { open: boolean; onClose: () => void; product: Product; - city: string; + city: CityName; } // Create a popup that let the player manage sales of a material export function SellProductModal(props: IProps): React.ReactElement { const [checked, setChecked] = useState(true); - const [iQty, setQty] = useState( - props.product.sllman[props.city][1] ? props.product.sllman[props.city][1] : "", - ); + const [iQty, setQty] = useState((props.product.cityData[props.city].desiredSellAmount ?? "").toString()); const [px, setPx] = useState(initialPrice(props.product, props.city)); function onCheckedChange(event: React.ChangeEvent): void { diff --git a/src/Corporation/ui/modals/SmartSupplyModal.tsx b/src/Corporation/ui/modals/SmartSupplyModal.tsx index b5a12f290..866e2c624 100644 --- a/src/Corporation/ui/modals/SmartSupplyModal.tsx +++ b/src/Corporation/ui/modals/SmartSupplyModal.tsx @@ -82,7 +82,7 @@ export function SmartSupplyModal(props: IProps): React.ReactElement { const mats = []; for (const matName of Object.values(materialNames)) { if (!props.warehouse.materials[matName]) continue; - if (!Object.keys(division.reqMats).includes(matName)) continue; + if (!Object.keys(division.requiredMaterials).includes(matName)) continue; mats.push(); } diff --git a/src/Corporation/ui/modals/ThrowPartyModal.tsx b/src/Corporation/ui/modals/ThrowPartyModal.tsx index 8282eedbd..164357663 100644 --- a/src/Corporation/ui/modals/ThrowPartyModal.tsx +++ b/src/Corporation/ui/modals/ThrowPartyModal.tsx @@ -23,7 +23,7 @@ export function ThrowPartyModal(props: IProps): React.ReactElement { const corp = useCorporation(); const [cost, setCost] = useState(0); - const totalCost = cost * props.office.totalEmployees; + const totalCost = cost * props.office.numEmployees; const canParty = corp.funds >= totalCost; function changeCost(event: React.ChangeEvent): void { let x = parseFloat(event.target.value); diff --git a/src/DevMenu/ui/Corporation.tsx b/src/DevMenu/ui/Corporation.tsx index 4b230c678..892d981ae 100644 --- a/src/DevMenu/ui/Corporation.tsx +++ b/src/DevMenu/ui/Corporation.tsx @@ -55,19 +55,17 @@ export function Corporation(): React.ReactElement { function finishCorporationProducts(): void { if (!Player.corporation) return; - Player.corporation.divisions.forEach((div) => { - Object.keys(div.products).forEach((prod) => { - const product = div.products[prod]; - if (product === undefined) throw new Error("Impossible product undefined"); - product.prog = 99.9; - }); - }); + for (const division of Player.corporation.divisions.values()) { + for (const product of division.products.values()) { + product.developmentProgress = 99.9; + } + } } function addCorporationResearch(): void { if (!Player.corporation) return; Player.corporation.divisions.forEach((div) => { - div.sciResearch += 1e10; + div.researchPoints += 1e10; }); } diff --git a/src/Hacknet/HacknetHelpers.tsx b/src/Hacknet/HacknetHelpers.tsx index d019ad54e..888bfeed0 100644 --- a/src/Hacknet/HacknetHelpers.tsx +++ b/src/Hacknet/HacknetHelpers.tsx @@ -531,8 +531,8 @@ export function purchaseHashUpgrade(upgName: string, upgTarget: string, count = Player.hashManager.refundUpgrade(upgName, count); return false; } - for (const division of corp.divisions) { - division.sciResearch += upg.value * count; + for (const division of corp.divisions.values()) { + division.researchPoints += upg.value * count; } break; } diff --git a/src/Locations/createCityMap.ts b/src/Locations/createCityMap.ts deleted file mode 100644 index 91b4cbf5b..000000000 --- a/src/Locations/createCityMap.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Utility function that creates a "city map", which is an object where - * each city is a key (property). - * - * This map uses the official name of the city, NOT its key in the 'Cities' object - */ -import { Cities } from "./Cities"; - -export function createCityMap(initValue: T): Record { - const map: Record = {}; - const cities = Object.keys(Cities); - for (let i = 0; i < cities.length; ++i) { - map[cities[i]] = initValue; - } - - // round try JSON so to make sure none of the initial values have the same references. - return JSON.parse(JSON.stringify(map)); -} diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index 244673b35..ec5eeb004 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -353,8 +353,8 @@ const corporation = { getMaterialData: RamCostConstants.Corporation, issueNewShares: RamCostConstants.Corporation, createCorporation: RamCostConstants.Corporation, - hasUnlockUpgrade: RamCostConstants.Corporation, - getUnlockUpgradeCost: RamCostConstants.Corporation, + hasUnlock: RamCostConstants.Corporation, + getUnlockCost: RamCostConstants.Corporation, getUpgradeLevel: RamCostConstants.Corporation, getUpgradeLevelCost: RamCostConstants.Corporation, getInvestmentOffer: RamCostConstants.Corporation, @@ -365,7 +365,7 @@ const corporation = { getDivision: RamCostConstants.Corporation, expandIndustry: RamCostConstants.Corporation, expandCity: RamCostConstants.Corporation, - unlockUpgrade: RamCostConstants.Corporation, + purchaseUnlock: RamCostConstants.Corporation, levelUpgrade: RamCostConstants.Corporation, issueDividends: RamCostConstants.Corporation, buyBackShares: RamCostConstants.Corporation, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index e7a4419d1..6827da6ce 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -86,11 +86,13 @@ import { cloneDeep, escapeRegExp } from "lodash"; import { FactionWorkType } from "./Enums"; import numeral from "numeral"; import { clearPort, peekPort, portHandle, readPort, tryWritePort, writePort } from "./NetscriptPort"; -import { FilePath } from "./Paths/FilePath"; +import { FilePath, resolveFilePath } from "./Paths/FilePath"; import { hasScriptExtension } from "./Paths/ScriptFilePath"; import { hasTextExtension } from "./Paths/TextFilePath"; import { ContentFilePath } from "./Paths/ContentFile"; import { LiteratureName } from "./Literature/data/LiteratureNames"; +import { hasProgramExtension } from "./Paths/ProgramFilePath"; +import { hasContractExtension } from "./Paths/ContractFilePath"; export const enums: NSEnums = { CityName, @@ -1104,19 +1106,13 @@ export const ns: InternalAPI = { const filename = helpers.string(ctx, "filename", _filename); const hostname = helpers.string(ctx, "hostname", _hostname); const server = helpers.getServer(ctx, hostname); - if (server.scripts.has(filename) || server.textFiles.has(filename)) return true; - for (let i = 0; i < server.programs.length; ++i) { - if (filename.toLowerCase() == server.programs[i].toLowerCase()) { - return true; - } - } - for (let i = 0; i < server.messages.length; ++i) { - if (filename.toLowerCase() === server.messages[i].toLowerCase()) { - return true; - } - } - const contract = server.contracts.find((c) => c.fn.toLowerCase() === filename.toLowerCase()); - if (contract) return true; + const path = resolveFilePath(filename, ctx.workerScript.name); + if (!path) return false; + if (hasScriptExtension(path)) return server.scripts.has(path); + if (hasTextExtension(path)) return server.textFiles.has(path); + if (hasProgramExtension(path)) return server.programs.includes(path); + if (path.endsWith(".lit") || path.endsWith(".msg")) return server.messages.includes(path as any); + if (hasContractExtension(path)) return !!server.contracts.find(({ fn }) => fn === path); return false; }, isRunning: diff --git a/src/NetscriptFunctions/Corporation.ts b/src/NetscriptFunctions/Corporation.ts index 1e15d3153..b38a13eeb 100644 --- a/src/NetscriptFunctions/Corporation.ts +++ b/src/NetscriptFunctions/Corporation.ts @@ -4,7 +4,7 @@ import { OfficeSpace } from "../Corporation/OfficeSpace"; import { Product } from "../Corporation/Product"; import { Material } from "../Corporation/Material"; import { Warehouse } from "../Corporation/Warehouse"; -import { Industry } from "../Corporation/Industry"; +import { Division } from "../Corporation/Division"; import { Corporation } from "../Corporation/Corporation"; import { cloneDeep, omit } from "lodash"; @@ -20,9 +20,7 @@ import { import { NewIndustry, - NewCity, - UnlockUpgrade, - LevelUpgrade, + purchaseOffice, IssueDividends, IssueNewShares, SellMaterial, @@ -30,7 +28,7 @@ import { SetSmartSupply, BuyMaterial, UpgradeOfficeSize, - PurchaseWarehouse, + purchaseWarehouse, UpgradeWarehouse, BuyTea, ThrowParty, @@ -51,9 +49,9 @@ import { LimitProductProduction, UpgradeWarehouseCost, } from "../Corporation/Actions"; -import { CorporationUnlockUpgrades } from "../Corporation/data/CorporationUnlockUpgrades"; -import { CorporationUpgrades } from "../Corporation/data/CorporationUpgrades"; -import { EmployeePositions, IndustryType } from "../Corporation/data/Enums"; +import { CorpUnlocks } from "../Corporation/data/CorporationUnlocks"; +import { CorpUpgrades } from "../Corporation/data/CorporationUpgrades"; +import { CorpUnlockName, CorpUpgradeName, CorpEmployeeJob, IndustryType } from "../Corporation/data/Enums"; import { IndustriesData, IndustryResearchTrees } from "../Corporation/IndustryData"; import * as corpConstants from "../Corporation/data/Constants"; import { ResearchMap } from "../Corporation/ResearchMap"; @@ -64,6 +62,9 @@ import { assertMember, helpers } from "../Netscript/NetscriptHelpers"; import { checkEnum } from "../utils/helpers/enum"; import { CityName } from "../Enums"; import { MaterialInfo } from "../Corporation/MaterialInfo"; +import { calculateUpgradeCost } from "../Corporation/helpers"; +import { PositiveInteger } from "../types"; +import { getRecordKeys } from "../Types/Record"; export function NetscriptCorporation(): InternalAPI { function createCorporation(corporationName: string, selfFund = true): boolean { @@ -84,39 +85,24 @@ export function NetscriptCorporation(): InternalAPI { return true; } - function hasUnlockUpgrade(upgradeName: string): boolean { + function hasUnlock(unlockName: CorpUnlockName): boolean { const corporation = getCorporation(); - const upgrade = Object.values(CorporationUnlockUpgrades).find((upgrade) => upgrade.name === upgradeName); - if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); - const upgN = upgrade.index; - return corporation.unlockUpgrades[upgN] === 1; + return corporation.unlocks.has(unlockName); } - function getUnlockUpgradeCost(upgradeName: string): number { - const upgrade = Object.values(CorporationUnlockUpgrades).find((upgrade) => upgrade.name === upgradeName); - if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); - return upgrade.price; + function getUnlockCost(unlockName: CorpUnlockName): number { + return CorpUnlocks[unlockName].price; } - function getUpgradeLevel(ctx: NetscriptContext, _upgradeName: string): number { - const upgradeName = helpers.string(ctx, "upgradeName", _upgradeName); + function getUpgradeLevel(upgradeName: CorpUpgradeName): number { const corporation = getCorporation(); - const upgrade = Object.values(CorporationUpgrades).find((upgrade) => upgrade.name === upgradeName); - if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); - const upgN = upgrade.index; - return corporation.upgrades[upgN]; + return corporation.upgrades[upgradeName].level; } - function getUpgradeLevelCost(ctx: NetscriptContext, _upgradeName: string): number { - const upgradeName = helpers.string(ctx, "upgradeName", _upgradeName); + function getUpgradeLevelCost(upgradeName: CorpUpgradeName): number { const corporation = getCorporation(); - const upgrade = Object.values(CorporationUpgrades).find((upgrade) => upgrade.name === upgradeName); - if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); - const upgN = upgrade.index; - const baseCost = upgrade.basePrice; - const priceMult = upgrade.priceMult; - const level = corporation.upgrades[upgN]; - return baseCost * Math.pow(priceMult, level); + const cost = calculateUpgradeCost(corporation, CorpUpgrades[upgradeName], 1 as PositiveInteger); + return cost; } function getInvestmentOffer(): InvestmentOffer { @@ -176,7 +162,7 @@ export function NetscriptCorporation(): InternalAPI { return true; } - function getResearchCost(division: Industry, researchName: CorpResearchName): number { + function getResearchCost(division: Division, researchName: CorpResearchName): number { const researchTree = IndustryResearchTrees[division.type]; if (researchTree === undefined) throw new Error(`No research tree for industry '${division.type}'`); const allResearch = researchTree.getAllNodes(); @@ -185,8 +171,8 @@ export function NetscriptCorporation(): InternalAPI { return research.cost; } - function hasResearched(division: Industry, researchName: CorpResearchName): boolean { - return division.researched[researchName] ?? false; + function hasResearched(division: Division, researchName: CorpResearchName): boolean { + return division.researched.has(researchName); } function bribe(factionName: string, amountCash: number): boolean { @@ -214,9 +200,9 @@ export function NetscriptCorporation(): InternalAPI { return corporation; } - function getDivision(divisionName: string): Industry { + function getDivision(divisionName: string): Division { const corporation = getCorporation(); - const division = corporation.divisions.find((div) => div.name === divisionName); + const division = corporation.divisions.get(divisionName); if (division === undefined) throw new Error(`No division named '${divisionName}'`); return division; } @@ -225,7 +211,7 @@ export function NetscriptCorporation(): InternalAPI { const division = getDivision(divisionName); if (!checkEnum(CityName, cityName)) throw new Error(`Invalid city name '${cityName}'`); const office = division.offices[cityName]; - if (office === 0) throw new Error(`${division.name} has not expanded to '${cityName}'`); + if (!office) throw new Error(`${division.name} has not expanded to '${cityName}'`); return office; } @@ -233,7 +219,7 @@ export function NetscriptCorporation(): InternalAPI { const division = getDivision(divisionName); if (!checkEnum(CityName, cityName)) throw new Error(`Invalid city name '${cityName}'`); const warehouse = division.warehouses[cityName]; - if (warehouse === 0) throw new Error(`${division.name} does not have a warehouse in '${cityName}'`); + if (!warehouse) throw new Error(`${division.name} does not have a warehouse in '${cityName}'`); return warehouse; } @@ -243,42 +229,38 @@ export function NetscriptCorporation(): InternalAPI { return material; } - function getProduct(divisionName: string, cityName: string, productName: string): Product { + function getProduct(divisionName: string, productName: string): Product { const division = getDivision(divisionName); - const product = division.products[productName]; + const product = division.products.get(productName); if (product === undefined) throw new Error(`Invalid product name: '${productName}'`); return product; } - function checkAccess(ctx: NetscriptContext, api?: number): void { - if (player.corporation === null) throw helpers.makeRuntimeErrorMsg(ctx, "Must own a corporation."); + function checkAccess(ctx: NetscriptContext, api?: CorpUnlockName): void { + if (!player.corporation) throw helpers.makeRuntimeErrorMsg(ctx, "Must own a corporation."); if (!api) return; - - if (!player.corporation.unlockUpgrades[api]) + if (!player.corporation.unlocks.has(api)) { throw helpers.makeRuntimeErrorMsg(ctx, "You do not have access to this API."); + } } - function getSafeDivision(division: Industry): NSDivision { - const cities: CityName[] = []; - for (const office of Object.values(division.offices)) { - if (!office) continue; - cities.push(office.loc); - } + function getSafeDivision(division: Division): NSDivision { + const cities = getRecordKeys(division.offices); return { name: division.name, type: division.type, awareness: division.awareness, popularity: division.popularity, - prodMult: division.prodMult, - research: division.sciResearch, + productionMult: division.productionMult, + researchPoints: division.researchPoints, lastCycleRevenue: division.lastCycleRevenue, lastCycleExpenses: division.lastCycleExpenses, thisCycleRevenue: division.thisCycleRevenue, thisCycleExpenses: division.thisCycleExpenses, - upgrades: [0, division.numAdVerts], + numAdVerts: division.numAdVerts, cities: cities, - products: division.products === undefined ? [] : Object.keys(division.products), + products: [...division.products.keys()], makesProducts: division.makesProducts, }; } @@ -287,7 +269,7 @@ export function NetscriptCorporation(): InternalAPI { getUpgradeWarehouseCost: (ctx) => (_divisionName, _cityName, _amt = 1) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const amt = helpers.number(ctx, "amount", _amt); @@ -298,91 +280,82 @@ export function NetscriptCorporation(): InternalAPI { return UpgradeWarehouseCost(warehouse, amt); }, hasWarehouse: (ctx) => (_divisionName, _cityName) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const division = getDivision(divisionName); - const warehouse = division.warehouses[cityName]; - return warehouse !== 0; + return cityName in division.warehouses; }, getWarehouse: (ctx) => (_divisionName, _cityName) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const warehouse = getWarehouse(divisionName, cityName); return { level: warehouse.level, - loc: warehouse.loc, + city: warehouse.city, size: warehouse.size, sizeUsed: warehouse.sizeUsed, smartSupplyEnabled: warehouse.smartSupplyEnabled, }; }, getMaterial: (ctx) => (_divisionName, _cityName, materialName) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); assertMember(ctx, corpConstants.materialNames, "Material Name", "materialName", materialName); const material = getMaterial(divisionName, cityName, materialName); const corporation = getCorporation(); - const exports = material.exp.map((e) => { - return { div: e.ind, loc: e.city, amt: e.amt }; - }); + const exports = cloneDeep(material.exports); return { - cost: material.bCost, - sCost: material.sCost, - sAmt: material.sllman[1], + marketPrice: material.marketPrice, + desiredSellPrice: material.desiredSellPrice, + desiredSellAmount: material.desiredSellAmount, name: material.name, - qty: material.qty, - qlt: material.qlt, - dmd: corporation.unlockUpgrades[2] ? material.dmd : undefined, - cmp: corporation.unlockUpgrades[3] ? material.cmp : undefined, - prod: material.prd, - sell: material.sll, - exp: exports, + stored: material.stored, + quality: material.quality, + demand: corporation.unlocks.has(CorpUnlockName.MarketResearchDemand) ? material.demand : undefined, + competition: corporation.unlocks.has(CorpUnlockName.MarketDataCompetition) ? material.competition : undefined, + productionAmount: material.productionAmount, + actualSellAmount: material.actualSellAmount, + exports: exports, }; }, getProduct: (ctx) => (_divisionName, _cityName, _productName) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const productName = helpers.string(ctx, "productName", _productName); const cityName = helpers.city(ctx, "cityName", _cityName); - const product = getProduct(divisionName, cityName, productName); + const product = getProduct(divisionName, productName); const corporation = getCorporation(); + const cityData = product.cityData[cityName]; return { name: product.name, - dmd: corporation.unlockUpgrades[2] ? product.dmd : undefined, - cmp: corporation.unlockUpgrades[3] ? product.cmp : undefined, - rat: product.rat, - effRat: product.data[cityName][3], - properties: { - qlt: product.qlt, - per: product.per, - dur: product.dur, - rel: product.rel, - aes: product.aes, - fea: product.fea, - }, - pCost: product.pCost, - sCost: product.sCost[cityName], - sAmt: product.sllman[cityName][1], - qty: product.data[cityName][0], - prod: product.data[cityName][1], - sell: product.data[cityName][2], - developmentProgress: product.prog, + demand: corporation.unlocks.has(CorpUnlockName.MarketResearchDemand) ? product.demand : undefined, + competition: corporation.unlocks.has(CorpUnlockName.MarketDataCompetition) ? product.competition : undefined, + rating: product.rating, + effectiveRating: cityData.effectiveRating, + stats: cloneDeep(product.stats), + productionCost: product.productionCost, + desiredSellPrice: cityData.desiredSellPrice, + desiredSellAmount: cityData.desiredSellAmount, + stored: cityData.stored, + productionAmount: cityData.productionAmount, + actualSellAmount: cityData.actualSellAmount, + developmentProgress: product.developmentProgress, }; }, purchaseWarehouse: (ctx) => (_divisionName, _cityName) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const corporation = getCorporation(); - PurchaseWarehouse(corporation, getDivision(divisionName), cityName); + purchaseWarehouse(corporation, getDivision(divisionName), cityName); }, upgradeWarehouse: (ctx) => (_divisionName, _cityName, _amt = 1): void => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const amt = helpers.number(ctx, "amount", _amt); @@ -393,7 +366,7 @@ export function NetscriptCorporation(): InternalAPI { UpgradeWarehouse(corporation, getDivision(divisionName), getWarehouse(divisionName, cityName), amt); }, sellMaterial: (ctx) => (_divisionName, _cityName, materialName, _amt, _price) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); assertMember(ctx, corpConstants.materialNames, "Material Name", "materialName", materialName); @@ -405,46 +378,46 @@ export function NetscriptCorporation(): InternalAPI { sellProduct: (ctx) => (_divisionName, _cityName, _productName, _amt, _price, _all): void => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const productName = helpers.string(ctx, "productName", _productName); const amt = helpers.string(ctx, "amt", _amt); const price = helpers.string(ctx, "price", _price); const all = !!_all; - const product = getProduct(divisionName, cityName, productName); + const product = getProduct(divisionName, productName); SellProduct(product, cityName, amt, price, all); }, discontinueProduct: (ctx) => (_divisionName, _productName) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const productName = helpers.string(ctx, "productName", _productName); - getDivision(divisionName).discontinueProduct(getProduct(divisionName, "Sector-12", productName)); + getDivision(divisionName).discontinueProduct(productName); }, setSmartSupply: (ctx) => (_divisionName, _cityName, _enabled) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const enabled = !!_enabled; const warehouse = getWarehouse(divisionName, cityName); - if (!hasUnlockUpgrade("Smart Supply")) + if (!hasUnlock(CorpUnlockName.SmartSupply)) throw helpers.makeRuntimeErrorMsg(ctx, `You have not purchased the Smart Supply upgrade!`); SetSmartSupply(warehouse, enabled); }, setSmartSupplyOption: (ctx) => (_divisionName, _cityName, materialName, _option) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); assertMember(ctx, corpConstants.materialNames, "Material Name", "materialName", materialName); const warehouse = getWarehouse(divisionName, cityName); const material = getMaterial(divisionName, cityName, materialName); const option = helpers.string(ctx, "option", _option); - if (!hasUnlockUpgrade("Smart Supply")) + if (!hasUnlock(CorpUnlockName.SmartSupply)) throw helpers.makeRuntimeErrorMsg(ctx, `You have not purchased the Smart Supply upgrade!`); SetSmartSupplyOption(warehouse, material, option); }, buyMaterial: (ctx) => (_divisionName, _cityName, materialName, _amt) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); assertMember(ctx, corpConstants.materialNames, "Material Name", "materialName", materialName); @@ -455,7 +428,7 @@ export function NetscriptCorporation(): InternalAPI { BuyMaterial(material, amt); }, bulkPurchase: (ctx) => (_divisionName, _cityName, materialName, _amt) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const corporation = getCorporation(); const cityName = helpers.city(ctx, "cityName", _cityName); @@ -468,7 +441,7 @@ export function NetscriptCorporation(): InternalAPI { makeProduct: (ctx) => (_divisionName, _cityName, _productName, _designInvest, _marketingInvest): void => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const productName = helpers.string(ctx, "productName", _productName); @@ -478,17 +451,17 @@ export function NetscriptCorporation(): InternalAPI { MakeProduct(corporation, getDivision(divisionName), cityName, productName, designInvest, marketingInvest); }, limitProductProduction: (ctx) => (_divisionName, _cityName, _productName, _qty) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const productName = helpers.string(ctx, "productName", _productName); const qty = helpers.number(ctx, "qty", _qty); - LimitProductProduction(getProduct(divisionName, cityName, productName), cityName, qty); + LimitProductProduction(getProduct(divisionName, productName), cityName, qty); }, exportMaterial: (ctx) => (_sourceDivision, sourceCity, _targetDivision, targetCity, materialName, _amt): void => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const sourceDivision = helpers.string(ctx, "sourceDivision", _sourceDivision); assertMember(ctx, CityName, "City", "sourceCity", sourceCity); const targetDivision = helpers.string(ctx, "targetDivision", _targetDivision); @@ -506,7 +479,7 @@ export function NetscriptCorporation(): InternalAPI { cancelExportMaterial: (ctx) => (_sourceDivision, sourceCity, _targetDivision, targetCity, materialName, _amt): void => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const sourceDivision = helpers.string(ctx, "sourceDivision", _sourceDivision); assertMember(ctx, CityName, "City Name", "sourceCity", sourceCity); const targetDivision = helpers.string(ctx, "targetDivision", _targetDivision); @@ -516,7 +489,7 @@ export function NetscriptCorporation(): InternalAPI { CancelExportMaterial(targetDivision, targetCity, getMaterial(sourceDivision, sourceCity, materialName), amt); }, limitMaterialProduction: (ctx) => (_divisionName, cityName, materialName, _qty) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); assertMember(ctx, CityName, "City Name", "cityName", cityName); assertMember(ctx, corpConstants.materialNames, "Material Name", "materialName", materialName); @@ -524,7 +497,7 @@ export function NetscriptCorporation(): InternalAPI { LimitMaterialProduction(getMaterial(divisionName, cityName, materialName), qty); }, setMaterialMarketTA1: (ctx) => (_divisionName, cityName, materialName, _on) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); assertMember(ctx, CityName, "City Name", "cityName", cityName); assertMember(ctx, corpConstants.materialNames, "Material Name", "materialName", materialName); @@ -534,7 +507,7 @@ export function NetscriptCorporation(): InternalAPI { SetMaterialMarketTA1(getMaterial(divisionName, cityName, materialName), on); }, setMaterialMarketTA2: (ctx) => (_divisionName, cityName, materialName, _on) => { - checkAccess(ctx, 7); + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); assertMember(ctx, CityName, "City Name", "cityName", cityName); assertMember(ctx, corpConstants.materialNames, "Material Name", "materialName", materialName); @@ -543,55 +516,53 @@ export function NetscriptCorporation(): InternalAPI { throw helpers.makeRuntimeErrorMsg(ctx, `You have not researched MarketTA.II for division: ${divisionName}`); SetMaterialMarketTA2(getMaterial(divisionName, cityName, materialName), on); }, - setProductMarketTA1: (ctx) => (_divisionName, _cityName, _productName, _on) => { - checkAccess(ctx, 7); + setProductMarketTA1: (ctx) => (_divisionName, _productName, _on) => { + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); - const cityName = helpers.city(ctx, "cityName", _cityName); const productName = helpers.string(ctx, "productName", _productName); const on = !!_on; if (!getDivision(divisionName).hasResearch("Market-TA.I")) throw helpers.makeRuntimeErrorMsg(ctx, `You have not researched MarketTA.I for division: ${divisionName}`); - SetProductMarketTA1(getProduct(divisionName, cityName, productName), on); + SetProductMarketTA1(getProduct(divisionName, productName), on); }, - setProductMarketTA2: (ctx) => (_divisionName, _cityName, _productName, _on) => { - checkAccess(ctx, 7); + setProductMarketTA2: (ctx) => (_divisionName, _productName, _on) => { + checkAccess(ctx, CorpUnlockName.WarehouseAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); - const cityName = helpers.city(ctx, "cityName", _cityName); const productName = helpers.string(ctx, "productName", _productName); const on = !!_on; if (!getDivision(divisionName).hasResearch("Market-TA.II")) throw helpers.makeRuntimeErrorMsg(ctx, `You have not researched MarketTA.II for division: ${divisionName}`); - SetProductMarketTA2(getProduct(divisionName, cityName, productName), on); + SetProductMarketTA2(getProduct(divisionName, productName), on); }, }; const officeAPI: InternalAPI = { getHireAdVertCost: (ctx) => (_divisionName) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const division = getDivision(divisionName); return division.getAdVertCost(); }, getHireAdVertCount: (ctx) => (_divisionName) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const division = getDivision(divisionName); return division.numAdVerts; }, getResearchCost: (ctx) => (_divisionName, researchName) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); assertMember(ctx, corpConstants.researchNames, "Research Name", "researchName", researchName); return getResearchCost(getDivision(divisionName), researchName); }, hasResearched: (ctx) => (_divisionName, researchName) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); assertMember(ctx, corpConstants.researchNames, "Research Name", "researchName", researchName); return hasResearched(getDivision(divisionName), researchName); }, getOfficeSizeUpgradeCost: (ctx) => (_divisionName, _cityName, _size) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const size = helpers.number(ctx, "size", _size); @@ -606,14 +577,14 @@ export function NetscriptCorporation(): InternalAPI { return corpConstants.officeInitialCost * mult; }, setAutoJobAssignment: (ctx) => (_divisionName, _cityName, _job, _amount) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const amount = helpers.number(ctx, "amount", _amount); const job = helpers.string(ctx, "job", _job); - if (!checkEnum(EmployeePositions, job)) throw new Error(`'${job}' is not a valid job.`); - if (job === EmployeePositions.Unassigned) return false; + if (!checkEnum(CorpEmployeeJob, job)) throw new Error(`'${job}' is not a valid job.`); + if (job === CorpEmployeeJob.Unassigned) return false; if (amount < 0 || !Number.isInteger(amount)) throw helpers.makeRuntimeErrorMsg( ctx, @@ -624,7 +595,7 @@ export function NetscriptCorporation(): InternalAPI { const totalNewEmployees = amount - office.employeeNextJobs[job]; - if (office.employeeNextJobs[EmployeePositions.Unassigned] < totalNewEmployees) + if (office.employeeNextJobs[CorpEmployeeJob.Unassigned] < totalNewEmployees) throw helpers.makeRuntimeErrorMsg( ctx, `Unable to bring '${job} employees to ${amount}. Requires ${totalNewEmployees} unassigned employees`, @@ -632,18 +603,18 @@ export function NetscriptCorporation(): InternalAPI { return office.autoAssignJob(job, amount); }, hireEmployee: (ctx) => (_divisionName, _cityName, _position?) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); - const position = _position ? helpers.string(ctx, "position", _position) : EmployeePositions.Unassigned; - if (!checkEnum(EmployeePositions, position)) { + const position = _position ? helpers.string(ctx, "position", _position) : CorpEmployeeJob.Unassigned; + if (!checkEnum(CorpEmployeeJob, position)) { throw helpers.makeRuntimeErrorMsg(ctx, `Invalid position: ${position}`); } const office = getOffice(divisionName, cityName); return office.hireRandomEmployee(position); }, upgradeOfficeSize: (ctx) => (_divisionName, _cityName, _size) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const size = helpers.number(ctx, "size", _size); @@ -653,7 +624,7 @@ export function NetscriptCorporation(): InternalAPI { UpgradeOfficeSize(corporation, office, size); }, throwParty: (ctx) => (_divisionName, _cityName, _costPerEmployee) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const costPerEmployee = helpers.number(ctx, "costPerEmployee", _costPerEmployee); @@ -667,7 +638,7 @@ export function NetscriptCorporation(): InternalAPI { return ThrowParty(corporation, office, costPerEmployee); }, buyTea: (ctx) => (_divisionName, _cityName) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); @@ -676,32 +647,32 @@ export function NetscriptCorporation(): InternalAPI { return BuyTea(corporation, office); }, hireAdVert: (ctx) => (_divisionName) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const corporation = getCorporation(); HireAdVert(corporation, getDivision(divisionName)); }, research: (ctx) => (_divisionName, researchName) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); assertMember(ctx, corpConstants.researchNames, "Research Name", "reseatchName", researchName); Research(getDivision(divisionName), researchName); }, getOffice: (ctx) => (_divisionName, _cityName) => { - checkAccess(ctx, 8); + checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = helpers.city(ctx, "cityName", _cityName); const office = getOffice(divisionName, cityName); return { - loc: office.loc, + city: office.city, size: office.size, - maxEne: office.maxEne, - maxMor: office.maxMor, - employees: office.totalEmployees, - avgEne: office.avgEne, - avgMor: office.avgMor, - totalExperience: office.totalExp, - employeeProd: Object.assign({}, office.employeeProd), + maxEnergy: office.maxEnergy, + maxMorale: office.maxMorale, + numEmployees: office.numEmployees, + avgEnergy: office.avgEnergy, + avgMorale: office.avgMorale, + totalExperience: office.totalExperience, + employeeProductionByJob: Object.assign({}, office.employeeProductionByJob), employeeJobs: Object.assign({}, office.employeeJobs), }; }, @@ -749,23 +720,23 @@ export function NetscriptCorporation(): InternalAPI { const cityName = helpers.city(ctx, "cityName", _cityName); const corporation = getCorporation(); const division = getDivision(divisionName); - NewCity(corporation, division, cityName); + purchaseOffice(corporation, division, cityName); }, - unlockUpgrade: (ctx) => (_upgradeName) => { + purchaseUnlock: (ctx) => (_unlockName) => { checkAccess(ctx); - const upgradeName = helpers.string(ctx, "upgradeName", _upgradeName); + const unlockName = helpers.string(ctx, "upgradeName", _unlockName); + if (!checkEnum(CorpUnlockName, unlockName)) throw new Error(`No unlock named ${unlockName}`); const corporation = getCorporation(); - const upgrade = Object.values(CorporationUnlockUpgrades).find((upgrade) => upgrade.name === upgradeName); - if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); - UnlockUpgrade(corporation, upgrade); + const message = corporation.purchaseUnlock(unlockName); + if (message) throw new Error(`Could not unlock ${unlockName}: ${message}`); }, levelUpgrade: (ctx) => (_upgradeName) => { checkAccess(ctx); const upgradeName = helpers.string(ctx, "upgradeName", _upgradeName); + if (!checkEnum(CorpUpgradeName, upgradeName)) throw new Error(`No upgrade named '${upgradeName}'`); const corporation = getCorporation(); - const upgrade = Object.values(CorporationUpgrades).find((upgrade) => upgrade.name === upgradeName); - if (upgrade === undefined) throw new Error(`No upgrade named '${upgradeName}'`); - LevelUpgrade(corporation, upgrade, 1); + const message = corporation.purchaseUpgrade(upgradeName, 1); + if (message) throw new Error(`Could not upgrade ${upgradeName}: ${message}`); }, issueDividends: (ctx) => (_rate) => { checkAccess(ctx); @@ -816,7 +787,7 @@ export function NetscriptCorporation(): InternalAPI { dividendTax: corporation.dividendTax, dividendEarnings: corporation.getCycleDividends() / corpConstants.secondsPerMarketCycle, state: corporation.state.getState(), - divisions: corporation.divisions.map((division) => division.name), + divisions: [...corporation.divisions.keys()], }; }, createCorporation: @@ -826,25 +797,29 @@ export function NetscriptCorporation(): InternalAPI { const selfFund = !!_selfFund; return createCorporation(corporationName, selfFund); }, - hasUnlockUpgrade: (ctx) => (_upgradeName) => { + hasUnlock: (ctx) => (_unlockName) => { checkAccess(ctx); - const upgradeName = helpers.string(ctx, "upgradeName", _upgradeName); - return hasUnlockUpgrade(upgradeName); + const unlockName = helpers.string(ctx, "upgradeName", _unlockName); + if (!checkEnum(CorpUnlockName, unlockName)) throw new Error(`${unlockName} is not a valid unlock name.`); + return hasUnlock(unlockName); }, - getUnlockUpgradeCost: (ctx) => (_upgradeName) => { + getUnlockCost: (ctx) => (_unlockName) => { checkAccess(ctx); - const upgradeName = helpers.string(ctx, "upgradeName", _upgradeName); - return getUnlockUpgradeCost(upgradeName); + const unlockName = helpers.string(ctx, "upgradeName", _unlockName); + if (!checkEnum(CorpUnlockName, unlockName)) throw new Error(`${unlockName} is not a valid unlock name.`); + return getUnlockCost(unlockName); }, getUpgradeLevel: (ctx) => (_upgradeName) => { checkAccess(ctx); const upgradeName = helpers.string(ctx, "upgradeName", _upgradeName); - return getUpgradeLevel(ctx, upgradeName); + if (!checkEnum(CorpUpgradeName, upgradeName)) throw new Error(`${upgradeName} is not a valid upgrade name.`); + return getUpgradeLevel(upgradeName); }, getUpgradeLevelCost: (ctx) => (_upgradeName) => { checkAccess(ctx); const upgradeName = helpers.string(ctx, "upgradeName", _upgradeName); - return getUpgradeLevelCost(ctx, upgradeName); + if (!checkEnum(CorpUpgradeName, upgradeName)) throw new Error(`${upgradeName} is not a valid upgrade name.`); + return getUpgradeLevelCost(upgradeName); }, getInvestmentOffer: (ctx) => () => { checkAccess(ctx); diff --git a/src/PersonObjects/Player/PlayerObjectCorporationMethods.ts b/src/PersonObjects/Player/PlayerObjectCorporationMethods.ts index d99f94e25..3f245c8ba 100644 --- a/src/PersonObjects/Player/PlayerObjectCorporationMethods.ts +++ b/src/PersonObjects/Player/PlayerObjectCorporationMethods.ts @@ -1,8 +1,5 @@ +import { CorpUnlockName } from "../../Corporation/data/Enums"; import { Corporation } from "../../Corporation/Corporation"; -import { - CorporationUnlockUpgradeIndex, - CorporationUnlockUpgrades, -} from "../../Corporation/data/CorporationUnlockUpgrades"; import { resetIndustryResearchTrees } from "../../Corporation/IndustryData"; import type { PlayerObject } from "./PlayerObject"; @@ -20,11 +17,8 @@ export function startCorporation(this: PlayerObject, corpName: string, seedFunde resetIndustryResearchTrees(); if (this.bitNodeN === 3 || this.sourceFileLvl(3) === 3) { - const warehouseApi = CorporationUnlockUpgrades[CorporationUnlockUpgradeIndex.WarehouseAPI].index; - const OfficeApi = CorporationUnlockUpgrades[CorporationUnlockUpgradeIndex.OfficeAPI].index; - - this.corporation.unlockUpgrades[warehouseApi] = 1; - this.corporation.unlockUpgrades[OfficeApi] = 1; + this.corporation.unlocks.add(CorpUnlockName.WarehouseAPI); + this.corporation.unlocks.add(CorpUnlockName.OfficeAPI); } this.corporation.totalShares += seedFunded ? 500_000_000 : 0; diff --git a/src/SaveObject.tsx b/src/SaveObject.tsx index 7b4590777..9f30c3239 100755 --- a/src/SaveObject.tsx +++ b/src/SaveObject.tsx @@ -40,6 +40,8 @@ import { TextFile } from "./TextFile"; import { ScriptFilePath, resolveScriptFilePath } from "./Paths/ScriptFilePath"; import { Directory, resolveDirectory } from "./Paths/Directory"; import { TextFilePath, resolveTextFilePath } from "./Paths/TextFilePath"; +import { Corporation } from "./Corporation/Corporation"; +import { Terminal } from "./Terminal"; /* SaveObject.js * Defines the object used to save/load games @@ -323,9 +325,6 @@ function evaluateVersionCompatibility(ver: string | number): void { if (anyPlayer.gang === 0) { anyPlayer.gang = null; } - if (anyPlayer.corporation === 0) { - anyPlayer.corporation = null; - } // convert all Messages to just filename to save space. const home = anyPlayer.getHomeComputer(); for (let i = 0; i < home.messages.length; i++) { @@ -373,19 +372,6 @@ function evaluateVersionCompatibility(ver: string | number): void { } if (ver < 3) { anyPlayer.money = parseFloat(anyPlayer.money); - if (anyPlayer.corporation) { - anyPlayer.corporation.funds = parseFloat(anyPlayer.corporation.funds); - anyPlayer.corporation.revenue = parseFloat(anyPlayer.corporation.revenue); - anyPlayer.corporation.expenses = parseFloat(anyPlayer.corporation.expenses); - - for (let i = 0; i < anyPlayer.corporation.divisions.length; ++i) { - const ind = anyPlayer.corporation.divisions[i]; - ind.lastCycleRevenue = parseFloat(ind.lastCycleRevenue); - ind.lastCycleExpenses = parseFloat(ind.lastCycleExpenses); - ind.thisCycleRevenue = parseFloat(ind.thisCycleRevenue); - ind.thisCycleExpenses = parseFloat(ind.thisCycleExpenses); - } - } } if (ver < 9) { if (Object.hasOwn(StockMarket, "Joes Guns")) { @@ -717,6 +703,17 @@ function evaluateVersionCompatibility(ver: string | number): void { } } } + // Reset corporation to new format. + const oldCorp = anyPlayer.corporation as Corporation | null | 0; + if (oldCorp && Array.isArray(oldCorp.divisions)) { + // Corp needs to be reset to new format, just keep some valuation data + let valuation = oldCorp.valuation * 2 + oldCorp.revenue * 100; + if (isNaN(valuation)) valuation = 300e9; + Player.startCorporation(String(oldCorp.name), !!oldCorp.seedFunded); + Player.corporation?.addFunds(valuation); + Terminal.warn("Loading corporation from version prior to 2.3. Corporation has been reset."); + } + // End 2.3 changes } } diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 4d7730e86..cb895900b 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -6958,21 +6958,19 @@ export interface WarehouseAPI { * @param amt - Amount of material to buy */ bulkPurchase(divisionName: string, city: CityName | `${CityName}`, materialName: string, amt: number): void; - /** - * Get warehouse data + + /** Get warehouse data * @param divisionName - Name of the division * @param city - Name of the city - * @returns warehouse data - */ + * @returns warehouse data */ getWarehouse(divisionName: string, city: CityName | `${CityName}`): Warehouse; - /** - * Get product data + + /** Get product data * @param divisionName - Name of the division * @param city - Name of the city * @param productName - Name of the product - * @returns product data - */ - getProduct(divisionName: string, city: CityName | `${CityName}`, productName: string): Product; + * @returns product data */ + getProduct(divisionName: string, cityName: CityName | `${CityName}`, productName: string): Product; /** * Get material data * @param divisionName - Name of the division @@ -6997,22 +6995,18 @@ export interface WarehouseAPI { * @param on - market ta enabled */ setMaterialMarketTA2(divisionName: string, city: CityName | `${CityName}`, materialName: string, on: boolean): void; - /** - * Set market TA 1 for a product. + + /** * Set market TA 1 for a product. * @param divisionName - Name of the division - * @param city - Name of the city * @param productName - Name of the product - * @param on - market ta enabled - */ - setProductMarketTA1(divisionName: string, city: CityName | `${CityName}`, productName: string, on: boolean): void; - /** - * Set market TA 2 for a product. + * @param on - market ta enabled */ + setProductMarketTA1(divisionName: string, productName: string, on: boolean): void; + + /** Set market TA 2 for a product. * @param divisionName - Name of the division - * @param city - Name of the city * @param productName - Name of the product - * @param on - market ta enabled - */ - setProductMarketTA2(divisionName: string, city: CityName | `${CityName}`, productName: string, on: boolean): void; + * @param on - market ta enabled */ + setProductMarketTA2(divisionName: string, productName: string, on: boolean): void; /** * Set material export data * @param sourceDivision - Source division @@ -7129,12 +7123,12 @@ export interface Corporation extends WarehouseAPI, OfficeAPI { /** Check if you have a one time unlockable upgrade * @param upgradeName - Name of the upgrade * @returns true if unlocked and false if not */ - hasUnlockUpgrade(upgradeName: string): boolean; + hasUnlock(upgradeName: string): boolean; /** Gets the cost to unlock a one time unlockable upgrade * @param upgradeName - Name of the upgrade * @returns cost of the upgrade */ - getUnlockUpgradeCost(upgradeName: string): number; + getUnlockCost(upgradeName: string): number; /** Get the level of a levelable upgrade * @param upgradeName - Name of the upgrade @@ -7198,7 +7192,7 @@ export interface Corporation extends WarehouseAPI, OfficeAPI { /** Unlock an upgrade * @param upgradeName - Name of the upgrade */ - unlockUpgrade(upgradeName: string): void; + purchaseUnlock(upgradeName: string): void; /** Level an upgrade. * @param upgradeName - Name of the upgrade */ @@ -7408,7 +7402,6 @@ type CorpResearchName = | "AutoBrew" | "AutoPartyManager" | "Automatic Drug Administration" - | "Bulk Purchasing" | "CPH4 Injections" | "Drones" | "Drones - Assembly" @@ -7416,7 +7409,6 @@ type CorpResearchName = | "Go-Juice" | "HRBuddy-Recruitment" | "HRBuddy-Training" - | "JoyWire" | "Market-TA.I" | "Market-TA.II" | "Overclock" @@ -7454,7 +7446,7 @@ interface CorpMaterialConstantData { interface IndustryData { /** Industry type */ type: CorpIndustryName; - /** Cost to expand to the division */ + /** Cost to make a new division of this industry type */ cost: number; /** Materials required for production and their amounts */ requiredMaterials: Record; @@ -7476,28 +7468,35 @@ interface Product { /** Name of the product */ name: string; /** Demand for the product, only present if "Market Research - Demand" unlocked */ - dmd: number | undefined; + demand: number | undefined; /** Competition for the product, only present if "Market Research - Competition" unlocked */ - cmp: number | undefined; - /** Product Rating */ - rat: number; - /** Effective rating */ - effRat: number; - /** Product Properties. The data is \{qlt, per, dur, rel, aes, fea\} */ - properties: { [key: string]: number }; + competition: number | undefined; + /** Rating based on stats */ + rating: number; + /** Effective rating in the specific city */ + effectiveRating: number; + /** Product stats */ + stats: { + quality: number; + performance: number; + durability: number; + reliability: number; + aesthetics: number; + features: number; + }; /** Production cost */ - pCost: number; - /** Sell cost, can be "MP+5" */ - sCost: string; - /** Sell amount, can be "PROD/2" */ - sAmt: string; - /** Amount of product */ - qty: number; - /** Amount of product produced */ - prod: number; - /** Amount of product sold */ - sell: number; - /** Creation progress - A number between 0-100 representing percentage */ + productionCost: number; + /** Desired sell price, can be "MP+5" */ + desiredSellPrice: string | number; + /** Desired sell amount, e.g. "PROD/2" */ + desiredSellAmount: string | number; + /** Amount of product stored in warehouse*/ + stored: number; + /** Amount of product produced last cycle */ + productionAmount: number; + /** Amount of product sold last cycle */ + actualSellAmount: number; + /** A number between 0-100 representing percentage completion */ developmentProgress: number; } @@ -7509,25 +7508,25 @@ interface Material { /** Name of the material */ name: CorpMaterialName; /** Amount of material */ - qty: number; + stored: number; /** Quality of the material */ - qlt: number; + quality: number; /** Demand for the material, only present if "Market Research - Demand" unlocked */ - dmd: number | undefined; + demand: number | undefined; /** Competition for the material, only present if "Market Research - Competition" unlocked */ - cmp: number | undefined; - /** Amount of material produced */ - prod: number; - /** Amount of material sold */ - sell: number; + competition: number | undefined; + /** Amount of material produced last cycle */ + productionAmount: number; + /** Amount of material sold last cycle */ + actualSellAmount: number; /** Cost to buy material */ - cost: number; + marketPrice: number; /** Sell cost, can be "MP+5" */ - sCost: string | number; + desiredSellPrice: string | number; /** Sell amount, can be "PROD/2" */ - sAmt: string | number; + desiredSellAmount: string | number; /** Export orders */ - exp: Export[]; + exports: Export[]; } /** @@ -7536,11 +7535,11 @@ interface Material { */ interface Export { /** Division the material is being exported to */ - div: string; + division: string; /** City the material is being exported to */ - loc: CityName; + city: CityName; /** Amount of material exported */ - amt: string; + amount: string; } /** @@ -7551,7 +7550,7 @@ interface Warehouse { /** Amount of size upgrade bought */ level: number; /** City in which the warehouse is located */ - loc: CityName; + city: CityName; /** Total space in the warehouse */ size: number; /** Used space in the warehouse */ @@ -7566,23 +7565,23 @@ interface Warehouse { */ export interface Office { /** City of the office */ - loc: CityName; + city: CityName; /** Maximum number of employee */ size: number; /** Maximum amount of energy of the employees */ - maxEne: number; + maxEnergy: number; /** Maximum morale of the employees */ - maxMor: number; + maxMorale: number; /** Amount of employees */ - employees: number; + numEmployees: number; /** Average energy of the employees */ - avgEne: number; + avgEnergy: number; /** Average morale of the employees */ - avgMor: number; + avgMorale: number; /** Total experience of all employees */ totalExperience: number; /** Production of the employees */ - employeeProd: Record; + employeeProductionByJob: Record; /** Positions of the employees */ employeeJobs: Record; } @@ -7601,9 +7600,9 @@ interface Division { /** Popularity of the division */ popularity: number; /** Production multiplier */ - prodMult: number; + productionMult: number; /** Amount of research in that division */ - research: number; + researchPoints: number; /** Revenue last cycle */ lastCycleRevenue: number; /** Expenses last cycle */ @@ -7612,11 +7611,11 @@ interface Division { thisCycleRevenue: number; /** Expenses this cycle */ thisCycleExpenses: number; - /** All research bought */ - upgrades: number[]; + /** Number of times AdVert has been bought */ + numAdVerts: number; /** Cities in which this division has expanded */ cities: CityName[]; - /** Products developed by this division */ + /** Names of Products developed by this division */ products: string[]; /** Whether the industry this division is in is capable of making products */ makesProducts: boolean; diff --git a/src/Types/Jsonable.ts b/src/Types/Jsonable.ts index 5d5075e93..42d5cbae1 100644 --- a/src/Types/Jsonable.ts +++ b/src/Types/Jsonable.ts @@ -1,9 +1,6 @@ import type { IReviverValue } from "../utils/JSONReviver"; +// Versions of js builtin classes that can be converted to and from JSON for use in save files -// Loosened type requirements on input for has, also has provides typecheck info. -export interface JSONSet { - has: (value: unknown) => value is T; -} export class JSONSet extends Set { toJSON(): IReviverValue { return { ctor: "JSONSet", data: Array.from(this) }; @@ -13,10 +10,6 @@ export class JSONSet extends Set { } } -// Loosened type requirements on input for has. has also provides typecheck info. -export interface JSONMap { - has: (key: unknown) => key is K; -} export class JSONMap extends Map { toJSON(): IReviverValue { return { ctor: "JSONMap", data: Array.from(this) }; diff --git a/src/Types/Record.ts b/src/Types/Record.ts new file mode 100644 index 000000000..6236a9cd8 --- /dev/null +++ b/src/Types/Record.ts @@ -0,0 +1,39 @@ +// Some extra utility to make Records with strongly typed keys (e.g enum member keys) simpler to work with in TS. +// Using maps instead of plain objects is another option, but maps require an extra step to convert to/from JSON +// So they should not be overused. + +/** Easier semantic name for a partial record */ +export type PartialRecord = Partial>; + +/** Get values from a partial record with proper type */ +export const getRecordValues = Object.values as (record: PartialRecord) => V[]; + +/** Get a keys array with proper type. Object.keys by default returns string[] */ +export const getRecordKeys = Object.keys as (record: PartialRecord) => K[]; + +/** Get an entries array with properly typed keys. Object.entries by default represents keys as string */ +export const getRecordEntries = Object.entries as (record: PartialRecord) => [K, V][]; + +/** Use this function only when entries is guaranteed to contain all members of K, + * e.g. when it's an array from mapping the enum values, or the keys from a different full record. + * If not all members of type K are used, use createPartialRecordFromEntries instead. */ +export const createFullRecordFromEntries = Object.fromEntries as ( + entries: [K, V][], +) => Record; + +/** Create a correctly typed object from entries with strongly typed keys. + * This is safe to use even if not all members of type K are present in the entries. + * If all members of K are guaranteed to be present, see createFullRecordFromEntries. */ +export const createPartialRecordFromEntries = Object.fromEntries as ( + entries: [K, V][], +) => PartialRecord; + +/** Create a correctly-typed full record keyed by an enum with values based on a value function + * @param enumObj The enum object + * @param valueFunction The function which will produce the value, taking in the key as a parameter */ +export function createEnumKeyedRecord( + enumObj: Record, + valueFunction: (key: K) => V, +): Record { + return createFullRecordFromEntries(Object.values(enumObj).map((member) => [member, valueFunction(member)])); +} diff --git a/src/ui/Components/ButtonWithTooltip.tsx b/src/ui/Components/ButtonWithTooltip.tsx new file mode 100644 index 000000000..db6871948 --- /dev/null +++ b/src/ui/Components/ButtonWithTooltip.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import Tooltip, { TooltipProps } from "@mui/material/Tooltip"; +import Button, { ButtonProps } from "@mui/material/Button"; + +interface ButtonWithTooltipProps { + /** "" if the button is not disabled. If this is truthy, the button is disabled and this tooltip is displayed. */ + disabledTooltip?: TooltipProps["title"]; + /** Text to display if button is enabled (if disabledTooltip is not provided or is "") */ + normalTooltip?: TooltipProps["title"]; + /** The onClick function */ + onClick: ButtonProps["onClick"]; + /** Button props other than "disabled" */ + buttonProps?: Omit; + /** Tooltip props other than "title" */ + tooltipProps?: Omit; + children: ButtonProps["children"]; +} + +/** Displays a tooltip on a button when the button is disabled, to explain why it is disabled */ +export function ButtonWithTooltip({ + disabledTooltip, + normalTooltip, + onClick, + buttonProps, + tooltipProps, + children, +}: ButtonWithTooltipProps) { + buttonProps ??= {}; + tooltipProps ??= {}; + const tooltipText = (disabledTooltip || normalTooltip) ?? ""; + const disabled = !!disabledTooltip; + return ( + + +