diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts index bf13b6d33..a16f8bdd0 100644 --- a/src/Corporation/Actions.ts +++ b/src/Corporation/Actions.ts @@ -16,6 +16,7 @@ import { ResearchMap } from "./ResearchMap"; import { isRelevantMaterial } from "./ui/Helpers"; import { checkEnum } from "../utils/helpers/enum"; import { CityName } from "../Locations/data/CityNames"; +import { getRandomInt } from "../utils/helpers/getRandomInt"; export function NewIndustry(corporation: Corporation, industry: IndustryType, name: string): void { if (corporation.divisions.find(({ type }) => industry == type)) @@ -90,6 +91,33 @@ export function IssueDividends(corporation: Corporation, rate: number): void { corporation.dividendRate = rate; } +export function IssueNewShares(corporation: Corporation, amount: number): [number, number, number] { + const max = corporation.calculateMaxNewShares(); + + // Round to nearest ten-millionth + amount = Math.round(amount / 10e6) * 10e6; + + if (isNaN(amount) || amount < 10e6 || amount > max) { + throw new Error(`Invalid value. Must be an number between 10m and ${max} (20% of total shares)`); + } + + const newSharePrice = Math.round(corporation.sharePrice * 0.9); + + const profit = amount * newSharePrice; + corporation.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown; + + const privateOwnedRatio = 1 - (corporation.numShares + corporation.issuedShares) / corporation.totalShares; + const maxPrivateShares = Math.round((amount / 2) * privateOwnedRatio); + const privateShares = Math.round(getRandomInt(0, maxPrivateShares) / 10e6) * 10e6; + + corporation.issuedShares += amount - privateShares; + corporation.totalShares += amount; + corporation.funds = corporation.funds + profit; + corporation.immediatelyUpdateSharePrice(); + + return [profit, amount, privateShares]; +} + export function SellMaterial(mat: Material, amt: string, price: string): void { if (price === "") price = "0"; if (amt === "") amt = "0"; diff --git a/src/Corporation/Corporation.tsx b/src/Corporation/Corporation.tsx index c7e2f2381..6f0d71595 100644 --- a/src/Corporation/Corporation.tsx +++ b/src/Corporation/Corporation.tsx @@ -225,6 +225,12 @@ export class Corporation { this.sharePrice = this.getTargetSharePrice(); } + calculateMaxNewShares(): number { + const maxNewSharesUnrounded = Math.round(this.totalShares * 0.2); + const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 10e6); + return maxNewShares; + } + // Calculates how much money will be made and what the resulting stock price // will be when the player sells his/her shares // @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property] diff --git a/src/Corporation/ui/modals/IssueNewSharesModal.tsx b/src/Corporation/ui/modals/IssueNewSharesModal.tsx index 944d58203..306c9f9aa 100644 --- a/src/Corporation/ui/modals/IssueNewSharesModal.tsx +++ b/src/Corporation/ui/modals/IssueNewSharesModal.tsx @@ -2,13 +2,12 @@ import React, { useState } from "react"; import { numeralWrapper } from "../../../ui/numeralFormat"; import { dialogBoxCreate } from "../../../ui/React/DialogBox"; import { Modal } from "../../../ui/React/Modal"; -import { getRandomInt } from "../../../utils/helpers/getRandomInt"; -import { CorporationConstants } from "../../data/Constants"; import { useCorporation } from "../Context"; import Typography from "@mui/material/Typography"; import { NumberInput } from "../../../ui/React/NumberInput"; import Button from "@mui/material/Button"; import { KEY } from "../../../utils/helpers/keyCodes"; +import { IssueNewShares } from "../../Actions"; interface IEffectTextProps { shares: number | null; @@ -18,8 +17,7 @@ function EffectText(props: IEffectTextProps): React.ReactElement { const corp = useCorporation(); if (props.shares === null) return <>; const newSharePrice = Math.round(corp.sharePrice * 0.9); - const maxNewSharesUnrounded = Math.round(corp.totalShares * 0.2); - const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); + const maxNewShares = corp.calculateMaxNewShares(); let newShares = props.shares; if (isNaN(newShares)) { return Invalid input; @@ -55,8 +53,7 @@ interface IProps { export function IssueNewSharesModal(props: IProps): React.ReactElement { const corp = useCorporation(); const [shares, setShares] = useState(NaN); - const maxNewSharesUnrounded = Math.round(corp.totalShares * 0.2); - const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); + const maxNewShares = corp.calculateMaxNewShares(); const newShares = Math.round((shares || 0) / 10e6) * 10e6; const disabled = isNaN(shares) || isNaN(newShares) || newShares < 10e6 || newShares > maxNewShares; @@ -64,27 +61,8 @@ export function IssueNewSharesModal(props: IProps): React.ReactElement { function issueNewShares(): void { if (isNaN(shares)) return; if (disabled) return; + const [profit, newShares, privateShares] = IssueNewShares(corp, shares); - const newSharePrice = Math.round(corp.sharePrice * 0.9); - let newShares = shares; - - // Round to nearest ten-millionth - newShares = Math.round(newShares / 10e6) * 10e6; - - const profit = newShares * newSharePrice; - corp.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown; - - // Determine how many are bought by private investors - // If private investors own n% of the company, private investors get up to 0.5n% at most - // Round # of private shares to the nearest million - const privateOwnedRatio = 1 - (corp.numShares + corp.issuedShares) / corp.totalShares; - const maxPrivateShares = Math.round((newShares / 2) * privateOwnedRatio); - const privateShares = Math.round(getRandomInt(0, maxPrivateShares) / 1e6) * 1e6; - - corp.issuedShares += newShares - privateShares; - corp.totalShares += newShares; - corp.funds = corp.funds + profit; - corp.immediatelyUpdateSharePrice(); props.onClose(); let dialogContents = diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index 296c22e53..ed631a5f7 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -373,6 +373,7 @@ const corporation = { unlockUpgrade: 0, levelUpgrade: 0, issueDividends: 0, + issueNewShares: 0, buyBackShares: 0, sellShares: 0, getBonusTime: 0, diff --git a/src/NetscriptFunctions/Corporation.ts b/src/NetscriptFunctions/Corporation.ts index cd51a3075..f34c142ad 100644 --- a/src/NetscriptFunctions/Corporation.ts +++ b/src/NetscriptFunctions/Corporation.ts @@ -24,6 +24,7 @@ import { UnlockUpgrade, LevelUpgrade, IssueDividends, + IssueNewShares, SellMaterial, SellProduct, SetSmartSupply, @@ -820,6 +821,21 @@ export function NetscriptCorporation(): InternalAPI { if (!corporation.public) throw helpers.makeRuntimeErrorMsg(ctx, `Your company has not gone public!`); IssueDividends(corporation, rate); }, + issueNewShares: (ctx) => (_amount) => { + checkAccess(ctx); + const corporation = getCorporation(); + const maxNewShares = corporation.calculateMaxNewShares(); + if (_amount == undefined) _amount = maxNewShares; + const amount = helpers.number(ctx, "amount", _amount); + if (corporation.issueNewSharesCooldown > 0) throw new Error(`Can't issue new shares, action on cooldown.`); + if (amount < 10e6 || amount > maxNewShares) + throw new Error( + `Invalid value for amount field! Must be numeric, greater than 10m, and less than ${maxNewShares} (20% of total shares)`, + ); + if (!corporation.public) throw helpers.makeRuntimeErrorMsg(ctx, `Your company has not gone public!`); + const [funds] = IssueNewShares(corporation, amount); + return funds; + }, getDivision: (ctx) => (_divisionName) => { checkAccess(ctx); const divisionName = helpers.string(ctx, "divisionName", _divisionName); diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index ff09b105d..63fe874e5 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -7545,6 +7545,11 @@ export interface Corporation extends WarehouseAPI, OfficeAPI { * @param rate - Fraction of profit to issue as dividends. */ issueDividends(rate: number): void; + /** Issue new shares + * @param amount - Number of new shares to issue, will be rounded to nearest 10m. Defaults to max amount. + * @returns Amount of funds generated for the corporation. */ + issueNewShares(amount?: number): number; + /** Buyback Shares * @param amount - Amount of shares to buy back. */ buyBackShares(amount: number): void;