Fix for invalid materials in warehouse (#660)

This commit is contained in:
Snarling 2023-07-05 19:36:22 -04:00 committed by GitHub
parent 26cdc502bf
commit 312e3eb71f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 30 deletions

@ -249,14 +249,26 @@ export function SetSmartSupplyOption(warehouse: Warehouse, material: Material, u
warehouse.smartSupplyOptions[material.name] = useOption; warehouse.smartSupplyOptions[material.name] = useOption;
} }
export function BuyMaterial(material: Material, amt: number): void { export function BuyMaterial(division: Division, material: Material, amt: number): void {
if (!isRelevantMaterial(material.name, division)) {
throw new Error(`${material.name} is not a relevant material for industry ${division.type}`);
}
if (isNaN(amt) || amt < 0) { if (isNaN(amt) || amt < 0) {
throw new Error(`Invalid amount '${amt}' to buy material '${material.name}'`); throw new Error(`Invalid amount '${amt}' to buy material '${material.name}'`);
} }
material.buyAmount = amt; material.buyAmount = amt;
} }
export function BulkPurchase(corp: Corporation, warehouse: Warehouse, material: Material, amt: number): void { export function BulkPurchase(
corp: Corporation,
division: Division,
warehouse: Warehouse,
material: Material,
amt: number,
): void {
if (!isRelevantMaterial(material.name, division)) {
throw new Error(`${material.name} is not a relevant material for industry ${division.type}`);
}
const matSize = MaterialInfo[material.name].size; const matSize = MaterialInfo[material.name].size;
const maxAmount = (warehouse.size - warehouse.sizeUsed) / matSize; const maxAmount = (warehouse.size - warehouse.sizeUsed) / matSize;
if (isNaN(amt) || amt < 0) { if (isNaN(amt) || amt < 0) {

@ -16,27 +16,30 @@ import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import { useRerender } from "../../../ui/React/hooks"; import { useRerender } from "../../../ui/React/hooks";
import { getRecordKeys } from "../../../Types/Record"; import { getRecordKeys } from "../../../Types/Record";
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
interface IProps { interface ExportModalProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
mat: Material; mat: Material;
} }
// Create a popup that lets the player manage exports // Create a popup that lets the player manage exports
export function ExportModal(props: IProps): React.ReactElement { export function ExportModal(props: ExportModalProps): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const [exportAmount, setExportAmount] = useState("");
const rerender = useRerender();
const possibleDivisions = [...corp.divisions.values()].filter((division: Division) => { const possibleDivisions = [...corp.divisions.values()].filter((division: Division) => {
return isRelevantMaterial(props.mat.name, division); return isRelevantMaterial(props.mat.name, division);
}); });
if (possibleDivisions.length === 0) throw new Error("Export popup created with no divisions."); // This weird assignment is used because ts thinks possibleDivisions[0] is always a division
const defaultDivision = possibleDivisions[0]; const defaultDivision = possibleDivisions.length ? possibleDivisions[0] : null;
if (Object.keys(defaultDivision.warehouses).length === 0) const [targetDivision, setTargetDivision] = useState<Division | null>(defaultDivision);
throw new Error("Export popup created in a division with no warehouses.");
const [targetDivision, setTargetDivision] = useState(defaultDivision); const possibleCities = targetDivision ? getRecordKeys(targetDivision.warehouses) : [];
const [targetCity, setTargetCity] = useState(CityName.Sector12); const defaultCity = possibleCities.length ? possibleCities[0] : null;
const [exportAmount, setExportAmount] = useState(""); const [targetCity, setTargetCity] = useState(defaultCity);
const rerender = useRerender();
function onCityChange(event: SelectChangeEvent<CityName>): void { function onCityChange(event: SelectChangeEvent<CityName>): void {
setTargetCity(event.target.value as CityName); setTargetCity(event.target.value as CityName);
@ -54,6 +57,7 @@ export function ExportModal(props: IProps): React.ReactElement {
function exportMaterial(): void { function exportMaterial(): void {
try { try {
if (!targetDivision || !targetCity) return;
ExportMaterial(targetDivision, targetCity, props.mat, exportAmount); ExportMaterial(targetDivision, targetCity, props.mat, exportAmount);
} catch (err) { } catch (err) {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
@ -75,9 +79,8 @@ export function ExportModal(props: IProps): React.ReactElement {
rerender(); rerender();
} }
const possibleCities = getRecordKeys(targetDivision.warehouses); if (targetCity && !possibleCities.includes(targetCity as CityName)) {
if (possibleCities.length > 0 && !possibleCities.includes(targetCity)) { setTargetCity(possibleCities.length ? possibleCities[0] : null);
setTargetCity(possibleCities[0]);
} }
return ( return (
@ -104,16 +107,14 @@ export function ExportModal(props: IProps): React.ReactElement {
<br /> <br />
For example: setting the amount "(EINV-20)/10" would try to export all except 20 of the material. For example: setting the amount "(EINV-20)/10" would try to export all except 20 of the material.
</Typography> </Typography>
<Select onChange={onTargetDivisionChange} value={targetDivision.name}> <Select onChange={onTargetDivisionChange} value={targetDivision?.name ?? ""}>
{[...corp.divisions.values()] {possibleDivisions.map((division) => (
.filter((division) => isRelevantMaterial(props.mat.name, division))
.map((division) => (
<MenuItem key={division.name} value={division.name}> <MenuItem key={division.name} value={division.name}>
{division.name} {division.name}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
<Select onChange={onCityChange} value={targetCity}> <Select onChange={onCityChange} value={targetCity ?? ""}>
{possibleCities.map((cityName) => ( {possibleCities.map((cityName) => (
<MenuItem key={cityName} value={cityName}> <MenuItem key={cityName} value={cityName}>
{cityName} {cityName}
@ -121,7 +122,12 @@ export function ExportModal(props: IProps): React.ReactElement {
))} ))}
</Select> </Select>
<TextField placeholder="Export amount / s" onChange={onAmtChange} value={exportAmount} /> <TextField placeholder="Export amount / s" onChange={onAmtChange} value={exportAmount} />
<Button onClick={exportMaterial}>Export</Button> <ButtonWithTooltip
disabledTooltip={!targetDivision ? "No target division selected" : !targetCity ? "No target city selected" : ""}
onClick={exportMaterial}
>
Export
</ButtonWithTooltip>
<Typography> <Typography>
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.

@ -6,7 +6,7 @@ import { Material } from "../../Material";
import { formatMatPurchaseAmount, formatMoney } from "../../../ui/formatNumber"; import { formatMatPurchaseAmount, formatMoney } from "../../../ui/formatNumber";
import { BulkPurchase, BuyMaterial } from "../../Actions"; import { BulkPurchase, BuyMaterial } from "../../Actions";
import { Modal } from "../../../ui/React/Modal"; import { Modal } from "../../../ui/React/Modal";
import { useCorporation } from "../Context"; import { useCorporation, useDivision } from "../Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -26,6 +26,7 @@ interface IBPProps {
function BulkPurchaseSection(props: IBPProps): React.ReactElement { function BulkPurchaseSection(props: IBPProps): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const division = useDivision();
const [buyAmt, setBuyAmt] = useState(""); const [buyAmt, setBuyAmt] = useState("");
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false);
@ -64,7 +65,7 @@ function BulkPurchaseSection(props: IBPProps): React.ReactElement {
function bulkPurchase(): void { function bulkPurchase(): void {
try { try {
BulkPurchase(corp, props.warehouse, props.mat, parseFloat(buyAmt)); BulkPurchase(corp, division, props.warehouse, props.mat, parseFloat(buyAmt));
} catch (err) { } catch (err) {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
} }
@ -110,12 +111,13 @@ interface IProps {
// Create a popup that lets the player purchase a Material // Create a popup that lets the player purchase a Material
export function PurchaseMaterialModal(props: IProps): React.ReactElement { export function PurchaseMaterialModal(props: IProps): React.ReactElement {
const division = useDivision();
const [buyAmt, setBuyAmt] = useState(props.mat.buyAmount ? props.mat.buyAmount : 0); const [buyAmt, setBuyAmt] = useState(props.mat.buyAmount ? props.mat.buyAmount : 0);
function purchaseMaterial(): void { function purchaseMaterial(): void {
if (buyAmt === null) return; if (buyAmt === null) return;
try { try {
BuyMaterial(props.mat, buyAmt); BuyMaterial(division, props.mat, buyAmt);
} catch (err) { } catch (err) {
dialogBoxCreate(err + ""); dialogBoxCreate(err + "");
} }

@ -416,24 +416,28 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
buyMaterial: (ctx) => (_divisionName, _cityName, _materialName, _amt) => { buyMaterial: (ctx) => (_divisionName, _cityName, _materialName, _amt) => {
checkAccess(ctx, CorpUnlockName.WarehouseAPI); checkAccess(ctx, CorpUnlockName.WarehouseAPI);
const divisionName = helpers.string(ctx, "divisionName", _divisionName); const divisionName = helpers.string(ctx, "divisionName", _divisionName);
const division = getCorporation().divisions.get(divisionName);
if (!division) throw helpers.makeRuntimeErrorMsg(ctx, `No division with provided name ${divisionName}`);
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName); const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
const materialName = getEnumHelper("CorpMaterialName").nsGetMember(ctx, _materialName, "materialName"); const materialName = getEnumHelper("CorpMaterialName").nsGetMember(ctx, _materialName, "materialName");
const amt = helpers.number(ctx, "amt", _amt); const amt = helpers.number(ctx, "amt", _amt);
if (amt < 0 || !Number.isFinite(amt)) if (amt < 0 || !Number.isFinite(amt))
throw new Error("Invalid value for amount field! Must be numeric and greater than 0"); throw new Error("Invalid value for amount field! Must be numeric and greater than 0");
const material = getMaterial(divisionName, cityName, materialName); const material = getMaterial(divisionName, cityName, materialName);
BuyMaterial(material, amt); BuyMaterial(division, material, amt);
}, },
bulkPurchase: (ctx) => (_divisionName, _cityName, _materialName, _amt) => { bulkPurchase: (ctx) => (_divisionName, _cityName, _materialName, _amt) => {
checkAccess(ctx, CorpUnlockName.WarehouseAPI); checkAccess(ctx, CorpUnlockName.WarehouseAPI);
const divisionName = helpers.string(ctx, "divisionName", _divisionName); const divisionName = helpers.string(ctx, "divisionName", _divisionName);
const division = getCorporation().divisions.get(divisionName);
if (!division) throw helpers.makeRuntimeErrorMsg(ctx, `No division with provided name ${divisionName}`);
const corporation = getCorporation(); const corporation = getCorporation();
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName); const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
const materialName = getEnumHelper("CorpMaterialName").nsGetMember(ctx, _materialName, "materialName"); const materialName = getEnumHelper("CorpMaterialName").nsGetMember(ctx, _materialName, "materialName");
const amt = helpers.number(ctx, "amt", _amt); const amt = helpers.number(ctx, "amt", _amt);
const warehouse = getWarehouse(divisionName, cityName); const warehouse = getWarehouse(divisionName, cityName);
const material = getMaterial(divisionName, cityName, materialName); const material = getMaterial(divisionName, cityName, materialName);
BulkPurchase(corporation, warehouse, material, amt); BulkPurchase(corporation, division, warehouse, material, amt);
}, },
makeProduct: makeProduct:
(ctx) => (ctx) =>