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,13 +78,7 @@ 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");
}
if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {
} else if (corp.funds.lt(money)) {
} else if (stock > corp.numShares) {
} else {
const rep = repGain(money, stock); const rep = repGain(money, stock);
dialogBoxCreate( dialogBoxCreate(
"You gained " + numeralWrapper.formatReputation(rep) + " reputation with " + fac.name + " by bribing them.", "You gained " + numeralWrapper.formatReputation(rep) + " reputation with " + fac.name + " by bribing them.",
@ -80,7 +88,6 @@ export function BribeFactionModal(props: IProps): React.ReactElement {
corp.numShares -= stock; corp.numShares -= stock;
props.onClose(); props.onClose();
} }
}
return ( return (
<Modal open={props.open} onClose={props.onClose}> <Modal open={props.open} onClose={props.onClose}>
@ -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,22 +28,15 @@ 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;
const buybackPrice = tempStockPrice * 1.1;
if (isNaN(shares) || shares <= 0) {
dialogBoxCreate("ERROR: Invalid value for number of shares");
} else if (shares > corp.issuedShares) {
dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back");
} else if (shares * buybackPrice > player.money) {
dialogBoxCreate(
"ERROR: You do not have enough money to purchase this many shares (you need " +
numeralWrapper.format(shares * buybackPrice, "$0.000a") +
")",
);
} else {
corp.numShares += shares; corp.numShares += shares;
if (isNaN(corp.issuedShares)) { if (isNaN(corp.issuedShares)) {
console.warn("Corporation issuedShares is NaN: " + corp.issuedShares); console.warn("Corporation issuedShares is NaN: " + corp.issuedShares);
@ -57,7 +53,6 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
props.onClose(); props.onClose();
props.rerender(); props.rerender();
} }
}
function CostIndicator(): React.ReactElement { function CostIndicator(): React.ReactElement {
if (shares === null) return <></>; if (shares === null) return <></>;
@ -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}>
<Button sx={{ mx: 2 }} onClick={() => removeExport(exp)}>
delete
</Button>
<Typography>
Industry: {exp.ind} Industry: {exp.ind}
<br /> <br />
City: {exp.city} City: {exp.city}
<br /> <br />
Amount/s: {exp.amt} Amount/s: {exp.amt}
</div> </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>
<Button disabled={corp.funds.lt(cost)} onClick={onClick}>
{upgrade[4]} -&nbsp; {upgrade[4]} -&nbsp;
<MoneyCost money={cost} corp={corp} /> <MoneyCost money={cost} corp={corp} />
</Button> </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 />
<u>
<strong>Market-TA.II</strong>
</u>
<br /> <br />
<Typography>
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={
<Tooltip
title={
<Typography>
If this is enabled, then this Material will automatically be sold at the price identified by Market-TA.I If this is enabled, then this Material will automatically be sold at the price identified by Market-TA.I
(i.e. the price shown above) (i.e. the price shown above)
</span> </Typography>
</label> }
<input >
id="cmpy-mgmt-marketa1-checkbox" <Typography>Use Market-TA.I for Auto-Sale Price</Typography>
type="checkbox" </Tooltip>
onChange={onMarketTA1} }
checked={props.mat.marketTa1}
style={{ margin: "3px" }}
/> />
</div> <MarketTA2 mat={props.mat} />
<MarketTA2 mat={props.mat} industry={props.industry} /> </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> <Typography>
Enter the amount of {props.mat.name} you would like to purchase per second. This material's cost changes Enter the amount of {props.mat.name} you would like to purchase per second. This material's cost changes
constantly. constantly.
</p> </Typography>
<input <TextField
value={buyAmt}
onChange={onChange} onChange={onChange}
className="text-input"
autoFocus={true} autoFocus={true}
placeholder="Purchase amount" placeholder="Purchase amount"
type="number" type="number"
style={{ margin: "5px" }}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
/> />
<button onClick={purchaseMaterial} className="std-button"> <Button onClick={purchaseMaterial}>Confirm</Button>
Confirm <Button onClick={clearPurchase}>Clear Purchase</Button>
</button> {division.hasResearch("Bulk Purchasing") && (
<button onClick={clearPurchase} className="std-button"> <BulkPurchase onClose={props.onClose} mat={props.mat} warehouse={props.warehouse} />
Clear Purchase
</button>
{props.industry.hasResearch("Bulk Purchasing") && (
<BulkPurchase
corp={props.corp}
mat={props.mat}
industry={props.industry}
warehouse={props.warehouse}
popupId={props.popupId}
/>
)} )}
</> </>
</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,11 +49,7 @@ 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");
} else if (shares > corp.numShares) {
dialogBoxCreate("ERROR: You don't have this many shares to sell");
} else {
const stockSaleResults = corp.calculateShareSale(shares); const stockSaleResults = corp.calculateShareSale(shares);
const profit = stockSaleResults[0]; const profit = stockSaleResults[0];
const newSharePrice = stockSaleResults[1]; const newSharePrice = stockSaleResults[1];
@ -75,7 +73,7 @@ export function SellSharesModal(props: IProps): React.ReactElement {
player.recordMoneySource(profit, "corporation"); player.recordMoneySource(profit, "corporation");
props.onClose(); props.onClose();
dialogBoxCreate( dialogBoxCreate(
`Sold ${numeralWrapper.formatMoney(shares)} shares for ` + `Sold {numeralWrapper.formatMoney(shares)} shares for ` +
`${numeralWrapper.formatMoney(profit)}. ` + `${numeralWrapper.formatMoney(profit)}. ` +
`The corporation's stock price fell to ${numeralWrapper.formatMoney(corp.sharePrice)} ` + `The corporation's stock price fell to ${numeralWrapper.formatMoney(corp.sharePrice)} ` +
`as a result of dilution.`, `as a result of dilution.`,
@ -83,7 +81,6 @@ export function SellSharesModal(props: IProps): React.ReactElement {
props.rerender(); props.rerender();
} }
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void { function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) sell(); if (event.keyCode === 13) sell();
@ -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. function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.ReactElement {
// useEffect(() => {
// const id = setInterval(rerender, 200);
// return () => clearInterval(id);
// }, []);
const faction = props.faction;
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,10 +110,6 @@ export function FactionRoot(props: IProps): React.ReactElement {
player.startFactionSecurityWork(router, faction); player.startFactionSecurityWork(router, faction);
} }
function MainPage({ faction }: { faction: Faction }): React.ReactElement {
const p = player;
const factionInfo = faction.getInfo();
// We have a special flag for whether the player this faction is the player's // We have a special flag for whether the player this faction is the player's
// gang faction because if the player has a gang, they cannot do any other action // gang faction because if the player has a gang, they cannot do any other action
const isPlayersGang = p.inGang() && p.getGangName() === faction.name; const isPlayersGang = p.inGang() && p.getGangName() === faction.name;
@ -187,7 +167,7 @@ export function FactionRoot(props: IProps): React.ReactElement {
disabled={!canDonate} disabled={!canDonate}
/> />
)} )}
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={routeToPurchaseAugs} /> <Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={onAugmentations} />
{canPurchaseSleeves && ( {canPurchaseSleeves && (
<> <>
<Option <Option
@ -202,9 +182,24 @@ export function FactionRoot(props: IProps): React.ReactElement {
); );
} }
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 ? ( return purchasingAugs ? (
<AugmentationsPage faction={faction} routeToMainPage={routeToMain} /> <AugmentationsPage faction={faction} routeToMainPage={() => setPurchasingAugs(false)} />
) : ( ) : (
<MainPage faction={faction} /> <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>