Skip to content

Commit

Permalink
Merge pull request #10430 from DestinyItemManager/separate-vendor-com…
Browse files Browse the repository at this point in the history
…ponent-calls

separate out vendor component calls
  • Loading branch information
nev-r authored May 22, 2024
2 parents 04aeeff + 5cb3393 commit 42648cb
Show file tree
Hide file tree
Showing 18 changed files with 295 additions and 61 deletions.
1 change: 1 addition & 0 deletions config/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,7 @@
},
"Infuse": "Infuse",
"InfuseTitle": "Open the infusion fuel finder",
"LoadingSockets": "Perk and stat details have not loaded yet for this item.",
"LockUnlock": {
"Lock": "Lock {{itemType}}",
"Unlock": "Unlock {{itemType}}",
Expand Down
43 changes: 35 additions & 8 deletions src/app/bungie-api/destiny2-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,29 @@ import { DimError } from 'app/utils/dim-error';
import { errorLog } from 'app/utils/log';
import {
AwaAuthorizationResult,
awaGetActionToken,
awaInitializeRequest,
AwaType,
BungieMembershipType,
clearLoadout,
DestinyComponentType,
DestinyItemComponentSetOfint32,
DestinyLinkedProfilesResponse,
DestinyManifest,
DestinyProfileResponse,
DestinyVendorResponse,
DestinyVendorsResponse,
PlatformErrorCodes,
ServerResponse,
awaGetActionToken,
awaInitializeRequest,
clearLoadout,
equipItem,
equipItems as equipItemsApi,
equipLoadout,
getDestinyManifest,
getLinkedProfiles,
getProfile as getProfileApi,
getVendor as getVendorApi,
getVendors as getVendorsApi,
PlatformErrorCodes,
pullFromPostmaster,
ServerResponse,
setItemLockState,
setQuestTrackedState,
snapshotLoadout,
Expand Down Expand Up @@ -138,27 +141,51 @@ async function getProfile(
return response.Response;
}

export type LimitedDestinyVendorsResponse = Omit<DestinyVendorsResponse, 'itemComponents'> &
Partial<{
itemComponents: {
[key: number]: Partial<DestinyItemComponentSetOfint32>;
};
}>;

export async function getVendors(
account: DestinyAccount,
characterId: string,
): Promise<DestinyVendorsResponse> {
): Promise<LimitedDestinyVendorsResponse> {
const response = await getVendorsApi(authenticatedHttpClient, {
characterId,
destinyMembershipId: account.membershipId,
membershipType: account.originalPlatformType,
components: [
DestinyComponentType.Vendors,
DestinyComponentType.VendorSales,
DestinyComponentType.ItemCommonData,
DestinyComponentType.CurrencyLookups,
],
});
return response.Response;
}

/** a single-vendor API fetch, focused on getting the sale item details. see loadAllVendors */
export async function getVendorSaleComponents(
account: DestinyAccount,
characterId: string,
vendorHash: number,
): Promise<DestinyVendorResponse> {
const response = await getVendorApi(authenticatedHttpClient, {
characterId,
destinyMembershipId: account.membershipId,
membershipType: account.originalPlatformType,
components: [
DestinyComponentType.ItemInstances,
DestinyComponentType.ItemObjectives,
DestinyComponentType.ItemSockets,
DestinyComponentType.ItemCommonData,
DestinyComponentType.CurrencyLookups,
DestinyComponentType.ItemPlugStates,
DestinyComponentType.ItemReusablePlugs,
// TODO: We should try to defer this until the popup is open!
DestinyComponentType.ItemPlugObjectives,
],
vendorHash,
});
return response.Response;
}
Expand Down
9 changes: 7 additions & 2 deletions src/app/compare/CompareItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PressTip } from 'app/dim-ui/PressTip';
import { t } from 'app/i18next-t';
import { t, tl } from 'app/i18next-t';
import ItemPopupTrigger from 'app/inventory/ItemPopupTrigger';
import { moveItemTo } from 'app/inventory/move-item';
import { currentStoreSelector, notesSelector } from 'app/inventory/selectors';
Expand Down Expand Up @@ -93,6 +93,11 @@ export default memo(function CompareItem({
[isInitialItem, item, itemClick, pullItem, remove, itemNotes, isFindable],
);

const missingSocketsMessage =
item.missingSockets === 'missing'
? tl('MovePopup.MissingSockets')
: tl('MovePopup.LoadingSockets');

return (
<div className={styles.compareItem}>
{itemHeader}
Expand All @@ -107,7 +112,7 @@ export default memo(function CompareItem({
))}
{isD1Item(item) && item.talentGrid && <ItemTalentGrid item={item} perksOnly={true} />}
{item.missingSockets && isInitialItem && (
<div className="item-details warning">{t('MovePopup.MissingSockets')}</div>
<div className="item-details warning">{t(missingSocketsMessage)}</div>
)}
{item.sockets && <ItemSockets item={item} minimal onPlugClicked={onPlugClicked} />}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/inventory/item-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export interface DimItem {
/** D2 items use sockets and plugs to represent everything from perks to mods to ornaments and shaders. */
sockets: DimSockets | null;
/** Sometimes the API doesn't return socket info. This tells whether the item *should* have socket info but doesn't. */
missingSockets: boolean;
missingSockets: false | 'missing' | 'not-loaded';
/** Detailed stats for the item. */
stats: DimStat[] | null;
/** Any objectives associated with the item. */
Expand Down
9 changes: 6 additions & 3 deletions src/app/inventory/store/d2-item-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export interface ItemCreationContext {
* Sometimes comes from the profile response, but also sometimes from vendors response or mocked out.
* If not present, the itemComponents from the DestinyProfileResponse should be used.
*/
itemComponents?: DestinyItemComponentSetOfint64;
itemComponents?: Partial<DestinyItemComponentSetOfint64>;
}

const damageDefsByDamageType = memoizeOne((defs: D2ManifestDefinitions) =>
Expand All @@ -245,7 +245,10 @@ export function makeItem(
/** the ID of the owning store - can be undefined for fake collections items */
owner: DimStore | undefined,
): DimItem | undefined {
itemComponents ??= profileResponse.itemComponents;
if (owner) {
// only makes sense to use the profile's itemComponents if it's a real item
itemComponents ??= profileResponse.itemComponents;
}

const itemDef = defs.InventoryItem.get(item.itemHash);

Expand All @@ -255,7 +258,7 @@ export function makeItem(
owner && !owner?.isVault ? profileResponse.characterProgressions?.data?.[owner.id] : undefined;

const itemInstanceData: Partial<DestinyItemInstanceComponent> = item.itemInstanceId
? itemComponents?.instances.data?.[item.itemInstanceId] ?? emptyObject()
? itemComponents?.instances?.data?.[item.itemInstanceId] ?? emptyObject()
: emptyObject();

// Missing definition
Expand Down
15 changes: 11 additions & 4 deletions src/app/inventory/store/sockets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
SocketCategoryHashes,
} from 'data/d2/generated-enums';
import {
DimItem,
DimPlug,
DimPlugSet,
DimSocket,
Expand All @@ -57,10 +58,10 @@ import {
*/
export function buildSockets(
item: DestinyItemComponent,
itemComponents: DestinyItemComponentSetOfint64 | undefined,
itemComponents: Partial<DestinyItemComponentSetOfint64> | undefined,
defs: D2ManifestDefinitions,
itemDef: DestinyInventoryItemDefinition,
) {
): { sockets: DimItem['sockets']; missingSockets: DimItem['missingSockets'] } {
let sockets: DimSockets | null = null;
if ($featureFlags.simulateMissingSockets) {
itemComponents = undefined;
Expand All @@ -76,6 +77,7 @@ export function buildSockets(
(item.itemInstanceId &&
itemComponents?.plugObjectives?.data?.[item.itemInstanceId]?.objectivesPerPlug) ||
undefined;

if (socketData) {
sockets = buildInstancedSockets(
defs,
Expand All @@ -90,10 +92,15 @@ export function buildSockets(
// If we didn't have live data (for example, when viewing vendor items or collections),
// get sockets from the item definition.
if (!sockets && itemDef.sockets) {
// a nice long instanceId is a real one and "should" have this data
// (short is actually a vendor item index. we'll let that build from defs.)
const isInstanced = Boolean(item.itemInstanceId && item.itemInstanceId.length > 14);

// If this really *should* have live sockets, but didn't...
if (item.itemInstanceId && item.itemInstanceId !== '0' && !socketData) {
return { sockets: null, missingSockets: true };
if (item.itemInstanceId !== '0' && !socketData) {
return { sockets: null, missingSockets: isInstanced ? 'missing' : 'not-loaded' };
}

sockets = buildDefinedSockets(defs, itemDef);
}

Expand Down
9 changes: 7 additions & 2 deletions src/app/item-popup/ItemDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DestinyTooltipText } from 'app/dim-ui/DestinyTooltipText';
import { t } from 'app/i18next-t';
import { t, tl } from 'app/i18next-t';
import { createItemContextSelector, storesSelector } from 'app/inventory/selectors';
import { isTrialsPassage } from 'app/inventory/store/objectives';
import { applySocketOverrides, useSocketOverrides } from 'app/inventory/store/override-sockets';
Expand Down Expand Up @@ -65,6 +65,11 @@ export default function ItemDetails({

const showVendor = useContext(SingleVendorSheetContext);

const missingSocketsMessage =
item.missingSockets === 'missing'
? tl('MovePopup.MissingSockets')
: tl('MovePopup.LoadingSockets');

return (
<div id={id} role="tabpanel" aria-labelledby={`${id}-tab`} className={styles.itemDetailsBody}>
{item.itemCategoryHashes.includes(ItemCategoryHashes.Shaders) && (
Expand Down Expand Up @@ -122,7 +127,7 @@ export default function ItemDetails({
)}

{item.missingSockets && (
<div className="item-details warning">{t('MovePopup.MissingSockets')}</div>
<div className="item-details warning">{t(missingSocketsMessage)}</div>
)}

{defs.isDestiny2() && item.energy && defs && <EnergyMeter item={item} />}
Expand Down
6 changes: 3 additions & 3 deletions src/app/loadout-builder/LoadoutBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { searchFilterSelector } from 'app/search/search-filter';
import { useSetSetting } from 'app/settings/hooks';
import { AppIcon, disabledIcon, redoIcon, refreshIcon, undoIcon } from 'app/shell/icons';
import { querySelector, useIsPhonePortrait } from 'app/shell/selectors';
import { emptyObject } from 'app/utils/empty';
import { emptyArray, emptyObject } from 'app/utils/empty';
import { isClassCompatible, itemCanBeEquippedBy } from 'app/utils/item-utils';
import { DestinyClass } from 'bungie-api-ts/destiny2';
import clsx from 'clsx';
Expand Down Expand Up @@ -170,8 +170,8 @@ export default memo(function LoadoutBuilder({
[resolvedMods, autoStatMods],
);

const { vendorItems } = useLoVendorItems(selectedStoreId);
const armorItems = useArmorItems(classType, vendorItems);
const { vendorItems, vendorItemsLoading } = useLoVendorItems(selectedStoreId);
const armorItems = useArmorItems(classType, vendorItemsLoading ? emptyArray() : vendorItems);

const { modMap: lockedModMap, unassignedMods } = useMemo(
() => categorizeArmorMods(modsToAssign, armorItems),
Expand Down
11 changes: 9 additions & 2 deletions src/app/loadout-builder/loadout-builder-vendors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ const allowedVendorHashes = [

export const loVendorItemsSelector = currySelector(
createSelector(characterVendorItemsSelector, (allVendorItems) =>
allVendorItems.filter((item) => allowedVendorHashes.includes(item.vendor?.vendorHash ?? -1)),
allVendorItems.filter(
(item) =>
allowedVendorHashes.includes(item.vendor?.vendorHash ?? -1) &&
// filters out some dummy exotics
item.type !== 'Unknown',
),
),
);

Expand All @@ -34,7 +39,9 @@ export function useLoVendorItems(selectedStoreId: string) {
useLoadVendors(account, selectedStoreId);

return {
vendorItemsLoading: !vendors[selectedStoreId]?.vendorsResponse,
vendorItemsLoading:
!vendors[selectedStoreId]?.vendorsResponse ||
vendorItems.some((i) => i.missingSockets === 'not-loaded'),
vendorItems,
error: vendors[selectedStoreId]?.error,
};
Expand Down
1 change: 0 additions & 1 deletion src/app/records/Records.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ export default function Records({ account }: Props) {
{nodeHashes
.map((h) => defs.PresentationNode.get(h))
.map((nodeDef) => (
// console.log(nodeDef)
<section key={nodeDef.hash} id={`p_${nodeDef.hash}`}>
<CollapsibleTitle
title={overrideTitles[nodeDef.hash] || nodeDef.displayProperties.name}
Expand Down
4 changes: 3 additions & 1 deletion src/app/utils/socket-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,9 @@ function filterSocketCategories(
continue;
}
const categorySockets = getSocketsByIndexes(sockets, category.socketIndexes).filter(
(socketInfo) => socketInfo.plugged?.plugDef.displayProperties.name && allowSocket(socketInfo),
(socketInfo) =>
(socketInfo.plugged || socketInfo.plugOptions[0])?.plugDef.displayProperties.name &&
allowSocket(socketInfo),
);
if (categorySockets.length) {
socketsByCategory.set(category, categorySockets);
Expand Down
2 changes: 2 additions & 0 deletions src/app/vendors/Vendor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export default function Vendor({
}
extra={refreshTime && <Countdown endTime={refreshTime} className={styles.countdown} />}
sectionId={`d2vendor-${vendor.def.hash}`}
// hi! this sectionId formatting matters for dispatching vendor detail api requests.
// please modify carefully and see how it's used in vendorsNeedingComponents in loadAllVendors
>
<VendorItems
vendor={vendor}
Expand Down
Loading

0 comments on commit 42648cb

Please sign in to comment.