finalize corp in mui

This commit is contained in:
Olivier Gagnon 2021-09-30 17:02:07 -04:00
parent 86ddc940aa
commit b0e4a2a775
23 changed files with 688 additions and 636 deletions

38
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -20,12 +20,26 @@ interface IProps {
export function BribeFactionModal(props: IProps): React.ReactElement { export function BribeFactionModal(props: IProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
const factions = player.factions.filter((name: string) => {
const info = Factions[name].getInfo();
if (!info.offersWork()) return false;
if (player.hasGangWith(name)) return false;
return true;
});
const corp = useCorporation(); const corp = useCorporation();
const [money, setMoney] = useState<number | null>(0); const [money, setMoney] = useState<number | null>(0);
const [stock, setStock] = useState<number | null>(0); const [stock, setStock] = useState<number | null>(0);
const [selectedFaction, setSelectedFaction] = useState( const [selectedFaction, setSelectedFaction] = useState(factions.length > 0 ? factions[0] : "");
player.factions.length > 0 ? player.factions.filter((faction) => Factions[faction].getInfo().offersWork())[0] : "", const disabled =
); money === null ||
stock === null ||
(money === 0 && stock === 0) ||
isNaN(money) ||
isNaN(stock) ||
money < 0 ||
stock < 0 ||
corp.funds.lt(money) ||
stock > corp.numShares;
function onMoneyChange(event: React.ChangeEvent<HTMLInputElement>): void { function onMoneyChange(event: React.ChangeEvent<HTMLInputElement>): void {
setMoney(parseFloat(event.target.value)); setMoney(parseFloat(event.target.value));
@ -64,22 +78,15 @@ export function BribeFactionModal(props: IProps): React.ReactElement {
function bribe(money: number, stock: number): void { function bribe(money: number, stock: number): void {
const fac = Factions[selectedFaction]; const fac = Factions[selectedFaction];
if (fac == null) { if (disabled) return;
dialogBoxCreate("ERROR: You must select a faction to bribe"); const rep = repGain(money, stock);
} dialogBoxCreate(
if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) { "You gained " + numeralWrapper.formatReputation(rep) + " reputation with " + fac.name + " by bribing them.",
} else if (corp.funds.lt(money)) { );
} else if (stock > corp.numShares) { fac.playerReputation += rep;
} else { corp.funds = corp.funds.minus(money);
const rep = repGain(money, stock); corp.numShares -= stock;
dialogBoxCreate( props.onClose();
"You gained " + numeralWrapper.formatReputation(rep) + " reputation with " + fac.name + " by bribing them.",
);
fac.playerReputation += rep;
corp.funds = corp.funds.minus(money);
corp.numShares -= stock;
props.onClose();
}
} }
return ( return (
@ -90,9 +97,10 @@ export function BribeFactionModal(props: IProps): React.ReactElement {
<Box display="flex" alignItems="center"> <Box display="flex" alignItems="center">
<Typography>Faction:</Typography> <Typography>Faction:</Typography>
<Select value={selectedFaction} onChange={changeFaction}> <Select value={selectedFaction} onChange={changeFaction}>
{player.factions.map((name: string) => { {factions.map((name: string) => {
const info = Factions[name].getInfo(); const info = Factions[name].getInfo();
if (!info.offersWork()) return; if (!info.offersWork()) return;
if (player.hasGangWith(name)) return;
return ( return (
<MenuItem key={name} value={name}> <MenuItem key={name} value={name}>
{name} {name}
@ -104,7 +112,7 @@ export function BribeFactionModal(props: IProps): React.ReactElement {
<Typography>{getRepText(money ? money : 0, stock ? stock : 0)}</Typography> <Typography>{getRepText(money ? money : 0, stock ? stock : 0)}</Typography>
<TextField onChange={onMoneyChange} placeholder="Corporation funds" /> <TextField onChange={onMoneyChange} placeholder="Corporation funds" />
<TextField sx={{ mx: 1 }} onChange={onStockChange} placeholder="Stock Shares" /> <TextField sx={{ mx: 1 }} onChange={onStockChange} placeholder="Stock Shares" />
<Button sx={{ mx: 1 }} onClick={() => bribe(money ? money : 0, stock ? stock : 0)}> <Button disabled={disabled} sx={{ mx: 1 }} onClick={() => bribe(money ? money : 0, stock ? stock : 0)}>
Bribe Bribe
</Button> </Button>
</Modal> </Modal>

@ -4,6 +4,9 @@ import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { useCorporation } from "./Context"; import { useCorporation } from "./Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
interface IProps { interface IProps {
open: boolean; open: boolean;
@ -25,38 +28,30 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
const currentStockPrice = corp.sharePrice; const currentStockPrice = corp.sharePrice;
const buybackPrice = currentStockPrice * 1.1; const buybackPrice = currentStockPrice * 1.1;
const disabled =
shares === null ||
isNaN(shares) ||
shares <= 0 ||
shares > corp.issuedShares ||
shares * buybackPrice > player.money;
function buy(): void { function buy(): void {
if (shares === null) return; if (shares === null) return;
const tempStockPrice = corp.sharePrice; corp.numShares += shares;
const buybackPrice = tempStockPrice * 1.1; if (isNaN(corp.issuedShares)) {
if (isNaN(shares) || shares <= 0) { console.warn("Corporation issuedShares is NaN: " + corp.issuedShares);
dialogBoxCreate("ERROR: Invalid value for number of shares"); console.warn("Converting to number now");
} else if (shares > corp.issuedShares) { const res = corp.issuedShares;
dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back"); if (isNaN(res)) {
} else if (shares * buybackPrice > player.money) { corp.issuedShares = 0;
dialogBoxCreate( } else {
"ERROR: You do not have enough money to purchase this many shares (you need " + corp.issuedShares = res;
numeralWrapper.format(shares * buybackPrice, "$0.000a") +
")",
);
} else {
corp.numShares += shares;
if (isNaN(corp.issuedShares)) {
console.warn("Corporation issuedShares is NaN: " + corp.issuedShares);
console.warn("Converting to number now");
const res = corp.issuedShares;
if (isNaN(res)) {
corp.issuedShares = 0;
} else {
corp.issuedShares = res;
}
} }
corp.issuedShares -= shares;
player.loseMoney(shares * buybackPrice);
props.onClose();
props.rerender();
} }
corp.issuedShares -= shares;
player.loseMoney(shares * buybackPrice);
props.onClose();
props.rerender();
} }
function CostIndicator(): React.ReactElement { function CostIndicator(): React.ReactElement {
@ -85,7 +80,7 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
<p> <Typography>
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. These shares must be bought at a 10% premium.
However, repurchasing shares from the market tends to lead to an increase in stock price. However, repurchasing shares from the market tends to lead to an increase in stock price.
<br /> <br />
@ -95,21 +90,19 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
<br /> <br />
The current buyback price of your company's stock is {numeralWrapper.formatMoney(buybackPrice)}. Your company The current buyback price of your company's stock is {numeralWrapper.formatMoney(buybackPrice)}. Your company
currently has {numeralWrapper.formatBigNumber(corp.issuedShares)} outstanding stock shares. currently has {numeralWrapper.formatBigNumber(corp.issuedShares)} outstanding stock shares.
</p> </Typography>
<CostIndicator /> <CostIndicator />
<br /> <br />
<input <TextField
autoFocus={true} autoFocus={true}
className="text-input"
type="number" type="number"
placeholder="Shares to buyback" placeholder="Shares to buyback"
style={{ margin: "5px" }}
onChange={changeShares} onChange={changeShares}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
/> />
<button onClick={buy} className="a-link-button" style={{ display: "inline-block" }}> <Button disabled={disabled} onClick={buy}>
Buy shares Buy shares
</button> </Button>
</Modal> </Modal>
); );
} }

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { Industries, IndustryDescriptions } from "../IndustryData"; import { IndustryStartingCosts, Industries, IndustryDescriptions } from "../IndustryData";
import { useCorporation } from "./Context"; import { useCorporation } from "./Context";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { NewIndustry } from "../Actions"; import { NewIndustry } from "../Actions";
@ -28,7 +28,14 @@ export function ExpandIndustryTab(props: IProps): React.ReactElement {
const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : ""); const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : "");
const [name, setName] = useState(""); const [name, setName] = useState("");
const cost = IndustryStartingCosts[industry];
if (cost === undefined) {
throw new Error(`Invalid industry: '${industry}'`);
}
const disabled = corp.funds.lt(cost) || name === "";
function newIndustry(): void { function newIndustry(): void {
if (disabled) return;
try { try {
NewIndustry(corp, industry, name); NewIndustry(corp, industry, name);
} catch (err) { } catch (err) {
@ -74,7 +81,7 @@ export function ExpandIndustryTab(props: IProps): React.ReactElement {
<Box display="flex" alignItems="center"> <Box display="flex" alignItems="center">
<TextField autoFocus={true} value={name} onChange={onNameChange} onKeyDown={onKeyDown} type="text" /> <TextField autoFocus={true} value={name} onChange={onNameChange} onKeyDown={onKeyDown} type="text" />
<Button sx={{ mx: 1 }} onClick={newIndustry}> <Button disabled={disabled} sx={{ mx: 1 }} onClick={newIndustry}>
Create Division Create Division
</Button> </Button>
</Box> </Box>

@ -19,6 +19,8 @@ export function ExpandNewCity(props: IProps): React.ReactElement {
const possibleCities = Object.keys(division.offices).filter((cityName: string) => division.offices[cityName] === 0); const possibleCities = Object.keys(division.offices).filter((cityName: string) => division.offices[cityName] === 0);
const [city, setCity] = useState(possibleCities[0]); const [city, setCity] = useState(possibleCities[0]);
const disabled = corp.funds.lt(CorporationConstants.OfficeInitialCost);
function onCityChange(event: SelectChangeEvent<string>): void { function onCityChange(event: SelectChangeEvent<string>): void {
setCity(event.target.value); setCity(event.target.value);
} }
@ -48,7 +50,7 @@ export function ExpandNewCity(props: IProps): React.ReactElement {
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
<Button onClick={expand} disabled={corp.funds.lt(0)}> <Button onClick={expand} disabled={disabled}>
Confirm Confirm
</Button> </Button>
</> </>

@ -1,25 +1,32 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation";
import { Material } from "../Material"; import { Material } from "../Material";
import { Export } from "../Export"; import { Export } from "../Export";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { ExportMaterial } from "../Actions"; import { ExportMaterial } from "../Actions";
import { Modal } from "../../ui/React/Modal";
import { useCorporation } from "./Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select";
interface IProps { interface IProps {
open: boolean;
onClose: () => void;
mat: Material; mat: Material;
corp: ICorporation;
popupId: string;
} }
// Create a popup that lets the player manage exports // Create a popup that lets the player manage exports
export function ExportPopup(props: IProps): React.ReactElement { export function ExportModal(props: IProps): React.ReactElement {
if (props.corp.divisions.length === 0) throw new Error("Export popup created with no divisions."); const corp = useCorporation();
if (Object.keys(props.corp.divisions[0].warehouses).length === 0) if (corp.divisions.length === 0) throw new Error("Export popup created with no divisions.");
if (Object.keys(corp.divisions[0].warehouses).length === 0)
throw new Error("Export popup created in a division with no warehouses."); throw new Error("Export popup created in a division with no warehouses.");
const [industry, setIndustry] = useState<string>(props.corp.divisions[0].name); const [industry, setIndustry] = useState<string>(corp.divisions[0].name);
const [city, setCity] = useState<string>(Object.keys(props.corp.divisions[0].warehouses)[0]); const [city, setCity] = useState<string>(Object.keys(corp.divisions[0].warehouses)[0]);
const [amt, setAmt] = useState(""); const [amt, setAmt] = useState("");
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
@ -27,11 +34,11 @@ export function ExportPopup(props: IProps): React.ReactElement {
setRerender((old) => !old); setRerender((old) => !old);
} }
function onCityChange(event: React.ChangeEvent<HTMLSelectElement>): void { function onCityChange(event: SelectChangeEvent<string>): void {
setCity(event.target.value); setCity(event.target.value);
} }
function onIndustryChange(event: React.ChangeEvent<HTMLSelectElement>): void { function onIndustryChange(event: SelectChangeEvent<string>): void {
setIndustry(event.target.value); setIndustry(event.target.value);
} }
@ -45,7 +52,7 @@ export function ExportPopup(props: IProps): React.ReactElement {
} catch (err) { } catch (err) {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
} }
removePopup(props.popupId); props.onClose();
} }
function removeExport(exp: Export): void { function removeExport(exp: Export): void {
@ -58,7 +65,7 @@ export function ExportPopup(props: IProps): React.ReactElement {
rerender(); rerender();
} }
const currentDivision = props.corp.divisions.find((division: IIndustry) => division.name === industry); const currentDivision = corp.divisions.find((division: IIndustry) => division.name === industry);
if (currentDivision === undefined) if (currentDivision === undefined)
throw new Error(`Export popup somehow ended up with undefined division '${currentDivision}'`); throw new Error(`Export popup somehow ended up with undefined division '${currentDivision}'`);
const possibleCities = Object.keys(currentDivision.warehouses).filter( const possibleCities = Object.keys(currentDivision.warehouses).filter(
@ -69,45 +76,48 @@ export function ExportPopup(props: IProps): React.ReactElement {
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <Typography>
Select the industry and city to export this material to, as well as how much of this material to export per Select the industry and city to export this material to, as well as how much of this material to export per
second. You can set the export amount to 'MAX' to export all of the materials in this warehouse. second. You can set the export amount to 'MAX' to export all of the materials in this warehouse.
</p> </Typography>
<select className="dropdown" onChange={onIndustryChange} defaultValue={industry}> <Select onChange={onIndustryChange} value={industry}>
{props.corp.divisions.map((division: IIndustry) => ( {corp.divisions.map((division: IIndustry) => (
<option key={division.name} value={division.name}> <MenuItem key={division.name} value={division.name}>
{division.name} {division.name}
</option> </MenuItem>
))} ))}
</select> </Select>
<select className="dropdown" onChange={onCityChange} defaultValue={city}> <Select onChange={onCityChange} value={city}>
{possibleCities.map((cityName: string) => { {possibleCities.map((cityName: string) => {
if (currentDivision.warehouses[cityName] === 0) return; if (currentDivision.warehouses[cityName] === 0) return;
return ( return (
<option key={cityName} value={cityName}> <MenuItem key={cityName} value={cityName}>
{cityName} {cityName}
</option> </MenuItem>
); );
})} })}
</select> </Select>
<input className="text-input" placeholder="Export amount / s" onChange={onAmtChange} /> <TextField placeholder="Export amount / s" onChange={onAmtChange} value={amt} />
<button className="std-button" style={{ display: "inline-block" }} onClick={exportMaterial}> <Button onClick={exportMaterial}>Export</Button>
Export <Typography>
</button>
<p>
Below is a list of all current exports of this material from this warehouse. Clicking on one of the exports Below is a list of all current exports of this material from this warehouse. Clicking on one of the exports
below will REMOVE that export. below will REMOVE that export.
</p> </Typography>
{props.mat.exp.map((exp: Export, index: number) => ( {props.mat.exp.map((exp: Export, index: number) => (
<div key={index} className="cmpy-mgmt-existing-export" onClick={() => removeExport(exp)}> <Box display="flex" alignItems="center" key={index}>
Industry: {exp.ind} <Button sx={{ mx: 2 }} onClick={() => removeExport(exp)}>
<br /> delete
City: {exp.city} </Button>
<br /> <Typography>
Amount/s: {exp.amt} Industry: {exp.ind}
</div> <br />
City: {exp.city}
<br />
Amount/s: {exp.amt}
</Typography>
</Box>
))} ))}
</> </Modal>
); );
} }

@ -136,6 +136,7 @@ function ManualManagement(props: IProps): React.ReactElement {
return ( return (
<> <>
<br />
<Select value={employee !== null ? employee.name : ""} onChange={employeeSelectorOnChange}> <Select value={employee !== null ? employee.name : ""} onChange={employeeSelectorOnChange}>
{employees} {employees}
</Select> </Select>

@ -259,10 +259,12 @@ function Upgrades(props: { office: OfficeSpace; rerender: () => void }): React.R
upgrades.push( upgrades.push(
<Tooltip key={index} title={upgrade[5]}> <Tooltip key={index} title={upgrade[5]}>
<Button onClick={onClick}> <span>
{upgrade[4]} -&nbsp; <Button disabled={corp.funds.lt(cost)} onClick={onClick}>
<MoneyCost money={cost} corp={corp} /> {upgrade[4]} -&nbsp;
</Button> <MoneyCost money={cost} corp={corp} />
</Button>
</span>
</Tooltip>, </Tooltip>,
); );
} }

@ -3,26 +3,19 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { OfficeSpace } from "../OfficeSpace";
import { Material } from "../Material"; import { Material } from "../Material";
import { Product } from "../Product"; import { Product } from "../Product";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../Warehouse";
import { ExportPopup } from "./ExportPopup";
import { MaterialMarketTaPopup } from "./MaterialMarketTaPopup";
import { SellMaterialPopup } from "./SellMaterialPopup";
import { PurchaseMaterialPopup } from "./PurchaseMaterialPopup";
import { SmartSupplyModal } from "./SmartSupplyModal"; import { SmartSupplyModal } from "./SmartSupplyModal";
import { ProductElem } from "./ProductElem"; import { ProductElem } from "./ProductElem";
import { MaterialElem } from "./MaterialElem";
import { MaterialSizes } from "../MaterialSizes"; import { MaterialSizes } from "../MaterialSizes";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { createPopup } from "../../ui/React/createPopup";
import { isString } from "../../utils/helpers/isString";
import { ICorporation } from "../ICorporation"; import { ICorporation } from "../ICorporation";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { Money } from "../../ui/React/Money";
import { MoneyCost } from "./MoneyCost"; import { MoneyCost } from "./MoneyCost";
import { isRelevantMaterial } from "./Helpers"; import { isRelevantMaterial } from "./Helpers";
import { IndustryProductEquation } from "./IndustryProductEquation"; import { IndustryProductEquation } from "./IndustryProductEquation";
@ -35,190 +28,6 @@ import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
interface IMaterialProps {
warehouse: Warehouse;
city: string;
mat: Material;
rerender: () => void;
}
// Creates the UI for a single Material type
function MaterialComponent(props: IMaterialProps): React.ReactElement {
const corp = useCorporation();
const division = useDivision();
const warehouse = props.warehouse;
const city = props.city;
const mat = props.mat;
const markupLimit = mat.getMarkupLimit();
const office = division.offices[city];
if (!(office instanceof OfficeSpace)) {
throw new Error(`Could not get OfficeSpace object for this city (${city})`);
}
// Numeraljs formatter
const nf = "0.000";
const nfB = "0.000a"; // For numbers that might be biger
// Total gain or loss of this material (per second)
const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp;
// Flag that determines whether this industry is "new" and the current material should be
// marked with flashing-red lights
const tutorial =
division.newInd && Object.keys(division.reqMats).includes(mat.name) && mat.buy === 0 && mat.imp === 0;
// Purchase material button
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
function openPurchaseMaterialPopup(): void {
const popupId = "cmpy-mgmt-material-purchase-popup";
createPopup(popupId, PurchaseMaterialPopup, {
mat: mat,
industry: division,
warehouse: warehouse,
corp: corp,
popupId: popupId,
});
}
function openExportPopup(): void {
const popupId = "cmpy-mgmt-export-popup";
createPopup(popupId, ExportPopup, {
mat: mat,
corp: corp,
popupId: popupId,
});
}
// Sell material button
let sellButtonText: JSX.Element;
if (mat.sllman[0]) {
if (isString(mat.sllman[1])) {
sellButtonText = (
<>
Sell ({numeralWrapper.format(mat.sll, nfB)}/{mat.sllman[1]})
</>
);
} else {
sellButtonText = (
<>
Sell ({numeralWrapper.format(mat.sll, nfB)}/{numeralWrapper.format(mat.sllman[1] as number, nfB)})
</>
);
}
if (mat.marketTa2) {
sellButtonText = (
<>
{sellButtonText} @ <Money money={mat.marketTa2Price} />
</>
);
} else if (mat.marketTa1) {
sellButtonText = (
<>
{sellButtonText} @ <Money money={mat.bCost + markupLimit} />
</>
);
} else if (mat.sCost) {
if (isString(mat.sCost)) {
const sCost = (mat.sCost as string).replace(/MP/g, mat.bCost + "");
sellButtonText = (
<>
{sellButtonText} @ <Money money={eval(sCost)} />
</>
);
} else {
sellButtonText = (
<>
{sellButtonText} @ <Money money={mat.sCost} />
</>
);
}
}
} else {
sellButtonText = <>Sell (0.000/0.000)</>;
}
function openSellMaterialPopup(): void {
const popupId = "cmpy-mgmt-material-sell-popup";
createPopup(popupId, SellMaterialPopup, {
mat: mat,
corp: corp,
popupId: popupId,
});
}
function openMaterialMarketTaPopup(): void {
const popupId = "cmpy-mgmt-export-popup";
createPopup(popupId, MaterialMarketTaPopup, {
mat: mat,
industry: division,
corp: corp,
popupId: popupId,
});
}
function shouldFlash(): boolean {
return division.prodMats.includes(props.mat.name) && !mat.sllman[0];
}
return (
<div className={"cmpy-mgmt-warehouse-material-div"}>
<div style={{ display: "inline-block" }}>
<p className={"tooltip"}>
{mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
<span className={"tooltiptext"}>
Buy: {numeralWrapper.format(mat.buy, nfB)} <br />
Prod: {numeralWrapper.format(mat.prd, nfB)} <br />
Sell: {numeralWrapper.format(mat.sll, nfB)} <br />
Export: {numeralWrapper.format(mat.totalExp, nfB)} <br />
Import: {numeralWrapper.format(mat.imp, nfB)}
{corp.unlockUpgrades[2] === 1 && <br />}
{corp.unlockUpgrades[2] === 1 && "Demand: " + numeralWrapper.format(mat.dmd, nf)}
{corp.unlockUpgrades[3] === 1 && <br />}
{corp.unlockUpgrades[3] === 1 && "Competition: " + numeralWrapper.format(mat.cmp, nf)}
</span>
</p>
<br />
<p className={"tooltip"}>
MP: {numeralWrapper.formatMoney(mat.bCost)}
<span className={"tooltiptext"}>
Market Price: The price you would pay if you were to buy this material on the market
</span>
</p>{" "}
<br />
<p className={"tooltip"}>
Quality: {numeralWrapper.format(mat.qlt, "0.00a")}
<span className={"tooltiptext"}>The quality of your material. Higher quality will lead to more sales</span>
</p>
</div>
<div style={{ display: "inline-block" }}>
<Button
className={purchaseButtonClass}
onClick={openPurchaseMaterialPopup}
disabled={props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name)}
>
{purchaseButtonText}
{tutorial && (
<span className={"tooltiptext"}>Purchase your required materials to get production started!</span>
)}
</Button>
{corp.unlockUpgrades[0] === 1 && <Button onClick={openExportPopup}>Export</Button>}
<br />
<Button className={`std-button${shouldFlash() ? " flashing-button" : ""}`} onClick={openSellMaterialPopup}>
{sellButtonText}
</Button>
{division.hasResearch("Market-TA.I") && <Button onClick={openMaterialMarketTaPopup}>Market-TA</Button>}
</div>
</div>
);
}
interface IProps { interface IProps {
corp: ICorporation; corp: ICorporation;
division: IIndustry; division: IIndustry;
@ -289,7 +98,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
// Only create UI for materials that are relevant for the industry // Only create UI for materials that are relevant for the industry
if (!isRelevantMaterial(matName, division)) continue; if (!isRelevantMaterial(matName, division)) continue;
mats.push( mats.push(
<MaterialComponent <MaterialElem
rerender={props.rerender} rerender={props.rerender}
city={props.currentCity} city={props.currentCity}
key={matName} key={matName}
@ -327,7 +136,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
} }
return ( return (
<div className={"cmpy-mgmt-warehouse-panel"}> <Paper>
<Box display="flex" alignItems="center"> <Box display="flex" alignItems="center">
<Tooltip title={props.warehouse.sizeUsed !== 0 ? breakdown : ""}> <Tooltip title={props.warehouse.sizeUsed !== 0 ? breakdown : ""}>
<Typography color={props.warehouse.sizeUsed >= props.warehouse.size ? "error" : "primary"}> <Typography color={props.warehouse.sizeUsed >= props.warehouse.size ? "error" : "primary"}>
@ -370,7 +179,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
{mats} {mats}
{products} {products}
</div> </Paper>
); );
} }

@ -40,6 +40,7 @@ export function IssueDividendsModal(props: IProps): React.ReactElement {
else { else {
let p = parseFloat(event.target.value); let p = parseFloat(event.target.value);
if (p > 50) p = 50; if (p > 50) p = 50;
if (p < 0) p = 0;
setPercent(p); setPercent(p);
} }
} }

@ -57,23 +57,19 @@ export function IssueNewSharesModal(props: IProps): React.ReactElement {
const maxNewSharesUnrounded = Math.round(corp.totalShares * 0.2); const maxNewSharesUnrounded = Math.round(corp.totalShares * 0.2);
const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6);
const newShares = Math.round((shares || 0) / 10e6) * 10e6;
const disabled = shares === null || isNaN(newShares) || newShares < 10e6 || newShares > maxNewShares;
function issueNewShares(): void { function issueNewShares(): void {
if (shares === null) return; if (shares === null) return;
if (disabled) return;
const newSharePrice = Math.round(corp.sharePrice * 0.9); const newSharePrice = Math.round(corp.sharePrice * 0.9);
let newShares = shares; let newShares = shares;
if (isNaN(newShares)) {
dialogBoxCreate("Invalid input for number of new shares");
return;
}
// Round to nearest ten-millionth // Round to nearest ten-millionth
newShares = Math.round(newShares / 10e6) * 10e6; newShares = Math.round(newShares / 10e6) * 10e6;
if (newShares < 10e6 || newShares > maxNewShares) {
dialogBoxCreate("Invalid input for number of new shares");
return;
}
const profit = newShares * newSharePrice; const profit = newShares * newSharePrice;
corp.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown; corp.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown;
corp.totalShares += newShares; corp.totalShares += newShares;
@ -128,7 +124,7 @@ export function IssueNewSharesModal(props: IProps): React.ReactElement {
</Typography> </Typography>
<EffectText shares={shares} /> <EffectText shares={shares} />
<TextField autoFocus placeholder="# New Shares" onChange={onChange} onKeyDown={onKeyDown} /> <TextField autoFocus placeholder="# New Shares" onChange={onChange} onKeyDown={onKeyDown} />
<Button onClick={issueNewShares} sx={{ mx: 1 }}> <Button disabled={disabled} onClick={issueNewShares} sx={{ mx: 1 }}>
Issue New Shares Issue New Shares
</Button> </Button>
</Modal> </Modal>

@ -0,0 +1,204 @@
// React Component for displaying an Industry's warehouse information
// (right-side panel in the Industry UI)
import React, { useState } from "react";
import { OfficeSpace } from "../OfficeSpace";
import { Material } from "../Material";
import { Warehouse } from "../Warehouse";
import { ExportModal } from "./ExportModal";
import { MaterialMarketTaModal } from "./MaterialMarketTaModal";
import { SellMaterialModal } from "./SellMaterialModal";
import { PurchaseMaterialModal } from "./PurchaseMaterialModal";
import { numeralWrapper } from "../../ui/numeralFormat";
import { isString } from "../../utils/helpers/isString";
import { Money } from "../../ui/React/Money";
import { useCorporation, useDivision } from "./Context";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
interface IMaterialProps {
warehouse: Warehouse;
city: string;
mat: Material;
rerender: () => void;
}
// Creates the UI for a single Material type
export function MaterialElem(props: IMaterialProps): React.ReactElement {
const corp = useCorporation();
const division = useDivision();
const [purchaseMaterialOpen, setPurchaseMaterialOpen] = useState(false);
const [exportOpen, setExportOpen] = useState(false);
const [sellMaterialOpen, setSellMaterialOpen] = useState(false);
const [materialMarketTaOpen, setMaterialMarketTaOpen] = useState(false);
const warehouse = props.warehouse;
const city = props.city;
const mat = props.mat;
const markupLimit = mat.getMarkupLimit();
const office = division.offices[city];
if (!(office instanceof OfficeSpace)) {
throw new Error(`Could not get OfficeSpace object for this city (${city})`);
}
// Numeraljs formatter
const nf = "0.000";
const nfB = "0.000a"; // For numbers that might be biger
// Total gain or loss of this material (per second)
const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp;
// Flag that determines whether this industry is "new" and the current material should be
// marked with flashing-red lights
const tutorial =
division.newInd && Object.keys(division.reqMats).includes(mat.name) && mat.buy === 0 && mat.imp === 0;
// Purchase material button
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
// Sell material button
let sellButtonText: JSX.Element;
if (mat.sllman[0]) {
if (isString(mat.sllman[1])) {
sellButtonText = (
<>
Sell ({numeralWrapper.format(mat.sll, nfB)}/{mat.sllman[1]})
</>
);
} else {
sellButtonText = (
<>
Sell ({numeralWrapper.format(mat.sll, nfB)}/{numeralWrapper.format(mat.sllman[1] as number, nfB)})
</>
);
}
if (mat.marketTa2) {
sellButtonText = (
<>
{sellButtonText} @ <Money money={mat.marketTa2Price} />
</>
);
} else if (mat.marketTa1) {
sellButtonText = (
<>
{sellButtonText} @ <Money money={mat.bCost + markupLimit} />
</>
);
} else if (mat.sCost) {
if (isString(mat.sCost)) {
const sCost = (mat.sCost as string).replace(/MP/g, mat.bCost + "");
sellButtonText = (
<>
{sellButtonText} @ <Money money={eval(sCost)} />
</>
);
} else {
sellButtonText = (
<>
{sellButtonText} @ <Money money={mat.sCost} />
</>
);
}
}
} else {
sellButtonText = <>Sell (0.000/0.000)</>;
}
return (
<Paper>
<Box display="flex">
<Box>
<Tooltip
title={
<Typography>
Buy: {numeralWrapper.format(mat.buy, nfB)} <br />
Prod: {numeralWrapper.format(mat.prd, nfB)} <br />
Sell: {numeralWrapper.format(mat.sll, nfB)} <br />
Export: {numeralWrapper.format(mat.totalExp, nfB)} <br />
Import: {numeralWrapper.format(mat.imp, nfB)}
{corp.unlockUpgrades[2] === 1 && <br />}
{corp.unlockUpgrades[2] === 1 && "Demand: " + numeralWrapper.format(mat.dmd, nf)}
{corp.unlockUpgrades[3] === 1 && <br />}
{corp.unlockUpgrades[3] === 1 && "Competition: " + numeralWrapper.format(mat.cmp, nf)}
</Typography>
}
>
<Typography>
{mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
</Typography>
</Tooltip>
<Tooltip
title={
<Typography>
Market Price: The price you would pay if you were to buy this material on the market
</Typography>
}
>
<Typography>MP: {numeralWrapper.formatMoney(mat.bCost)}</Typography>
</Tooltip>
<Tooltip
title={<Typography>The quality of your material. Higher quality will lead to more sales</Typography>}
>
<Typography>Quality: {numeralWrapper.format(mat.qlt, "0.00a")}</Typography>
</Tooltip>
</Box>
<Box>
<Tooltip
title={tutorial ? <Typography>Purchase your required materials to get production started!</Typography> : ""}
>
<span>
<Button
color={tutorial ? "error" : "primary"}
onClick={() => setPurchaseMaterialOpen(true)}
disabled={props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name)}
>
{purchaseButtonText}
</Button>
</span>
</Tooltip>
<PurchaseMaterialModal
mat={mat}
warehouse={warehouse}
open={purchaseMaterialOpen}
onClose={() => setPurchaseMaterialOpen(false)}
/>
{corp.unlockUpgrades[0] === 1 && (
<>
<Button onClick={() => setExportOpen(true)}>Export</Button>
<ExportModal mat={mat} open={exportOpen} onClose={() => setExportOpen(false)} />
</>
)}
<br />
<Button
color={division.prodMats.includes(props.mat.name) && !mat.sllman[0] ? "error" : "primary"}
onClick={() => setSellMaterialOpen(true)}
>
{sellButtonText}
</Button>
<SellMaterialModal mat={mat} open={sellMaterialOpen} onClose={() => setSellMaterialOpen(false)} />
{division.hasResearch("Market-TA.I") && (
<>
<Button onClick={() => setMaterialMarketTaOpen(true)}>Market-TA</Button>
<MaterialMarketTaModal
mat={mat}
open={materialMarketTaOpen}
onClose={() => setMaterialMarketTaOpen(false)}
/>
</>
)}
</Box>
</Box>
</Paper>
);
}

@ -1,16 +1,21 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { IIndustry } from "../IIndustry";
import { ICorporation } from "../ICorporation";
import { Material } from "../Material"; import { Material } from "../Material";
import { Modal } from "../../ui/React/Modal";
import { useDivision } from "./Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import Tooltip from "@mui/material/Tooltip";
interface IMarketTA2Props { interface IMarketTA2Props {
industry: IIndustry;
mat: Material; mat: Material;
} }
function MarketTA2(props: IMarketTA2Props): React.ReactElement { function MarketTA2(props: IMarketTA2Props): React.ReactElement {
if (!props.industry.hasResearch("Market-TA.II")) return <></>; const division = useDivision();
if (!division.hasResearch("Market-TA.II")) return <></>;
const [newCost, setNewCost] = useState<number>(props.mat.bCost); const [newCost, setNewCost] = useState<number>(props.mat.bCost);
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
@ -47,50 +52,47 @@ function MarketTA2(props: IMarketTA2Props): React.ReactElement {
return ( return (
<> <>
<p> <Typography variant="h4">Market-TA.II</Typography>
<br /> <br />
<u> <Typography>
<strong>Market-TA.II</strong>
</u>
<br />
If you sell at {numeralWrapper.formatMoney(sCost)}, then you will sell{" "} If you sell at {numeralWrapper.formatMoney(sCost)}, then you will sell{" "}
{numeralWrapper.format(markup, "0.00000")}x as much compared to if you sold at market price. {numeralWrapper.format(markup, "0.00000")}x as much compared to if you sold at market price.
</p> </Typography>
<input className="text-input" type="number" style={{ marginTop: "4px" }} onChange={onChange} value={newCost} /> <TextField type="number" onChange={onChange} value={newCost} />
<div style={{ display: "block" }}> <br />
<label className="tooltip" htmlFor="cmpy-mgmt-marketa2-checkbox" style={{ color: "white" }}> <FormControlLabel
Use Market-TA.II for Auto-Sale Price control={<Switch checked={props.mat.marketTa2} onChange={onMarketTA2} />}
<span className="tooltiptext"> label={
If this is enabled, then this Material will automatically be sold at the optimal price such that the amount <Tooltip
sold matches the amount produced. (i.e. the highest possible price, while still ensuring that all produced title={
materials will be sold) <Typography>
</span> If this is enabled, then this Material will automatically be sold at the optimal price such that the
</label> amount sold matches the amount produced. (i.e. the highest possible price, while still ensuring that all
<input produced materials will be sold)
id="cmpy-mgmt-marketa2-checkbox" </Typography>
type="checkbox" }
onChange={onMarketTA2} >
checked={props.mat.marketTa2} <Typography>Use Market-TA.II for Auto-Sale Price</Typography>
style={{ margin: "3px" }} </Tooltip>
/> }
</div> />
<p>
<Typography>
Note that Market-TA.II overrides Market-TA.I. This means that if both are enabled, then Market-TA.II will take Note that Market-TA.II overrides Market-TA.I. This means that if both are enabled, then Market-TA.II will take
effect, not Market-TA.I effect, not Market-TA.I
</p> </Typography>
</> </>
); );
} }
interface IProps { interface IProps {
open: boolean;
onClose: () => void;
mat: Material; mat: Material;
industry: IIndustry;
corp: ICorporation;
popupId: string;
} }
// Create a popup that lets the player use the Market TA research for Materials // Create a popup that lets the player use the Market TA research for Materials
export function MaterialMarketTaPopup(props: IProps): React.ReactElement { export function MaterialMarketTaModal(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
setRerender((old) => !old); setRerender((old) => !old);
@ -103,33 +105,30 @@ export function MaterialMarketTaPopup(props: IProps): React.ReactElement {
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <Typography variant="h4">Market-TA.I</Typography>
<u> <Typography>
<strong>Market-TA.I</strong>
</u>
<br />
The maximum sale price you can mark this up to is {numeralWrapper.formatMoney(props.mat.bCost + markupLimit)}. The maximum sale price you can mark this up to is {numeralWrapper.formatMoney(props.mat.bCost + markupLimit)}.
This means that if you set the sale price higher than this, you will begin to experience a loss in number of This means that if you set the sale price higher than this, you will begin to experience a loss in number of
sales sales
</p> </Typography>
<div style={{ display: "block" }}>
<label className="tooltip" htmlFor="cmpy-mgmt-marketa1-checkbox" style={{ color: "white" }}> <FormControlLabel
Use Market-TA.I for Auto-Sale Price control={<Switch checked={props.mat.marketTa1} onChange={onMarketTA1} />}
<span className="tooltiptext"> label={
If this is enabled, then this Material will automatically be sold at the price identified by Market-TA.I <Tooltip
(i.e. the price shown above) title={
</span> <Typography>
</label> If this is enabled, then this Material will automatically be sold at the price identified by Market-TA.I
<input (i.e. the price shown above)
id="cmpy-mgmt-marketa1-checkbox" </Typography>
type="checkbox" }
onChange={onMarketTA1} >
checked={props.mat.marketTa1} <Typography>Use Market-TA.I for Auto-Sale Price</Typography>
style={{ margin: "3px" }} </Tooltip>
/> }
</div> />
<MarketTA2 mat={props.mat} industry={props.industry} /> <MarketTA2 mat={props.mat} />
</> </Modal>
); );
} }

@ -220,7 +220,11 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
</Tooltip> </Tooltip>
<SellSharesModal open={sellSharesOpen} onClose={() => setSellSharesOpen(false)} rerender={rerender} /> <SellSharesModal open={sellSharesOpen} onClose={() => setSellSharesOpen(false)} rerender={rerender} />
<Tooltip title={<Typography>Buy back shares you that previously issued or sold at market price.</Typography>}> <Tooltip title={<Typography>Buy back shares you that previously issued or sold at market price.</Typography>}>
<Button onClick={() => setBuybackSharesOpen(true)}>Buyback shares</Button> <span>
<Button disabled={corp.issuedShares > 0.5} onClick={() => setBuybackSharesOpen(true)}>
Buyback shares
</Button>
</span>
</Tooltip> </Tooltip>
<BuybackSharesModal open={buybackSharesOpen} onClose={() => setBuybackSharesOpen(false)} rerender={rerender} /> <BuybackSharesModal open={buybackSharesOpen} onClose={() => setBuybackSharesOpen(false)} rerender={rerender} />
<br /> <br />

@ -1,13 +1,15 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { MaterialSizes } from "../MaterialSizes"; import { MaterialSizes } from "../MaterialSizes";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../Warehouse";
import { Material } from "../Material"; import { Material } from "../Material";
import { IIndustry } from "../IIndustry";
import { ICorporation } from "../ICorporation";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { BuyMaterial } from "../Actions"; import { BuyMaterial } from "../Actions";
import { Modal } from "../../ui/React/Modal";
import { useCorporation, useDivision } from "./Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
interface IBulkPurchaseTextProps { interface IBulkPurchaseTextProps {
warehouse: Warehouse; warehouse: Warehouse;
@ -36,15 +38,14 @@ function BulkPurchaseText(props: IBulkPurchaseTextProps): React.ReactElement {
} }
} }
interface IProps { interface IBPProps {
onClose: () => void;
mat: Material; mat: Material;
industry: IIndustry;
warehouse: Warehouse; warehouse: Warehouse;
corp: ICorporation;
popupId: string;
} }
function BulkPurchase(props: IProps): React.ReactElement { function BulkPurchase(props: IBPProps): React.ReactElement {
const corp = useCorporation();
const [buyAmt, setBuyAmt] = useState(""); const [buyAmt, setBuyAmt] = useState("");
function bulkPurchase(): void { function bulkPurchase(): void {
@ -61,15 +62,14 @@ function BulkPurchase(props: IProps): React.ReactElement {
dialogBoxCreate("Invalid input amount"); dialogBoxCreate("Invalid input amount");
} else { } else {
const cost = amount * props.mat.bCost; const cost = amount * props.mat.bCost;
if (props.corp.funds.gt(cost)) { if (corp.funds.gt(cost)) {
props.corp.funds = props.corp.funds.minus(cost); corp.funds = corp.funds.minus(cost);
props.mat.qty += amount; props.mat.qty += amount;
} else { } else {
dialogBoxCreate(`You cannot afford this purchase.`); dialogBoxCreate(`You cannot afford this purchase.`);
return; return;
} }
props.onClose();
removePopup(props.popupId);
} }
} }
@ -83,28 +83,34 @@ function BulkPurchase(props: IProps): React.ReactElement {
return ( return (
<> <>
<p> <Typography>
Enter the amount of {props.mat.name} you would like to bulk purchase. This purchases the specified amount Enter the amount of {props.mat.name} you would like to bulk purchase. This purchases the specified amount
instantly (all at once). instantly (all at once).
</p> </Typography>
<BulkPurchaseText warehouse={props.warehouse} mat={props.mat} amount={buyAmt} /> <BulkPurchaseText warehouse={props.warehouse} mat={props.mat} amount={buyAmt} />
<input <TextField
value={buyAmt}
onChange={onChange} onChange={onChange}
type="number" type="number"
placeholder="Bulk Purchase amount" placeholder="Bulk Purchase amount"
style={{ margin: "5px" }}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
/> />
<button className="std-button" onClick={bulkPurchase}> <Button onClick={bulkPurchase}>Confirm Bulk Purchase</Button>
Confirm Bulk Purchase
</button>
</> </>
); );
} }
interface IProps {
open: boolean;
onClose: () => void;
mat: Material;
warehouse: Warehouse;
}
// Create a popup that lets the player purchase a Material // Create a popup that lets the player purchase a Material
export function PurchaseMaterialPopup(props: IProps): React.ReactElement { export function PurchaseMaterialModal(props: IProps): React.ReactElement {
const [buyAmt, setBuyAmt] = useState(props.mat.buy ? props.mat.buy : null); const division = useDivision();
const [buyAmt, setBuyAmt] = useState(props.mat.buy ? props.mat.buy : 0);
function purchaseMaterial(): void { function purchaseMaterial(): void {
if (buyAmt === null) return; if (buyAmt === null) return;
@ -114,12 +120,12 @@ export function PurchaseMaterialPopup(props: IProps): React.ReactElement {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
} }
removePopup(props.popupId); props.onClose();
} }
function clearPurchase(): void { function clearPurchase(): void {
props.mat.buy = 0; props.mat.buy = 0;
removePopup(props.popupId); props.onClose();
} }
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void { function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
@ -131,35 +137,26 @@ export function PurchaseMaterialPopup(props: IProps): React.ReactElement {
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <>
Enter the amount of {props.mat.name} you would like to purchase per second. This material's cost changes <Typography>
constantly. Enter the amount of {props.mat.name} you would like to purchase per second. This material's cost changes
</p> constantly.
<input </Typography>
onChange={onChange} <TextField
className="text-input" value={buyAmt}
autoFocus={true} onChange={onChange}
placeholder="Purchase amount" autoFocus={true}
type="number" placeholder="Purchase amount"
style={{ margin: "5px" }} type="number"
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
/>
<button onClick={purchaseMaterial} className="std-button">
Confirm
</button>
<button onClick={clearPurchase} className="std-button">
Clear Purchase
</button>
{props.industry.hasResearch("Bulk Purchasing") && (
<BulkPurchase
corp={props.corp}
mat={props.mat}
industry={props.industry}
warehouse={props.warehouse}
popupId={props.popupId}
/> />
)} <Button onClick={purchaseMaterial}>Confirm</Button>
</> <Button onClick={clearPurchase}>Clear Purchase</Button>
{division.hasResearch("Bulk Purchasing") && (
<BulkPurchase onClose={props.onClose} mat={props.mat} warehouse={props.warehouse} />
)}
</>
</Modal>
); );
} }

@ -1,9 +1,11 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation";
import { Material } from "../Material"; import { Material } from "../Material";
import { SellMaterial } from "../Actions"; import { SellMaterial } from "../Actions";
import { Modal } from "../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
function initialPrice(mat: Material): string { function initialPrice(mat: Material): string {
let val = mat.sCost ? mat.sCost + "" : ""; let val = mat.sCost ? mat.sCost + "" : "";
@ -16,13 +18,13 @@ function initialPrice(mat: Material): string {
} }
interface IProps { interface IProps {
open: boolean;
onClose: () => void;
mat: Material; mat: Material;
corp: ICorporation;
popupId: string;
} }
// Create a popup that let the player manage sales of a material // Create a popup that let the player manage sales of a material
export function SellMaterialPopup(props: IProps): React.ReactElement { export function SellMaterialModal(props: IProps): React.ReactElement {
const [amt, setAmt] = useState<string>(props.mat.sllman[1] ? props.mat.sllman[1] + "" : ""); const [amt, setAmt] = useState<string>(props.mat.sllman[1] ? props.mat.sllman[1] + "" : "");
const [price, setPrice] = useState<string>(initialPrice(props.mat)); const [price, setPrice] = useState<string>(initialPrice(props.mat));
@ -32,8 +34,7 @@ export function SellMaterialPopup(props: IProps): React.ReactElement {
} catch (err) { } catch (err) {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
} }
props.onClose();
removePopup(props.popupId);
} }
function onAmtChange(event: React.ChangeEvent<HTMLInputElement>): void { function onAmtChange(event: React.ChangeEvent<HTMLInputElement>): void {
@ -49,8 +50,8 @@ export function SellMaterialPopup(props: IProps): React.ReactElement {
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <Typography>
Enter the maximum amount of {props.mat.name} you would like to sell per second, as well as the price at which Enter the maximum amount of {props.mat.name} you would like to sell per second, as well as the price at which
you would like to sell at. you would like to sell at.
<br /> <br />
@ -70,30 +71,18 @@ export function SellMaterialPopup(props: IProps): React.ReactElement {
When setting the sell price, you can use the 'MP' variable to designate a dynamically changing price that When setting the sell price, you can use the 'MP' variable to designate a dynamically changing price that
depends on the market price. For example, if you set the sell price to 'MP+10' then it will always be sold at depends on the market price. For example, if you set the sell price to 'MP+10' then it will always be sold at
$10 above the market price. $10 above the market price.
</p> </Typography>
<br /> <br />
<input <TextField
className="text-input"
value={amt} value={amt}
autoFocus={true} autoFocus={true}
type="text" type="text"
placeholder="Sell amount" placeholder="Sell amount"
style={{ marginTop: "4px" }}
onChange={onAmtChange} onChange={onAmtChange}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
/> />
<input <TextField value={price} type="text" placeholder="Sell price" onChange={onPriceChange} onKeyDown={onKeyDown} />
className="text-input" <Button onClick={sellMaterial}>Confirm</Button>
value={price} </Modal>
type="text"
placeholder="Sell price"
style={{ marginTop: "4px" }}
onChange={onPriceChange}
onKeyDown={onKeyDown}
/>
<button className="std-button" onClick={sellMaterial}>
Confirm
</button>
</>
); );
} }

@ -23,6 +23,8 @@ export function SellSharesModal(props: IProps): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const [shares, setShares] = useState<number | null>(null); const [shares, setShares] = useState<number | null>(null);
const disabled = shares === null || isNaN(shares) || shares <= 0 || shares > corp.numShares;
function changeShares(event: React.ChangeEvent<HTMLInputElement>): void { function changeShares(event: React.ChangeEvent<HTMLInputElement>): void {
if (event.target.value === "") setShares(null); if (event.target.value === "") setShares(null);
else setShares(Math.round(parseFloat(event.target.value))); else setShares(Math.round(parseFloat(event.target.value)));
@ -47,42 +49,37 @@ export function SellSharesModal(props: IProps): React.ReactElement {
function sell(): void { function sell(): void {
if (shares === null) return; if (shares === null) return;
if (isNaN(shares) || shares <= 0) { if (disabled) return;
dialogBoxCreate("ERROR: Invalid value for number of shares"); const stockSaleResults = corp.calculateShareSale(shares);
} else if (shares > corp.numShares) { const profit = stockSaleResults[0];
dialogBoxCreate("ERROR: You don't have this many shares to sell"); const newSharePrice = stockSaleResults[1];
} else { const newSharesUntilUpdate = stockSaleResults[2];
const stockSaleResults = corp.calculateShareSale(shares);
const profit = stockSaleResults[0];
const newSharePrice = stockSaleResults[1];
const newSharesUntilUpdate = stockSaleResults[2];
corp.numShares -= shares; corp.numShares -= shares;
if (isNaN(corp.issuedShares)) { if (isNaN(corp.issuedShares)) {
console.error(`Corporation issuedShares is NaN: ${corp.issuedShares}`); console.error(`Corporation issuedShares is NaN: ${corp.issuedShares}`);
const res = corp.issuedShares; const res = corp.issuedShares;
if (isNaN(res)) { if (isNaN(res)) {
corp.issuedShares = 0; corp.issuedShares = 0;
} else { } else {
corp.issuedShares = res; corp.issuedShares = res;
}
} }
corp.issuedShares += shares;
corp.sharePrice = newSharePrice;
corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
corp.shareSaleCooldown = CorporationConstants.SellSharesCooldown;
player.gainMoney(profit);
player.recordMoneySource(profit, "corporation");
props.onClose();
dialogBoxCreate(
`Sold ${numeralWrapper.formatMoney(shares)} shares for ` +
`${numeralWrapper.formatMoney(profit)}. ` +
`The corporation's stock price fell to ${numeralWrapper.formatMoney(corp.sharePrice)} ` +
`as a result of dilution.`,
);
props.rerender();
} }
corp.issuedShares += shares;
corp.sharePrice = newSharePrice;
corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
corp.shareSaleCooldown = CorporationConstants.SellSharesCooldown;
player.gainMoney(profit);
player.recordMoneySource(profit, "corporation");
props.onClose();
dialogBoxCreate(
`Sold {numeralWrapper.formatMoney(shares)} shares for ` +
`${numeralWrapper.formatMoney(profit)}. ` +
`The corporation's stock price fell to ${numeralWrapper.formatMoney(corp.sharePrice)} ` +
`as a result of dilution.`,
);
props.rerender();
} }
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void { function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
@ -112,7 +109,7 @@ export function SellSharesModal(props: IProps): React.ReactElement {
onChange={changeShares} onChange={changeShares}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
/> />
<Button onClick={sell} sx={{ mx: 1 }}> <Button disabled={disabled} onClick={sell} sx={{ mx: 1 }}>
Sell shares Sell shares
</Button> </Button>
</Modal> </Modal>

@ -11,6 +11,37 @@ import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
interface IUpgradeButton {
cost: number;
size: number;
corp: ICorporation;
office: OfficeSpace;
onClose: () => void;
rerender: () => void;
}
function UpgradeSizeButton(props: IUpgradeButton): React.ReactElement {
const corp = useCorporation();
function upgradeSize(cost: number, size: number): void {
if (corp.funds.lt(cost)) {
return;
}
UpgradeOfficeSize(corp, props.office, size);
props.rerender();
props.onClose();
}
return (
<Tooltip title={numeralWrapper.formatMoney(props.cost)}>
<span>
<Button disabled={corp.funds.lt(props.cost)} onClick={() => upgradeSize(props.cost, props.size)}>
+{props.size}
</Button>
</span>
</Tooltip>
);
}
interface IProps { interface IProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@ -48,44 +79,35 @@ export function UpgradeOfficeSizeModal(props: IProps): React.ReactElement {
} }
const upgradeCostMax = CorporationConstants.OfficeInitialCost * mult; const upgradeCostMax = CorporationConstants.OfficeInitialCost * mult;
function upgradeSize(cost: number, size: number): void {
if (corp.funds.lt(cost)) {
return;
}
UpgradeOfficeSize(corp, props.office, size);
props.rerender();
props.rerender();
props.onClose();
}
interface IUpgradeButton {
cost: number;
size: number;
corp: ICorporation;
}
function UpgradeSizeButton(props: IUpgradeButton): React.ReactElement {
return (
<Tooltip title={numeralWrapper.formatMoney(props.cost)}>
<span>
<Button disabled={corp.funds.lt(props.cost)} onClick={() => upgradeSize(props.cost, props.size)}>
+{props.size}
</Button>
</span>
</Tooltip>
);
}
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
<Typography>Increase the size of your office space to fit additional employees!</Typography> <Typography>Increase the size of your office space to fit additional employees!</Typography>
<Box display="flex" alignItems="center"> <Box display="flex" alignItems="center">
<Typography>Upgrade size: </Typography> <Typography>Upgrade size: </Typography>
<UpgradeSizeButton corp={corp} cost={upgradeCost} size={CorporationConstants.OfficeInitialSize} /> <UpgradeSizeButton
<UpgradeSizeButton corp={corp} cost={upgradeCost15} size={CorporationConstants.OfficeInitialSize * 5} /> onClose={props.onClose}
<UpgradeSizeButton corp={corp} cost={upgradeCostMax} size={maxNum * CorporationConstants.OfficeInitialSize} /> rerender={props.rerender}
office={props.office}
corp={corp}
cost={upgradeCost}
size={CorporationConstants.OfficeInitialSize}
/>
<UpgradeSizeButton
onClose={props.onClose}
rerender={props.rerender}
office={props.office}
corp={corp}
cost={upgradeCost15}
size={CorporationConstants.OfficeInitialSize * 5}
/>
<UpgradeSizeButton
onClose={props.onClose}
rerender={props.rerender}
office={props.office}
corp={corp}
cost={upgradeCostMax}
size={maxNum * CorporationConstants.OfficeInitialSize}
/>
</Box> </Box>
</Modal> </Modal>
); );

@ -65,24 +65,18 @@ const GangNames = [
"The Black Hand", "The Black Hand",
]; ];
export function FactionRoot(props: IProps): React.ReactElement { interface IMainProps {
const [sleevesOpen, setSleevesOpen] = useState(false); faction: Faction;
const setRerender = useState(false)[1]; rerender: () => void;
function rerender(): void { onAugmentations: () => void;
setRerender((old) => !old); }
}
// Enabling this breaks donations.
// useEffect(() => {
// const id = setInterval(rerender, 200);
// return () => clearInterval(id);
// }, []);
const faction = props.faction;
function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
const router = use.Router(); const router = use.Router();
const [purchasingAugs, setPurchasingAugs] = useState(false); const [sleevesOpen, setSleevesOpen] = useState(false);
const p = player;
const factionInfo = faction.getInfo();
function manageGang(faction: Faction): void { function manageGang(faction: Faction): void {
// If player already has a gang, just go to the gang UI // If player already has a gang, just go to the gang UI
@ -99,16 +93,6 @@ export function FactionRoot(props: IProps): React.ReactElement {
}); });
} }
// Route to the main faction page
function routeToMain(): void {
setPurchasingAugs(false);
}
// Route to the purchase augmentation UI for this faction
function routeToPurchaseAugs(): void {
setPurchasingAugs(true);
}
function startFieldWork(faction: Faction): void { function startFieldWork(faction: Faction): void {
player.startFactionFieldWork(router, faction); player.startFactionFieldWork(router, faction);
} }
@ -126,85 +110,96 @@ export function FactionRoot(props: IProps): React.ReactElement {
player.startFactionSecurityWork(router, faction); player.startFactionSecurityWork(router, faction);
} }
function MainPage({ faction }: { faction: Faction }): React.ReactElement { // We have a special flag for whether the player this faction is the player's
const p = player; // gang faction because if the player has a gang, they cannot do any other action
const factionInfo = faction.getInfo(); const isPlayersGang = p.inGang() && p.getGangName() === faction.name;
// We have a special flag for whether the player this faction is the player's // Flags for whether special options (gang, sleeve purchases, donate, etc.)
// gang faction because if the player has a gang, they cannot do any other action // should be shown
const isPlayersGang = p.inGang() && p.getGangName() === faction.name; const favorToDonate = Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
const canDonate = faction.favor >= favorToDonate;
// Flags for whether special options (gang, sleeve purchases, donate, etc.) const canPurchaseSleeves = faction.name === "The Covenant" && p.bitNodeN >= 10 && SourceFileFlags[10];
// should be shown
const favorToDonate = Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
const canDonate = faction.favor >= favorToDonate;
const canPurchaseSleeves = faction.name === "The Covenant" && p.bitNodeN >= 10 && SourceFileFlags[10]; let canAccessGang = p.canAccessGang() && GangNames.includes(faction.name);
if (p.inGang()) {
let canAccessGang = p.canAccessGang() && GangNames.includes(faction.name); if (p.getGangName() !== faction.name) {
if (p.inGang()) { canAccessGang = false;
if (p.getGangName() !== faction.name) { } else if (p.getGangName() === faction.name) {
canAccessGang = false; canAccessGang = true;
} else if (p.getGangName() === faction.name) {
canAccessGang = true;
}
} }
return (
<>
<Button onClick={() => router.toFactions()}>Back</Button>
<Typography variant="h4" color="primary">
{faction.name}
</Typography>
<Info faction={faction} factionInfo={factionInfo} />
{canAccessGang && <Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={() => manageGang(faction)} />}
{!isPlayersGang && factionInfo.offerHackingMission && (
<Option
buttonText={"Hacking Mission"}
infoText={hackingMissionInfo}
onClick={() => startHackingMission(faction)}
/>
)}
{!isPlayersGang && factionInfo.offerHackingWork && (
<Option
buttonText={"Hacking Contracts"}
infoText={hackingContractsInfo}
onClick={() => startHackingContracts(faction)}
/>
)}
{!isPlayersGang && factionInfo.offerFieldWork && (
<Option buttonText={"Field Work"} infoText={fieldWorkInfo} onClick={() => startFieldWork(faction)} />
)}
{!isPlayersGang && factionInfo.offerSecurityWork && (
<Option buttonText={"Security Work"} infoText={securityWorkInfo} onClick={() => startSecurityWork(faction)} />
)}
{!isPlayersGang && factionInfo.offersWork() && (
<DonateOption
faction={faction}
p={player}
rerender={rerender}
favorToDonate={favorToDonate}
disabled={!canDonate}
/>
)}
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={routeToPurchaseAugs} />
{canPurchaseSleeves && (
<>
<Option
buttonText={"Purchase & Upgrade Duplicate Sleeves"}
infoText={sleevePurchasesInfo}
onClick={() => setSleevesOpen(true)}
/>
<CovenantPurchasesRoot open={sleevesOpen} onClose={() => setSleevesOpen(false)} />
</>
)}
</>
);
} }
return purchasingAugs ? ( return (
<AugmentationsPage faction={faction} routeToMainPage={routeToMain} /> <>
) : ( <Button onClick={() => router.toFactions()}>Back</Button>
<MainPage faction={faction} /> <Typography variant="h4" color="primary">
{faction.name}
</Typography>
<Info faction={faction} factionInfo={factionInfo} />
{canAccessGang && <Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={() => manageGang(faction)} />}
{!isPlayersGang && factionInfo.offerHackingMission && (
<Option
buttonText={"Hacking Mission"}
infoText={hackingMissionInfo}
onClick={() => startHackingMission(faction)}
/>
)}
{!isPlayersGang && factionInfo.offerHackingWork && (
<Option
buttonText={"Hacking Contracts"}
infoText={hackingContractsInfo}
onClick={() => startHackingContracts(faction)}
/>
)}
{!isPlayersGang && factionInfo.offerFieldWork && (
<Option buttonText={"Field Work"} infoText={fieldWorkInfo} onClick={() => startFieldWork(faction)} />
)}
{!isPlayersGang && factionInfo.offerSecurityWork && (
<Option buttonText={"Security Work"} infoText={securityWorkInfo} onClick={() => startSecurityWork(faction)} />
)}
{!isPlayersGang && factionInfo.offersWork() && (
<DonateOption
faction={faction}
p={player}
rerender={rerender}
favorToDonate={favorToDonate}
disabled={!canDonate}
/>
)}
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={onAugmentations} />
{canPurchaseSleeves && (
<>
<Option
buttonText={"Purchase & Upgrade Duplicate Sleeves"}
infoText={sleevePurchasesInfo}
onClick={() => setSleevesOpen(true)}
/>
<CovenantPurchasesRoot open={sleevesOpen} onClose={() => setSleevesOpen(false)} />
</>
)}
</>
);
}
export function FactionRoot(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 200);
return () => clearInterval(id);
}, []);
const faction = props.faction;
const [purchasingAugs, setPurchasingAugs] = useState(false);
return purchasingAugs ? (
<AugmentationsPage faction={faction} routeToMainPage={() => setPurchasingAugs(false)} />
) : (
<MainPage rerender={rerender} faction={faction} onAugmentations={() => setPurchasingAugs(true)} />
); );
} }

@ -1,6 +1,7 @@
/** /**
* Implementation for what happens when you destroy a BitNode * Implementation for what happens when you destroy a BitNode
*/ */
import React from "react";
import { Player } from "./Player"; import { Player } from "./Player";
import { prestigeSourceFile } from "./Prestige"; import { prestigeSourceFile } from "./Prestige";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile"; import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
@ -58,7 +59,15 @@ function giveSourceFile(bitNodeNumber: number): void {
Player.intelligence = 1; Player.intelligence = 1;
} }
dialogBoxCreate( dialogBoxCreate(
"You received a Source-File for destroying a BitNode!<br><br>" + sourceFile.name + "<br><br>" + sourceFile.info, <>
You received a Source-File for destroying a BitNode!
<br />
<br />
{sourceFile.name}
<br />
<br />
{sourceFile.info}
</>,
); );
} }
} }

@ -33,13 +33,20 @@ const useStyles = makeStyles((theme: Theme) =>
interface IProps { interface IProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
children: JSX.Element[] | JSX.Element | React.ReactElement[] | React.ReactElement; children: React.ReactNode;
} }
export const Modal = (props: IProps): React.ReactElement => { export const Modal = (props: IProps): React.ReactElement => {
const classes = useStyles(); const classes = useStyles();
return ( return (
<M open={props.open} onClose={props.onClose} closeAfterTransition className={classes.modal}> <M
disableRestoreFocus
disableScrollLock
open={props.open}
onClose={props.onClose}
closeAfterTransition
className={classes.modal}
>
<Fade in={props.open}> <Fade in={props.open}>
<div className={classes.paper}> <div className={classes.paper}>
<Box sx={{ m: 2 }}>{props.children}</Box> <Box sx={{ m: 2 }}>{props.children}</Box>