mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-17 13:13:49 +01:00
CORPORATION: Rework share price calculation + UI improvements (#782)
This commit is contained in:
parent
f6e1c171ae
commit
3ae3f947ac
@ -4,7 +4,7 @@
|
||||
|
||||
## Corporation.buyBackShares() method
|
||||
|
||||
Buyback Shares
|
||||
Buyback Shares. Spend money from the player's wallet to transfer shares from public traders to the CEO.
|
||||
|
||||
**Signature:**
|
||||
|
||||
|
@ -19,7 +19,7 @@ export interface Corporation extends WarehouseAPI, OfficeAPI
|
||||
| --- | --- |
|
||||
| [acceptInvestmentOffer()](./bitburner.corporation.acceptinvestmentoffer.md) | Accept investment based on you companies current valuation |
|
||||
| [bribe(factionName, amountCash)](./bitburner.corporation.bribe.md) | Bribe a faction |
|
||||
| [buyBackShares(amount)](./bitburner.corporation.buybackshares.md) | Buyback Shares |
|
||||
| [buyBackShares(amount)](./bitburner.corporation.buybackshares.md) | Buyback Shares. Spend money from the player's wallet to transfer shares from public traders to the CEO. |
|
||||
| [createCorporation(corporationName, selfFund)](./bitburner.corporation.createcorporation.md) | Create a Corporation |
|
||||
| [expandCity(divisionName, city)](./bitburner.corporation.expandcity.md) | Expand to a new city |
|
||||
| [expandIndustry(industryType, divisionName)](./bitburner.corporation.expandindustry.md) | Expand to a new industry |
|
||||
@ -40,5 +40,5 @@ export interface Corporation extends WarehouseAPI, OfficeAPI
|
||||
| [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 |
|
||||
| [sellShares(amount)](./bitburner.corporation.sellshares.md) | Sell Shares. Transfer shares from the CEO to public traders to receive money in the player's wallet. |
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
## Corporation.sellShares() method
|
||||
|
||||
Sell Shares
|
||||
Sell Shares. Transfer shares from the CEO to public traders to receive money in the player's wallet.
|
||||
|
||||
**Signature:**
|
||||
|
||||
|
13
markdown/bitburner.corporationinfo.investorshares.md
Normal file
13
markdown/bitburner.corporationinfo.investorshares.md
Normal file
@ -0,0 +1,13 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [bitburner](./bitburner.md) > [CorporationInfo](./bitburner.corporationinfo.md) > [investorShares](./bitburner.corporationinfo.investorshares.md)
|
||||
|
||||
## CorporationInfo.investorShares property
|
||||
|
||||
Amount of shares owned by private investors. Not available for public sale or CEO buyback.
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
investorShares: number;
|
||||
```
|
@ -4,7 +4,7 @@
|
||||
|
||||
## CorporationInfo.issuedShares property
|
||||
|
||||
Amount of acquirable shares.
|
||||
Amount of shares owned by public traders. Available for CEO buyback.
|
||||
|
||||
**Signature:**
|
||||
|
||||
|
@ -22,14 +22,15 @@ interface CorporationInfo
|
||||
| [divisions](./bitburner.corporationinfo.divisions.md) | | string\[\] | Array of all division names |
|
||||
| [expenses](./bitburner.corporationinfo.expenses.md) | | number | Expenses per second this cycle |
|
||||
| [funds](./bitburner.corporationinfo.funds.md) | | number | Funds available |
|
||||
| [issuedShares](./bitburner.corporationinfo.issuedshares.md) | | number | Amount of acquirable shares. |
|
||||
| [investorShares](./bitburner.corporationinfo.investorshares.md) | | number | Amount of shares owned by private investors. Not available for public sale or CEO buyback. |
|
||||
| [issuedShares](./bitburner.corporationinfo.issuedshares.md) | | number | Amount of shares owned by public traders. Available for CEO buyback. |
|
||||
| [issueNewSharesCooldown](./bitburner.corporationinfo.issuenewsharescooldown.md) | | number | Cooldown until new shares can be issued |
|
||||
| [name](./bitburner.corporationinfo.name.md) | | string | Name of the corporation |
|
||||
| [numShares](./bitburner.corporationinfo.numshares.md) | | number | Amount of share owned |
|
||||
| [numShares](./bitburner.corporationinfo.numshares.md) | | number | Amount of shares owned by the CEO. |
|
||||
| [public](./bitburner.corporationinfo.public.md) | | boolean | Indicating if the company is public |
|
||||
| [revenue](./bitburner.corporationinfo.revenue.md) | | number | Revenue per second this cycle |
|
||||
| [sharePrice](./bitburner.corporationinfo.shareprice.md) | | number | Price of the shares |
|
||||
| [shareSaleCooldown](./bitburner.corporationinfo.sharesalecooldown.md) | | number | Cooldown until shares can be sold again |
|
||||
| [state](./bitburner.corporationinfo.state.md) | | string | <p>The next state to be processed.</p><p>I.e. when the state is PURCHASE, it means purchasing will occur during the next state transition.</p><p>Possible states are START, PURCHASE, PRODUCTION, EXPORT, SALE.</p> |
|
||||
| [totalShares](./bitburner.corporationinfo.totalshares.md) | | number | Total number of shares issues by this corporation |
|
||||
| [totalShares](./bitburner.corporationinfo.totalshares.md) | | number | Total number of shares issued by this corporation. |
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
## CorporationInfo.numShares property
|
||||
|
||||
Amount of share owned
|
||||
Amount of shares owned by the CEO.
|
||||
|
||||
**Signature:**
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
## CorporationInfo.totalShares property
|
||||
|
||||
Total number of shares issues by this corporation
|
||||
Total number of shares issued by this corporation.
|
||||
|
||||
**Signature:**
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { isInteger } from "lodash";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { CorpResearchName, CorpSmartSupplyOption } from "@nsdefs";
|
||||
|
||||
@ -18,6 +16,7 @@ import { isRelevantMaterial } from "./ui/Helpers";
|
||||
import { CityName } from "@enums";
|
||||
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
||||
import { getRecordValues } from "../Types/Record";
|
||||
import { sellSharesFailureReason, buybackSharesFailureReason, issueNewSharesFailureReason } from "./helpers";
|
||||
|
||||
export function NewDivision(corporation: Corporation, industry: IndustryType, name: string): void {
|
||||
if (corporation.divisions.size >= corporation.maxDivisions)
|
||||
@ -85,33 +84,72 @@ export function IssueDividends(corporation: Corporation, rate: number): void {
|
||||
corporation.dividendRate = rate;
|
||||
}
|
||||
|
||||
export function IssueNewShares(corporation: Corporation, amount: number): [number, number, number] {
|
||||
const max = corporation.calculateMaxNewShares();
|
||||
export function GoPublic(corporation: Corporation, numShares: number): void {
|
||||
const ceoOwnership = (corporation.numShares - numShares) / corporation.totalShares;
|
||||
const initialSharePrice = corporation.getTargetSharePrice(ceoOwnership);
|
||||
|
||||
// Round to nearest ten-millionth
|
||||
amount = Math.round(amount / 10e6) * 10e6;
|
||||
|
||||
if (isNaN(amount) || amount < 10e6 || amount > max) {
|
||||
throw new Error(`Invalid value. Must be an number between 10m and ${max} (20% of total shares)`);
|
||||
if (isNaN(numShares) || numShares < 0) {
|
||||
throw new Error("Invalid value for number of issued shares");
|
||||
}
|
||||
if (numShares > corporation.numShares) {
|
||||
throw new Error("You don't have that many shares to issue!");
|
||||
}
|
||||
corporation.public = true;
|
||||
corporation.sharePrice = initialSharePrice;
|
||||
corporation.issuedShares += numShares;
|
||||
corporation.numShares -= numShares;
|
||||
corporation.addNonIncomeFunds(numShares * initialSharePrice);
|
||||
}
|
||||
|
||||
const newSharePrice = Math.round(corporation.sharePrice * 0.8);
|
||||
export function IssueNewShares(
|
||||
corporation: Corporation,
|
||||
amount: number,
|
||||
): [profit: number, amount: number, privateShares: number] {
|
||||
const failureReason = issueNewSharesFailureReason(corporation, amount);
|
||||
if (failureReason) throw new Error(failureReason);
|
||||
|
||||
const profit = amount * newSharePrice;
|
||||
corporation.issueNewSharesCooldown = corpConstants.issueNewSharesCooldown;
|
||||
const ceoOwnership = corporation.numShares / (corporation.totalShares + amount);
|
||||
const newSharePrice = corporation.getTargetSharePrice(ceoOwnership);
|
||||
|
||||
const privateOwnedRatio = 1 - (corporation.numShares + corporation.issuedShares) / corporation.totalShares;
|
||||
const profit = (amount * (corporation.sharePrice + newSharePrice)) / 2;
|
||||
|
||||
const cooldownMultiplier = corporation.totalShares / corpConstants.initialShares;
|
||||
corporation.issueNewSharesCooldown = corpConstants.issueNewSharesCooldown * cooldownMultiplier;
|
||||
|
||||
const privateOwnedRatio = corporation.investorShares / corporation.totalShares;
|
||||
const maxPrivateShares = Math.round((amount / 2) * privateOwnedRatio);
|
||||
const privateShares = Math.round(getRandomInt(0, maxPrivateShares) / 10e6) * 10e6;
|
||||
|
||||
corporation.issuedShares += amount - privateShares;
|
||||
corporation.investorShares += privateShares;
|
||||
corporation.totalShares += amount;
|
||||
corporation.addNonIncomeFunds(profit);
|
||||
corporation.immediatelyUpdateSharePrice();
|
||||
// Set sharePrice directly because all formulas will be based on stale cycleValuation data
|
||||
corporation.sharePrice = newSharePrice;
|
||||
|
||||
return [profit, amount, privateShares];
|
||||
}
|
||||
|
||||
export function AcceptInvestmentOffer(corporation: Corporation): void {
|
||||
if (
|
||||
corporation.fundingRound >= corpConstants.fundingRoundShares.length ||
|
||||
corporation.fundingRound >= corpConstants.fundingRoundMultiplier.length ||
|
||||
corporation.public
|
||||
) {
|
||||
throw new Error("No more investment offers are available.");
|
||||
}
|
||||
const val = corporation.valuation;
|
||||
const percShares = corpConstants.fundingRoundShares[corporation.fundingRound];
|
||||
const roundMultiplier = corpConstants.fundingRoundMultiplier[corporation.fundingRound];
|
||||
const funding = val * percShares * roundMultiplier;
|
||||
const investShares = Math.floor(corpConstants.initialShares * percShares);
|
||||
corporation.fundingRound++;
|
||||
corporation.addNonIncomeFunds(funding);
|
||||
|
||||
corporation.numShares -= investShares;
|
||||
corporation.investorShares += investShares;
|
||||
}
|
||||
|
||||
export function SellMaterial(material: Material, amount: string, price: string): void {
|
||||
if (price === "") price = "0";
|
||||
if (amount === "") amount = "0";
|
||||
@ -280,17 +318,10 @@ export function BulkPurchase(
|
||||
}
|
||||
|
||||
export function SellShares(corporation: Corporation, numShares: number): number {
|
||||
if (isNaN(numShares) || !isInteger(numShares)) throw new Error("Invalid value for number of shares");
|
||||
if (numShares <= 0) throw new Error("Invalid value for number of shares");
|
||||
if (numShares > corporation.numShares) throw new Error("You don't have that many shares to sell!");
|
||||
if (numShares === corporation.numShares) throw new Error("You cant't sell all your shares!");
|
||||
if (numShares > 1e14) throw new Error("Invalid value for number of shares");
|
||||
if (!corporation.public) throw new Error("You haven't gone public!");
|
||||
if (corporation.shareSaleCooldown) throw new Error("Share sale on cooldown!");
|
||||
const stockSaleResults = corporation.calculateShareSale(numShares);
|
||||
const profit = stockSaleResults[0];
|
||||
const newSharePrice = stockSaleResults[1];
|
||||
const newSharesUntilUpdate = stockSaleResults[2];
|
||||
const failureReason = sellSharesFailureReason(corporation, numShares);
|
||||
if (failureReason) throw new Error(failureReason);
|
||||
|
||||
const [profit, newSharePrice, newSharesUntilUpdate] = corporation.calculateShareSale(numShares);
|
||||
|
||||
corporation.numShares -= numShares;
|
||||
corporation.issuedShares += numShares;
|
||||
@ -302,15 +333,16 @@ export function SellShares(corporation: Corporation, numShares: number): number
|
||||
}
|
||||
|
||||
export function BuyBackShares(corporation: Corporation, numShares: number): boolean {
|
||||
if (isNaN(numShares) || !isInteger(numShares)) throw new Error("Invalid value for number of shares");
|
||||
if (numShares <= 0) throw new Error("Invalid value for number of shares");
|
||||
if (numShares > corporation.issuedShares) throw new Error("You don't have that many shares to buy!");
|
||||
if (!corporation.public) throw new Error("You haven't gone public!");
|
||||
const buybackPrice = corporation.sharePrice * 1.1;
|
||||
if (Player.money < numShares * buybackPrice) throw new Error("You cant afford that many shares!");
|
||||
const failureReason = buybackSharesFailureReason(corporation, numShares);
|
||||
if (failureReason) throw new Error(failureReason);
|
||||
|
||||
const [cost, newSharePrice, newSharesUntilUpdate] = corporation.calculateShareBuyback(numShares);
|
||||
|
||||
corporation.numShares += numShares;
|
||||
corporation.issuedShares -= numShares;
|
||||
Player.loseMoney(numShares * buybackPrice, "corporation");
|
||||
corporation.sharePrice = newSharePrice;
|
||||
corporation.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
|
||||
Player.loseMoney(cost, "corporation");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import { showLiterature } from "../Literature/LiteratureHelpers";
|
||||
|
||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
|
||||
import { CorpStateName } from "@nsdefs";
|
||||
import { CorpStateName, InvestmentOffer } from "@nsdefs";
|
||||
import { calculateUpgradeCost } from "./helpers";
|
||||
import { JSONMap, JSONSet } from "../Types/Jsonable";
|
||||
import { formatMoney } from "../ui/formatNumber";
|
||||
@ -46,6 +46,7 @@ export class Corporation {
|
||||
issueNewSharesCooldown = 0; // Game cycles until player can issue shares again
|
||||
dividendRate = 0;
|
||||
dividendTax = 1 - currentNodeMults.CorporationSoftcap + 0.15;
|
||||
investorShares = 0;
|
||||
issuedShares = 0;
|
||||
sharePrice = 0;
|
||||
storedCycles = 0;
|
||||
@ -232,10 +233,17 @@ export class Corporation {
|
||||
this.totalAssets = assets;
|
||||
}
|
||||
|
||||
getTargetSharePrice(): number {
|
||||
// Note: totalShares - numShares is not the same as issuedShares because
|
||||
// issuedShares does not account for private investors
|
||||
return this.valuation / (2 * (this.totalShares - this.numShares) + 1);
|
||||
getTargetSharePrice(ceoOwnership: number | null = null): number {
|
||||
// Share price is proportional to total corporation valuation.
|
||||
// When the CEO owns 0% of the company, market cap is 0.5x valuation.
|
||||
// When the CEO owns 25% of the company, market cap is 1.0x valuation.
|
||||
// When the CEO owns 100% of shares, market cap is 1.5x valuation.
|
||||
if (ceoOwnership === null) {
|
||||
ceoOwnership = this.numShares / this.totalShares;
|
||||
}
|
||||
const ceoConfidence = 0.5 + Math.sqrt(Math.max(0, ceoOwnership));
|
||||
const marketCap = this.valuation * ceoConfidence;
|
||||
return marketCap / this.totalShares;
|
||||
}
|
||||
|
||||
updateSharePrice(): void {
|
||||
@ -250,10 +258,6 @@ export class Corporation {
|
||||
}
|
||||
}
|
||||
|
||||
immediatelyUpdateSharePrice(): void {
|
||||
this.sharePrice = this.getTargetSharePrice();
|
||||
}
|
||||
|
||||
calculateMaxNewShares(): number {
|
||||
const maxNewSharesUnrounded = Math.round(this.totalShares * 0.2);
|
||||
const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 10e6);
|
||||
@ -261,17 +265,18 @@ export class Corporation {
|
||||
}
|
||||
|
||||
// Calculates how much money will be made and what the resulting stock price
|
||||
// will be when the player sells his/her shares
|
||||
// will be when the player sells their shares
|
||||
// @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property]
|
||||
calculateShareSale(numShares: number): [number, number, number] {
|
||||
let sharesTracker = numShares;
|
||||
calculateShareSale(numShares: number): [profit: number, sharePrice: number, sharesUntilUpdate: number] {
|
||||
let sharesRemaining = numShares;
|
||||
let sharesUntilUpdate = this.shareSalesUntilPriceUpdate;
|
||||
let sharePrice = this.sharePrice;
|
||||
let sharesSold = 0;
|
||||
let profit = 0;
|
||||
let targetPrice = this.getTargetSharePrice();
|
||||
|
||||
const maxIterations = Math.ceil(numShares / corpConstants.sharesPerPriceUpdate);
|
||||
const sharesPerStep = Math.sign(numShares || 1) * corpConstants.sharesPerPriceUpdate;
|
||||
const maxIterations = Math.ceil(numShares / sharesPerStep);
|
||||
|
||||
if (isNaN(maxIterations) || maxIterations > 10e6) {
|
||||
console.error(
|
||||
`Something went wrong or unexpected when calculating share sale. Max iterations calculated to be ${maxIterations}`,
|
||||
@ -280,28 +285,59 @@ export class Corporation {
|
||||
}
|
||||
|
||||
for (let i = 0; i < maxIterations; ++i) {
|
||||
if (sharesTracker < sharesUntilUpdate) {
|
||||
profit += sharePrice * sharesTracker;
|
||||
sharesUntilUpdate -= sharesTracker;
|
||||
if (Math.abs(sharesRemaining) < Math.abs(sharesUntilUpdate)) {
|
||||
profit += sharePrice * sharesRemaining;
|
||||
sharesUntilUpdate -= sharesRemaining;
|
||||
break;
|
||||
} else {
|
||||
profit += sharePrice * sharesUntilUpdate;
|
||||
sharesUntilUpdate = corpConstants.sharesPerPriceUpdate;
|
||||
sharesTracker -= sharesUntilUpdate;
|
||||
sharesSold += sharesUntilUpdate;
|
||||
targetPrice = this.valuation / (2 * (this.totalShares + sharesSold - this.numShares));
|
||||
// Calculate what new share price would be
|
||||
profit += sharePrice * sharesPerStep;
|
||||
sharesRemaining -= sharesPerStep;
|
||||
sharesSold += sharesPerStep;
|
||||
|
||||
// Update the share price
|
||||
const ceoOwnership = (this.numShares - sharesSold) / this.totalShares;
|
||||
const targetPrice = this.getTargetSharePrice(ceoOwnership);
|
||||
if (sharePrice <= targetPrice) {
|
||||
sharePrice *= 1 + 0.5 * 0.01;
|
||||
} else {
|
||||
sharePrice *= 1 - 0.5 * 0.01;
|
||||
}
|
||||
sharesUntilUpdate = corpConstants.sharesPerPriceUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
return [profit, sharePrice, sharesUntilUpdate];
|
||||
}
|
||||
|
||||
calculateShareBuyback(numShares: number): [cost: number, sharePrice: number, sharesUntilUpdate: number] {
|
||||
const [profit, sharePrice, sharesUntilUpdate] = this.calculateShareSale(-numShares);
|
||||
const cost = -1.1 * profit;
|
||||
return [cost, sharePrice, sharesUntilUpdate];
|
||||
}
|
||||
|
||||
getInvestmentOffer(): InvestmentOffer {
|
||||
if (
|
||||
this.fundingRound >= corpConstants.fundingRoundShares.length ||
|
||||
this.fundingRound >= corpConstants.fundingRoundMultiplier.length ||
|
||||
this.public
|
||||
)
|
||||
return {
|
||||
funds: 0,
|
||||
shares: 0,
|
||||
round: this.fundingRound + 1, // Make more readable
|
||||
}; // Don't throw an error here, no reason to have a second function to check if you can get investment.
|
||||
const val = this.valuation;
|
||||
const percShares = corpConstants.fundingRoundShares[this.fundingRound];
|
||||
const roundMultiplier = corpConstants.fundingRoundMultiplier[this.fundingRound];
|
||||
const funding = val * percShares * roundMultiplier;
|
||||
const investShares = Math.floor(corpConstants.initialShares * percShares);
|
||||
return {
|
||||
funds: funding,
|
||||
shares: investShares,
|
||||
round: this.fundingRound + 1, // Make more readable
|
||||
};
|
||||
}
|
||||
|
||||
convertCooldownToString(cd: number): string {
|
||||
// The cooldown value is based on game cycles. Convert to a simple string
|
||||
const seconds = cd / 5;
|
||||
|
@ -33,7 +33,7 @@ export const stateNames: CorpStateName[] = ["START", "PURCHASE", "PRODUCTION", "
|
||||
/** Names of all one-time corporation-wide unlocks */
|
||||
unlockNames: APIUnlockName[] = Object.values(CorpUnlockName),
|
||||
upgradeNames: APIUpgradeName[] = Object.values(CorpUpgradeName),
|
||||
/** Names of all reasearches common to all industries */
|
||||
/** Names of all researches common to all industries */
|
||||
researchNamesBase: CorpResearchName[] = Object.values(CorpBaseResearchName),
|
||||
/** Names of all researches only available to product industries */
|
||||
researchNamesProductOnly: CorpResearchName[] = Object.values(CorpProductResearchName),
|
||||
@ -42,8 +42,8 @@ export const stateNames: CorpStateName[] = ["START", "PURCHASE", "PRODUCTION", "
|
||||
initialShares = 1e9,
|
||||
/** When selling large number of shares, price is dynamically updated for every batch of this amount */
|
||||
sharesPerPriceUpdate = 1e6,
|
||||
/** Cooldown for issue new shares cooldown in game cycles. 12 hours. */
|
||||
issueNewSharesCooldown = 216e3,
|
||||
/** Cooldown for issue new shares cooldown in game cycles. Initially 4 hours. */
|
||||
issueNewSharesCooldown = 72e3,
|
||||
/** Cooldown for selling shares in game cycles. 1 hour. */
|
||||
sellSharesCooldown = 18e3,
|
||||
teaCostPerEmployee = 500e3,
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { PositiveInteger } from "../types";
|
||||
import { Player } from "@player";
|
||||
import { PositiveInteger, isPositiveInteger } from "../types";
|
||||
import { formatShares } from "../ui/formatNumber";
|
||||
import { Corporation } from "./Corporation";
|
||||
import { CorpUpgrade } from "./data/CorporationUpgrades";
|
||||
|
||||
@ -29,3 +31,43 @@ export function calculateMaxAffordableUpgrade(corp: Corporation, upgrade: CorpUp
|
||||
const sanitizedValue = maxAffordableUpgrades >= 0 ? maxAffordableUpgrades : 0;
|
||||
return sanitizedValue as PositiveInteger | 0;
|
||||
}
|
||||
|
||||
/** Returns a string representing the reason a share sale should fail, or empty string if there is no issue. */
|
||||
export function sellSharesFailureReason(corp: Corporation, numShares: number): string {
|
||||
if (!isPositiveInteger(numShares)) return "Number of shares must be a positive integer.";
|
||||
else if (numShares > corp.numShares) return "You do not have that many shares to sell.";
|
||||
else if (numShares === corp.numShares) return "You cannot sell all your shares.";
|
||||
else if (numShares > 1e14) return `Cannot sell more than ${formatShares(1e14)} shares at a time.`;
|
||||
else if (!corp.public) return "Cannot sell shares before going public.";
|
||||
else if (corp.shareSaleCooldown)
|
||||
return `Cannot sell shares for another ${corp.convertCooldownToString(corp.shareSaleCooldown)}.`;
|
||||
return "";
|
||||
}
|
||||
|
||||
/** Returns a string representing the reason a share buyback should fail, or empty string if there is no issue. */
|
||||
export function buybackSharesFailureReason(corp: Corporation, numShares: number): string {
|
||||
if (!isPositiveInteger(numShares)) return "Number of shares must be a positive integer.";
|
||||
if (numShares > corp.issuedShares) return "Not enough shares are available for buyback.";
|
||||
if (numShares > 1e14) return `Cannot buy more than ${formatShares(1e14)} shares at a time.`;
|
||||
if (!corp.public) return "Cannot buy back shares before going public.";
|
||||
|
||||
const [cost] = corp.calculateShareBuyback(numShares);
|
||||
if (Player.money < cost) return "You cannot afford that many shares.";
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/** Returns a string representing the reason issuing new shares should fail, or empty string if there is no issue. */
|
||||
export function issueNewSharesFailureReason(corp: Corporation, numShares: number): string {
|
||||
if (!isPositiveInteger(numShares)) return "Number of shares must be a positive integer.";
|
||||
if (numShares % 10e6 !== 0) return "Number of shares must be a multiple of 10 million.";
|
||||
if (!corp.public) return "Cannot issue new shares before going public.";
|
||||
|
||||
const maxNewShares = corp.calculateMaxNewShares();
|
||||
if (numShares > maxNewShares) return `Number of shares cannot exceed ${maxNewShares} (20% of total shares).`;
|
||||
|
||||
const cooldown = corp.issueNewSharesCooldown;
|
||||
if (cooldown > 0) return `Cannot issue new shares for another ${corp.convertCooldownToString(cooldown)}.`;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { Warehouse } from "../Warehouse";
|
||||
import { ExportModal } from "./modals/ExportModal";
|
||||
import { SellMaterialModal } from "./modals/SellMaterialModal";
|
||||
import { PurchaseMaterialModal } from "./modals/PurchaseMaterialModal";
|
||||
import { formatBigNumber, formatCorpStat, formatMoney, formatQuality } from "../../ui/formatNumber";
|
||||
import { formatBigNumber, formatCorpStat, formatQuality } from "../../ui/formatNumber";
|
||||
import { isString } from "../../utils/helpers/string";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { useCorporation, useDivision } from "./Context";
|
||||
@ -118,7 +118,9 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>MP: {formatMoney(mat.marketPrice)}</Typography>
|
||||
<Typography>
|
||||
MP: <Money money={mat.marketPrice} />
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={<Typography>The quality of your material. Higher quality will lead to more sales</Typography>}
|
||||
|
@ -77,8 +77,21 @@ export function Overview({ rerender }: IProps): React.ReactElement {
|
||||
title={
|
||||
<StatsTable
|
||||
rows={[
|
||||
["Outstanding Shares:", formatShares(corp.issuedShares)],
|
||||
["Private Shares:", formatShares(corp.totalShares - corp.issuedShares - corp.numShares)],
|
||||
[
|
||||
"Owned Stock Shares:",
|
||||
<> {formatShares(corp.numShares)} </>,
|
||||
<>({formatPercent(corp.numShares / corp.totalShares)})</>,
|
||||
],
|
||||
[
|
||||
"Outstanding Shares:",
|
||||
<> {formatShares(corp.issuedShares)} </>,
|
||||
<>({formatPercent(corp.issuedShares / corp.totalShares)})</>,
|
||||
],
|
||||
[
|
||||
"Private Shares:",
|
||||
<> {formatShares(corp.investorShares)} </>,
|
||||
<>({formatPercent(corp.investorShares / corp.totalShares)})</>,
|
||||
],
|
||||
]}
|
||||
/>
|
||||
}
|
||||
@ -96,8 +109,8 @@ export function Overview({ rerender }: IProps): React.ReactElement {
|
||||
<ButtonWithTooltip
|
||||
normalTooltip={
|
||||
<>
|
||||
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
|
||||
Get a copy of and read <i>The Complete Handbook for Creating a Successful Corporation</i>. 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.
|
||||
</>
|
||||
}
|
||||
@ -125,7 +138,7 @@ function PrivateButtons({ rerender }: IPrivateButtonsProps): React.ReactElement
|
||||
const [findInvestorsopen, setFindInvestorsopen] = useState(false);
|
||||
const [goPublicopen, setGoPublicopen] = useState(false);
|
||||
|
||||
const fundingAvailable = corp.fundingRound < 4;
|
||||
const fundingAvailable = corp.fundingRound < corpConstants.fundingRoundShares.length;
|
||||
const findInvestorsTooltip = fundingAvailable
|
||||
? "Search for private investors who will give you startup funding in exchange for equity (stock shares) in your company"
|
||||
: "";
|
||||
@ -213,29 +226,27 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
|
||||
const [issueDividendsOpen, setIssueDividendsOpen] = useState(false);
|
||||
|
||||
const sellSharesOnCd = corp.shareSaleCooldown > 0;
|
||||
const sellSharesTooltip = sellSharesOnCd
|
||||
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
|
||||
: "Sell your shares in the company. The money earned from selling your " +
|
||||
"shares goes into your personal account, not the Corporation's. " +
|
||||
"This is one of the only ways to profit from your business venture.";
|
||||
const sellSharesTooltip =
|
||||
"Sell your shares in the company. The money earned from selling your " +
|
||||
"shares goes into your personal account, not the Corporation's. " +
|
||||
"This is one of the only ways to profit from your business venture.";
|
||||
|
||||
const issueNewSharesOnCd = corp.issueNewSharesCooldown > 0;
|
||||
const issueNewSharesTooltip = issueNewSharesOnCd
|
||||
? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown)
|
||||
: "Issue new equity shares to raise capital.";
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonWithTooltip
|
||||
normalTooltip={sellSharesTooltip}
|
||||
disabledTooltip={sellSharesOnCd ? "On cooldown" : ""}
|
||||
disabledTooltip={
|
||||
sellSharesOnCd ? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown) : ""
|
||||
}
|
||||
onClick={() => setSellSharesOpen(true)}
|
||||
>
|
||||
Sell Shares
|
||||
</ButtonWithTooltip>
|
||||
<SellSharesModal open={sellSharesOpen} onClose={() => setSellSharesOpen(false)} rerender={rerender} />
|
||||
<ButtonWithTooltip
|
||||
normalTooltip={"Buy back shares you that previously issued or sold at market price."}
|
||||
normalTooltip={"Buy back shares you that previously issued or sold on the market"}
|
||||
disabledTooltip={corp.issuedShares < 1 ? "No shares available to buy back" : ""}
|
||||
onClick={() => setBuybackSharesOpen(true)}
|
||||
>
|
||||
@ -243,13 +254,15 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
|
||||
</ButtonWithTooltip>
|
||||
<BuybackSharesModal open={buybackSharesOpen} onClose={() => setBuybackSharesOpen(false)} rerender={rerender} />
|
||||
<ButtonWithTooltip
|
||||
normalTooltip={issueNewSharesTooltip}
|
||||
disabledTooltip={issueNewSharesOnCd ? "On cooldown" : ""}
|
||||
normalTooltip={"Issue new equity shares to raise capital"}
|
||||
disabledTooltip={
|
||||
issueNewSharesOnCd ? `On cooldown for ${corp.convertCooldownToString(corp.issueNewSharesCooldown)}` : ""
|
||||
}
|
||||
onClick={() => setIssueNewSharesOpen(true)}
|
||||
>
|
||||
Issue New Shares
|
||||
</ButtonWithTooltip>
|
||||
<IssueNewSharesModal open={issueNewSharesOpen} onClose={() => setIssueNewSharesOpen(false)} />
|
||||
<IssueNewSharesModal open={issueNewSharesOpen} onClose={() => setIssueNewSharesOpen(false)} rerender={rerender} />
|
||||
<ButtonWithTooltip
|
||||
normalTooltip={"Manage the dividends that are paid out to shareholders (including yourself)"}
|
||||
onClick={() => setIssueDividendsOpen(true)}
|
||||
@ -306,13 +319,22 @@ function SellDivisionButton(): React.ReactElement {
|
||||
function RestartButton(): React.ReactElement {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const corp = useCorporation();
|
||||
const sellSharesOnCd = corp.shareSaleCooldown > 0;
|
||||
|
||||
function restart(): void {
|
||||
setOpen(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonWithTooltip normalTooltip={"Sell corporation and start over"} onClick={restart}>
|
||||
<ButtonWithTooltip
|
||||
normalTooltip={"Sell corporation and start over"}
|
||||
disabledTooltip={
|
||||
sellSharesOnCd ? "Sell corporation and start over. Cannot do this while Sell Shares is on cooldown." : ""
|
||||
}
|
||||
onClick={restart}
|
||||
>
|
||||
Sell CEO position
|
||||
</ButtonWithTooltip>
|
||||
<SellCorporationModal open={open} onClose={() => setOpen(false)} />
|
||||
|
@ -8,7 +8,7 @@ import { LimitProductProductionModal } from "./modals/LimitProductProductionModa
|
||||
import { SellProductModal } from "./modals/SellProductModal";
|
||||
import { CancelProductModal } from "./modals/CancelProductModal";
|
||||
|
||||
import { formatBigNumber, formatMoney, formatPercent } from "../../ui/formatNumber";
|
||||
import { formatBigNumber, formatPercent } from "../../ui/formatNumber";
|
||||
|
||||
import { isString } from "../../utils/helpers/string";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
@ -135,7 +135,7 @@ export function ProductElem(props: IProductProps): React.ReactElement {
|
||||
<Box display="flex">
|
||||
<Tooltip title={<Typography>An estimate of the material cost it takes to create this Product.</Typography>}>
|
||||
<Typography>
|
||||
Est. Production Cost: {formatMoney(product.productionCost / corpConstants.baseProductProfitMult)}
|
||||
Est. Production Cost: <Money money={product.productionCost / corpConstants.baseProductProfitMult} />
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
@ -148,7 +148,9 @@ export function ProductElem(props: IProductProps): React.ReactElement {
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>Est. Market Price: {formatMoney(product.productionCost)}</Typography>
|
||||
<Typography>
|
||||
Est. Market Price: <Money money={product.productionCost} />
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Button onClick={() => setDiscontinueOpen(true)}>Discontinue</Button>
|
||||
|
@ -1,15 +1,15 @@
|
||||
import React, { useState } from "react";
|
||||
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { formatBigNumber, formatMoney } from "../../../ui/formatNumber";
|
||||
import { Player } from "@player";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { formatShares } from "../../../ui/formatNumber";
|
||||
import { useCorporation } from "../Context";
|
||||
import Typography from "@mui/material/Typography";
|
||||
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";
|
||||
import { buybackSharesFailureReason } from "../../helpers";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
@ -23,44 +23,28 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
|
||||
const corp = useCorporation();
|
||||
const [shares, setShares] = useState<number>(NaN);
|
||||
|
||||
const currentStockPrice = corp.sharePrice;
|
||||
const buybackPrice = currentStockPrice * 1.1;
|
||||
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"
|
||||
: "";
|
||||
const [cost, sharePrice] = corp.calculateShareBuyback((props.open && shares) || 0);
|
||||
const disabledText = buybackSharesFailureReason(corp, shares);
|
||||
|
||||
function buy(): void {
|
||||
if (disabledText) return;
|
||||
try {
|
||||
BuyBackShares(corp, shares);
|
||||
dialogBoxCreate(
|
||||
<>
|
||||
<Typography>
|
||||
You bought {formatShares(shares)} shares for <Money money={cost} />.
|
||||
</Typography>
|
||||
<Typography>
|
||||
<b>{corp.name}</b>'s stock price rose to <Money money={sharePrice} /> per share.
|
||||
</Typography>
|
||||
</>,
|
||||
);
|
||||
props.onClose();
|
||||
props.rerender();
|
||||
setShares(NaN);
|
||||
} catch (err) {
|
||||
dialogBoxCreate(err + "");
|
||||
}
|
||||
props.onClose();
|
||||
props.rerender();
|
||||
}
|
||||
|
||||
function CostIndicator(): React.ReactElement {
|
||||
if (shares === null) return <></>;
|
||||
if (isNaN(shares) || shares <= 0) {
|
||||
return <>ERROR: Invalid value entered for number of shares to buyback</>;
|
||||
} else if (shares > corp.issuedShares) {
|
||||
return (
|
||||
<>
|
||||
There are not this many shares available to buy back. There are only {formatBigNumber(corp.issuedShares)}{" "}
|
||||
outstanding shares.
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
Purchase {shares} shares for a total of {formatMoney(shares * buybackPrice)}
|
||||
</>
|
||||
);
|
||||
dialogBoxCreate(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,23 +54,45 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Typography>
|
||||
Enter the number of outstanding shares you would like to buy back. These shares must be bought at a 10% premium.
|
||||
However, repurchasing shares from the market tends to lead to an increase in stock price.
|
||||
<br />
|
||||
<br />
|
||||
To purchase these shares, you must use your own money (NOT your Corporation's funds).
|
||||
<br />
|
||||
<br />
|
||||
The current buyback price of your company's stock is {formatMoney(buybackPrice)}. Your company currently has{" "}
|
||||
{formatBigNumber(corp.issuedShares)} outstanding stock shares.
|
||||
<Typography component="div">
|
||||
Enter the number of outstanding shares you would like to buy back.
|
||||
<ul>
|
||||
<li>Buying back shares will cause the stock price to rise due to market forces.</li>
|
||||
<li>These shares must be bought at a 10% premium over the market price.</li>
|
||||
<li>You purchase these shares with your own money (NOT your Corporation's funds).</li>
|
||||
</ul>
|
||||
<b>{corp.name}</b> currently has {formatShares(corp.issuedShares)} outstanding stock shares, valued at{" "}
|
||||
<Money money={corp.sharePrice} /> per share.
|
||||
</Typography>
|
||||
<CostIndicator />
|
||||
<br />
|
||||
<NumberInput autoFocus={true} placeholder="Shares to buyback" onChange={setShares} onKeyDown={onKeyDown} />
|
||||
<NumberInput
|
||||
defaultValue={shares || ""}
|
||||
autoFocus={true}
|
||||
placeholder="Shares to buyback"
|
||||
onChange={setShares}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<ButtonWithTooltip disabledTooltip={disabledText} onClick={buy}>
|
||||
Buy shares
|
||||
{cost > 0 ? (
|
||||
<>
|
||||
-
|
||||
<Money money={cost} forPurchase={true} />{" "}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</ButtonWithTooltip>
|
||||
<br />
|
||||
<Typography sx={{ minHeight: "1.5em" }}>
|
||||
{!shares ? null : disabledText ? (
|
||||
disabledText
|
||||
) : (
|
||||
<>
|
||||
<b>{corp.name}</b>'s stock price will rise to <Money money={sharePrice} /> per share.
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { Money } from "../../../ui/React/Money";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { Router } from "../../../ui/GameRoot";
|
||||
import { Page } from "../../../ui/Router";
|
||||
import { formatShares } from "../../../ui/formatNumber";
|
||||
import { Player } from "@player";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
|
||||
@ -54,15 +55,19 @@ export function CreateCorporationModal(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Typography>
|
||||
Would you like to start a corporation? This will require $150b for registration and initial funding.{" "}
|
||||
{Player.bitNodeN === 3 &&
|
||||
`This $150b
|
||||
can either be self-funded, or you can obtain the seed money from the government in exchange for 500 million
|
||||
shares`}
|
||||
Would you like to start a corporation? This will require <Money money={150e9} forPurchase={true} /> for
|
||||
registration and initial funding.{" "}
|
||||
{Player.bitNodeN === 3 && (
|
||||
<>
|
||||
This <Money money={150e9} /> can either be self-funded, or you can obtain the seed money from the government
|
||||
in exchange for {formatShares(500e6)} shares (a <b>33.3%</b> stake in the company).
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
If you would like to start one, please enter a name for your corporation below:
|
||||
</Typography>
|
||||
<br />
|
||||
<TextField autoFocus={true} placeholder="Corporation Name" onChange={onChange} value={name} />
|
||||
{Player.bitNodeN === 3 && (
|
||||
<ButtonWithTooltip onClick={seed} disabledTooltip={disabledTextForNoName}>
|
||||
@ -71,7 +76,7 @@ export function CreateCorporationModal(props: IProps): React.ReactElement {
|
||||
)}
|
||||
<ButtonWithTooltip
|
||||
onClick={selfFund}
|
||||
disabledTooltip={disabledTextForNoName || canSelfFund ? "" : "Insufficient player funds"}
|
||||
disabledTooltip={disabledTextForNoName || (canSelfFund ? "" : "Insufficient player funds")}
|
||||
>
|
||||
Self-Fund (<Money money={150e9} forPurchase={true} />)
|
||||
</ButtonWithTooltip>
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React from "react";
|
||||
import { formatMoney, formatPercent, formatShares } from "../../../ui/formatNumber";
|
||||
import * as corpConstants from "../../data/Constants";
|
||||
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
import { formatPercent, formatShares } from "../../../ui/formatNumber";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { useCorporation } from "../Context";
|
||||
import { AcceptInvestmentOffer } from "../../Actions";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
@ -13,40 +15,52 @@ interface IProps {
|
||||
rerender: () => void;
|
||||
}
|
||||
|
||||
// Create a popup that lets the player manage exports
|
||||
// Create a popup that lets the player manage investment offers
|
||||
export function FindInvestorsModal(props: IProps): React.ReactElement {
|
||||
const corporation = useCorporation();
|
||||
const val = corporation.valuation;
|
||||
if (
|
||||
corporation.fundingRound >= corpConstants.fundingRoundShares.length ||
|
||||
corporation.fundingRound >= corpConstants.fundingRoundMultiplier.length
|
||||
)
|
||||
return <></>;
|
||||
const percShares = corpConstants.fundingRoundShares[corporation.fundingRound];
|
||||
const roundMultiplier = corpConstants.fundingRoundMultiplier[corporation.fundingRound];
|
||||
const funding = val * percShares * roundMultiplier;
|
||||
const investShares = Math.floor(corpConstants.initialShares * percShares);
|
||||
const corp = useCorporation();
|
||||
const { funds, shares } = corp.getInvestmentOffer();
|
||||
|
||||
function findInvestors(): void {
|
||||
corporation.fundingRound++;
|
||||
corporation.addNonIncomeFunds(funding);
|
||||
corporation.numShares -= investShares;
|
||||
props.rerender();
|
||||
props.onClose();
|
||||
if (shares === 0) return;
|
||||
try {
|
||||
AcceptInvestmentOffer(corp);
|
||||
dialogBoxCreate(
|
||||
<>
|
||||
<Typography>You accepted the investment offer.</Typography>
|
||||
<Typography>
|
||||
<b>{corp.name}</b> received <Money money={funds} />.
|
||||
</Typography>
|
||||
<Typography>
|
||||
Your remaining equity is <b>{formatPercent(corp.numShares / corp.totalShares, 1)}</b>.
|
||||
</Typography>
|
||||
</>,
|
||||
);
|
||||
props.onClose();
|
||||
props.rerender();
|
||||
} catch (err) {
|
||||
dialogBoxCreate(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Typography>
|
||||
An investment firm has offered you {formatMoney(funding)} in funding in exchange for a{" "}
|
||||
{formatPercent(percShares, 3)} stake in the company ({formatShares(investShares)} shares).
|
||||
An investment firm has offered to buy {formatShares(shares)} shares of stock (a{" "}
|
||||
<b>{formatPercent(shares / corp.totalShares, 1)}</b> stake in the company).
|
||||
<br />
|
||||
<br />
|
||||
Do you accept or reject this offer?
|
||||
<b>{corp.name}</b> will receive <Money money={funds} />.
|
||||
<br />
|
||||
Your equity will fall to <b>{formatPercent((corp.numShares - shares) / corp.totalShares, 1)}</b>.
|
||||
<br />
|
||||
<br />
|
||||
Hint: Investment firms will offer more money if your corporation is turning a profit
|
||||
<b>Hint</b>: Investment firms will offer more money if your Corporation is turning a profit.
|
||||
<br />
|
||||
<br />
|
||||
Do you accept this offer?
|
||||
</Typography>
|
||||
<Button onClick={findInvestors}>Accept</Button>
|
||||
<br />
|
||||
<Button onClick={findInvestors}>Accept</Button> <Button onClick={props.onClose}>Ignore</Button>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { formatMoney, formatShares } from "../../../ui/formatNumber";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { formatShares } from "../../../ui/formatNumber";
|
||||
import { useCorporation } from "../Context";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
|
||||
@ -9,6 +10,7 @@ import { NumberInput } from "../../../ui/React/NumberInput";
|
||||
import Box from "@mui/material/Box";
|
||||
import { KEY } from "../../../utils/helpers/keyCodes";
|
||||
import { isPositiveInteger } from "../../../types";
|
||||
import { GoPublic } from "../../Actions";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
@ -20,26 +22,9 @@ interface IProps {
|
||||
export function GoPublicModal(props: IProps): React.ReactElement {
|
||||
const corp = useCorporation();
|
||||
const [shares, setShares] = useState<number>(NaN);
|
||||
const initialSharePrice = corp.valuation / corp.totalShares;
|
||||
|
||||
function goPublic(): void {
|
||||
const initialSharePrice = corp.valuation / corp.totalShares;
|
||||
if (shares >= corp.numShares || (shares !== 0 && !isPositiveInteger(shares))) return;
|
||||
corp.public = true;
|
||||
corp.sharePrice = initialSharePrice;
|
||||
corp.issuedShares = shares;
|
||||
corp.numShares -= shares;
|
||||
corp.addFunds(shares * initialSharePrice);
|
||||
props.rerender();
|
||||
dialogBoxCreate(
|
||||
`You took your ${corp.name} public and earned ` + `${formatMoney(shares * initialSharePrice)} in your IPO`,
|
||||
);
|
||||
props.onClose();
|
||||
}
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (event.key === KEY.ENTER) goPublic();
|
||||
}
|
||||
const ceoOwnership = (corp.numShares - (shares || 0)) / corp.totalShares;
|
||||
const initialSharePrice = corp.getTargetSharePrice(ceoOwnership);
|
||||
|
||||
const disabledText =
|
||||
shares >= corp.numShares
|
||||
@ -48,22 +33,62 @@ export function GoPublicModal(props: IProps): React.ReactElement {
|
||||
? "Must issue an non-negative integer number of shares"
|
||||
: "";
|
||||
|
||||
function goPublic(): void {
|
||||
if (disabledText) return;
|
||||
try {
|
||||
GoPublic(corp, shares);
|
||||
dialogBoxCreate(
|
||||
<Typography>
|
||||
<b>{corp.name}</b> went public and earned <Money money={shares * initialSharePrice} /> in its IPO.
|
||||
</Typography>,
|
||||
);
|
||||
props.onClose();
|
||||
props.rerender();
|
||||
setShares(NaN);
|
||||
} catch (err) {
|
||||
dialogBoxCreate(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (event.key === KEY.ENTER) goPublic();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Typography>
|
||||
Enter the number of shares you would like to issue for your IPO. These shares will be publicly sold and you will
|
||||
no longer own them. Your Corporation will receive {formatMoney(initialSharePrice)} per share (the IPO money will
|
||||
be deposited directly into your Corporation's funds).
|
||||
<br />
|
||||
<br />
|
||||
<Typography component="div">
|
||||
Enter the number of shares you would like to issue for your IPO.
|
||||
<ul>
|
||||
<li>These shares will be publicly sold and you will no longer own them.</li>
|
||||
<li>The IPO money will be deposited directly into your Corporation's funds.</li>
|
||||
</ul>
|
||||
You can issue some, but not all, of your {formatShares(corp.numShares)} shares.
|
||||
</Typography>
|
||||
<br />
|
||||
<Box display="flex" alignItems="center">
|
||||
<NumberInput onChange={setShares} autoFocus placeholder="Shares to issue" onKeyDown={onKeyDown} />
|
||||
<NumberInput
|
||||
defaultValue={shares || ""}
|
||||
onChange={setShares}
|
||||
autoFocus
|
||||
placeholder="Shares to issue"
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<ButtonWithTooltip disabledTooltip={disabledText} onClick={goPublic}>
|
||||
Go Public
|
||||
</ButtonWithTooltip>
|
||||
</Box>
|
||||
<br />
|
||||
<Typography sx={{ minHeight: "3em" }}>
|
||||
{isNaN(shares) ? null : disabledText ? (
|
||||
disabledText
|
||||
) : (
|
||||
<>
|
||||
Go public at <Money money={initialSharePrice} /> per share?
|
||||
<br />
|
||||
<b>{corp.name}</b> will receive <Money money={initialSharePrice * (shares || 0)} />.
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { MoneyRate } from "../../../ui/React/MoneyRate";
|
||||
import * as corpConstants from "../../data/Constants";
|
||||
import { IssueDividends } from "../../Actions";
|
||||
import { useCorporation } from "../Context";
|
||||
@ -53,20 +55,19 @@ export function IssueDividendsModal(props: IProps): React.ReactElement {
|
||||
yourself, as well.
|
||||
<br />
|
||||
<br />
|
||||
In order to issue dividends, simply allocate some percentage of your corporation's profits to dividends. This
|
||||
percentage must be an integer between 0 and 100. (A percentage of 0 means no dividends will be issued)
|
||||
Note that issuing dividends will negatively affect <b>{corp.name}</b>'s stock price.
|
||||
<br />
|
||||
<br />
|
||||
Two important things to note:
|
||||
<br />
|
||||
* Issuing dividends will negatively affect your corporation's stock price
|
||||
In order to issue dividends, simply allocate some percentage of your Corporation's profits to dividends. This
|
||||
percentage must be an integer between 0 and 100. (A percentage of 0 means no dividends will be issued.)
|
||||
<br />
|
||||
<br />
|
||||
Example: Assume your corporation makes $100m / sec in profit and you allocate 40% of that towards dividends.
|
||||
That means your corporation will gain $60m / sec in funds and the remaining $40m / sec will be paid as
|
||||
dividends. Since your corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share
|
||||
per second before taxes.
|
||||
<b>Example:</b> Assume your corporation makes <MoneyRate money={100e6} /> in profit and you allocate 40% of that
|
||||
towards dividends. That means your corporation will gain <MoneyRate money={60e6} /> in funds and the remaining{" "}
|
||||
<MoneyRate money={40e6} /> will be paid as dividends. Since your corporation starts with 1 billion shares, every
|
||||
shareholder will be paid <Money money={0.04} /> per share per second before taxes.
|
||||
</Typography>
|
||||
<br />
|
||||
<TextField
|
||||
autoFocus
|
||||
value={percent}
|
||||
|
@ -1,50 +1,21 @@
|
||||
import React, { useState } from "react";
|
||||
import { formatMoney, formatShares } from "../../../ui/formatNumber";
|
||||
import { formatShares, formatPercent } from "../../../ui/formatNumber";
|
||||
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { useCorporation } from "../Context";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { NumberInput } from "../../../ui/React/NumberInput";
|
||||
import Button from "@mui/material/Button";
|
||||
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
|
||||
import { KEY } from "../../../utils/helpers/keyCodes";
|
||||
import { IssueNewShares } from "../../Actions";
|
||||
|
||||
interface IEffectTextProps {
|
||||
shares: number | null;
|
||||
}
|
||||
|
||||
function EffectText(props: IEffectTextProps): React.ReactElement {
|
||||
const corp = useCorporation();
|
||||
if (props.shares === null) return <></>;
|
||||
const newSharePrice = Math.round(corp.sharePrice * 0.9);
|
||||
const maxNewShares = corp.calculateMaxNewShares();
|
||||
let newShares = props.shares;
|
||||
if (isNaN(newShares)) {
|
||||
return <Typography>Invalid input</Typography>;
|
||||
}
|
||||
|
||||
// Round to nearest ten-millionth
|
||||
newShares /= 10e6;
|
||||
newShares = Math.round(newShares) * 10e6;
|
||||
|
||||
if (newShares < 10e6) {
|
||||
return <Typography>Must issue at least 10 million new shares</Typography>;
|
||||
}
|
||||
|
||||
if (newShares > maxNewShares) {
|
||||
return <Typography>You cannot issue that many shares</Typography>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography>
|
||||
Issue {formatShares(newShares)} new shares for {formatMoney(newShares * newSharePrice)}?
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
import * as corpConstants from "../../data/Constants";
|
||||
import { issueNewSharesFailureReason } from "../../helpers";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
rerender: () => void;
|
||||
}
|
||||
|
||||
// Create a popup that lets the player issue new shares
|
||||
@ -52,56 +23,103 @@ interface IProps {
|
||||
export function IssueNewSharesModal(props: IProps): React.ReactElement {
|
||||
const corp = useCorporation();
|
||||
const [shares, setShares] = useState<number>(NaN);
|
||||
const maxNewShares = corp.calculateMaxNewShares();
|
||||
|
||||
const maxNewShares = corp.calculateMaxNewShares();
|
||||
const newShares = Math.round((shares || 0) / 10e6) * 10e6;
|
||||
const disabled = isNaN(shares) || isNaN(newShares) || newShares < 10e6 || newShares > maxNewShares;
|
||||
|
||||
const ceoOwnership = corp.numShares / (corp.totalShares + (newShares || 0));
|
||||
const newSharePrice = corp.getTargetSharePrice(ceoOwnership);
|
||||
const profit = ((shares || 0) * (corp.sharePrice + newSharePrice)) / 2;
|
||||
|
||||
const privateOwnedRatio = corp.investorShares / corp.totalShares;
|
||||
const maxPrivateShares = Math.round(((newShares / 2) * privateOwnedRatio) / 10e6) * 10e6;
|
||||
|
||||
const disabledText = issueNewSharesFailureReason(corp, shares);
|
||||
|
||||
function issueNewShares(): void {
|
||||
if (isNaN(shares)) return;
|
||||
if (disabled) return;
|
||||
const [profit, newShares, privateShares] = IssueNewShares(corp, shares);
|
||||
|
||||
props.onClose();
|
||||
|
||||
let dialogContents =
|
||||
`Issued ${formatShares(newShares)} new shares` + ` and raised ${formatMoney(profit)}.` + (privateShares > 0)
|
||||
? "\n" + formatShares(privateShares) + " of these shares were bought by private investors."
|
||||
: "";
|
||||
dialogContents += `\n\nStock price decreased to ${formatMoney(corp.sharePrice)}`;
|
||||
dialogBoxCreate(dialogContents);
|
||||
if (disabledText) return;
|
||||
try {
|
||||
const [profit, newShares, privateShares] = IssueNewShares(corp, shares);
|
||||
dialogBoxCreate(
|
||||
<>
|
||||
<Typography>
|
||||
Issued {formatShares(newShares)} new shares and raised <Money money={profit} />.
|
||||
</Typography>
|
||||
{privateShares > 0 ? (
|
||||
<Typography>{formatShares(privateShares)} of these shares were bought by private investors.</Typography>
|
||||
) : null}
|
||||
<Typography>
|
||||
<b>{corp.name}</b>'s stock price fell to <Money money={corp.sharePrice} />.
|
||||
</Typography>
|
||||
</>,
|
||||
);
|
||||
props.onClose();
|
||||
props.rerender();
|
||||
} catch (err) {
|
||||
dialogBoxCreate(`${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (event.key === KEY.ENTER) issueNewShares();
|
||||
}
|
||||
|
||||
const nextCooldown = corpConstants.issueNewSharesCooldown * (corp.totalShares / corpConstants.initialShares);
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Typography>
|
||||
You can issue new equity shares (i.e. stocks) in order to raise capital for your corporation.
|
||||
<Typography component="div">
|
||||
You can issue new equity shares (i.e. stocks) in order to raise capital.
|
||||
<ul>
|
||||
<li>Issuing new shares will cause dilution, lowering stock price and reducing dividends per share.</li>
|
||||
<li>New shares are sold between the current price and the updated price.</li>
|
||||
<li>The money from issuing new shares will be deposited directly into your Corporation's funds.</li>
|
||||
<li>
|
||||
Private shareholders have first priority for buying new shares, up to half of their existing stake in the
|
||||
company <b>({formatPercent(privateOwnedRatio / 2, 1)})</b>.
|
||||
<br />
|
||||
If they choose to exercise this option, these newly issued shares become private, restricted shares, which
|
||||
means you cannot buy them back.
|
||||
</li>
|
||||
<li>
|
||||
You will not be able to issue new shares again for <b>{corp.convertCooldownToString(nextCooldown)}</b>.
|
||||
</li>
|
||||
</ul>
|
||||
You can issue at most {formatShares(maxNewShares)} new shares.
|
||||
<br />
|
||||
<br />
|
||||
* You can issue at most {formatShares(maxNewShares)} new shares
|
||||
<br />
|
||||
* New shares are sold at a 10% discount
|
||||
<br />
|
||||
* You can only issue new shares once every 12 hours
|
||||
<br />
|
||||
* Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share
|
||||
<br />
|
||||
* Number of new shares issued must be a multiple of 10 million
|
||||
<br />
|
||||
<br />
|
||||
When you choose to issue new equity, private shareholders have first priority for up to 0.5n% of the new shares,
|
||||
where n is the percentage of the company currently owned by private shareholders. If they choose to exercise
|
||||
this option, these newly issued shares become private, restricted shares, which means you cannot buy them back.
|
||||
The number of new shares issued must be a multiple of 10 million.
|
||||
</Typography>
|
||||
<EffectText shares={shares} />
|
||||
<NumberInput autoFocus placeholder="# New Shares" onChange={setShares} onKeyDown={onKeyDown} />
|
||||
<Button disabled={disabled} onClick={issueNewShares} sx={{ mx: 1 }}>
|
||||
<br />
|
||||
<NumberInput
|
||||
defaultValue={shares || ""}
|
||||
autoFocus
|
||||
placeholder="# New Shares"
|
||||
onChange={setShares}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<ButtonWithTooltip disabledTooltip={disabledText} onClick={issueNewShares}>
|
||||
Issue New Shares
|
||||
</Button>
|
||||
</ButtonWithTooltip>
|
||||
<br />
|
||||
<Typography sx={{ minHeight: "6em" }}>
|
||||
{disabledText ? (
|
||||
disabledText
|
||||
) : (
|
||||
<>
|
||||
Issue {formatShares(newShares)} new shares?
|
||||
<br />
|
||||
{maxPrivateShares > 0
|
||||
? `Private investors may buy up to ${formatShares(
|
||||
maxPrivateShares,
|
||||
)} of these shares and keep them off the market.`
|
||||
: null}
|
||||
<br />
|
||||
<b>{corp.name}</b> will receive <Money money={profit} />.
|
||||
<br />
|
||||
<b>{corp.name}</b>'s stock price will fall to <Money money={newSharePrice} /> per share.
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -3,9 +3,10 @@ import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
import { MaterialInfo } from "../../MaterialInfo";
|
||||
import { Warehouse } from "../../Warehouse";
|
||||
import { Material } from "../../Material";
|
||||
import { formatMatPurchaseAmount, formatMoney } from "../../../ui/formatNumber";
|
||||
import { formatMatPurchaseAmount } from "../../../ui/formatNumber";
|
||||
import { BulkPurchase, BuyMaterial } from "../../Actions";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { useCorporation, useDivision } from "../Context";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import TextField from "@mui/material/TextField";
|
||||
@ -56,7 +57,7 @@ function BulkPurchaseSection(props: IBPProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
Purchasing {formatMatPurchaseAmount(parsedAmt)} of {props.mat.name} will cost {formatMoney(cost)}
|
||||
Purchasing {formatMatPurchaseAmount(parsedAmt)} of {props.mat.name} will cost <Money money={cost} />
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
@ -57,6 +57,7 @@ export function SellCorporationModal(props: IProps): React.ReactElement {
|
||||
<br />
|
||||
If you would like to start new one, please enter a name for your corporation below:
|
||||
</Typography>
|
||||
<br />
|
||||
<TextField autoFocus={true} placeholder="Corporation Name" onChange={onChange} value={name} />
|
||||
{Player.bitNodeN === 3 && (
|
||||
<Button onClick={seed} disabled={name == ""}>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
@ -8,7 +9,6 @@ import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import { useCorporation } from "../../ui/Context";
|
||||
import { CityName } from "@enums";
|
||||
import * as corpConstants from "../../data/Constants";
|
||||
import { formatMoney } from "../../../ui/formatNumber";
|
||||
import { removeDivision as removeDivision } from "../../Actions";
|
||||
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
import { getRecordKeys } from "../../../Types/Record";
|
||||
@ -47,9 +47,10 @@ export function SellDivisionModal(props: IProps): React.ReactElement {
|
||||
corp.funds += price;
|
||||
props.onClose();
|
||||
dialogBoxCreate(
|
||||
`Sold ${divisionToSell.name} for ${formatMoney(price)}, you now have space for ${
|
||||
corp.maxDivisions - corp.divisions.size
|
||||
} more divisions.`,
|
||||
<Typography>
|
||||
Sold <b>{divisionToSell.name}</b> for <Money money={price} />, you now have space for
|
||||
{corp.maxDivisions - corp.divisions.size} more divisions.
|
||||
</Typography>,
|
||||
);
|
||||
}
|
||||
|
||||
@ -70,13 +71,15 @@ export function SellDivisionModal(props: IProps): React.ReactElement {
|
||||
</Select>
|
||||
<Typography>Division {divisionToSell.name} has:</Typography>
|
||||
<Typography>
|
||||
Profit: {formatMoney((divisionToSell.lastCycleRevenue - divisionToSell.lastCycleExpenses) / 10)} / sec{" "}
|
||||
Profit: <Money money={(divisionToSell.lastCycleRevenue - divisionToSell.lastCycleExpenses) / 10} /> / sec{" "}
|
||||
</Typography>
|
||||
<Typography>Cities:{getRecordKeys(divisionToSell.offices).length}</Typography>
|
||||
<Typography>Warehouses:{getRecordKeys(divisionToSell.warehouses).length}</Typography>
|
||||
{divisionToSell.makesProducts ?? <Typography>Products: {divisionToSell.products.size}</Typography>}
|
||||
<br />
|
||||
<Typography>Sell price: {formatMoney(price)}</Typography>
|
||||
<Typography>
|
||||
Sell price: <Money money={price} />
|
||||
</Typography>
|
||||
<Button onClick={sellDivision}>Sell division</Button>
|
||||
</>
|
||||
</Modal>
|
||||
|
@ -1,16 +1,17 @@
|
||||
import React, { useState } from "react";
|
||||
import { formatMoney } from "../../../ui/formatNumber";
|
||||
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
import { formatShares } from "../../../ui/formatNumber";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { useCorporation } from "../Context";
|
||||
import { Corporation } from "../../Corporation";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { useCorporation } from "../Context";
|
||||
import * as corpConstants from "../../data/Constants";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
|
||||
import { SellShares } from "../../Actions";
|
||||
import { KEY } from "../../../utils/helpers/keyCodes";
|
||||
import { NumberInput } from "../../../ui/React/NumberInput";
|
||||
import { isInteger } from "lodash";
|
||||
import { sellSharesFailureReason } from "../../helpers";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@ -23,48 +24,28 @@ export function SellSharesModal(props: IProps): React.ReactElement {
|
||||
const corp = useCorporation();
|
||||
const [shares, setShares] = useState<number>(NaN);
|
||||
|
||||
const disabled = isNaN(shares) || shares <= 0 || shares >= corp.numShares;
|
||||
|
||||
function ProfitIndicator(props: { shares: number | null; corp: Corporation }): React.ReactElement {
|
||||
if (props.shares === null) return <></>;
|
||||
let text = "";
|
||||
if (isNaN(props.shares) || props.shares <= 0 || !isInteger(props.shares)) {
|
||||
text = `ERROR: Invalid value entered for number of shares to sell`;
|
||||
} else if (props.shares > corp.numShares) {
|
||||
text = `You don't have this many shares to sell!`;
|
||||
} else if (props.shares === corp.numShares) {
|
||||
text = `You can not sell all your shares!`;
|
||||
} else if (props.shares > 1e14) {
|
||||
text = `You can't sell more than 100t shares at once!`;
|
||||
} else {
|
||||
const stockSaleResults = corp.calculateShareSale(props.shares);
|
||||
const profit = stockSaleResults[0];
|
||||
text = `Sell ${props.shares} shares for a total of ${formatMoney(profit)}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography>
|
||||
<small>{text}</small>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
const [profit, sharePrice] = corp.calculateShareSale((props.open && shares) || 0);
|
||||
const disabledText = sellSharesFailureReason(corp, shares);
|
||||
|
||||
function sell(): void {
|
||||
if (disabled) return;
|
||||
if (disabledText) return;
|
||||
try {
|
||||
const profit = SellShares(corp, shares);
|
||||
props.onClose();
|
||||
SellShares(corp, shares);
|
||||
dialogBoxCreate(
|
||||
<>
|
||||
Sold {formatMoney(shares)} shares for
|
||||
<Money money={profit} />. The corporation's stock price fell to <Money money={corp.sharePrice} />
|
||||
as a result of dilution.
|
||||
<Typography>
|
||||
You sold {formatShares(shares)} shares for <Money money={profit} />.
|
||||
</Typography>
|
||||
<Typography>
|
||||
<b>{corp.name}</b>'s stock price fell to <Money money={sharePrice} /> per share.
|
||||
</Typography>
|
||||
</>,
|
||||
);
|
||||
|
||||
props.onClose();
|
||||
props.rerender();
|
||||
setShares(NaN);
|
||||
} catch (err) {
|
||||
dialogBoxCreate(err + "");
|
||||
dialogBoxCreate(`${err as Error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,32 +55,43 @@ export function SellSharesModal(props: IProps): React.ReactElement {
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Typography>
|
||||
Enter the number of shares you would like to sell. The money from selling your shares will go directly to you
|
||||
(NOT your Corporation).
|
||||
<br />
|
||||
<br />
|
||||
The amount sold must be an integer between 1 and 100t.
|
||||
<br />
|
||||
<br />
|
||||
Selling your shares will cause your corporation's stock price to fall due to dilution. Furthermore, selling a
|
||||
large number of shares all at once will have an immediate effect in reducing your stock price.
|
||||
<br />
|
||||
<br />
|
||||
The current price of your company's stock is {formatMoney(corp.sharePrice)}
|
||||
<Typography component="div">
|
||||
Enter the number of shares you would like to sell.
|
||||
<ul>
|
||||
<li>Selling shares will cause stock price to fall due to market forces.</li>
|
||||
<li>The money from selling your shares will go directly to you (NOT your Corporation).</li>
|
||||
<li>
|
||||
You will not be able to sell shares again (or dissolve the corporation) for{" "}
|
||||
<b>{corp.convertCooldownToString(corpConstants.sellSharesCooldown)}</b>.
|
||||
</li>
|
||||
</ul>
|
||||
You currently have {formatShares(corp.numShares)} shares of <b>{corp.name}</b> stock, valued at{" "}
|
||||
<Money money={corp.sharePrice} /> per share.
|
||||
</Typography>
|
||||
<br />
|
||||
<NumberInput
|
||||
defaultValue={shares || ""}
|
||||
variant="standard"
|
||||
autoFocus
|
||||
placeholder="Shares to sell"
|
||||
onChange={setShares}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<Button disabled={disabled} onClick={sell} sx={{ mx: 1 }}>
|
||||
<ButtonWithTooltip disabledTooltip={disabledText} onClick={sell}>
|
||||
Sell shares
|
||||
</Button>
|
||||
<ProfitIndicator shares={shares} corp={corp} />
|
||||
</ButtonWithTooltip>
|
||||
<br />
|
||||
<Typography sx={{ minHeight: "3em" }}>
|
||||
{!shares ? null : disabledText ? (
|
||||
disabledText
|
||||
) : (
|
||||
<>
|
||||
You will receive <Money money={profit} />.
|
||||
<br />
|
||||
<b>{corp.name}</b>'s stock price will fall to <Money money={sharePrice} /> per share.
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ export function SmartSupplyModal(props: IProps): React.ReactElement {
|
||||
label={<Typography>Enable Smart Supply</Typography>}
|
||||
/>
|
||||
<br />
|
||||
<Typography>
|
||||
<Typography component="div">
|
||||
Options:
|
||||
<ul>
|
||||
<li>
|
||||
|
@ -3,7 +3,7 @@ import { formatMultiplier, formatPercent } from "../../../ui/formatNumber";
|
||||
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
|
||||
import { OfficeSpace } from "../../OfficeSpace";
|
||||
import { ThrowParty } from "../../Actions";
|
||||
import { Money } from "../../../ui/React/Money";
|
||||
import { MoneyCost } from "../MoneyCost";
|
||||
import { Modal } from "../../../ui/React/Modal";
|
||||
import { useCorporation } from "../Context";
|
||||
import Typography from "@mui/material/Typography";
|
||||
@ -59,7 +59,7 @@ export function ThrowPartyModal(props: IProps): React.ReactElement {
|
||||
if (isNaN(cost) || cost < 0) return <Typography>Invalid value entered!</Typography>;
|
||||
return (
|
||||
<Typography>
|
||||
Throwing this party will cost a total of <Money money={totalCost} />
|
||||
Throwing this party will cost a total of <MoneyCost money={totalCost} corp={corp} />
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
@ -69,6 +69,12 @@ export function CorporationDev(): React.ReactElement {
|
||||
});
|
||||
}
|
||||
|
||||
function resetCorporationCooldowns(): void {
|
||||
if (!Player.corporation) return;
|
||||
Player.corporation.shareSaleCooldown = 0;
|
||||
Player.corporation.issueNewSharesCooldown = 0;
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion TransitionProps={{ unmountOnExit: true }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
@ -117,6 +123,11 @@ export function CorporationDev(): React.ReactElement {
|
||||
<Button onClick={addCorporationResearch}>Tons of research</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Button onClick={resetCorporationCooldowns}>Reset stock cooldowns</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</AccordionDetails>
|
||||
|
@ -130,7 +130,7 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => {
|
||||
{!props.joined && facInfo.enemies.length > 0 && (
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
<Typography component="div">
|
||||
This Faction is enemies with:
|
||||
<ul>
|
||||
{facInfo.enemies.map((enemy) => (
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
Division as NSDivision,
|
||||
WarehouseAPI,
|
||||
OfficeAPI,
|
||||
InvestmentOffer,
|
||||
CorpResearchName,
|
||||
CorpMaterialName,
|
||||
} from "@nsdefs";
|
||||
@ -22,7 +21,9 @@ import {
|
||||
NewDivision,
|
||||
purchaseOffice,
|
||||
IssueDividends,
|
||||
GoPublic,
|
||||
IssueNewShares,
|
||||
AcceptInvestmentOffer,
|
||||
SellMaterial,
|
||||
SellProduct,
|
||||
SetSmartSupply,
|
||||
@ -104,63 +105,6 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
||||
return cost;
|
||||
}
|
||||
|
||||
function getInvestmentOffer(): InvestmentOffer {
|
||||
const corporation = getCorporation();
|
||||
if (
|
||||
corporation.fundingRound >= corpConstants.fundingRoundShares.length ||
|
||||
corporation.fundingRound >= corpConstants.fundingRoundMultiplier.length ||
|
||||
corporation.public
|
||||
)
|
||||
return {
|
||||
funds: 0,
|
||||
shares: 0,
|
||||
round: corporation.fundingRound + 1, // Make more readable
|
||||
}; // Don't throw an error here, no reason to have a second function to check if you can get investment.
|
||||
const val = corporation.valuation;
|
||||
const percShares = corpConstants.fundingRoundShares[corporation.fundingRound];
|
||||
const roundMultiplier = corpConstants.fundingRoundMultiplier[corporation.fundingRound];
|
||||
const funding = val * percShares * roundMultiplier;
|
||||
const investShares = Math.floor(corpConstants.initialShares * percShares);
|
||||
return {
|
||||
funds: funding,
|
||||
shares: investShares,
|
||||
round: corporation.fundingRound + 1, // Make more readable
|
||||
};
|
||||
}
|
||||
|
||||
function acceptInvestmentOffer(): boolean {
|
||||
const corporation = getCorporation();
|
||||
if (
|
||||
corporation.fundingRound >= corpConstants.fundingRoundShares.length ||
|
||||
corporation.fundingRound >= corpConstants.fundingRoundMultiplier.length ||
|
||||
corporation.public
|
||||
)
|
||||
return false;
|
||||
const val = corporation.valuation;
|
||||
const percShares = corpConstants.fundingRoundShares[corporation.fundingRound];
|
||||
const roundMultiplier = corpConstants.fundingRoundMultiplier[corporation.fundingRound];
|
||||
const funding = val * percShares * roundMultiplier;
|
||||
const investShares = Math.floor(corpConstants.initialShares * percShares);
|
||||
corporation.fundingRound++;
|
||||
corporation.addNonIncomeFunds(funding);
|
||||
corporation.numShares -= investShares;
|
||||
return true;
|
||||
}
|
||||
|
||||
function goPublic(numShares: number): boolean {
|
||||
const corporation = getCorporation();
|
||||
const initialSharePrice = corporation.valuation / corporation.totalShares;
|
||||
if (isNaN(numShares)) throw new Error("Invalid value for number of issued shares");
|
||||
if (numShares < 0) throw new Error("Invalid value for number of issued shares");
|
||||
if (numShares > corporation.numShares) throw new Error("You don't have that many shares to issue!");
|
||||
corporation.public = true;
|
||||
corporation.sharePrice = initialSharePrice;
|
||||
corporation.issuedShares = numShares;
|
||||
corporation.numShares -= numShares;
|
||||
corporation.addNonIncomeFunds(numShares * initialSharePrice);
|
||||
return true;
|
||||
}
|
||||
|
||||
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}'`);
|
||||
@ -741,12 +685,6 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
||||
const maxNewShares = corporation.calculateMaxNewShares();
|
||||
if (_amount == undefined) _amount = maxNewShares;
|
||||
const amount = helpers.number(ctx, "amount", _amount);
|
||||
if (corporation.issueNewSharesCooldown > 0) throw new Error(`Can't issue new shares, action on cooldown.`);
|
||||
if (amount < 10e6 || amount > maxNewShares)
|
||||
throw new Error(
|
||||
`Invalid value for amount field! Must be numeric, greater than 10m, and less than ${maxNewShares} (20% of total shares)`,
|
||||
);
|
||||
if (!corporation.public) throw helpers.makeRuntimeErrorMsg(ctx, `Your company has not gone public!`);
|
||||
const [funds] = IssueNewShares(corporation, amount);
|
||||
return funds;
|
||||
},
|
||||
@ -768,6 +706,7 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
||||
totalShares: corporation.totalShares,
|
||||
numShares: corporation.numShares,
|
||||
shareSaleCooldown: corporation.shareSaleCooldown,
|
||||
investorShares: corporation.investorShares,
|
||||
issuedShares: corporation.issuedShares,
|
||||
issueNewSharesCooldown: corporation.issueNewSharesCooldown,
|
||||
sharePrice: corporation.sharePrice,
|
||||
@ -807,18 +746,26 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
||||
},
|
||||
getInvestmentOffer: (ctx) => () => {
|
||||
checkAccess(ctx);
|
||||
return getInvestmentOffer();
|
||||
const corporation = getCorporation();
|
||||
return corporation.getInvestmentOffer();
|
||||
},
|
||||
acceptInvestmentOffer: (ctx) => () => {
|
||||
checkAccess(ctx);
|
||||
return acceptInvestmentOffer();
|
||||
const corporation = getCorporation();
|
||||
try {
|
||||
AcceptInvestmentOffer(corporation);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
goPublic: (ctx) => (_numShares) => {
|
||||
checkAccess(ctx);
|
||||
const corporation = getCorporation();
|
||||
if (corporation.public) throw helpers.makeRuntimeErrorMsg(ctx, "corporation is already public");
|
||||
const numShares = helpers.number(ctx, "numShares", _numShares);
|
||||
return goPublic(numShares);
|
||||
GoPublic(corporation, numShares);
|
||||
return true;
|
||||
},
|
||||
sellShares: (ctx) => (_numShares) => {
|
||||
checkAccess(ctx);
|
||||
|
@ -21,5 +21,8 @@ export function startCorporation(this: PlayerObject, corpName: string, seedFunde
|
||||
this.corporation.unlocks.add(CorpUnlockName.OfficeAPI);
|
||||
}
|
||||
|
||||
this.corporation.totalShares += seedFunded ? 500_000_000 : 0;
|
||||
if (seedFunded) {
|
||||
this.corporation.investorShares += 500e6;
|
||||
this.corporation.totalShares += 500e6;
|
||||
}
|
||||
}
|
||||
|
14
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
14
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -7437,11 +7437,13 @@ export interface Corporation extends WarehouseAPI, OfficeAPI {
|
||||
* @returns Amount of funds generated for the corporation. */
|
||||
issueNewShares(amount?: number): number;
|
||||
|
||||
/** Buyback Shares
|
||||
/** Buyback Shares.
|
||||
* Spend money from the player's wallet to transfer shares from public traders to the CEO.
|
||||
* @param amount - Amount of shares to buy back, must be integer and larger than 0 */
|
||||
buyBackShares(amount: number): void;
|
||||
|
||||
/** Sell Shares
|
||||
/** Sell Shares.
|
||||
* Transfer shares from the CEO to public traders to receive money in the player's wallet.
|
||||
* @param amount - Amount of shares to sell, must be integer between 1 and 100t */
|
||||
sellShares(amount: number): void;
|
||||
|
||||
@ -7515,13 +7517,15 @@ interface CorporationInfo {
|
||||
expenses: number;
|
||||
/** Indicating if the company is public */
|
||||
public: boolean;
|
||||
/** Total number of shares issues by this corporation */
|
||||
/** Total number of shares issued by this corporation. */
|
||||
totalShares: number;
|
||||
/** Amount of share owned */
|
||||
/** Amount of shares owned by the CEO. */
|
||||
numShares: number;
|
||||
/** Cooldown until shares can be sold again */
|
||||
shareSaleCooldown: number;
|
||||
/** Amount of acquirable shares. */
|
||||
/** Amount of shares owned by private investors. Not available for public sale or CEO buyback. */
|
||||
investorShares: number;
|
||||
/** Amount of shares owned by public traders. Available for CEO buyback. */
|
||||
issuedShares: number;
|
||||
/** Cooldown until new shares can be issued */
|
||||
issueNewSharesCooldown: number;
|
||||
|
@ -2,11 +2,22 @@ import { PositiveInteger } from "../../src/types";
|
||||
import { Corporation } from "../../src/Corporation/Corporation";
|
||||
import { CorpUpgrades } from "../../src/Corporation/data/CorporationUpgrades";
|
||||
import { calculateMaxAffordableUpgrade, calculateUpgradeCost } from "../../src/Corporation/helpers";
|
||||
import { Player, setPlayer } from "../../src/Player";
|
||||
import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject";
|
||||
import {
|
||||
AcceptInvestmentOffer,
|
||||
BuyBackShares,
|
||||
GoPublic,
|
||||
IssueNewShares,
|
||||
SellShares,
|
||||
} from "../../src/Corporation/Actions";
|
||||
|
||||
describe("Corporation", () => {
|
||||
let corporation: Corporation;
|
||||
|
||||
beforeEach(() => {
|
||||
setPlayer(new PlayerObject());
|
||||
Player.init();
|
||||
corporation = new Corporation({ name: "Test" });
|
||||
});
|
||||
|
||||
@ -57,4 +68,48 @@ describe("Corporation", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Corporation totalShares", () => {
|
||||
function expectSharesToAddUp(corp: Corporation) {
|
||||
expect(corp.totalShares).toEqual(corp.numShares + corp.investorShares + corp.issuedShares);
|
||||
}
|
||||
|
||||
it("should equal the sum of each kind of shares", () => {
|
||||
expectSharesToAddUp(corporation);
|
||||
});
|
||||
it("should be preserved by seed funding", () => {
|
||||
const seedFunded = true;
|
||||
Player.startCorporation("TestCorp", seedFunded);
|
||||
expectSharesToAddUp(Player.corporation!);
|
||||
});
|
||||
it("should be preserved by acceptInvestmentOffer", () => {
|
||||
AcceptInvestmentOffer(corporation);
|
||||
expectSharesToAddUp(corporation);
|
||||
});
|
||||
it("should be preserved by goPublic", () => {
|
||||
const numShares = 1e8;
|
||||
GoPublic(corporation, numShares);
|
||||
expectSharesToAddUp(corporation);
|
||||
});
|
||||
it("should be preserved by IssueNewShares", () => {
|
||||
const numShares = 1e8;
|
||||
GoPublic(corporation, numShares);
|
||||
corporation.issueNewSharesCooldown = 0;
|
||||
IssueNewShares(corporation, numShares);
|
||||
expectSharesToAddUp(corporation);
|
||||
});
|
||||
it("should be preserved by BuyBackShares", () => {
|
||||
const numShares = 1e8;
|
||||
GoPublic(corporation, numShares);
|
||||
BuyBackShares(corporation, numShares);
|
||||
expectSharesToAddUp(corporation);
|
||||
});
|
||||
it("should be preserved by SellShares", () => {
|
||||
const numShares = 1e8;
|
||||
GoPublic(corporation, numShares);
|
||||
corporation.shareSaleCooldown = 0;
|
||||
SellShares(corporation, numShares);
|
||||
expectSharesToAddUp(corporation);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1150,6 +1150,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"expenses": 0,
|
||||
"fundingRound": 0,
|
||||
"funds": 150000000000,
|
||||
"investorShares": 0,
|
||||
"issueNewSharesCooldown": 0,
|
||||
"issuedShares": 0,
|
||||
"maxDivisions": 20,
|
||||
|
Loading…
Reference in New Issue
Block a user