From e22900b883e6a6e47ddc342256de80bffcfc24de Mon Sep 17 00:00:00 2001 From: dragoni7 Date: Sat, 31 Aug 2024 11:30:33 -0700 Subject: [PATCH 1/2] added unique property to manifest armor mods --- src/features/armor/components/ArmorConfig.tsx | 14 +++++++------- src/features/armor/components/ModCustomization.tsx | 5 +++-- src/features/loadouts/constants/index.ts | 2 -- src/lib/bungie_api/manifest.ts | 5 +++++ src/store/db.ts | 2 +- src/types/manifest-types.ts | 1 + 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/features/armor/components/ArmorConfig.tsx b/src/features/armor/components/ArmorConfig.tsx index 762dd74..453c994 100644 --- a/src/features/armor/components/ArmorConfig.tsx +++ b/src/features/armor/components/ArmorConfig.tsx @@ -13,8 +13,8 @@ import { PLUG_CATEGORY_HASH } from '../../../lib/bungie_api/constants'; interface ArmorConfigProps { armor: DestinyArmor; - statMods: ManifestArmorMod[]; - artificeMods: ManifestArmorMod[]; + statMods: (ManifestArmorMod | ManifestArmorStatMod)[]; + artificeMods: (ManifestArmorMod | ManifestArmorStatMod)[]; } const ArmorConfig: React.FC = ({ armor, statMods, artificeMods }) => { @@ -107,7 +107,7 @@ const ArmorConfig: React.FC = ({ armor, statMods, artificeMods { + onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) => { onSelectMod(mod, 0); }} /> @@ -116,7 +116,7 @@ const ArmorConfig: React.FC = ({ armor, statMods, artificeMods { + onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) => { onSelectMod(mod, 1); }} /> @@ -125,7 +125,7 @@ const ArmorConfig: React.FC = ({ armor, statMods, artificeMods { + onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) => { onSelectMod(mod, 2); }} /> @@ -134,7 +134,7 @@ const ArmorConfig: React.FC = ({ armor, statMods, artificeMods { + onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) => { onSelectMod(mod, 3); }} /> @@ -144,7 +144,7 @@ const ArmorConfig: React.FC = ({ armor, statMods, artificeMods { + onSelectMod={(mod: ManifestArmorMod | ManifestArmorStatMod) => { onSelectMod(mod, 4); }} /> diff --git a/src/features/armor/components/ModCustomization.tsx b/src/features/armor/components/ModCustomization.tsx index 7aa42ed..0051ae0 100644 --- a/src/features/armor/components/ModCustomization.tsx +++ b/src/features/armor/components/ModCustomization.tsx @@ -5,6 +5,7 @@ import useArtificeMods from '../hooks/use-artifice-mods'; import useStatMods from '../hooks/use-stat-mods'; import RequiredMod from './RequiredMod'; import { useSelector } from 'react-redux'; +import { ManifestArmorMod, ManifestArmorStatMod } from '../../../types/manifest-types'; const StyledTitle = styled(Typography)(({ theme }) => ({ paddingBottom: theme.spacing(1), @@ -23,8 +24,8 @@ const StyledSubTitle = styled(Typography)(({ theme }) => ({ const ModCustomization: React.FC = () => { const currentConfig = store.getState().loadoutConfig.loadout; - const statMods = useStatMods(); - const artificeMods = useArtificeMods(); + const statMods: (ManifestArmorMod | ManifestArmorStatMod)[] = useStatMods(); + const artificeMods: (ManifestArmorMod | ManifestArmorStatMod)[] = useArtificeMods(); const requiredMods = useSelector( (state: RootState) => state.loadoutConfig.loadout.requiredStatMods ); diff --git a/src/features/loadouts/constants/index.ts b/src/features/loadouts/constants/index.ts index 85a5d55..ed4c86c 100644 --- a/src/features/loadouts/constants/index.ts +++ b/src/features/loadouts/constants/index.ts @@ -1,5 +1,3 @@ -import { armor } from '../types'; - export enum STATUS { SUCCESS = 1, FAIL = 0, diff --git a/src/lib/bungie_api/manifest.ts b/src/lib/bungie_api/manifest.ts index 74819ce..167c133 100644 --- a/src/lib/bungie_api/manifest.ts +++ b/src/lib/bungie_api/manifest.ts @@ -151,6 +151,11 @@ export async function updateManifest() { perkName: '', perkDescription: '', perkIcon: '', + unique: current.tooltipNotifications.some( + (notification: any) => + notification.displayString === + 'Equipping additional copies of this mod provides no benefit.' + ), }); } } else if (current.itemCategoryHashes.includes(ITEM_CATEGORY_HASHES.SUBCLASS_MODS)) { diff --git a/src/store/db.ts b/src/store/db.ts index 8e6c362..bd6ba14 100644 --- a/src/store/db.ts +++ b/src/store/db.ts @@ -29,7 +29,7 @@ db.version(2).stores({ manifestExoticArmorCollection: 'itemHash, name, icon, class, slot, isOwned, collectibleHash', manifestEmblemDef: 'itemHash, name, icon, secondaryOverlay, secondarySpecial', manifestArmorModDef: - 'itemHash, name, icon, category, perkName, perkDescription, perkIcon, isOwned, collectibleHash', + 'itemHash, name, icon, category, perkName, perkDescription, perkIcon, isOwned, unique, collectibleHash', manifestArmorStatModDef: 'itemHash, name, icon, category, perkName, perkDescription, perkIcon, isOwned, collectibleHash, mobilityMod, resilienceMod, recoveryMod, discipline, intellect, strength', manifestSubclassModDef: diff --git a/src/types/manifest-types.ts b/src/types/manifest-types.ts index 1da8105..509d4d8 100644 --- a/src/types/manifest-types.ts +++ b/src/types/manifest-types.ts @@ -51,6 +51,7 @@ export interface ManifestAspect extends ManifestPlug { export interface ManifestArmorMod extends ManifestPlug { energyCost: number; collectibleHash: number; + unique: boolean; } export interface ManifestArmorStatMod extends ManifestStatPlug { From 46066812b12f34c0eb103002810025bd3b761f27 Mon Sep 17 00:00:00 2001 From: dragoni7 Date: Sat, 31 Aug 2024 12:28:16 -0700 Subject: [PATCH 2/2] Added snackbar for providing feedback when equipping mods. Prevented equipping of mulitple unique mods --- src/features/armor/components/ArmorConfig.tsx | 206 ++++++++++++------ .../armor/components/ArmorModSelector.tsx | 10 +- 2 files changed, 139 insertions(+), 77 deletions(-) diff --git a/src/features/armor/components/ArmorConfig.tsx b/src/features/armor/components/ArmorConfig.tsx index 453c994..433868b 100644 --- a/src/features/armor/components/ArmorConfig.tsx +++ b/src/features/armor/components/ArmorConfig.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, SyntheticEvent } from 'react'; import { useDispatch } from 'react-redux'; import ArmorIcon from '../../../components/ArmorIcon'; import { updateLoadoutArmorMods, updateRequiredStatMods } from '../../../store/LoadoutReducer'; @@ -6,7 +6,7 @@ import { armorMods, DestinyArmor } from '../../../types/d2l-types'; import ArmorModSelector from './ArmorModSelector'; import { getModsBySlot } from '../mod-utils'; import { ManifestArmorMod, ManifestArmorStatMod } from '../../../types/manifest-types'; -import { Grid } from '@mui/material'; +import { Alert, Grid, Fade, Snackbar, SnackbarCloseReason } from '@mui/material'; import { useSelector } from 'react-redux'; import { RootState, store } from '../../../store'; import { PLUG_CATEGORY_HASH } from '../../../lib/bungie_api/constants'; @@ -17,7 +17,15 @@ interface ArmorConfigProps { artificeMods: (ManifestArmorMod | ManifestArmorStatMod)[]; } +interface SnackbarMessage { + message: string; + key: number; +} + const ArmorConfig: React.FC = ({ armor, statMods, artificeMods }) => { + const [snackPack, setSnackPack] = useState([]); + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [messageInfo, setMessageInfo] = useState(undefined); const [armorMods, setArmorMods] = useState<(ManifestArmorMod | ManifestArmorStatMod)[]>([]); const selectedMods: (ManifestArmorMod | ManifestArmorStatMod)[] = useSelector( (state: RootState) => state.loadoutConfig.loadout[(armor.type + 'Mods') as armorMods] @@ -38,6 +46,25 @@ const ArmorConfig: React.FC = ({ armor, statMods, artificeMods const onSelectMod = async (mod: ManifestArmorMod | ManifestArmorStatMod, slot: number) => { let totalCost = mod.energyCost; + + if (selectedMods[slot].itemHash === mod.itemHash) return; + + if (!mod.isOwned) { + setSnackPack((prev) => [ + ...prev, + { message: 'You do not own ' + mod.name, key: new Date().getTime() }, + ]); + return; + } + + if ('unique' in mod && mod.unique && selectedMods.includes(mod)) { + setSnackPack((prev) => [ + ...prev, + { message: mod.name + ' is unique. Only equip one copy', key: new Date().getTime() }, + ]); + return; + } + for (const key in selectedMods) { if (Number(key) !== slot) { let statEnergyCost = armorMods.find( @@ -63,11 +90,13 @@ const ArmorConfig: React.FC = ({ armor, statMods, artificeMods }) ); + const requiredMods = store.getState().loadoutConfig.loadout.requiredStatMods; + if ( - mod.category === PLUG_CATEGORY_HASH.ARMOR_MODS.STAT_ARMOR_MODS || + (requiredMods.length > 0 && mod.category === PLUG_CATEGORY_HASH.ARMOR_MODS.STAT_ARMOR_MODS) || mod.category === PLUG_CATEGORY_HASH.ARMOR_MODS.ARTIFICE_ARMOR_MODS ) { - const newRequired = [...store.getState().loadoutConfig.loadout.requiredStatMods]; + const newRequired = [...requiredMods]; const idx = newRequired.findIndex((required) => required.mod === selectedMods[slot]); newRequired[idx] = { mod: newRequired[idx].mod, equipped: false }; @@ -75,84 +104,119 @@ const ArmorConfig: React.FC = ({ armor, statMods, artificeMods } }; + function handleSnackbarClose(event: SyntheticEvent | Event, reason?: SnackbarCloseReason) { + if (reason === 'clickaway') return; + + setSnackbarOpen(false); + } + + function handleExited() { + setMessageInfo(undefined); + } + useEffect(() => { updateMods().catch(console.error); }, []); + useEffect(() => { + if (snackPack.length && !messageInfo) { + setMessageInfo({ ...snackPack[0] }); + setSnackPack((prev) => prev.slice(1)); + setSnackbarOpen(true); + } else if (snackPack.length && messageInfo && snackbarOpen) { + setSnackbarOpen(false); + } + }, [snackPack, messageInfo, snackbarOpen]); + return ( - - - - - -
-
- - { - onSelectMod(mod, 0); - }} - /> - - - { - onSelectMod(mod, 1); - }} - /> - - - { - onSelectMod(mod, 2); - }} - /> - - - { - onSelectMod(mod, 3); - }} - /> - - {armor.artifice === true ? ( + <> + + + + + +
+
+ + { + onSelectMod(mod, 0); + }} + /> + + + { + onSelectMod(mod, 1); + }} + /> + { - onSelectMod(mod, 4); + onSelectMod(mod, 2); }} /> - ) : ( - - )} - + + { + onSelectMod(mod, 3); + }} + /> + + {armor.artifice === true ? ( + + { + onSelectMod(mod, 4); + }} + /> + + ) : ( + + )} + + + + {messageInfo ? messageInfo.message : undefined} + + + ); }; diff --git a/src/features/armor/components/ArmorModSelector.tsx b/src/features/armor/components/ArmorModSelector.tsx index 86b6cef..4e41ea5 100644 --- a/src/features/armor/components/ArmorModSelector.tsx +++ b/src/features/armor/components/ArmorModSelector.tsx @@ -1,6 +1,6 @@ import { Box } from '@mui/system'; import { ManifestArmorMod, ManifestArmorStatMod } from '../../../types/manifest-types'; -import { useState } from 'react'; + import { Tooltip } from '@mui/material'; interface ModSelectorProps { @@ -29,22 +29,20 @@ const ArmorModSelector: React.FC = ({ selected, mods, onSelect maxWidth: '91px', width: '58%', height: 'auto', - backgroundColor: 'rgba(60, 60, 60, 0.45)', + backgroundColor: 'rgba(10, 10, 10, 0.8)', }} /> {mods.map((mod) => ( -
{ - if (selected.itemHash !== mod.itemHash && mod.isOwned) { - onSelectMod(mod); - } + onSelectMod(mod); }} />