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]) {
|
if (division.offices[city]) {
|
||||||
throw new Error(`You have already expanded into ${city} for ${division.name}`);
|
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({
|
division.offices[city] = new OfficeSpace({
|
||||||
city: city,
|
city: city,
|
||||||
size: corpConstants.officeInitialSize,
|
size: corpConstants.officeInitialSize,
|
||||||
@ -106,7 +106,7 @@ export function IssueNewShares(corporation: Corporation, amount: number): [numbe
|
|||||||
|
|
||||||
corporation.issuedShares += amount - privateShares;
|
corporation.issuedShares += amount - privateShares;
|
||||||
corporation.totalShares += amount;
|
corporation.totalShares += amount;
|
||||||
corporation.funds = corporation.funds + profit;
|
corporation.addNonIncomeFunds(profit);
|
||||||
corporation.immediatelyUpdateSharePrice();
|
corporation.immediatelyUpdateSharePrice();
|
||||||
|
|
||||||
return [profit, amount, privateShares];
|
return [profit, amount, privateShares];
|
||||||
@ -325,7 +325,7 @@ export function UpgradeOfficeSize(corp: Corporation, office: OfficeSpace, size:
|
|||||||
const cost = corpConstants.officeInitialCost * mult;
|
const cost = corpConstants.officeInitialCost * mult;
|
||||||
if (corp.funds < cost) return;
|
if (corp.funds < cost) return;
|
||||||
office.size += size;
|
office.size += size;
|
||||||
corp.funds = corp.funds - cost;
|
corp.addNonIncomeFunds(-cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BuyTea(corp: Corporation, office: OfficeSpace): boolean {
|
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 {
|
export function purchaseWarehouse(corp: Corporation, division: Division, city: CityName): void {
|
||||||
if (corp.funds < corpConstants.warehouseInitialCost) return;
|
if (corp.funds < corpConstants.warehouseInitialCost) return;
|
||||||
if (division.warehouses[city]) return;
|
if (division.warehouses[city]) return;
|
||||||
corp.funds = corp.funds - corpConstants.warehouseInitialCost;
|
corp.addNonIncomeFunds(-corpConstants.warehouseInitialCost);
|
||||||
division.warehouses[city] = new Warehouse({
|
division.warehouses[city] = new Warehouse({
|
||||||
division: division,
|
division: division,
|
||||||
loc: city,
|
loc: city,
|
||||||
@ -373,7 +373,7 @@ export function UpgradeWarehouse(corp: Corporation, division: Division, warehous
|
|||||||
if (corp.funds < sizeUpgradeCost) return;
|
if (corp.funds < sizeUpgradeCost) return;
|
||||||
warehouse.level += amt;
|
warehouse.level += amt;
|
||||||
warehouse.updateSize(corp, division);
|
warehouse.updateSize(corp, division);
|
||||||
corp.funds = corp.funds - sizeUpgradeCost;
|
corp.addNonIncomeFunds(-sizeUpgradeCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HireAdVert(corp: Corporation, division: Division): void {
|
export function HireAdVert(corp: Corporation, division: Division): void {
|
||||||
|
@ -4,6 +4,7 @@ import { CorporationState } from "./CorporationState";
|
|||||||
import { CorpUnlocks } from "./data/CorporationUnlocks";
|
import { CorpUnlocks } from "./data/CorporationUnlocks";
|
||||||
import { CorpUpgrades } from "./data/CorporationUpgrades";
|
import { CorpUpgrades } from "./data/CorporationUpgrades";
|
||||||
import * as corpConstants from "./data/Constants";
|
import * as corpConstants from "./data/Constants";
|
||||||
|
import { IndustriesData } from "./data/IndustryData";
|
||||||
import { Division } from "./Division";
|
import { Division } from "./Division";
|
||||||
|
|
||||||
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
||||||
@ -56,6 +57,8 @@ export class Corporation {
|
|||||||
value: name === CorpUpgradeName.DreamSense ? 0 : 1,
|
value: name === CorpUpgradeName.DreamSense ? 0 : 1,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
previousTotalAssets = 150e9;
|
||||||
|
totalAssets = 150e9;
|
||||||
cycleValuation = 0;
|
cycleValuation = 0;
|
||||||
valuationsList = [0];
|
valuationsList = [0];
|
||||||
valuation = 0;
|
valuation = 0;
|
||||||
@ -77,6 +80,17 @@ export class Corporation {
|
|||||||
this.funds += amt;
|
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 {
|
getState(): CorpStateName {
|
||||||
return this.state.getState();
|
return this.state.getState();
|
||||||
}
|
}
|
||||||
@ -126,8 +140,6 @@ export class Corporation {
|
|||||||
this.expenses = this.expenses + ind.lastCycleExpenses;
|
this.expenses = this.expenses + ind.lastCycleExpenses;
|
||||||
});
|
});
|
||||||
const profit = this.revenue - this.expenses;
|
const profit = this.revenue - this.expenses;
|
||||||
this.cycleValuation = this.determineCycleValuation();
|
|
||||||
this.determineValuation();
|
|
||||||
const cycleProfit = profit * (marketCycles * corpConstants.secondsPerMarketCycle);
|
const cycleProfit = profit * (marketCycles * corpConstants.secondsPerMarketCycle);
|
||||||
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
|
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
|
||||||
dialogBoxCreate(
|
dialogBoxCreate(
|
||||||
@ -137,7 +149,6 @@ export class Corporation {
|
|||||||
);
|
);
|
||||||
this.funds = 150e9;
|
this.funds = 150e9;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.dividendRate > 0 && cycleProfit > 0) {
|
if (this.dividendRate > 0 && cycleProfit > 0) {
|
||||||
// Validate input again, just to be safe
|
// Validate input again, just to be safe
|
||||||
if (isNaN(this.dividendRate) || this.dividendRate < 0 || this.dividendRate > corpConstants.dividendMaxRate) {
|
if (isNaN(this.dividendRate) || this.dividendRate < 0 || this.dividendRate > corpConstants.dividendMaxRate) {
|
||||||
@ -151,7 +162,9 @@ export class Corporation {
|
|||||||
} else {
|
} else {
|
||||||
this.addFunds(cycleProfit);
|
this.addFunds(cycleProfit);
|
||||||
}
|
}
|
||||||
|
this.updateTotalAssets();
|
||||||
|
this.cycleValuation = this.determineCycleValuation();
|
||||||
|
this.determineValuation();
|
||||||
this.updateSharePrice();
|
this.updateSharePrice();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,24 +183,27 @@ export class Corporation {
|
|||||||
|
|
||||||
determineCycleValuation(): number {
|
determineCycleValuation(): number {
|
||||||
let val,
|
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) {
|
if (this.public) {
|
||||||
// Account for dividends
|
// Account for dividends
|
||||||
if (this.dividendRate > 0) {
|
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.pow(1.1, this.divisions.size);
|
||||||
val = Math.max(val, 0);
|
val = Math.max(val, 0);
|
||||||
} else {
|
} else {
|
||||||
val = 10e9 + Math.max(this.funds, 0) / 3; //Base valuation
|
val = 10e9 + this.funds / 3;
|
||||||
if (profit > 0) {
|
if (assetDelta > 0) {
|
||||||
val += profit * 315e3;
|
val += assetDelta * 315e3;
|
||||||
}
|
}
|
||||||
val *= Math.pow(1.1, this.divisions.size);
|
val *= Math.pow(1.1, this.divisions.size);
|
||||||
val -= val % 1e6; //Round down to nearest millionth
|
val -= val % 1e6; //Round down to nearest millionth
|
||||||
}
|
}
|
||||||
|
if (val < 10e9) val = 10e9; // Base valuation
|
||||||
return val * currentNodeMults.CorporationValuation;
|
return val * currentNodeMults.CorporationValuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,10 +211,27 @@ export class Corporation {
|
|||||||
this.valuationsList.push(this.cycleValuation); //Add current valuation to the list
|
this.valuationsList.push(this.cycleValuation); //Add current valuation to the list
|
||||||
if (this.valuationsList.length > corpConstants.valuationLength) this.valuationsList.shift();
|
if (this.valuationsList.length > corpConstants.valuationLength) this.valuationsList.shift();
|
||||||
let val = this.valuationsList.reduce((a, b) => a + b); //Calculate valuations sum
|
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;
|
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 {
|
getTargetSharePrice(): number {
|
||||||
// Note: totalShares - numShares is not the same as issuedShares because
|
// Note: totalShares - numShares is not the same as issuedShares because
|
||||||
// issuedShares does not account for private investors
|
// 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}`;
|
if (this.unlocks.has(unlockName)) return `The corporation has already unlocked ${unlockName}`;
|
||||||
const price = CorpUnlocks[unlockName].price;
|
const price = CorpUnlocks[unlockName].price;
|
||||||
if (this.funds < price) return `Insufficient funds to purchase ${unlockName}, requires ${formatMoney(price)}`;
|
if (this.funds < price) return `Insufficient funds to purchase ${unlockName}, requires ${formatMoney(price)}`;
|
||||||
this.funds -= price;
|
this.addNonIncomeFunds(-price);
|
||||||
this.unlocks.add(unlockName);
|
this.unlocks.add(unlockName);
|
||||||
|
|
||||||
// Apply effects for one-time unlocks
|
// Apply effects for one-time unlocks
|
||||||
@ -308,7 +341,7 @@ export class Corporation {
|
|||||||
const upgrade = CorpUpgrades[upgradeName];
|
const upgrade = CorpUpgrades[upgradeName];
|
||||||
const totalCost = calculateUpgradeCost(this, upgrade, amount);
|
const totalCost = calculateUpgradeCost(this, upgrade, amount);
|
||||||
if (this.funds < totalCost) return `Not enough funds to purchase ${amount} of upgrade ${upgradeName}.`;
|
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].level += amount;
|
||||||
this.upgrades[upgradeName].value += upgrade.benefit * amount;
|
this.upgrades[upgradeName].value += upgrade.benefit * amount;
|
||||||
|
|
||||||
|
@ -311,6 +311,7 @@ export class Division {
|
|||||||
buyAmt = Math.min(buyAmt, maxAmt);
|
buyAmt = Math.min(buyAmt, maxAmt);
|
||||||
if (buyAmt > 0) {
|
if (buyAmt > 0) {
|
||||||
mat.quality = Math.max(0.1, (mat.quality * mat.stored + 1 * buyAmt) / (mat.stored + buyAmt));
|
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;
|
mat.stored += buyAmt;
|
||||||
expenses += buyAmt * mat.marketPrice;
|
expenses += buyAmt * mat.marketPrice;
|
||||||
}
|
}
|
||||||
@ -364,8 +365,13 @@ export class Division {
|
|||||||
// buy them
|
// buy them
|
||||||
for (const [matName, [buyAmt]] of getRecordEntries(smartBuy)) {
|
for (const [matName, [buyAmt]] of getRecordEntries(smartBuy)) {
|
||||||
const mat = warehouse.materials[matName];
|
const mat = warehouse.materials[matName];
|
||||||
if (mat.stored + buyAmt != 0) mat.quality = (mat.quality * mat.stored + 1 * buyAmt) / (mat.stored + buyAmt);
|
if (mat.stored + buyAmt != 0) {
|
||||||
else mat.quality = 1;
|
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.stored += buyAmt;
|
||||||
mat.buyAmount = buyAmt / (corpConstants.secondsPerMarketCycle * marketCycles);
|
mat.buyAmount = buyAmt / (corpConstants.secondsPerMarketCycle * marketCycles);
|
||||||
expenses += buyAmt * mat.marketPrice;
|
expenses += buyAmt * mat.marketPrice;
|
||||||
@ -460,6 +466,11 @@ export class Division {
|
|||||||
tempQlt * prod * producableFrac) /
|
tempQlt * prod * producableFrac) /
|
||||||
(warehouse.materials[this.producedMaterials[j]].stored + 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;
|
warehouse.materials[this.producedMaterials[j]].stored += prod * producableFrac;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -678,6 +689,10 @@ export class Division {
|
|||||||
(expWarehouse.materials[matName].stored + amt),
|
(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;
|
expWarehouse.materials[matName].stored += amt;
|
||||||
mat.stored -= amt;
|
mat.stored -= amt;
|
||||||
mat.exportedLastCycle += amt;
|
mat.exportedLastCycle += amt;
|
||||||
|
@ -52,6 +52,9 @@ export class Material {
|
|||||||
// Cost / sec to buy this material. AKA Market Price
|
// Cost / sec to buy this material. AKA Market Price
|
||||||
marketPrice = 0;
|
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. */
|
/** null if there is no limit set on production. 0 actually limits production to 0. */
|
||||||
productionLimit: number | null = null;
|
productionLimit: number | null = null;
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ export function Overview({ rerender }: IProps): React.ReactElement {
|
|||||||
<StatsTable
|
<StatsTable
|
||||||
rows={[
|
rows={[
|
||||||
["Total Funds:", <Money key="funds" money={corp.funds} />],
|
["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 Revenue:", <MoneyRate key="revenue" money={corp.revenue} />],
|
||||||
["Total Expenses:", <MoneyRate key="expenses" money={corp.expenses} />],
|
["Total Expenses:", <MoneyRate key="expenses" money={corp.expenses} />],
|
||||||
["Total Profit:", <MoneyRate key="profit" money={corp.revenue - 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 {
|
function findInvestors(): void {
|
||||||
corporation.fundingRound++;
|
corporation.fundingRound++;
|
||||||
corporation.addFunds(funding);
|
corporation.addNonIncomeFunds(funding);
|
||||||
corporation.numShares -= investShares;
|
corporation.numShares -= investShares;
|
||||||
props.rerender();
|
props.rerender();
|
||||||
props.onClose();
|
props.onClose();
|
||||||
|
@ -479,7 +479,7 @@ export function purchaseHashUpgrade(upgName: string, upgTarget: string, count =
|
|||||||
Player.hashManager.refundUpgrade(upgName, count);
|
Player.hashManager.refundUpgrade(upgName, count);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
corp.funds = corp.funds + upg.value * count;
|
corp.addNonIncomeFunds(upg.value * count);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Reduce Minimum Security": {
|
case "Reduce Minimum Security": {
|
||||||
|
@ -142,7 +142,7 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
|||||||
const funding = val * percShares * roundMultiplier;
|
const funding = val * percShares * roundMultiplier;
|
||||||
const investShares = Math.floor(corpConstants.initialShares * percShares);
|
const investShares = Math.floor(corpConstants.initialShares * percShares);
|
||||||
corporation.fundingRound++;
|
corporation.fundingRound++;
|
||||||
corporation.addFunds(funding);
|
corporation.addNonIncomeFunds(funding);
|
||||||
corporation.numShares -= investShares;
|
corporation.numShares -= investShares;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -157,7 +157,7 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
|||||||
corporation.sharePrice = initialSharePrice;
|
corporation.sharePrice = initialSharePrice;
|
||||||
corporation.issuedShares = numShares;
|
corporation.issuedShares = numShares;
|
||||||
corporation.numShares -= numShares;
|
corporation.numShares -= numShares;
|
||||||
corporation.addFunds(numShares * initialSharePrice);
|
corporation.addNonIncomeFunds(numShares * initialSharePrice);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1155,6 +1155,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
|||||||
"maxDivisions": 20,
|
"maxDivisions": 20,
|
||||||
"name": "Test Corp",
|
"name": "Test Corp",
|
||||||
"numShares": 1000000000,
|
"numShares": 1000000000,
|
||||||
|
"previousTotalAssets": 150000000000,
|
||||||
"public": false,
|
"public": false,
|
||||||
"revenue": 0,
|
"revenue": 0,
|
||||||
"seedFunded": false,
|
"seedFunded": false,
|
||||||
@ -1168,6 +1169,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"storedCycles": 0,
|
"storedCycles": 0,
|
||||||
|
"totalAssets": 150000000000,
|
||||||
"totalShares": 1000000000,
|
"totalShares": 1000000000,
|
||||||
"unlocks": {
|
"unlocks": {
|
||||||
"ctor": "JSONSet",
|
"ctor": "JSONSet",
|
||||||
|
Loading…
Reference in New Issue
Block a user