diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts
index 438ce97a6..afcb0c888 100644
--- a/src/Corporation/Actions.ts
+++ b/src/Corporation/Actions.ts
@@ -70,7 +70,7 @@ export function purchaseOffice(corporation: Corporation, division: Division, cit
if (division.offices[city]) {
throw new Error(`You have already expanded into ${city} for ${division.name}`);
}
- corporation.funds = corporation.funds - corpConstants.officeInitialCost;
+ corporation.addNonIncomeFunds(-corpConstants.officeInitialCost);
division.offices[city] = new OfficeSpace({
city: city,
size: corpConstants.officeInitialSize,
@@ -106,7 +106,7 @@ export function IssueNewShares(corporation: Corporation, amount: number): [numbe
corporation.issuedShares += amount - privateShares;
corporation.totalShares += amount;
- corporation.funds = corporation.funds + profit;
+ corporation.addNonIncomeFunds(profit);
corporation.immediatelyUpdateSharePrice();
return [profit, amount, privateShares];
@@ -325,7 +325,7 @@ export function UpgradeOfficeSize(corp: Corporation, office: OfficeSpace, size:
const cost = corpConstants.officeInitialCost * mult;
if (corp.funds < cost) return;
office.size += size;
- corp.funds = corp.funds - cost;
+ corp.addNonIncomeFunds(-cost);
}
export function BuyTea(corp: Corporation, office: OfficeSpace): boolean {
@@ -353,7 +353,7 @@ export function ThrowParty(corp: Corporation, office: OfficeSpace, costPerEmploy
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;
+ corp.addNonIncomeFunds(-corpConstants.warehouseInitialCost);
division.warehouses[city] = new Warehouse({
division: division,
loc: city,
@@ -373,7 +373,7 @@ export function UpgradeWarehouse(corp: Corporation, division: Division, warehous
if (corp.funds < sizeUpgradeCost) return;
warehouse.level += amt;
warehouse.updateSize(corp, division);
- corp.funds = corp.funds - sizeUpgradeCost;
+ corp.addNonIncomeFunds(-sizeUpgradeCost);
}
export function HireAdVert(corp: Corporation, division: Division): void {
diff --git a/src/Corporation/Corporation.ts b/src/Corporation/Corporation.ts
index e849d692a..5156f0b10 100644
--- a/src/Corporation/Corporation.ts
+++ b/src/Corporation/Corporation.ts
@@ -4,6 +4,7 @@ import { CorporationState } from "./CorporationState";
import { CorpUnlocks } from "./data/CorporationUnlocks";
import { CorpUpgrades } from "./data/CorporationUpgrades";
import * as corpConstants from "./data/Constants";
+import { IndustriesData } from "./data/IndustryData";
import { Division } from "./Division";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
@@ -56,6 +57,8 @@ export class Corporation {
value: name === CorpUpgradeName.DreamSense ? 0 : 1,
}));
+ previousTotalAssets = 150e9;
+ totalAssets = 150e9;
cycleValuation = 0;
valuationsList = [0];
valuation = 0;
@@ -77,6 +80,17 @@ export class Corporation {
this.funds += amt;
}
+ // Add or subtract funds which should not be counted for valuation; e.g. investments,
+ // upgrades, stock issuance
+ addNonIncomeFunds(amt: number): void {
+ if (!isFinite(amt)) {
+ console.error("Trying to add invalid amount of funds. Report to a developer.");
+ return;
+ }
+ this.totalAssets += amt;
+ this.funds += amt;
+ }
+
getState(): CorpStateName {
return this.state.getState();
}
@@ -126,8 +140,6 @@ export class Corporation {
this.expenses = this.expenses + ind.lastCycleExpenses;
});
const profit = this.revenue - this.expenses;
- this.cycleValuation = this.determineCycleValuation();
- this.determineValuation();
const cycleProfit = profit * (marketCycles * corpConstants.secondsPerMarketCycle);
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
dialogBoxCreate(
@@ -137,7 +149,6 @@ export class Corporation {
);
this.funds = 150e9;
}
-
if (this.dividendRate > 0 && cycleProfit > 0) {
// Validate input again, just to be safe
if (isNaN(this.dividendRate) || this.dividendRate < 0 || this.dividendRate > corpConstants.dividendMaxRate) {
@@ -151,7 +162,9 @@ export class Corporation {
} else {
this.addFunds(cycleProfit);
}
-
+ this.updateTotalAssets();
+ this.cycleValuation = this.determineCycleValuation();
+ this.determineValuation();
this.updateSharePrice();
}
@@ -170,24 +183,27 @@ export class Corporation {
determineCycleValuation(): number {
let val,
- profit = this.revenue - this.expenses;
+ assetDelta = (this.totalAssets - this.previousTotalAssets) / corpConstants.secondsPerMarketCycle;
+ // Handle pre-totalAssets saves
+ assetDelta ??= this.revenue - this.expenses;
if (this.public) {
// Account for dividends
if (this.dividendRate > 0) {
- profit *= 1 - this.dividendRate;
+ assetDelta *= 1 - this.dividendRate;
}
- val = this.funds + profit * 85e3;
+ val = this.funds + assetDelta * 85e3;
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 = 10e9 + this.funds / 3;
+ if (assetDelta > 0) {
+ val += assetDelta * 315e3;
}
val *= Math.pow(1.1, this.divisions.size);
val -= val % 1e6; //Round down to nearest millionth
}
+ if (val < 10e9) val = 10e9; // Base valuation
return val * currentNodeMults.CorporationValuation;
}
@@ -195,10 +211,27 @@ export class Corporation {
this.valuationsList.push(this.cycleValuation); //Add current valuation to the list
if (this.valuationsList.length > corpConstants.valuationLength) this.valuationsList.shift();
let val = this.valuationsList.reduce((a, b) => a + b); //Calculate valuations sum
- val /= corpConstants.valuationLength; //Calculate the average
+ val /= this.valuationsList.length; //Calculate the average
this.valuation = val;
}
+ updateTotalAssets(): void {
+ let assets = this.funds;
+ this.divisions.forEach((ind) => {
+ assets += IndustriesData[ind.type].startingCost;
+ for (const warehouse of getRecordValues(ind.warehouses)) {
+ for (const mat of getRecordValues(warehouse.materials)) {
+ assets += mat.stored * mat.averagePrice;
+ }
+ for (const prod of ind.products.values()) {
+ assets += prod.cityData[warehouse.city].stored * prod.productionCost;
+ }
+ }
+ });
+ this.previousTotalAssets = this.totalAssets;
+ this.totalAssets = assets;
+ }
+
getTargetSharePrice(): number {
// Note: totalShares - numShares is not the same as issuedShares because
// issuedShares does not account for private investors
@@ -291,7 +324,7 @@ export class Corporation {
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.addNonIncomeFunds(-price);
this.unlocks.add(unlockName);
// Apply effects for one-time unlocks
@@ -308,7 +341,7 @@ export class Corporation {
const upgrade = CorpUpgrades[upgradeName];
const totalCost = calculateUpgradeCost(this, upgrade, amount);
if (this.funds < totalCost) return `Not enough funds to purchase ${amount} of upgrade ${upgradeName}.`;
- this.funds -= totalCost;
+ this.addNonIncomeFunds(-totalCost);
this.upgrades[upgradeName].level += amount;
this.upgrades[upgradeName].value += upgrade.benefit * amount;
diff --git a/src/Corporation/Division.ts b/src/Corporation/Division.ts
index bd84301e0..5c5414cf7 100644
--- a/src/Corporation/Division.ts
+++ b/src/Corporation/Division.ts
@@ -311,6 +311,7 @@ export class Division {
buyAmt = Math.min(buyAmt, maxAmt);
if (buyAmt > 0) {
mat.quality = Math.max(0.1, (mat.quality * mat.stored + 1 * buyAmt) / (mat.stored + buyAmt));
+ mat.averagePrice = (mat.stored * mat.averagePrice + buyAmt * mat.marketPrice) / (mat.stored + buyAmt);
mat.stored += buyAmt;
expenses += buyAmt * mat.marketPrice;
}
@@ -364,8 +365,13 @@ export class Division {
// 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;
+ if (mat.stored + buyAmt != 0) {
+ mat.quality = (mat.quality * mat.stored + 1 * buyAmt) / (mat.stored + buyAmt);
+ mat.averagePrice = (mat.averagePrice * mat.stored + mat.marketPrice * buyAmt) / (mat.stored + buyAmt);
+ } else {
+ mat.quality = 1;
+ mat.averagePrice = mat.marketPrice;
+ }
mat.stored += buyAmt;
mat.buyAmount = buyAmt / (corpConstants.secondsPerMarketCycle * marketCycles);
expenses += buyAmt * mat.marketPrice;
@@ -460,6 +466,11 @@ export class Division {
tempQlt * prod * producableFrac) /
(warehouse.materials[this.producedMaterials[j]].stored + prod * producableFrac),
);
+ warehouse.materials[this.producedMaterials[j]].averagePrice =
+ (warehouse.materials[this.producedMaterials[j]].averagePrice *
+ warehouse.materials[this.producedMaterials[j]].stored +
+ warehouse.materials[this.producedMaterials[j]].marketPrice * prod * producableFrac) /
+ (warehouse.materials[this.producedMaterials[j]].stored + prod * producableFrac);
warehouse.materials[this.producedMaterials[j]].stored += prod * producableFrac;
}
} else {
@@ -678,6 +689,10 @@ export class Division {
(expWarehouse.materials[matName].stored + amt),
);
+ expWarehouse.materials[matName].averagePrice =
+ (expWarehouse.materials[matName].averagePrice * expWarehouse.materials[matName].stored +
+ expWarehouse.materials[matName].marketPrice * amt) /
+ (expWarehouse.materials[matName].stored + amt);
expWarehouse.materials[matName].stored += amt;
mat.stored -= amt;
mat.exportedLastCycle += amt;
diff --git a/src/Corporation/Material.ts b/src/Corporation/Material.ts
index 574233c6a..4d5136fcd 100644
--- a/src/Corporation/Material.ts
+++ b/src/Corporation/Material.ts
@@ -52,6 +52,9 @@ export class Material {
// Cost / sec to buy this material. AKA Market Price
marketPrice = 0;
+ // Average price paid for the material (accounted as marketPrice for produced/imported materials)
+ averagePrice = 0;
+
/** null if there is no limit set on production. 0 actually limits production to 0. */
productionLimit: number | null = null;
diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx
index 833d9e40c..9df0ae8a3 100644
--- a/src/Corporation/ui/Overview.tsx
+++ b/src/Corporation/ui/Overview.tsx
@@ -62,6 +62,7 @@ export function Overview({ rerender }: IProps): React.ReactElement {
],
+ ["Total Assets:", ],
["Total Revenue:", ],
["Total Expenses:", ],
["Total Profit:", ],
diff --git a/src/Corporation/ui/modals/FindInvestorsModal.tsx b/src/Corporation/ui/modals/FindInvestorsModal.tsx
index e1d0b168f..b7657e32e 100644
--- a/src/Corporation/ui/modals/FindInvestorsModal.tsx
+++ b/src/Corporation/ui/modals/FindInvestorsModal.tsx
@@ -29,7 +29,7 @@ export function FindInvestorsModal(props: IProps): React.ReactElement {
function findInvestors(): void {
corporation.fundingRound++;
- corporation.addFunds(funding);
+ corporation.addNonIncomeFunds(funding);
corporation.numShares -= investShares;
props.rerender();
props.onClose();
diff --git a/src/Hacknet/HacknetHelpers.tsx b/src/Hacknet/HacknetHelpers.tsx
index 3a862fc24..d7649feda 100644
--- a/src/Hacknet/HacknetHelpers.tsx
+++ b/src/Hacknet/HacknetHelpers.tsx
@@ -479,7 +479,7 @@ export function purchaseHashUpgrade(upgName: string, upgTarget: string, count =
Player.hashManager.refundUpgrade(upgName, count);
return false;
}
- corp.funds = corp.funds + upg.value * count;
+ corp.addNonIncomeFunds(upg.value * count);
break;
}
case "Reduce Minimum Security": {
diff --git a/src/NetscriptFunctions/Corporation.ts b/src/NetscriptFunctions/Corporation.ts
index 43f60dcf2..189c23026 100644
--- a/src/NetscriptFunctions/Corporation.ts
+++ b/src/NetscriptFunctions/Corporation.ts
@@ -142,7 +142,7 @@ export function NetscriptCorporation(): InternalAPI {
const funding = val * percShares * roundMultiplier;
const investShares = Math.floor(corpConstants.initialShares * percShares);
corporation.fundingRound++;
- corporation.addFunds(funding);
+ corporation.addNonIncomeFunds(funding);
corporation.numShares -= investShares;
return true;
}
@@ -157,7 +157,7 @@ export function NetscriptCorporation(): InternalAPI {
corporation.sharePrice = initialSharePrice;
corporation.issuedShares = numShares;
corporation.numShares -= numShares;
- corporation.addFunds(numShares * initialSharePrice);
+ corporation.addNonIncomeFunds(numShares * initialSharePrice);
return true;
}
diff --git a/test/jest/__snapshots__/FullSave.test.ts.snap b/test/jest/__snapshots__/FullSave.test.ts.snap
index 5050dd2ef..d58fcf253 100644
--- a/test/jest/__snapshots__/FullSave.test.ts.snap
+++ b/test/jest/__snapshots__/FullSave.test.ts.snap
@@ -1155,6 +1155,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
"maxDivisions": 20,
"name": "Test Corp",
"numShares": 1000000000,
+ "previousTotalAssets": 150000000000,
"public": false,
"revenue": 0,
"seedFunded": false,
@@ -1168,6 +1169,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
},
},
"storedCycles": 0,
+ "totalAssets": 150000000000,
"totalShares": 1000000000,
"unlocks": {
"ctor": "JSONSet",