mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-18 12:15:44 +01:00
CORPORATION: Rework valuation calculation (#789)
This commit is contained in:
parent
e1d2e12747
commit
1b81fe8766
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -62,6 +62,7 @@ export function Overview({ rerender }: IProps): React.ReactElement {
|
||||
<StatsTable
|
||||
rows={[
|
||||
["Total Funds:", <Money key="funds" money={corp.funds} />],
|
||||
["Total Assets:", <Money key="assets" money={corp.totalAssets} />],
|
||||
["Total Revenue:", <MoneyRate key="revenue" money={corp.revenue} />],
|
||||
["Total Expenses:", <MoneyRate key="expenses" money={corp.expenses} />],
|
||||
["Total Profit:", <MoneyRate key="profit" money={corp.revenue - corp.expenses} />],
|
||||
|
@ -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();
|
||||
|
@ -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": {
|
||||
|
@ -142,7 +142,7 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
||||
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<NSCorporation> {
|
||||
corporation.sharePrice = initialSharePrice;
|
||||
corporation.issuedShares = numShares;
|
||||
corporation.numShares -= numShares;
|
||||
corporation.addFunds(numShares * initialSharePrice);
|
||||
corporation.addNonIncomeFunds(numShares * initialSharePrice);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user