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