From bb1545fd8faf14a303e7295c2f3e09a0949da7af Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Sun, 15 Sep 2024 18:00:24 -0700 Subject: [PATCH 1/8] Getting rid of DimItem#type, easy cases --- config/i18n.json | 4 +--- src/app/bungie-api/bungie-service-helper.ts | 6 +----- src/app/bungie-api/destiny1-api.ts | 2 +- src/app/bungie-api/destiny2-api.ts | 2 +- src/app/inventory/inventory-buckets.ts | 1 + src/app/inventory/item-move-service.ts | 21 +++++++++++++------ src/app/inventory/item-types.ts | 5 ++++- src/app/inventory/move-item.ts | 2 +- src/app/inventory/store/stats.ts | 4 ++-- src/app/item-popup/ItemDetails.tsx | 6 +++--- .../loadout-builder-vendors.ts | 2 +- src/app/loadout-drawer/loadout-apply.ts | 4 ++-- src/app/search/items/search-filters/dupes.ts | 10 ++++++--- src/app/search/items/search-filters/stats.ts | 4 ++-- 14 files changed, 42 insertions(+), 31 deletions(-) diff --git a/config/i18n.json b/config/i18n.json index 2ecacab5c8..442eb03cde 100644 --- a/config/i18n.json +++ b/config/i18n.json @@ -75,9 +75,7 @@ "DevVersion": "Are you running a development version of DIM? You must register your chrome extension with Bungie.net.", "Difficulties": "Bungie.net is currently experiencing difficulties.", "ErrorTitle": "Bungie.net Error", - "ItemUniquenessExplanation": "You tried to move the '{{name}}' {{type}} to your {{character}} but that destination already has that item and is only allowed one.", - "ItemUniquenessExplanation_female": "You tried to move the '{{name}}' {{type}} to your {{character}} but that destination already has that item and is only allowed one.", - "ItemUniquenessExplanation_male": "You tried to move the '{{name}}' {{type}} to your {{character}} but that destination already has that item and is only allowed one.", + "ItemUniquenessExplanation": "A character can only have one of '{{name}}' on it.", "Maintenance": "Bungie.net servers are down for maintenance.", "MissingInventory": "Bungie.net did not return your inventory, possibly because your privacy settings prevent it. Try logging out and logging back in.", "DestinyCannotPerformActionAtThisLocation": "You cannot equip items or change mods while in an activity. Try heading to orbit or a social area. This is a limitation of the Bungie.net API, not DIM.", diff --git a/src/app/bungie-api/bungie-service-helper.ts b/src/app/bungie-api/bungie-service-helper.ts index c8ebb83222..6fca74696b 100644 --- a/src/app/bungie-api/bungie-service-helper.ts +++ b/src/app/bungie-api/bungie-service-helper.ts @@ -6,7 +6,6 @@ import { PlatformErrorCodes } from 'bungie-api-ts/destiny2'; import { HttpClient, HttpClientConfig } from 'bungie-api-ts/http'; import _ from 'lodash'; import { DimItem } from '../inventory/item-types'; -import { DimStore } from '../inventory/store-types'; import { FatalTokenError, fetchWithBungieOAuth } from './authenticated-fetch'; import { API_KEY } from './bungie-api-utils'; import { @@ -216,7 +215,7 @@ function handleErrors(error: unknown): never { } // Handle "DestinyUniquenessViolation" (1648) -export function handleUniquenessViolation(error: unknown, item: DimItem, store: DimStore): never { +export function handleUniquenessViolation(error: unknown, item: DimItem): never { if ( error instanceof BungieError && error.code === PlatformErrorCodes.DestinyUniquenessViolation @@ -225,9 +224,6 @@ export function handleUniquenessViolation(error: unknown, item: DimItem, store: 'BungieService.ItemUniquenessExplanation', t('BungieService.ItemUniquenessExplanation', { name: item.name, - type: item.type.toLowerCase(), - character: store.name, - context: store.genderName, }), ).withError(error); } diff --git a/src/app/bungie-api/destiny1-api.ts b/src/app/bungie-api/destiny1-api.ts index 71e278f5f1..86d39115ea 100644 --- a/src/app/bungie-api/destiny1-api.ts +++ b/src/app/bungie-api/destiny1-api.ts @@ -162,7 +162,7 @@ export async function transfer( }), ); } catch (e) { - return handleUniquenessViolation(e, item, store); + return handleUniquenessViolation(e, item); } } diff --git a/src/app/bungie-api/destiny2-api.ts b/src/app/bungie-api/destiny2-api.ts index 1c467cf32c..3a297be2c5 100644 --- a/src/app/bungie-api/destiny2-api.ts +++ b/src/app/bungie-api/destiny2-api.ts @@ -212,7 +212,7 @@ export async function transfer( try { return await response; } catch (e) { - return handleUniquenessViolation(e, item, store); + return handleUniquenessViolation(e, item); } } diff --git a/src/app/inventory/inventory-buckets.ts b/src/app/inventory/inventory-buckets.ts index 3bf060ec09..e5bd6478a4 100644 --- a/src/app/inventory/inventory-buckets.ts +++ b/src/app/inventory/inventory-buckets.ts @@ -17,6 +17,7 @@ export type InventoryBucket = { readonly capacity: number; readonly accountWide: boolean; readonly category: BucketCategory; + /** @deprecated use bucket hash */ readonly type?: DimBucketType; readonly sort?: BucketSortType; /** diff --git a/src/app/inventory/item-move-service.ts b/src/app/inventory/item-move-service.ts index 6dad64b34c..1735e6be8b 100644 --- a/src/app/inventory/item-move-service.ts +++ b/src/app/inventory/item-move-service.ts @@ -331,7 +331,7 @@ function equipItem(item: DimItem, cancelToken: CancelToken): ThunkResult 1) { return store.id + item.hash; } else { - return store.id + item.type; + return store.id + item.bucket.hash; } }, ); @@ -826,7 +835,7 @@ function ensureCanMoveToStore( ? moveAsideItem.destinyVersion === 1 ? moveAsideItem.bucket.sort! : '' - : moveAsideItem.type; + : moveAsideItem.typeName; throw new DimError( 'no-space', diff --git a/src/app/inventory/item-types.ts b/src/app/inventory/item-types.ts index 9d12aaa8cb..fd81cb9190 100644 --- a/src/app/inventory/item-types.ts +++ b/src/app/inventory/item-types.ts @@ -46,7 +46,10 @@ export interface DimItem { classified: boolean; /** The version of Destiny this comes from. */ destinyVersion: DestinyVersion; - /** This is the type of the item (see InventoryBuckets) regardless of location. This string is a DIM concept with no direct correlation to the API types. It should generally be avoided in favor of using bucket hash. */ + /** + * This is the type of the item (see InventoryBuckets) regardless of location. This string is a DIM concept with no direct correlation to the API types. It should generally be avoided in favor of using bucket hash. + * @deprecated use bucket.hash instead. + */ type: DimBucketType; /** Localized name of this item's type. */ typeName: string; diff --git a/src/app/inventory/move-item.ts b/src/app/inventory/move-item.ts index b1ff4d58e1..751e09fbff 100644 --- a/src/app/inventory/move-item.ts +++ b/src/app/inventory/move-item.ts @@ -126,7 +126,7 @@ export function moveItemTo( 'User initiated move:', moveAmount, item.name, - item.type, + item.typeName, 'to', store.name, 'from', diff --git a/src/app/inventory/store/stats.ts b/src/app/inventory/store/stats.ts index 4fb922dff2..a3beb675af 100644 --- a/src/app/inventory/store/stats.ts +++ b/src/app/inventory/store/stats.ts @@ -14,7 +14,7 @@ import { DestinyStatGroupDefinition, } from 'bungie-api-ts/destiny2'; import adeptWeaponHashes from 'data/d2/adept-weapon-hashes.json'; -import { ItemCategoryHashes, StatHashes } from 'data/d2/generated-enums'; +import { BucketHashes, ItemCategoryHashes, StatHashes } from 'data/d2/generated-enums'; import { Draft } from 'immer'; import _ from 'lodash'; import { socketContainsIntrinsicPlug } from '../../utils/socket-utils'; @@ -157,7 +157,7 @@ export function buildStats( investmentStats.push(tStat!); // synthesize custom stats for meaningfully stat-bearing items - if (createdItem.type !== 'ClassItem') { + if (createdItem.bucket.hash !== BucketHashes.ClassArmor) { for (const customStat of customStats) { if (isClassCompatible(customStat.class, createdItem.classType)) { const cStat = makeCustomStat( diff --git a/src/app/item-popup/ItemDetails.tsx b/src/app/item-popup/ItemDetails.tsx index d4f3296432..c6c2cd07cc 100644 --- a/src/app/item-popup/ItemDetails.tsx +++ b/src/app/item-popup/ItemDetails.tsx @@ -13,7 +13,7 @@ import { RootState } from 'app/store/types'; import { getItemKillTrackerInfo, isD1Item } from 'app/utils/item-utils'; import { SingleVendorSheetContext } from 'app/vendors/single-vendor/SingleVendorSheetContainer'; import clsx from 'clsx'; -import { ItemCategoryHashes } from 'data/d2/generated-enums'; +import { BucketHashes, ItemCategoryHashes } from 'data/d2/generated-enums'; import helmetIcon from 'destiny-icons/armor_types/helmet.svg'; import modificationIcon from 'destiny-icons/general/modifications.svg'; import handCannonIcon from 'destiny-icons/weapons/hand_cannon.svg'; @@ -76,14 +76,14 @@ export default function ItemDetails({ )} - {(item.type === 'Milestone' || + {(item.bucket.hash === BucketHashes.Quests || item.itemCategoryHashes.includes(ItemCategoryHashes.Mods_Ornament)) && item.secondaryIcon && ( )} diff --git a/src/app/loadout-builder/loadout-builder-vendors.ts b/src/app/loadout-builder/loadout-builder-vendors.ts index 9ec86c8ab5..92540c059e 100644 --- a/src/app/loadout-builder/loadout-builder-vendors.ts +++ b/src/app/loadout-builder/loadout-builder-vendors.ts @@ -29,7 +29,7 @@ export const loVendorItemsSelector = currySelector( (item) => allowedVendorHashes.includes(item.vendor?.vendorHash ?? -1) && // filters out some dummy exotics - item.type !== 'Unknown', + item.bucket.hash !== -1, ); // some signs that vendor items aren't yet loaded. to prevent recalcs, only add in vendor items once they're all ready return !relevantItems.length || relevantItems.some((i) => i.missingSockets === 'not-loaded') diff --git a/src/app/loadout-drawer/loadout-apply.ts b/src/app/loadout-drawer/loadout-apply.ts index 4737508537..355ff0e732 100644 --- a/src/app/loadout-drawer/loadout-apply.ts +++ b/src/app/loadout-drawer/loadout-apply.ts @@ -879,7 +879,7 @@ export function clearItemsOffCharacter( 'clearItemsOffCharacter initiated move:', item.amount, item.name, - item.type, + item.typeName, 'to', otherStoresWithSpace[0].name, 'from', @@ -911,7 +911,7 @@ export function clearItemsOffCharacter( 'clearItemsOffCharacter initiated move:', item.amount, item.name, - item.type, + item.typeName, 'to', vault.name, 'from', diff --git a/src/app/search/items/search-filters/dupes.ts b/src/app/search/items/search-filters/dupes.ts index 02089d2767..b2655684d0 100644 --- a/src/app/search/items/search-filters/dupes.ts +++ b/src/app/search/items/search-filters/dupes.ts @@ -27,7 +27,7 @@ export const makeDupeID = (item: DimItem) => item.tier }${ // The engram that dispenses the Taraxippos scout rifle is also called Taraxippos - item.type + item.bucket.hash }`; const sortDupes = ( @@ -209,9 +209,13 @@ const dupeFilters: ItemFilterDefinition[] = [ description: tl('Filter.DupePerks'), filter: ({ allItems }) => { const duplicates = new Map(); + function getDupeId(item: DimItem) { + // Don't compare across buckets or across types (e.g. Titan armor vs Hunter armor) + return `${item.bucket.hash}|${item.classType}`; + } for (const i of allItems) { if (i.sockets?.allSockets.some((s) => s.isPerk && s.socketDefinition.defaultVisible)) { - const dupeId = i.classType + i.type; + const dupeId = getDupeId(i); if (!duplicates.has(dupeId)) { duplicates.set(dupeId, new PerksSet()); } @@ -220,7 +224,7 @@ const dupeFilters: ItemFilterDefinition[] = [ } return (item) => item.sockets?.allSockets.some((s) => s.isPerk && s.socketDefinition.defaultVisible) && - Boolean(duplicates.get(item.classType + item.type)?.hasPerkDupes(item)); + Boolean(duplicates.get(getDupeId(item))?.hasPerkDupes(item)); }, }, ]; diff --git a/src/app/search/items/search-filters/stats.ts b/src/app/search/items/search-filters/stats.ts index 268d42b183..03c0a9da7b 100644 --- a/src/app/search/items/search-filters/stats.ts +++ b/src/app/search/items/search-filters/stats.ts @@ -293,7 +293,7 @@ function checkIfStatMatchesMaxValue( const statHashes: number[] = statName === 'any' ? armorStatHashes : [statHashByName[statName]]; const byWhichValue = byBaseValue ? 'base' : 'value'; const useWhichMaxes = item.isExotic ? 'all' : 'nonexotic'; - const itemSlot = `${item.classType}${item.type}`; + const itemSlot = `${item.bucket.hash}|${item.classType}`; const maxStatsForSlot = maxStatValues[useWhichMaxes][itemSlot]; const matchingStats = item.stats?.filter( (s) => @@ -312,7 +312,7 @@ function gatherHighestStats(allItems: DimItem[]) { continue; } - const itemSlot = `${i.classType}${i.type}`; + const itemSlot = `${i.bucket.hash}|${i.classType}`; // if this is an exotic item, update overall maxes, but don't ruin the curve for the nonexotic maxes const itemTiers: ('all' | 'nonexotic')[] = i.isExotic ? ['all'] : ['all', 'nonexotic']; const thisSlotMaxGroups = itemTiers.map((t) => (maxStatValues[t][itemSlot] ??= {})); From 811e96069c276e1ffb6d7bdcff47cae7131feac1 Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Sun, 15 Sep 2024 19:45:08 -0700 Subject: [PATCH 2/8] A few more --- src/app/farming/actions.ts | 2 +- src/app/inventory/item-types.ts | 10 ++++++++-- src/app/organizer/Columns.tsx | 2 +- src/locale/en.json | 4 +--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app/farming/actions.ts b/src/app/farming/actions.ts index 98023126db..521d60bc2a 100644 --- a/src/app/farming/actions.ts +++ b/src/app/farming/actions.ts @@ -118,7 +118,7 @@ function makeRoomForItems(store: DimStore, cancelToken: CancelToken): ThunkResul return (dispatch, getState) => { const buckets = bucketsSelector(getState())!; const makeRoomBuckets = Object.values(buckets.byHash).filter( - (b) => b.category === BucketCategory.Equippable && b.type, + (b) => b.category === BucketCategory.Equippable && b.vaultBucket, ); return dispatch(makeRoomForItemsInBuckets(store, makeRoomBuckets, cancelToken)); }; diff --git a/src/app/inventory/item-types.ts b/src/app/inventory/item-types.ts index fd81cb9190..1ef240ff27 100644 --- a/src/app/inventory/item-types.ts +++ b/src/app/inventory/item-types.ts @@ -47,11 +47,17 @@ export interface DimItem { /** The version of Destiny this comes from. */ destinyVersion: DestinyVersion; /** - * This is the type of the item (see InventoryBuckets) regardless of location. This string is a DIM concept with no direct correlation to the API types. It should generally be avoided in favor of using bucket hash. + * This is the type of the item (see InventoryBuckets) regardless of location. + * This string is a DIM concept with no direct correlation to the API types. + * It should generally be avoided in favor of using bucket hash. * @deprecated use bucket.hash instead. */ type: DimBucketType; - /** Localized name of this item's type. */ + /** + * Localized name of this item's type. Only used for display - use bucket.hash + * or itemCategoryHashes to figure out what kind of item this is + * programmatically. + */ typeName: string; /** The bucket the item normally resides in (even though it may currently be elsewhere, such as in the postmaster). */ bucket: InventoryBucket; diff --git a/src/app/organizer/Columns.tsx b/src/app/organizer/Columns.tsx index 3189a09f50..a7ae55b7be 100644 --- a/src/app/organizer/Columns.tsx +++ b/src/app/organizer/Columns.tsx @@ -482,7 +482,7 @@ export function getColumns( id: 'Category', header: 'Category', csv: 'Category', - value: (i) => i.type, + value: (i) => i.bucket.name, }), isSpreadsheet && c({ diff --git a/src/locale/en.json b/src/locale/en.json index 6f0024c429..e6821d2354 100644 --- a/src/locale/en.json +++ b/src/locale/en.json @@ -76,9 +76,7 @@ "DevVersion": "Are you running a development version of DIM? You must register your chrome extension with Bungie.net.", "Difficulties": "Bungie.net is currently experiencing difficulties.", "ErrorTitle": "Bungie.net Error", - "ItemUniquenessExplanation": "You tried to move the '{{name}}' {{type}} to your {{character}} but that destination already has that item and is only allowed one.", - "ItemUniquenessExplanation_female": "You tried to move the '{{name}}' {{type}} to your {{character}} but that destination already has that item and is only allowed one.", - "ItemUniquenessExplanation_male": "You tried to move the '{{name}}' {{type}} to your {{character}} but that destination already has that item and is only allowed one.", + "ItemUniquenessExplanation": "A character can only have one of '{{name}}' on it.", "Maintenance": "Bungie.net servers are down for maintenance.", "MissingInventory": "Bungie.net did not return your inventory, possibly because your privacy settings prevent it. Try logging out and logging back in.", "NetworkError": "Network error - {{status}} {{statusText}}", From b76a5e77199f6fa851ab294fad32c54fc106747e Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Sun, 15 Sep 2024 20:43:59 -0700 Subject: [PATCH 3/8] Update D1 loadout builder --- .../loadout-builder/D1LoadoutBuilder.tsx | 235 +++++++++--------- .../destiny1/loadout-builder/GeneratedSet.tsx | 19 +- .../LoadoutBuilderLockPerk.tsx | 36 +-- src/app/destiny1/loadout-builder/calculate.ts | 31 +-- src/app/destiny1/loadout-builder/types.ts | 16 +- src/app/destiny1/loadout-builder/utils.ts | 56 +++-- 6 files changed, 198 insertions(+), 195 deletions(-) diff --git a/src/app/destiny1/loadout-builder/D1LoadoutBuilder.tsx b/src/app/destiny1/loadout-builder/D1LoadoutBuilder.tsx index 8504102dc0..5f98e28725 100644 --- a/src/app/destiny1/loadout-builder/D1LoadoutBuilder.tsx +++ b/src/app/destiny1/loadout-builder/D1LoadoutBuilder.tsx @@ -6,14 +6,14 @@ import { t } from 'app/i18next-t'; import { useLoadStores } from 'app/inventory/store/hooks'; import { getCurrentStore } from 'app/inventory/stores-helpers'; import { useD1Definitions } from 'app/manifest/selectors'; -import { D1_StatHashes } from 'app/search/d1-known-values'; +import { D1_StatHashes, D1BucketHashes } from 'app/search/d1-known-values'; import { getColor } from 'app/shell/formatters'; import { useThunkDispatch } from 'app/store/thunk-dispatch'; import { uniqBy } from 'app/utils/collections'; import { itemCanBeInLoadout } from 'app/utils/item-utils'; import { errorLog } from 'app/utils/log'; import { DestinyClass } from 'bungie-api-ts/destiny2'; -import { ItemCategoryHashes } from 'data/d2/generated-enums'; +import { BucketHashes, ItemCategoryHashes } from 'data/d2/generated-enums'; import { produce } from 'immer'; import _ from 'lodash'; import React, { useEffect, useMemo, useRef, useState } from 'react'; @@ -25,7 +25,7 @@ import { D1GridNode, D1Item, DimItem } from '../../inventory/item-types'; import { bucketsSelector, sortedStoresSelector } from '../../inventory/selectors'; import { D1Store } from '../../inventory/store-types'; import { AppIcon, refreshIcon } from '../../shell/icons'; -import { Vendor, loadVendors } from '../vendors/vendor.service'; +import { loadVendors, Vendor } from '../vendors/vendor.service'; import ExcludeItemsDropTarget from './ExcludeItemsDropTarget'; import GeneratedSet from './GeneratedSet'; import LoadoutBuilderItem from './LoadoutBuilderItem'; @@ -69,11 +69,20 @@ interface State { loadingVendors: boolean; } +const armorTypes = [ + BucketHashes.Helmet, + BucketHashes.Gauntlets, + BucketHashes.ChestArmor, + BucketHashes.LegArmor, + BucketHashes.ClassArmor, + D1BucketHashes.Artifact, + BucketHashes.Ghost, +] as ArmorTypes[]; const allClassTypes: ClassTypes[] = [DestinyClass.Titan, DestinyClass.Warlock, DestinyClass.Hunter]; const initialState: State = { activesets: '5/5/2', - type: 'Helmet', + type: BucketHashes.Helmet, scaleType: 'scaled', progress: 0, fullMode: false, @@ -85,22 +94,22 @@ const initialState: State = { highestsets: {}, excludeditems: [], lockeditems: { - Helmet: null, - Gauntlets: null, - Chest: null, - Leg: null, - ClassItem: null, - Artifact: null, - Ghost: null, + [BucketHashes.Helmet]: null, + [BucketHashes.Gauntlets]: null, + [BucketHashes.ChestArmor]: null, + [BucketHashes.LegArmor]: null, + [BucketHashes.ClassArmor]: null, + [D1BucketHashes.Artifact]: null, + [BucketHashes.Ghost]: null, }, lockedperks: { - Helmet: {}, - Gauntlets: {}, - Chest: {}, - Leg: {}, - ClassItem: {}, - Artifact: {}, - Ghost: {}, + [BucketHashes.Helmet]: {}, + [BucketHashes.Gauntlets]: {}, + [BucketHashes.ChestArmor]: {}, + [BucketHashes.LegArmor]: {}, + [BucketHashes.ClassArmor]: {}, + [D1BucketHashes.Artifact]: {}, + [BucketHashes.Ghost]: {}, }, }; @@ -211,61 +220,61 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount const perks: { [classType in ClassTypes]: PerkCombination } = { [DestinyClass.Warlock]: { - Helmet: [], - Gauntlets: [], - Chest: [], - Leg: [], - ClassItem: [], - Ghost: [], - Artifact: [], + [BucketHashes.Helmet]: [], + [BucketHashes.Gauntlets]: [], + [BucketHashes.ChestArmor]: [], + [BucketHashes.LegArmor]: [], + [BucketHashes.ClassArmor]: [], + [D1BucketHashes.Artifact]: [], + [BucketHashes.Ghost]: [], }, [DestinyClass.Titan]: { - Helmet: [], - Gauntlets: [], - Chest: [], - Leg: [], - ClassItem: [], - Ghost: [], - Artifact: [], + [BucketHashes.Helmet]: [], + [BucketHashes.Gauntlets]: [], + [BucketHashes.ChestArmor]: [], + [BucketHashes.LegArmor]: [], + [BucketHashes.ClassArmor]: [], + [D1BucketHashes.Artifact]: [], + [BucketHashes.Ghost]: [], }, [DestinyClass.Hunter]: { - Helmet: [], - Gauntlets: [], - Chest: [], - Leg: [], - ClassItem: [], - Ghost: [], - Artifact: [], + [BucketHashes.Helmet]: [], + [BucketHashes.Gauntlets]: [], + [BucketHashes.ChestArmor]: [], + [BucketHashes.LegArmor]: [], + [BucketHashes.ClassArmor]: [], + [D1BucketHashes.Artifact]: [], + [BucketHashes.Ghost]: [], }, }; const vendorPerks: { [classType in ClassTypes]: PerkCombination } = { [DestinyClass.Warlock]: { - Helmet: [], - Gauntlets: [], - Chest: [], - Leg: [], - ClassItem: [], - Ghost: [], - Artifact: [], + [BucketHashes.Helmet]: [], + [BucketHashes.Gauntlets]: [], + [BucketHashes.ChestArmor]: [], + [BucketHashes.LegArmor]: [], + [BucketHashes.ClassArmor]: [], + [D1BucketHashes.Artifact]: [], + [BucketHashes.Ghost]: [], }, [DestinyClass.Titan]: { - Helmet: [], - Gauntlets: [], - Chest: [], - Leg: [], - ClassItem: [], - Ghost: [], - Artifact: [], + [BucketHashes.Helmet]: [], + [BucketHashes.Gauntlets]: [], + [BucketHashes.ChestArmor]: [], + [BucketHashes.LegArmor]: [], + [BucketHashes.ClassArmor]: [], + [D1BucketHashes.Artifact]: [], + [BucketHashes.Ghost]: [], }, [DestinyClass.Hunter]: { - Helmet: [], - Gauntlets: [], - Chest: [], - Leg: [], - ClassItem: [], - Ghost: [], - Artifact: [], + [BucketHashes.Helmet]: [], + [BucketHashes.Gauntlets]: [], + [BucketHashes.ChestArmor]: [], + [BucketHashes.LegArmor]: [], + [BucketHashes.ClassArmor]: [], + [D1BucketHashes.Artifact]: [], + [BucketHashes.Ghost]: [], }, }; @@ -287,7 +296,7 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount // Build a map of perks for (const item of items) { - const itemType = item.type as ArmorTypes; + const itemType = item.bucket.hash as ArmorTypes; if (item.classType === DestinyClass.Unknown) { for (const classType of allClassTypes) { perks[classType][itemType] = filterPerks(perks[classType][itemType], item); @@ -306,14 +315,16 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount .map((i) => i.item) .filter( (item) => - item.bucket.sort === 'Armor' || item.type === 'Artifact' || item.type === 'Ghost', + item.bucket.sort === 'Armor' || + item.bucket.hash === D1BucketHashes.Artifact || + item.bucket.hash === BucketHashes.Ghost, ), ); vendorItems = vendorItems.concat(vendItems); // Build a map of perks for (const item of vendItems) { - const itemType = item.type as ArmorTypes; + const itemType = item.bucket.hash as ArmorTypes; if (item.classType === DestinyClass.Unknown) { for (const classType of allClassTypes) { vendorPerks[classType][itemType] = filterPerks( @@ -367,10 +378,17 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount errorLog('loadout optimizer', new Error('You need to have a name on the form input')); } - setState({ - [e.target.name as 'type' | 'scaleType']: e.target.value, - progress: 0, - }); + if (e.target.name === 'type') { + setState({ + [e.target.name]: parseInt(e.target.value, 10), + progress: 0, + }); + } else { + setState({ + [e.target.name]: e.target.value, + progress: 0, + }); + } }; const onActiveSetsChange: React.ChangeEventHandler = (e) => { @@ -422,7 +440,7 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount const onItemLocked = (item: DimItem) => { setStateFull((state) => ({ ...state, - lockeditems: { ...state.lockeditems, [item.type]: item }, + lockeditems: { ...state.lockeditems, [item.bucket.hash]: item }, progress: 0, })); }; @@ -454,39 +472,28 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount }; const lockEquipped = () => { - const lockEquippedTypes = [ - 'helmet', - 'gauntlets', - 'chest', - 'leg', - 'classitem', - 'artifact', - 'ghost', - ]; - const items = Object.groupBy( + const items = Map.groupBy( selectedCharacter!.items.filter( (item) => - itemCanBeInLoadout(item) && - item.equipped && - lockEquippedTypes.includes(item.type.toLowerCase()), + itemCanBeInLoadout(item) && item.equipped && armorTypes.includes(item.bucket.hash), ), - (i) => i.type.toLowerCase(), + (i) => i.bucket.hash as ArmorTypes, ); - function nullWithoutStats(items: DimItem[]) { - return items[0].stats ? (items[0] as D1Item) : null; + function nullWithoutStats(items: DimItem[] | undefined) { + return items?.[0].stats ? (items[0] as D1Item) : null; } // Do not lock items with no stats setState({ lockeditems: { - Helmet: nullWithoutStats(items.helmet), - Gauntlets: nullWithoutStats(items.gauntlets), - Chest: nullWithoutStats(items.chest), - Leg: nullWithoutStats(items.leg), - ClassItem: nullWithoutStats(items.classitem), - Artifact: nullWithoutStats(items.artifact), - Ghost: nullWithoutStats(items.ghost), + [BucketHashes.Helmet]: nullWithoutStats(items.get(BucketHashes.Helmet)), + [BucketHashes.Gauntlets]: nullWithoutStats(items.get(BucketHashes.Gauntlets)), + [BucketHashes.ChestArmor]: nullWithoutStats(items.get(BucketHashes.ChestArmor)), + [BucketHashes.LegArmor]: nullWithoutStats(items.get(BucketHashes.LegArmor)), + [BucketHashes.ClassArmor]: nullWithoutStats(items.get(BucketHashes.ClassArmor)), + [D1BucketHashes.Artifact]: nullWithoutStats(items.get(D1BucketHashes.Artifact)), + [BucketHashes.Ghost]: nullWithoutStats(items.get(BucketHashes.Ghost)), }, progress: 0, }); @@ -495,13 +502,13 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount const clearLocked = () => { setState({ lockeditems: { - Helmet: null, - Gauntlets: null, - Chest: null, - Leg: null, - ClassItem: null, - Artifact: null, - Ghost: null, + [BucketHashes.Helmet]: null, + [BucketHashes.Gauntlets]: null, + [BucketHashes.ChestArmor]: null, + [BucketHashes.LegArmor]: null, + [BucketHashes.ClassArmor]: null, + [D1BucketHashes.Artifact]: null, + [BucketHashes.Ghost]: null, }, activesets: '', progress: 0, @@ -530,18 +537,20 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount return ; } - const i18nItemNames: { [key: string]: string } = _.zipObject( - ['Helmet', 'Gauntlets', 'Chest', 'Leg', 'ClassItem', 'Artifact', 'Ghost'], - [ - ItemCategoryHashes.Helmets, - ItemCategoryHashes.Arms, - ItemCategoryHashes.Chest, - ItemCategoryHashes.Legs, - ItemCategoryHashes.ClassItems, - 38, // D1 Artifact - ItemCategoryHashes.Ghost, - ].map((key) => defs.ItemCategory.get(key).title), - ); + const i18nItemNames = Object.fromEntries( + _.zip( + armorTypes, + [ + ItemCategoryHashes.Helmets, + ItemCategoryHashes.Arms, + ItemCategoryHashes.Chest, + ItemCategoryHashes.Legs, + ItemCategoryHashes.ClassItems, + 38, // D1 Artifact + ItemCategoryHashes.Ghost, + ].map((key) => defs.ItemCategory.get(key).title), + ) as [ArmorTypes, string][], + ) as { [key in ArmorTypes]: string }; // Armor of each type on a particular character // TODO: don't even need to load this much! @@ -575,9 +584,9 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount {/* TODO: break into its own component */} {t('Bucket.Armor')}:{' '} @@ -641,7 +650,7 @@ export default function D1LoadoutBuilder({ account }: { account: DestinyAccount lockeditem={lockeditem} activePerks={activePerks} lockedPerks={lockedperks} - type={type as ArmorTypes} + type={parseInt(type, 10) as ArmorTypes} i18nItemNames={i18nItemNames} onRemove={onRemove} onPerkLocked={onPerkLocked} diff --git a/src/app/destiny1/loadout-builder/GeneratedSet.tsx b/src/app/destiny1/loadout-builder/GeneratedSet.tsx index f4646217c8..217796374a 100644 --- a/src/app/destiny1/loadout-builder/GeneratedSet.tsx +++ b/src/app/destiny1/loadout-builder/GeneratedSet.tsx @@ -77,13 +77,16 @@ export default function GeneratedSet({ setType, store, activesets, excludeItem }
- {setType.tiers[activesets].configs[0][armorpiece.item.type as ArmorTypes] === 'int' + {setType.tiers[activesets].configs[0][armorpiece.item.bucket.hash as ArmorTypes] === + 'int' ? t('Stats.Intellect') - : setType.tiers[activesets].configs[0][armorpiece.item.type as ArmorTypes] === - 'dis' + : setType.tiers[activesets].configs[0][ + armorpiece.item.bucket.hash as ArmorTypes + ] === 'dis' ? t('Stats.Discipline') - : setType.tiers[activesets].configs[0][armorpiece.item.type as ArmorTypes] === - 'str' + : setType.tiers[activesets].configs[0][ + armorpiece.item.bucket.hash as ArmorTypes + ] === 'str' ? t('Stats.Strength') : t('Stats.NoBonus')} @@ -94,11 +97,11 @@ export default function GeneratedSet({ setType, store, activesets, excludeItem } !collapsed && (
- {config[armorpiece.item.type as ArmorTypes] === 'int' + {config[armorpiece.item.bucket.hash as ArmorTypes] === 'int' ? t('Stats.Intellect') - : config[armorpiece.item.type as ArmorTypes] === 'dis' + : config[armorpiece.item.bucket.hash as ArmorTypes] === 'dis' ? t('Stats.Discipline') - : config[armorpiece.item.type as ArmorTypes] === 'str' + : config[armorpiece.item.bucket.hash as ArmorTypes] === 'str' ? t('Stats.Strength') : t('Stats.NoBonus')} diff --git a/src/app/destiny1/loadout-builder/LoadoutBuilderLockPerk.tsx b/src/app/destiny1/loadout-builder/LoadoutBuilderLockPerk.tsx index 29190ed764..916e10d326 100644 --- a/src/app/destiny1/loadout-builder/LoadoutBuilderLockPerk.tsx +++ b/src/app/destiny1/loadout-builder/LoadoutBuilderLockPerk.tsx @@ -1,7 +1,5 @@ import ClosableContainer from 'app/dim-ui/ClosableContainer'; import { t } from 'app/i18next-t'; -import { D1BucketHashes } from 'app/search/d1-known-values'; -import { BucketHashes } from 'data/d2/generated-enums'; import React, { useState } from 'react'; import BungieImage from '../../dim-ui/BungieImage'; import { D1GridNode, DimItem } from '../../inventory/item-types'; @@ -11,27 +9,6 @@ import LoadoutBuilderItem from './LoadoutBuilderItem'; import LoadoutBuilderLocksDialog from './LoadoutBuilderLocksDialog'; import { ArmorTypes, D1ItemWithNormalStats, LockedPerkHash, PerkCombination } from './types'; -interface Props { - type: ArmorTypes; - lockeditem: D1ItemWithNormalStats | null; - lockedPerks: { [armorType in ArmorTypes]: LockedPerkHash }; - activePerks: PerkCombination; - i18nItemNames: { [key: string]: string }; - onRemove: ({ type }: { type: ArmorTypes }) => void; - onPerkLocked: (perk: D1GridNode, type: ArmorTypes, $event: React.MouseEvent) => void; - onItemLocked: (item: DimItem) => void; -} - -const typeToHash: { [key in ArmorTypes]: BucketHashes | D1BucketHashes } = { - Helmet: BucketHashes.Helmet, - Gauntlets: BucketHashes.Gauntlets, - Chest: BucketHashes.ChestArmor, - Leg: BucketHashes.LegArmor, - ClassItem: BucketHashes.ClassArmor, - Ghost: BucketHashes.Ghost, - Artifact: D1BucketHashes.Artifact, -}; - export default function LoadoutBuilderLockPerk({ type, lockeditem, @@ -41,7 +18,16 @@ export default function LoadoutBuilderLockPerk({ onRemove, onPerkLocked, onItemLocked, -}: Props) { +}: { + type: ArmorTypes; + lockeditem: D1ItemWithNormalStats | null; + lockedPerks: { [armorType in ArmorTypes]: LockedPerkHash }; + activePerks: PerkCombination; + i18nItemNames: { [key in ArmorTypes]: string }; + onRemove: ({ type }: { type: ArmorTypes }) => void; + onPerkLocked: (perk: D1GridNode, type: ArmorTypes, $event: React.MouseEvent) => void; + onItemLocked: (item: DimItem) => void; +}) { const [dialogOpen, setDialogOpen] = useState(false); const closeDialog = () => setDialogOpen(false); @@ -57,7 +43,7 @@ export default function LoadoutBuilderLockPerk({ return (
- + {lockeditem === null ? (
diff --git a/src/app/destiny1/loadout-builder/calculate.ts b/src/app/destiny1/loadout-builder/calculate.ts index 50edf5295a..276ba98dd8 100644 --- a/src/app/destiny1/loadout-builder/calculate.ts +++ b/src/app/destiny1/loadout-builder/calculate.ts @@ -1,6 +1,7 @@ +import { D1BucketHashes } from 'app/search/d1-known-values'; import { infoLog } from 'app/utils/log'; import { delay } from 'app/utils/promises'; -import { StatHashes } from 'data/d2/generated-enums'; +import { BucketHashes, StatHashes } from 'data/d2/generated-enums'; import _ from 'lodash'; import { D1Item } from '../../inventory/item-types'; import { D1ManifestDefinitions } from '../d1-definitions'; @@ -48,31 +49,31 @@ export async function getSetBucketsStep( const helms: { item: D1Item; bonusType: string; - }[] = bestArmor.Helmet || []; + }[] = bestArmor[BucketHashes.Helmet] || []; const gauntlets: { item: D1Item; bonusType: string; - }[] = bestArmor.Gauntlets || []; + }[] = bestArmor[BucketHashes.Gauntlets] || []; const chests: { item: D1Item; bonusType: string; - }[] = bestArmor.Chest || []; + }[] = bestArmor[BucketHashes.ChestArmor] || []; const legs: { item: D1Item; bonusType: string; - }[] = bestArmor.Leg || []; + }[] = bestArmor[BucketHashes.LegArmor] || []; const classItems: { item: D1Item; bonusType: string; - }[] = bestArmor.ClassItem || []; + }[] = bestArmor[BucketHashes.ClassArmor] || []; const ghosts: { item: D1Item; bonusType: string; - }[] = bestArmor.Ghost || []; + }[] = bestArmor[BucketHashes.Ghost] || []; const artifacts: { item: D1Item; bonusType: string; - }[] = bestArmor.Artifact || []; + }[] = bestArmor[D1BucketHashes.Artifact] || []; const setMap: { [setHash: string]: SetType } = {}; const tiersSet = new Set(); const combos = @@ -113,13 +114,13 @@ export async function getSetBucketsStep( if (validSet) { const set: ArmorSet = { armor: { - Helmet: helm, - Gauntlets: gauntlet, - Chest: chest, - Leg: leg, - ClassItem: classItem, - Artifact: artifact, - Ghost: ghost, + [BucketHashes.Helmet]: helm, + [BucketHashes.Gauntlets]: gauntlet, + [BucketHashes.ChestArmor]: chest, + [BucketHashes.LegArmor]: leg, + [BucketHashes.ClassArmor]: classItem, + [D1BucketHashes.Artifact]: artifact, + [BucketHashes.Ghost]: ghost, }, stats: { 144602215: { diff --git a/src/app/destiny1/loadout-builder/types.ts b/src/app/destiny1/loadout-builder/types.ts index 91e0e5e0bb..d43e5cbcab 100644 --- a/src/app/destiny1/loadout-builder/types.ts +++ b/src/app/destiny1/loadout-builder/types.ts @@ -1,5 +1,7 @@ import { DimCharacterStat } from 'app/inventory/store-types'; +import { D1BucketHashes } from 'app/search/d1-known-values'; import { DestinyClass } from 'bungie-api-ts/destiny2'; +import { BucketHashes } from 'data/d2/generated-enums'; import { D1GridNode, D1Item } from '../../inventory/item-types'; export interface D1ItemWithNormalStats extends D1Item { @@ -17,13 +19,13 @@ export interface D1ItemWithNormalStats extends D1Item { } export type ArmorTypes = - | 'Helmet' - | 'Gauntlets' - | 'Chest' - | 'Leg' - | 'ClassItem' - | 'Artifact' - | 'Ghost'; + | BucketHashes.Helmet + | BucketHashes.Gauntlets + | BucketHashes.ChestArmor + | BucketHashes.LegArmor + | BucketHashes.ClassArmor + | D1BucketHashes.Artifact + | BucketHashes.Ghost; export type ClassTypes = DestinyClass.Titan | DestinyClass.Warlock | DestinyClass.Hunter; diff --git a/src/app/destiny1/loadout-builder/utils.ts b/src/app/destiny1/loadout-builder/utils.ts index 86a7f6f377..388cbcc13a 100644 --- a/src/app/destiny1/loadout-builder/utils.ts +++ b/src/app/destiny1/loadout-builder/utils.ts @@ -98,15 +98,7 @@ export function calcArmorStats( } export function getBonusConfig(armor: ArmorSet['armor']): { [armorType in ArmorTypes]: string } { - return { - Helmet: armor.Helmet.bonusType, - Gauntlets: armor.Gauntlets.bonusType, - Chest: armor.Chest.bonusType, - Leg: armor.Leg.bonusType, - ClassItem: armor.ClassItem.bonusType, - Artifact: armor.Artifact.bonusType, - Ghost: armor.Ghost.bonusType, - }; + return _.mapValues(armor, (armorPiece) => armorPiece.bonusType); } export function genSetHash(armorPieces: ItemWithBonus[]) { @@ -139,11 +131,11 @@ export function getBestArmor( let best: { item: D1ItemWithNormalStats; bonusType: string }[] = []; let curbest; let bestCombs: { item: D1ItemWithNormalStats; bonusType: string }[]; - let armortype: ArmorTypes; const excludedIndices = new Set(excluded.map((i) => i.index)); - for (armortype in bucket) { + for (const armortypestr in bucket) { + const armortype = parseInt(armortypestr, 10) as ArmorTypes; const combined = includeVendors ? bucket[armortype].concat(vendorBucket[armortype]) : bucket[armortype]; @@ -191,7 +183,7 @@ export function getBestArmor( curbest = getBestItem(filtered, hash.stats, hash.type, scaleTypeArg); best.push(curbest); // add the best -> if best is exotic -> get best legendary - if (curbest.item.isExotic && armortype !== 'ClassItem') { + if (curbest.item.isExotic && armortype !== BucketHashes.ClassArmor) { best.push(getBestItem(filtered, hash.stats, hash.type, scaleTypeArg, true)); } } @@ -243,7 +235,9 @@ export function mergeBuckets( ) { const merged: Partial<{ [armorType in ArmorTypes]: T }> = {}; for (const [type, bucket] of Object.entries(bucket1)) { - merged[type as ArmorTypes] = bucket.concat(bucket2[type as ArmorTypes]) as T; + merged[parseInt(type, 10) as ArmorTypes] = bucket.concat( + bucket2[parseInt(type, 10) as ArmorTypes], + ) as T; } return merged as { [armorType in ArmorTypes]: T }; } @@ -265,13 +259,13 @@ export function loadVendorsBucket( ): ItemBucket { if (!vendors) { return { - Helmet: [], - Gauntlets: [], - Chest: [], - Leg: [], - ClassItem: [], - Artifact: [], - Ghost: [], + [BucketHashes.Helmet]: [], + [BucketHashes.Gauntlets]: [], + [BucketHashes.ChestArmor]: [], + [BucketHashes.LegArmor]: [], + [BucketHashes.ClassArmor]: [], + [D1BucketHashes.Artifact]: [], + [BucketHashes.Ghost]: [], }; } return Object.values(vendors) @@ -307,19 +301,27 @@ export function loadBucket(currentStore: DimStore, stores: D1Store[]): ItemBucke function getBuckets(items: D1Item[]): ItemBucket { return { - Helmet: items.filter((item) => item.bucket.hash === BucketHashes.Helmet).map(normalizeStats), - Gauntlets: items + [BucketHashes.Helmet]: items + .filter((item) => item.bucket.hash === BucketHashes.Helmet) + .map(normalizeStats), + [BucketHashes.Gauntlets]: items .filter((item) => item.bucket.hash === BucketHashes.Gauntlets) .map(normalizeStats), - Chest: items.filter((item) => item.bucket.hash === BucketHashes.ChestArmor).map(normalizeStats), - Leg: items.filter((item) => item.bucket.hash === BucketHashes.LegArmor).map(normalizeStats), - ClassItem: items + [BucketHashes.ChestArmor]: items + .filter((item) => item.bucket.hash === BucketHashes.ChestArmor) + .map(normalizeStats), + [BucketHashes.LegArmor]: items + .filter((item) => item.bucket.hash === BucketHashes.LegArmor) + .map(normalizeStats), + [BucketHashes.ClassArmor]: items .filter((item) => item.bucket.hash === BucketHashes.ClassArmor) .map(normalizeStats), - Artifact: items + [D1BucketHashes.Artifact]: items .filter((item) => item.bucket.hash === D1BucketHashes.Artifact) .map(normalizeStats), - Ghost: items.filter((item) => item.bucket.hash === BucketHashes.Ghost).map(normalizeStats), + [BucketHashes.Ghost]: items + .filter((item) => item.bucket.hash === BucketHashes.Ghost) + .map(normalizeStats), }; } From e1bc9f483133d7e495304593f9e861a9e7cc39c1 Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Sun, 15 Sep 2024 21:48:11 -0700 Subject: [PATCH 4/8] Remove type --- src/app/inventory/store/armor-quality.ts | 22 +++++----- src/app/inventory/store/character-utils.ts | 31 ++++++-------- src/app/inventory/store/d1-item-factory.ts | 47 ++++++++++++++-------- src/app/inventory/store/d2-item-factory.ts | 11 ++--- src/app/progress/milestone-items.ts | 2 - 5 files changed, 58 insertions(+), 55 deletions(-) diff --git a/src/app/inventory/store/armor-quality.ts b/src/app/inventory/store/armor-quality.ts index ad7bbb320d..57501e712b 100644 --- a/src/app/inventory/store/armor-quality.ts +++ b/src/app/inventory/store/armor-quality.ts @@ -1,3 +1,5 @@ +import { D1BucketHashes } from 'app/search/d1-known-values'; +import { BucketHashes } from 'data/d2/generated-enums'; import { D1Stat } from '../item-types'; /** @@ -14,7 +16,7 @@ import { D1Stat } from '../item-types'; export function getQualityRating( stats: D1Stat[] | null, light: { value: number }, - type: string, + bucketHash: BucketHashes | D1BucketHashes, ): { min: number; max: number; @@ -25,24 +27,24 @@ export function getQualityRating( } let split = 0; - switch (type.toLowerCase()) { - case 'helmet': + switch (bucketHash) { + case BucketHashes.Helmet: split = 46; // bungie reports 48, but i've only seen 46 break; - case 'gauntlets': + case BucketHashes.Gauntlets: split = 41; // bungie reports 43, but i've only seen 41 break; - case 'chest': + case BucketHashes.ChestArmor: split = 61; break; - case 'leg': + case BucketHashes.LegArmor: split = 56; break; - case 'classitem': - case 'ghost': + case BucketHashes.ClassArmor: + case BucketHashes.Ghost: split = 25; break; - case 'artifact': + case D1BucketHashes.Artifact: split = 38; break; default: @@ -102,7 +104,7 @@ export function getQualityRating( range: '', }; - if (type.toLowerCase() !== 'artifact') { + if (bucketHash !== D1BucketHashes.Artifact) { for (const stat of stats) { if (stat.qualityPercentage) { stat.qualityPercentage = { diff --git a/src/app/inventory/store/character-utils.ts b/src/app/inventory/store/character-utils.ts index c551909d99..abb044d666 100644 --- a/src/app/inventory/store/character-utils.ts +++ b/src/app/inventory/store/character-utils.ts @@ -1,7 +1,8 @@ import { D1ManifestDefinitions } from 'app/destiny1/d1-definitions'; import { D1Character, D1StatLabel } from 'app/destiny1/d1-manifest-types'; -import { warnLog } from 'app/utils/log'; -import { StatHashes } from 'data/d2/generated-enums'; +import { ArmorTypes } from 'app/destiny1/loadout-builder/types'; +import { D1BucketHashes } from 'app/search/d1-known-values'; +import { BucketHashes, StatHashes } from 'data/d2/generated-enums'; import { DimCharacterStat } from '../store-types'; // Cooldowns @@ -13,15 +14,13 @@ const cooldownsMelee = ['1:10', '1:04', '0:57', '0:49', '0:40', '0:29']; // thanks to /u/iihavetoes for the bonuses at each level // thanks to /u/tehdaw for the spreadsheet with bonuses // https://docs.google.com/spreadsheets/d/1YyFDoHtaiOOeFoqc5Wc_WC2_qyQhBlZckQx5Jd4bJXI/edit?pref=2&pli=1#gid=0 -export function getBonus(light: number, type: string): number { - switch (type.toLowerCase()) { - case 'helmet': - case 'helmets': +export function getBonus(light: number, bucketHash: ArmorTypes): number { + switch (bucketHash) { + case BucketHashes.Helmet: return light < 292 ? 15 : light < 307 ? 16 : light < 319 ? 17 : light < 332 ? 18 : 19; - case 'gauntlets': + case BucketHashes.Gauntlets: return light < 287 ? 13 : light < 305 ? 14 : light < 319 ? 15 : light < 333 ? 16 : 17; - case 'chest': - case 'chest armor': + case BucketHashes.ChestArmor: return light < 287 ? 20 : light < 300 @@ -33,8 +32,7 @@ export function getBonus(light: number, type: string): number { : light < 328 ? 24 : 25; - case 'leg': - case 'leg armor': + case BucketHashes.LegArmor: return light < 284 ? 18 : light < 298 @@ -46,13 +44,10 @@ export function getBonus(light: number, type: string): number { : light < 329 ? 22 : 23; - case 'classitem': - case 'class items': - case 'ghost': - case 'ghosts': + case BucketHashes.ClassArmor: + case BucketHashes.Ghost: return light < 295 ? 8 : light < 319 ? 9 : 10; - case 'artifact': - case 'artifacts': + case D1BucketHashes.Artifact: return light < 287 ? 34 : light < 295 @@ -73,8 +68,6 @@ export function getBonus(light: number, type: string): number { ? 42 : 43; } - warnLog('getBonus', 'item bonus not found', type); - return 0; } export const statsWithTiers = [StatHashes.Discipline, StatHashes.Intellect, StatHashes.Strength]; diff --git a/src/app/inventory/store/d1-item-factory.ts b/src/app/inventory/store/d1-item-factory.ts index 6b08b162e9..6f4184301f 100644 --- a/src/app/inventory/store/d1-item-factory.ts +++ b/src/app/inventory/store/d1-item-factory.ts @@ -6,6 +6,7 @@ import { D1StatDefinition, D1TalentGridDefinition, } from 'app/destiny1/d1-manifest-types'; +import { ArmorTypes } from 'app/destiny1/loadout-builder/types'; import { t } from 'app/i18next-t'; import { D1BucketHashes, D1_StatHashes } from 'app/search/d1-known-values'; import { lightStats } from 'app/search/search-filter-values'; @@ -269,8 +270,6 @@ function makeItem( } } - const itemType = normalBucket.type || 'Unknown'; - const element = (item.damageTypeHash && toD2DamageType(defs.DamageType.get(item.damageTypeHash))) || null; @@ -290,8 +289,6 @@ function makeItem( // The bucket the item normally resides in (even though it may be in the vault/postmaster) bucket: normalBucket, hash: item.itemHash, - // This is the type of the item (see dimCategory/dimBucketService) regardless of location - type: itemType, itemCategoryHashes: itemDef.itemCategoryHashes || [], tier: tiers[itemDef.tierType] || 'Common', isExotic: tiers[itemDef.tierType] === 'Exotic', @@ -322,7 +319,7 @@ function makeItem( classType: itemDef.classType, classTypeNameLocalized: getClassTypeNameLocalized(itemDef.classType, defs), element, - ammoType: getAmmoType(itemType), + ammoType: getAmmoType(normalBucket.hash), sourceHashes: itemDef.sourceHashes, lockable: normalBucket.hash !== BucketHashes.Subclass && @@ -406,10 +403,22 @@ function makeItem( ); try { - createdItem.stats = buildStats(item, itemDef, defs.Stat, createdItem.talentGrid, itemType); + createdItem.stats = buildStats( + item, + itemDef, + defs.Stat, + createdItem.talentGrid, + createdItem.bucket.hash, + ); if (createdItem.stats?.length === 0) { - createdItem.stats = buildStats(item, item, defs.Stat, createdItem.talentGrid, itemType); + createdItem.stats = buildStats( + item, + item, + defs.Stat, + createdItem.talentGrid, + createdItem.bucket.hash, + ); } } catch (e) { errorLog(TAG, `Error building stats for ${createdItem.name}`, item, itemDef, e); @@ -428,7 +437,11 @@ function makeItem( if (createdItem.talentGrid && createdItem.infusable && item.primaryStat) { try { - createdItem.quality = getQualityRating(createdItem.stats, item.primaryStat, itemType); + createdItem.quality = getQualityRating( + createdItem.stats, + item.primaryStat, + createdItem.bucket.hash, + ); } catch (e) { errorLog( 'd1-stores', @@ -476,17 +489,17 @@ function makeItem( return createdItem; } -function getAmmoType(itemType: string) { - switch (itemType) { - case 'Primary': +function getAmmoType(bucketHash: BucketHashes) { + switch (bucketHash) { + case BucketHashes.KineticWeapons: return DestinyAmmunitionType.Primary; - case 'Special': + case BucketHashes.EnergyWeapons: return DestinyAmmunitionType.Special; - case 'Heavy': + case BucketHashes.PowerWeapons: return DestinyAmmunitionType.Heavy; + default: + return DestinyAmmunitionType.None; } - - return DestinyAmmunitionType.None; } function buildTalentGrid( @@ -679,7 +692,7 @@ function buildStats( itemDef: D1InventoryItemDefinition | D1ItemComponent, statDefs: DefinitionTable, grid: D1TalentGrid | null, - type: string, + bucketHash: ArmorTypes, ): D1Stat[] | null { if (!item.stats?.length || !itemDef.stats) { return null; @@ -747,7 +760,7 @@ function buildStats( (identifier === 'STAT_STRENGTH' && armorNodes.find((n) => n.hash === 193091484 /* Increase Strength */))) ) { - bonus = getBonus(item.primaryStat.value, type); + bonus = getBonus(item.primaryStat.value, bucketHash); if ( activeArmorNode && diff --git a/src/app/inventory/store/d2-item-factory.ts b/src/app/inventory/store/d2-item-factory.ts index 8c0ab6cb6a..cb6e5aa67d 100644 --- a/src/app/inventory/store/d2-item-factory.ts +++ b/src/app/inventory/store/d2-item-factory.ts @@ -337,8 +337,6 @@ export function makeItem( currentBucket = normalBucket; } - const itemType = normalBucket.type || 'Unknown'; - const isEngram = normalBucket.hash === BucketHashes.Engrams || itemDef.itemCategoryHashes?.includes(ItemCategoryHashes.Engrams) || @@ -349,7 +347,7 @@ export function makeItem( let primaryStat: DimItem['primaryStat'] = null; if ( itemInstanceData.primaryStat && - itemType !== 'Class' && + normalBucket.hash !== BucketHashes.ClassArmor && !itemDef.stats?.disablePrimaryStatDisplay ) { primaryStat = itemInstanceData.primaryStat; @@ -452,8 +450,6 @@ export function makeItem( // The bucket the item normally resides in (even though it may be in the vault/postmaster) bucket: normalBucket, hash: item.itemHash, - // This is the type of the item (see DimCategory/DimBuckets) regardless of location - type: itemType, itemCategoryHashes: getItemCategoryHashes(itemDef), tier: D2ItemTiers[itemDef.inventory!.tierType] || 'Common', isExotic: D2ItemTiers[itemDef.inventory!.tierType] === 'Exotic', @@ -488,11 +484,12 @@ export function makeItem( classTypeNameLocalized: getClassTypeNameLocalized(defs)(itemDef.classType), element, energy: itemInstanceData.energy ?? null, - lockable: itemType !== 'Finishers' ? item.lockable : true, + lockable: normalBucket.hash !== BucketHashes.Finishers ? item.lockable : true, trackable: Boolean(item.itemInstanceId && itemDef.objectives?.questlineItemHash), tracked: Boolean(item.state & ItemState.Tracked), locked: Boolean(item.state & ItemState.Locked), - masterwork: Boolean(item.state & ItemState.Masterwork) && itemType !== 'Class', + masterwork: + Boolean(item.state & ItemState.Masterwork) && normalBucket.hash !== BucketHashes.ClassArmor, crafted: item.state & ItemState.Crafted ? 'crafted' : false, highlightedObjective: Boolean(item.state & ItemState.HighlightedObjective), classified: Boolean(itemDef.redacted), diff --git a/src/app/progress/milestone-items.ts b/src/app/progress/milestone-items.ts index e86d0a5a6e..0a63c36329 100644 --- a/src/app/progress/milestone-items.ts +++ b/src/app/progress/milestone-items.ts @@ -248,8 +248,6 @@ function makeFakePursuitItem( // The bucket the item normally resides in (even though it may be in the vault/postmaster) bucket: bucket, hash, - // This is the type of the item (see DimCategory/DimBuckets) regardless of location - type: 'Milestone', itemCategoryHashes: [], // see defs.ItemCategory tier: 'Rare', isExotic: false, From 98c5a0a64c8c0d1e679cb62d23441b02b7c17290 Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Sun, 15 Sep 2024 22:00:16 -0700 Subject: [PATCH 5/8] Get D1 type search working --- src/app/destiny1/d1-buckets.ts | 45 ------- src/app/destiny2/d2-buckets.ts | 52 -------- src/app/inventory/inventory-buckets.ts | 5 - src/app/inventory/item-types.ts | 9 +- .../items/search-filters/known-values.ts | 121 ++++++++++++++++-- 5 files changed, 112 insertions(+), 120 deletions(-) diff --git a/src/app/destiny1/d1-buckets.ts b/src/app/destiny1/d1-buckets.ts index f8d5a686e4..6cda345f2a 100644 --- a/src/app/destiny1/d1-buckets.ts +++ b/src/app/destiny1/d1-buckets.ts @@ -1,56 +1,14 @@ -import { D1BucketHashes } from 'app/search/d1-known-values'; import { filterMap } from 'app/utils/collections'; import { HashLookup, StringLookup } from 'app/utils/util-types'; import { BucketCategory } from 'bungie-api-ts/destiny2'; -import { BucketHashes } from 'data/d2/generated-enums'; import type { D1BucketCategory, - DimBucketType, InventoryBucket, InventoryBuckets, } from '../inventory/inventory-buckets'; import { D1Categories } from './d1-bucket-categories'; import type { D1ManifestDefinitions } from './d1-definitions'; -// A mapping from the bucket hash to DIM item types -const bucketToTypeRaw = { - [BucketHashes.ChestArmor]: 'Chest', - [BucketHashes.LegArmor]: 'Leg', - [BucketHashes.LostItems]: 'LostItems', - [BucketHashes.Ships]: 'Ship', - [D1BucketHashes.Missions]: 'Missions', - [D1BucketHashes.Artifact]: 'Artifact', - [BucketHashes.PowerWeapons]: 'Heavy', - [BucketHashes.SpecialOrders]: 'SpecialOrders', - [BucketHashes.Consumables]: 'Consumable', - [BucketHashes.KineticWeapons]: 'Primary', - [BucketHashes.ClassArmor]: 'ClassItem', - [D1BucketHashes.RecordBook]: 'RecordBook', - [D1BucketHashes.RecordBookLegacy]: 'RecordBookLegacy', - [D1BucketHashes.Quests]: 'Quests', - [BucketHashes.Vehicle]: 'Vehicle', - [D1BucketHashes.Bounties]: 'Bounties', - [BucketHashes.EnergyWeapons]: 'Special', - [D1BucketHashes.Shader]: 'Shader', - [BucketHashes.Modifications]: 'Ornaments', - [BucketHashes.Emotes_Equippable]: 'Emote', - [BucketHashes.Messages]: 'Messages', - [BucketHashes.Subclass]: 'Class', - [BucketHashes.Helmet]: 'Helmet', - [BucketHashes.Gauntlets]: 'Gauntlets', - [D1BucketHashes.Horn]: 'Horn', - [BucketHashes.Materials]: 'Material', - [BucketHashes.Ghost]: 'Ghost', - [BucketHashes.Emblems]: 'Emblem', -} as const; - -export type D1BucketTypes = (typeof bucketToTypeRaw)[keyof typeof bucketToTypeRaw]; - -// A mapping from the bucket hash to DIM item types -const bucketToType: { - [hash: number]: DimBucketType | undefined; -} = bucketToTypeRaw; - export const vaultTypes: HashLookup = { 3003523923: 'Armor', 4046403665: 'Weapons', @@ -84,7 +42,6 @@ export function getBuckets(defs: D1ManifestDefinitions) { category: BucketCategory.Item, capacity: Number.MAX_SAFE_INTEGER, sort: 'Unknown', - type: 'Unknown', accountWide: false, }, setHasUnknown() { @@ -93,7 +50,6 @@ export function getBuckets(defs: D1ManifestDefinitions) { }; for (const def of Object.values(defs.InventoryBucket.getAll())) { if (def.enabled) { - const type = bucketToType[def.hash]; const sort = bucketHashToSort[def.hash] ?? vaultTypes[def.hash]; const bucket: InventoryBucket = { description: def.bucketDescription, @@ -104,7 +60,6 @@ export function getBuckets(defs: D1ManifestDefinitions) { capacity: def.itemCount, accountWide: false, category: BucketCategory.Item, - type, sort, }; if (sort) { diff --git a/src/app/destiny2/d2-buckets.ts b/src/app/destiny2/d2-buckets.ts index a69cc1d0ac..a8418d4836 100644 --- a/src/app/destiny2/d2-buckets.ts +++ b/src/app/destiny2/d2-buckets.ts @@ -1,63 +1,14 @@ import { VendorHashes } from 'app/search/d2-known-values'; import { filterMap } from 'app/utils/collections'; import { BucketCategory } from 'bungie-api-ts/destiny2'; -import { BucketHashes } from 'data/d2/generated-enums'; import type { D2BucketCategory, - DimBucketType, InventoryBucket, InventoryBuckets, } from '../inventory/inventory-buckets'; import { D2Categories } from './d2-bucket-categories'; import { D2ManifestDefinitions } from './d2-definitions'; -// A mapping from the bucket hash to DIM item types -const bucketToTypeRaw = { - [BucketHashes.EnergyWeapons]: 'Energy', - [BucketHashes.UpgradePoint]: 'UpgradePoint', - [BucketHashes.StrangeCoin]: 'StrangeCoin', - [BucketHashes.Glimmer]: 'Glimmer', - [BucketHashes.LegendaryShards]: 'Legendary Shards', - [BucketHashes.Silver]: 'Silver', - [BucketHashes.BrightDust]: 'Bright Dust', - [BucketHashes.Messages]: 'Messages', - [BucketHashes.Subclass]: 'Class', - [BucketHashes.Modifications]: 'Modifications', - [BucketHashes.Helmet]: 'Helmet', - [BucketHashes.Gauntlets]: 'Gauntlets', - [BucketHashes.Materials]: 'Materials', - [BucketHashes.Ghost]: 'Ghost', - [BucketHashes.Emblems]: 'Emblems', - [BucketHashes.ChestArmor]: 'Chest', - [BucketHashes.LegArmor]: 'Leg', - [BucketHashes.LostItems]: 'LostItems', - [BucketHashes.Ships]: 'Ships', - [BucketHashes.Engrams]: 'Engrams', - [BucketHashes.PowerWeapons]: 'Power', - [BucketHashes.Auras]: 'Auras', - [BucketHashes.SpecialOrders]: 'SpecialOrders', - [BucketHashes.KineticWeapons]: 'KineticSlot', - [BucketHashes.ClassArmor]: 'ClassItem', - [BucketHashes.Vehicle]: 'Vehicle', - [BucketHashes.Consumables]: 'Consumables', - [BucketHashes.General]: 'General', - [BucketHashes.Emotes_Invisible]: 'Emotes', - [BucketHashes.Quests]: 'Pursuits', - [BucketHashes.SeasonalArtifact]: 'SeasonalArtifacts', - [BucketHashes.Finishers]: 'Finishers', - [BucketHashes.ClanBanners]: 'ClanBanner', -} as const; - -export type D2BucketTypes = (typeof bucketToTypeRaw)[keyof typeof bucketToTypeRaw]; - -// these don't have bucket hashes but may be manually assigned to DimItems -export type D2AdditionalBucketTypes = 'Milestone' | 'Unknown'; - -// A mapping from the bucket hash to DIM item types -export const bucketToType: { - [hash: number]: DimBucketType | undefined; -} = bucketToTypeRaw; - const bucketHashToSort: { [bucketHash: number]: D2BucketCategory } = {}; for (const [category, bucketHashes] of Object.entries(D2Categories)) { for (const bucketHash of bucketHashes) { @@ -78,7 +29,6 @@ export function getBuckets(defs: D2ManifestDefinitions) { hasTransferDestination: false, capacity: Number.MAX_SAFE_INTEGER, sort: 'Unknown', - type: 'Unknown', accountWide: false, category: BucketCategory.Item, }, @@ -87,7 +37,6 @@ export function getBuckets(defs: D2ManifestDefinitions) { }, }; for (const def of Object.values(defs.InventoryBucket.getAll())) { - const type = bucketToType[def.hash]; const sort = bucketHashToSort[def.hash]; const bucket: InventoryBucket = { description: def.displayProperties.description, @@ -98,7 +47,6 @@ export function getBuckets(defs: D2ManifestDefinitions) { capacity: def.itemCount, accountWide: def.scope === 1, category: def.category, - type, sort, }; // Add an easy helper property like "inPostmaster" diff --git a/src/app/inventory/inventory-buckets.ts b/src/app/inventory/inventory-buckets.ts index e5bd6478a4..12ebad72a5 100644 --- a/src/app/inventory/inventory-buckets.ts +++ b/src/app/inventory/inventory-buckets.ts @@ -1,11 +1,8 @@ -import { D1BucketTypes } from 'app/destiny1/d1-buckets'; -import type { D2AdditionalBucketTypes, D2BucketTypes } from 'app/destiny2/d2-buckets'; import { BucketCategory } from 'bungie-api-ts/destiny2'; /** The major toplevel sections of the inventory. "Progress" is only in D1. */ export type D2BucketCategory = 'Postmaster' | 'Weapons' | 'Armor' | 'General' | 'Inventory'; export type D1BucketCategory = 'Postmaster' | 'Weapons' | 'Armor' | 'General' | 'Progress'; -export type DimBucketType = D2BucketTypes | D2AdditionalBucketTypes | D1BucketTypes; export type BucketSortType = D2BucketCategory | D1BucketCategory | 'Unknown'; export type InventoryBucket = { @@ -17,8 +14,6 @@ export type InventoryBucket = { readonly capacity: number; readonly accountWide: boolean; readonly category: BucketCategory; - /** @deprecated use bucket hash */ - readonly type?: DimBucketType; readonly sort?: BucketSortType; /** * The corresponding vault bucket where these items would go if they were placed in the vault. diff --git a/src/app/inventory/item-types.ts b/src/app/inventory/item-types.ts index 1ef240ff27..32d939a88c 100644 --- a/src/app/inventory/item-types.ts +++ b/src/app/inventory/item-types.ts @@ -20,7 +20,7 @@ import { DestinySocketCategoryDefinition, DestinyStat, } from 'bungie-api-ts/destiny2'; -import { DimBucketType, InventoryBucket } from './inventory-buckets'; +import { InventoryBucket } from './inventory-buckets'; /** * A generic DIM item, representing almost anything. This completely represents any D2 item, and most D1 items, @@ -46,13 +46,6 @@ export interface DimItem { classified: boolean; /** The version of Destiny this comes from. */ destinyVersion: DestinyVersion; - /** - * This is the type of the item (see InventoryBuckets) regardless of location. - * This string is a DIM concept with no direct correlation to the API types. - * It should generally be avoided in favor of using bucket hash. - * @deprecated use bucket.hash instead. - */ - type: DimBucketType; /** * Localized name of this item's type. Only used for display - use bucket.hash * or itemCategoryHashes to figure out what kind of item this is diff --git a/src/app/search/items/search-filters/known-values.ts b/src/app/search/items/search-filters/known-values.ts index 0cb5661e2b..2fcf2d115f 100644 --- a/src/app/search/items/search-filters/known-values.ts +++ b/src/app/search/items/search-filters/known-values.ts @@ -1,9 +1,9 @@ +import { D1Categories } from 'app/destiny1/d1-bucket-categories'; import { D2Categories } from 'app/destiny2/d2-bucket-categories'; -import { bucketToType } from 'app/destiny2/d2-buckets'; import { tl } from 'app/i18next-t'; import { DimItem } from 'app/inventory/item-types'; import { getEvent } from 'app/inventory/store/season'; -import { D1ItemCategoryHashes } from 'app/search/d1-known-values'; +import { D1BucketHashes, D1ItemCategoryHashes } from 'app/search/d1-known-values'; import { D2ItemCategoryHashesByName, ItemTierName, @@ -22,7 +22,7 @@ import { import artifactBreakerMods from 'data/d2/artifact-breaker-weapon-types.json'; import { D2EventEnum, D2EventInfo } from 'data/d2/d2-event-info-v2'; import focusingOutputs from 'data/d2/focusing-item-outputs.json'; -import { BreakerTypeHashes, ItemCategoryHashes } from 'data/d2/generated-enums'; +import { BreakerTypeHashes, BucketHashes, ItemCategoryHashes } from 'data/d2/generated-enums'; import powerfulSources from 'data/d2/powerful-rewards.json'; import { ItemFilterDefinition } from '../item-filter-types'; import D2Sources from './d2-sources'; @@ -110,22 +110,122 @@ export const classFilter = { item.classType === DestinyClass.Unknown ? '' : `is:${classes[item.classType]}`, } satisfies ItemFilterDefinition; +// A mapping from the bucket hash to DIM item types +const bucketToType: LookupTable = { + [BucketHashes.Engrams]: 'engrams', + [BucketHashes.LostItems]: 'lostitems', + [BucketHashes.Messages]: 'messages', + [BucketHashes.SpecialOrders]: 'specialorders', + + [BucketHashes.KineticWeapons]: 'kineticslot', + [BucketHashes.EnergyWeapons]: 'energy', + [BucketHashes.PowerWeapons]: 'power', + + [BucketHashes.Helmet]: 'helmet', + [BucketHashes.Gauntlets]: 'gauntlets', + [BucketHashes.ChestArmor]: 'chest', + [BucketHashes.LegArmor]: 'leg', + [BucketHashes.ClassArmor]: 'classitem', + + [BucketHashes.Subclass]: 'subclass', + [BucketHashes.Ghost]: 'ghost', + [BucketHashes.Emblems]: 'emblems', + [BucketHashes.Ships]: 'ships', + [BucketHashes.Vehicle]: 'vehicle', + [BucketHashes.Emotes_Invisible]: 'emotes', + [BucketHashes.Finishers]: 'finishers', + [BucketHashes.SeasonalArtifact]: 'seasonalartifacts', + + [BucketHashes.Consumables]: 'consumables', + [BucketHashes.Modifications]: 'modifications', +}; + +const d1BucketToType: LookupTable = { + [BucketHashes.LostItems]: 'lostitems', + [BucketHashes.SpecialOrders]: 'specialorders', + [BucketHashes.Messages]: 'messages', + + [BucketHashes.KineticWeapons]: 'primary', + [BucketHashes.EnergyWeapons]: 'special', + [BucketHashes.PowerWeapons]: 'heavy', + + [BucketHashes.Helmet]: 'helmet', + [BucketHashes.Gauntlets]: 'gauntlets', + [BucketHashes.ChestArmor]: 'chest', + [BucketHashes.LegArmor]: 'leg', + [BucketHashes.ClassArmor]: 'classitem', + + [BucketHashes.Subclass]: 'subclass', + [D1BucketHashes.Artifact]: 'artifact', + [BucketHashes.Ghost]: 'ghost', + [BucketHashes.Consumables]: 'consumables', + [BucketHashes.Materials]: 'material', + [BucketHashes.Modifications]: 'ornaments', + [BucketHashes.Emblems]: 'emblems', + [D1BucketHashes.Shader]: 'shader', + [BucketHashes.Emotes_Equippable]: 'emote', + [BucketHashes.Ships]: 'ships', + [BucketHashes.Vehicle]: 'vehicle', + [D1BucketHashes.Horn]: 'horn', + + [D1BucketHashes.Bounties]: 'bounties', + [D1BucketHashes.Quests]: 'quests', + [D1BucketHashes.Missions]: 'missions', +}; + export const itemTypeFilter = { keywords: Object.values(D2Categories) // stuff like Engrams, Kinetic, Gauntlets, Emblems, Finishers, Modifications .flat() .map((v) => { const type = bucketToType[v]; if (!type && $DIM_FLAVOR === 'dev') { - throw new Error(`You forgot to map a string type name for bucket hash ${v}`); + throw new Error( + `itemTypeFilter: You forgot to map a string type name for bucket hash ${v}`, + ); } - return type!.toLowerCase(); + return type!; }), + destinyVersion: 2, description: tl('Filter.ArmorCategory'), // or 'Filter.WeaponClass' - filter: - ({ filterValue }) => - (item) => - item.type.toLowerCase() === filterValue, - fromItem: (item) => `is:${item.type.toLowerCase()}`, + filter: ({ filterValue }) => { + let bucketHash: BucketHashes; + for (const [bucketHashStr, type] of Object.entries(bucketToType)) { + if (type === filterValue) { + bucketHash = parseInt(bucketHashStr, 10); + break; + } + } + return (item) => item.bucket.hash === bucketHash; + }, + fromItem: (item) => `is:${bucketToType[item.bucket.hash as BucketHashes]}`, +} satisfies ItemFilterDefinition; + +// D1 has different item types, otherwise this is the same as itemTypeFilter. +export const d1itemTypeFilter = { + keywords: Object.values(D1Categories) // stuff like Engrams, Kinetic, Gauntlets, Emblems, Finishers, Modifications + .flat() + .map((v) => { + const type = d1BucketToType[v]; + if (!type && $DIM_FLAVOR === 'dev') { + throw new Error( + `d1itemTypeFilter You forgot to map a string type name for bucket hash ${v}`, + ); + } + return type!; + }), + destinyVersion: 1, + description: tl('Filter.ArmorCategory'), // or 'Filter.WeaponClass' + filter: ({ filterValue }) => { + let bucketHash: BucketHashes | D1BucketHashes; + for (const [bucketHashStr, type] of Object.entries(d1BucketToType)) { + if (type === filterValue) { + bucketHash = parseInt(bucketHashStr, 10); + break; + } + } + return (item) => item.bucket.hash === bucketHash; + }, + fromItem: (item) => `is:${d1BucketToType[item.bucket.hash as BucketHashes]}`, } satisfies ItemFilterDefinition; export const itemCategoryFilter = { @@ -174,6 +274,7 @@ const knownValuesFilters: ItemFilterDefinition[] = [ classFilter, itemCategoryFilter, itemTypeFilter, + d1itemTypeFilter, { keywords: [ 'common', From e44a49457fc7393d29e420627f845ba388fe411e Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Sun, 15 Sep 2024 22:15:00 -0700 Subject: [PATCH 6/8] Make DimItem#itemCategoryHashes type be ItemCategoryHash --- src/app/compare/reducer.ts | 11 +++++++---- src/app/destiny1/d1-manifest-types.ts | 3 ++- src/app/dim-ui/svgs/itemCategory.ts | 8 ++++---- src/app/inventory/item-types.ts | 3 ++- src/app/inventory/store/d2-item-factory.ts | 4 ++-- src/app/organizer/ItemTable.tsx | 2 +- src/app/organizer/Organizer.tsx | 3 ++- src/app/settings/vault-grouping.test.ts | 3 ++- src/app/shell/item-comparators.ts | 4 ++-- src/app/utils/item-utils.ts | 4 ++-- 10 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/app/compare/reducer.ts b/src/app/compare/reducer.ts index 257bca5726..8f93fb64ff 100644 --- a/src/app/compare/reducer.ts +++ b/src/app/compare/reducer.ts @@ -4,7 +4,7 @@ import { showNotification } from 'app/notifications/notifications'; import { getSelectionTree } from 'app/organizer/ItemTypeSelector'; import { quoteFilterString } from 'app/search/query-parser'; import { getInterestingSocketMetadatas, isD1Item } from 'app/utils/item-utils'; -import { PlugCategoryHashes } from 'data/d2/generated-enums'; +import { ItemCategoryHashes, PlugCategoryHashes } from 'data/d2/generated-enums'; import { ActionType, Reducer, getType } from 'typesafe-actions'; import * as actions from './actions'; import { compareNameQuery } from './compare-utils'; @@ -14,7 +14,7 @@ export interface CompareSession { * A list of itemCategoryHashes must be provided in order to limit the type of items which can be compared. * This list should match the item category drill-down from Organizer's ItemTypeSelector. */ - readonly itemCategoryHashes: number[]; + readonly itemCategoryHashes: ItemCategoryHashes[]; /** * The query further filters the items to be shown. Since this query is modified * when adding or removing items, external queries must be parenthesized first @@ -258,11 +258,14 @@ function getItemCategoryHashesFromExampleItem(item: DimItem) { // Dive two layers down (weapons/armor => type) for (const node of itemSelectionTree.subCategories!) { - if (item.itemCategoryHashes.includes(node.itemCategoryHash)) { + if (node.itemCategoryHash && item.itemCategoryHashes.includes(node.itemCategoryHash)) { hashes.push(node.itemCategoryHash); if (node.subCategories) { for (const subNode of node.subCategories) { - if (item.itemCategoryHashes.includes(subNode.itemCategoryHash)) { + if ( + subNode.itemCategoryHash && + item.itemCategoryHashes.includes(subNode.itemCategoryHash) + ) { hashes.push(subNode.itemCategoryHash); break; } diff --git a/src/app/destiny1/d1-manifest-types.ts b/src/app/destiny1/d1-manifest-types.ts index 8a1dac0fc9..5cc0800b30 100644 --- a/src/app/destiny1/d1-manifest-types.ts +++ b/src/app/destiny1/d1-manifest-types.ts @@ -22,6 +22,7 @@ import { TierType, TransferStatuses, } from 'bungie-api-ts/destiny2'; +import { ItemCategoryHashes } from 'data/d2/generated-enums'; export interface AllD1DestinyManifestComponents { DestinyRecordDefinition: { [hash: number]: D1RecordDefinition }; @@ -140,7 +141,7 @@ export interface D1InventoryItemDefinition { itemSubType: DestinyItemSubType; classType: DestinyClass; sources: D1ItemSourceDefinition[]; - itemCategoryHashes: number[]; + itemCategoryHashes: ItemCategoryHashes[]; sourceHashes: number[]; nonTransferrable: boolean; exclusive: BungieMembershipType; diff --git a/src/app/dim-ui/svgs/itemCategory.ts b/src/app/dim-ui/svgs/itemCategory.ts index 32fd2bc300..d3fc3fe2d6 100644 --- a/src/app/dim-ui/svgs/itemCategory.ts +++ b/src/app/dim-ui/svgs/itemCategory.ts @@ -117,11 +117,11 @@ const bucketHashToItemCategoryHash: LookupTable(); } - const categoryHashes = categories.map((s) => s.itemCategoryHash).filter(Boolean); + const categoryHashes = categories.map((s) => s.itemCategoryHash).filter((h) => h !== 0); // a top level class-specific category implies armor if (armorTopLevelCatHashes.some((h) => categoryHashes.includes(h))) { categoryHashes.push(ItemCategoryHashes.Armor); diff --git a/src/app/organizer/Organizer.tsx b/src/app/organizer/Organizer.tsx index 5818b8ea09..c626423a2e 100644 --- a/src/app/organizer/Organizer.tsx +++ b/src/app/organizer/Organizer.tsx @@ -7,6 +7,7 @@ import { setSearchQuery } from 'app/shell/actions'; import { querySelector, useIsPhonePortrait } from 'app/shell/selectors'; import { useThunkDispatch } from 'app/store/thunk-dispatch'; import { usePageTitle } from 'app/utils/hooks'; +import { ItemCategoryHashes } from 'data/d2/generated-enums'; import { useEffect, useRef } from 'react'; import { useSelector } from 'react-redux'; import { useLocation, useNavigate } from 'react-router'; @@ -25,7 +26,7 @@ interface Props { */ function drillToSelection( selectionTree: ItemCategoryTreeNode | undefined, - selectedItemCategoryHashes: number[], + selectedItemCategoryHashes: ItemCategoryHashes[], ): ItemCategoryTreeNode[] { const selectedItemCategoryHash = selectedItemCategoryHashes[0]; diff --git a/src/app/settings/vault-grouping.test.ts b/src/app/settings/vault-grouping.test.ts index 08283f1434..e43d337a7b 100644 --- a/src/app/settings/vault-grouping.test.ts +++ b/src/app/settings/vault-grouping.test.ts @@ -1,5 +1,6 @@ import { DimItem } from 'app/inventory/item-types'; import store from 'app/store/store'; +import { ItemCategoryHashes } from 'data/d2/generated-enums'; import { getTestDefinitions, getTestStores } from 'testing/test-utils'; import { vaultWeaponGroupingSelector, vaultWeaponGroupingSettingSelector } from './vault-grouping'; @@ -45,7 +46,7 @@ describe('vaultWeaponGroupingSelector', () => { { ...firstItem, typeName: undefined as unknown as string, - itemCategoryHashes: [0], + itemCategoryHashes: [0 as ItemCategoryHashes], }, { ...firstItem, diff --git a/src/app/shell/item-comparators.ts b/src/app/shell/item-comparators.ts index 1a9290f1f3..f341bc43e7 100644 --- a/src/app/shell/item-comparators.ts +++ b/src/app/shell/item-comparators.ts @@ -5,7 +5,7 @@ import { D2ItemTiers } from 'app/search/d2-known-values'; import { ItemSortSettings } from 'app/settings/item-sort'; import { isD1Item } from 'app/utils/item-utils'; import { DestinyAmmunitionType, DestinyDamageTypeDefinition } from 'bungie-api-ts/destiny2'; -import { BucketHashes } from 'data/d2/generated-enums'; +import { BucketHashes, ItemCategoryHashes } from 'data/d2/generated-enums'; import _ from 'lodash'; import { TagValue, tagConfig, vaultGroupTagOrder } from '../inventory/dim-item-info'; import { Comparator, chainComparator, compareBy, reverseComparator } from '../utils/comparators'; @@ -123,7 +123,7 @@ interface VaultGroupIconTag { interface VaultGroupIconTypeName { type: 'typeName'; - itemCategoryHashes: number[]; + itemCategoryHashes: ItemCategoryHashes[]; } interface VaultGroupIconAmmoType { diff --git a/src/app/utils/item-utils.ts b/src/app/utils/item-utils.ts index cecd698760..c3838cd79c 100644 --- a/src/app/utils/item-utils.ts +++ b/src/app/utils/item-utils.ts @@ -364,8 +364,8 @@ export function getBreakerTypeHash(item: DimItem): number | undefined { return item.breakerType.hash; } else if (item.bucket.inWeapons) { for (const ich of item.itemCategoryHashes) { - if (ichToBreakerType[ich as ItemCategoryHashes]) { - return ichToBreakerType[ich as ItemCategoryHashes]; + if (ichToBreakerType[ich]) { + return ichToBreakerType[ich]; } } } From cebfaf5fdcecc59297c87f8e94f2ba61adad01f0 Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Tue, 17 Sep 2024 23:16:54 -0700 Subject: [PATCH 7/8] Small fix --- src/app/search/__snapshots__/search-config.test.ts.snap | 2 +- src/app/search/items/search-filters/known-values.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/search/__snapshots__/search-config.test.ts.snap b/src/app/search/__snapshots__/search-config.test.ts.snap index 21a1958238..9324f333a4 100644 --- a/src/app/search/__snapshots__/search-config.test.ts.snap +++ b/src/app/search/__snapshots__/search-config.test.ts.snap @@ -12,7 +12,6 @@ exports[`buildSearchConfig generates a reasonable filter map: is filters 1`] = ` "blue", "bow", "chest", - "class", "classitem", "common", "consumables", @@ -125,6 +124,7 @@ exports[`buildSearchConfig generates a reasonable filter map: is filters 1`] = ` "stasis", "statlower", "strand", + "subclass", "submachine", "sunset", "sword", diff --git a/src/app/search/items/search-filters/known-values.ts b/src/app/search/items/search-filters/known-values.ts index 2fcf2d115f..80beeef735 100644 --- a/src/app/search/items/search-filters/known-values.ts +++ b/src/app/search/items/search-filters/known-values.ts @@ -155,7 +155,7 @@ const d1BucketToType: LookupTable = { [BucketHashes.LegArmor]: 'leg', [BucketHashes.ClassArmor]: 'classitem', - [BucketHashes.Subclass]: 'subclass', + [BucketHashes.Subclass]: 'class', [D1BucketHashes.Artifact]: 'artifact', [BucketHashes.Ghost]: 'ghost', [BucketHashes.Consumables]: 'consumables', From f491e1fcbe9888df4a8ebc71c669d2361641a137 Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Tue, 17 Sep 2024 23:35:58 -0700 Subject: [PATCH 8/8] Subclass vs class gets me one last time --- docs/CHANGELOG.md | 3 +++ src/app/inventory/store/d1-item-factory.ts | 3 ++- src/app/inventory/store/d2-item-factory.ts | 7 ++++--- src/app/organizer/Columns.tsx | 14 +++++++++++++- .../search/items/search-filters/known-values.ts | 2 +- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cf0147be1e..3ad9f3c551 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,8 @@ ## Next +* Renamed `is:class` to `is:subclass`. +* Fixed D1 item category searches such as `is:primary` and `is:horn`. Now they're up to parity with the D2 categories. + ## 8.37.0 (2024-09-15) ## 8.36.1 (2024-09-12) diff --git a/src/app/inventory/store/d1-item-factory.ts b/src/app/inventory/store/d1-item-factory.ts index 6f4184301f..5d56b2559d 100644 --- a/src/app/inventory/store/d1-item-factory.ts +++ b/src/app/inventory/store/d1-item-factory.ts @@ -24,6 +24,7 @@ import { ItemBindStatus, ItemLocation, ItemState, + TierType, TransferStatuses, } from 'bungie-api-ts/destiny2'; import missingSources from 'data/d1/missing_sources.json'; @@ -291,7 +292,7 @@ function makeItem( hash: item.itemHash, itemCategoryHashes: itemDef.itemCategoryHashes || [], tier: tiers[itemDef.tierType] || 'Common', - isExotic: tiers[itemDef.tierType] === 'Exotic', + isExotic: itemDef.tierType === TierType.Exotic, name: itemDef.itemName, description: itemDef.itemDescription || '', // Added description for Bounties for now JFLAY2015 icon: itemDef.icon, diff --git a/src/app/inventory/store/d2-item-factory.ts b/src/app/inventory/store/d2-item-factory.ts index 65e22991de..17adb9c3a5 100644 --- a/src/app/inventory/store/d2-item-factory.ts +++ b/src/app/inventory/store/d2-item-factory.ts @@ -36,6 +36,7 @@ import { ItemPerkVisibility, ItemState, SingleComponentResponse, + TierType, TransferStatuses, } from 'bungie-api-ts/destiny2'; import enhancedIntrinsics from 'data/d2/crafting-enhanced-intrinsics'; @@ -347,7 +348,7 @@ export function makeItem( let primaryStat: DimItem['primaryStat'] = null; if ( itemInstanceData.primaryStat && - normalBucket.hash !== BucketHashes.ClassArmor && + normalBucket.hash !== BucketHashes.Subclass && !itemDef.stats?.disablePrimaryStatDisplay ) { primaryStat = itemInstanceData.primaryStat; @@ -452,7 +453,7 @@ export function makeItem( hash: item.itemHash, itemCategoryHashes: getItemCategoryHashes(itemDef), tier: D2ItemTiers[itemDef.inventory!.tierType] || 'Common', - isExotic: D2ItemTiers[itemDef.inventory!.tierType] === 'Exotic', + isExotic: itemDef.inventory!.tierType === TierType.Exotic, name, description: displayProperties.description, icon: overrideStyleItem?.displayProperties.icon || displayProperties.icon || d2MissingIcon, @@ -489,7 +490,7 @@ export function makeItem( tracked: Boolean(item.state & ItemState.Tracked), locked: Boolean(item.state & ItemState.Locked), masterwork: - Boolean(item.state & ItemState.Masterwork) && normalBucket.hash !== BucketHashes.ClassArmor, + Boolean(item.state & ItemState.Masterwork) && normalBucket.hash !== BucketHashes.Subclass, crafted: item.state & ItemState.Crafted ? 'crafted' : false, highlightedObjective: Boolean(item.state & ItemState.HighlightedObjective), classified: Boolean(itemDef.redacted), diff --git a/src/app/organizer/Columns.tsx b/src/app/organizer/Columns.tsx index a7ae55b7be..5196095f76 100644 --- a/src/app/organizer/Columns.tsx +++ b/src/app/organizer/Columns.tsx @@ -67,6 +67,7 @@ import clsx from 'clsx'; import { D2EventInfo } from 'data/d2/d2-event-info-v2'; import { BreakerTypeHashes, + BucketHashes, ItemCategoryHashes, PlugCategoryHashes, StatHashes, @@ -482,7 +483,18 @@ export function getColumns( id: 'Category', header: 'Category', csv: 'Category', - value: (i) => i.bucket.name, + value: (i) => { + switch (i.bucket.hash) { + case BucketHashes.KineticWeapons: + return i.destinyVersion === 2 ? 'KineticSlot' : 'Primary'; + case BucketHashes.EnergyWeapons: + return i.destinyVersion === 2 ? 'Energy' : 'Special'; + case BucketHashes.PowerWeapons: + return i.destinyVersion === 2 ? 'Power' : 'Heavy'; + default: + return i.bucket.name; + } + }, }), isSpreadsheet && c({ diff --git a/src/app/search/items/search-filters/known-values.ts b/src/app/search/items/search-filters/known-values.ts index 80beeef735..2fcf2d115f 100644 --- a/src/app/search/items/search-filters/known-values.ts +++ b/src/app/search/items/search-filters/known-values.ts @@ -155,7 +155,7 @@ const d1BucketToType: LookupTable = { [BucketHashes.LegArmor]: 'leg', [BucketHashes.ClassArmor]: 'classitem', - [BucketHashes.Subclass]: 'class', + [BucketHashes.Subclass]: 'subclass', [D1BucketHashes.Artifact]: 'artifact', [BucketHashes.Ghost]: 'ghost', [BucketHashes.Consumables]: 'consumables',