From 312e3eb71f0dac7ffbbe77519c64732ccdd6e631 Mon Sep 17 00:00:00 2001 From: Snarling <84951833+Snarling@users.noreply.github.com> Date: Wed, 5 Jul 2023 19:36:22 -0400 Subject: [PATCH] Fix for invalid materials in warehouse (#660) --- src/Corporation/Actions.ts | 16 +++++- src/Corporation/ui/modals/ExportModal.tsx | 52 +++++++++++-------- .../ui/modals/PurchaseMaterialModal.tsx | 8 +-- src/NetscriptFunctions/Corporation.ts | 8 ++- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts index ef5192f13..bb7d0dfc4 100644 --- a/src/Corporation/Actions.ts +++ b/src/Corporation/Actions.ts @@ -249,14 +249,26 @@ export function SetSmartSupplyOption(warehouse: Warehouse, material: Material, u 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) { throw new Error(`Invalid amount '${amt}' to buy material '${material.name}'`); } 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 maxAmount = (warehouse.size - warehouse.sizeUsed) / matSize; if (isNaN(amt) || amt < 0) { diff --git a/src/Corporation/ui/modals/ExportModal.tsx b/src/Corporation/ui/modals/ExportModal.tsx index a3884f814..8b6d0ff0c 100644 --- a/src/Corporation/ui/modals/ExportModal.tsx +++ b/src/Corporation/ui/modals/ExportModal.tsx @@ -16,27 +16,30 @@ import MenuItem from "@mui/material/MenuItem"; import Select, { SelectChangeEvent } from "@mui/material/Select"; import { useRerender } from "../../../ui/React/hooks"; import { getRecordKeys } from "../../../Types/Record"; +import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip"; -interface IProps { +interface ExportModalProps { open: boolean; onClose: () => void; mat: Material; } // 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 [exportAmount, setExportAmount] = useState(""); + const rerender = useRerender(); + const possibleDivisions = [...corp.divisions.values()].filter((division: Division) => { return isRelevantMaterial(props.mat.name, division); }); - if (possibleDivisions.length === 0) throw new Error("Export popup created with no divisions."); - const defaultDivision = possibleDivisions[0]; - if (Object.keys(defaultDivision.warehouses).length === 0) - throw new Error("Export popup created in a division with no warehouses."); - const [targetDivision, setTargetDivision] = useState(defaultDivision); - const [targetCity, setTargetCity] = useState(CityName.Sector12); - const [exportAmount, setExportAmount] = useState(""); - const rerender = useRerender(); + // This weird assignment is used because ts thinks possibleDivisions[0] is always a division + const defaultDivision = possibleDivisions.length ? possibleDivisions[0] : null; + const [targetDivision, setTargetDivision] = useState(defaultDivision); + + const possibleCities = targetDivision ? getRecordKeys(targetDivision.warehouses) : []; + const defaultCity = possibleCities.length ? possibleCities[0] : null; + const [targetCity, setTargetCity] = useState(defaultCity); function onCityChange(event: SelectChangeEvent): void { setTargetCity(event.target.value as CityName); @@ -54,6 +57,7 @@ export function ExportModal(props: IProps): React.ReactElement { function exportMaterial(): void { try { + if (!targetDivision || !targetCity) return; ExportMaterial(targetDivision, targetCity, props.mat, exportAmount); } catch (err) { dialogBoxCreate(err + ""); @@ -75,9 +79,8 @@ export function ExportModal(props: IProps): React.ReactElement { rerender(); } - const possibleCities = getRecordKeys(targetDivision.warehouses); - if (possibleCities.length > 0 && !possibleCities.includes(targetCity)) { - setTargetCity(possibleCities[0]); + if (targetCity && !possibleCities.includes(targetCity as CityName)) { + setTargetCity(possibleCities.length ? possibleCities[0] : null); } return ( @@ -104,16 +107,14 @@ export function ExportModal(props: IProps): React.ReactElement {
For example: setting the amount "(EINV-20)/10" would try to export all except 20 of the material. - + {possibleDivisions.map((division) => ( + + {division.name} + + ))} - {possibleCities.map((cityName) => ( {cityName} @@ -121,7 +122,12 @@ export function ExportModal(props: IProps): React.ReactElement { ))} - + + Export + 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. diff --git a/src/Corporation/ui/modals/PurchaseMaterialModal.tsx b/src/Corporation/ui/modals/PurchaseMaterialModal.tsx index 0792c8482..f206d1185 100644 --- a/src/Corporation/ui/modals/PurchaseMaterialModal.tsx +++ b/src/Corporation/ui/modals/PurchaseMaterialModal.tsx @@ -6,7 +6,7 @@ import { Material } from "../../Material"; import { formatMatPurchaseAmount, formatMoney } from "../../../ui/formatNumber"; import { BulkPurchase, BuyMaterial } from "../../Actions"; import { Modal } from "../../../ui/React/Modal"; -import { useCorporation } from "../Context"; +import { useCorporation, useDivision } from "../Context"; import Typography from "@mui/material/Typography"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; @@ -26,6 +26,7 @@ interface IBPProps { function BulkPurchaseSection(props: IBPProps): React.ReactElement { const corp = useCorporation(); + const division = useDivision(); const [buyAmt, setBuyAmt] = useState(""); const [disabled, setDisabled] = useState(false); @@ -64,7 +65,7 @@ function BulkPurchaseSection(props: IBPProps): React.ReactElement { function bulkPurchase(): void { try { - BulkPurchase(corp, props.warehouse, props.mat, parseFloat(buyAmt)); + BulkPurchase(corp, division, props.warehouse, props.mat, parseFloat(buyAmt)); } catch (err) { dialogBoxCreate(err + ""); } @@ -110,12 +111,13 @@ interface IProps { // Create a popup that lets the player purchase a Material export function PurchaseMaterialModal(props: IProps): React.ReactElement { + const division = useDivision(); const [buyAmt, setBuyAmt] = useState(props.mat.buyAmount ? props.mat.buyAmount : 0); function purchaseMaterial(): void { if (buyAmt === null) return; try { - BuyMaterial(props.mat, buyAmt); + BuyMaterial(division, props.mat, buyAmt); } catch (err) { dialogBoxCreate(err + ""); } diff --git a/src/NetscriptFunctions/Corporation.ts b/src/NetscriptFunctions/Corporation.ts index 8ddcf1258..85b190e7a 100644 --- a/src/NetscriptFunctions/Corporation.ts +++ b/src/NetscriptFunctions/Corporation.ts @@ -416,24 +416,28 @@ export function NetscriptCorporation(): InternalAPI { buyMaterial: (ctx) => (_divisionName, _cityName, _materialName, _amt) => { checkAccess(ctx, CorpUnlockName.WarehouseAPI); 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 materialName = getEnumHelper("CorpMaterialName").nsGetMember(ctx, _materialName, "materialName"); const amt = helpers.number(ctx, "amt", _amt); if (amt < 0 || !Number.isFinite(amt)) throw new Error("Invalid value for amount field! Must be numeric and greater than 0"); const material = getMaterial(divisionName, cityName, materialName); - BuyMaterial(material, amt); + BuyMaterial(division, material, amt); }, bulkPurchase: (ctx) => (_divisionName, _cityName, _materialName, _amt) => { checkAccess(ctx, CorpUnlockName.WarehouseAPI); 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 cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName); const materialName = getEnumHelper("CorpMaterialName").nsGetMember(ctx, _materialName, "materialName"); const amt = helpers.number(ctx, "amt", _amt); const warehouse = getWarehouse(divisionName, cityName); const material = getMaterial(divisionName, cityName, materialName); - BulkPurchase(corporation, warehouse, material, amt); + BulkPurchase(corporation, division, warehouse, material, amt); }, makeProduct: (ctx) =>