_.each(overlaps, (overlappingItems) => { if (overlappingItems.length <= 1) { return; } const options: { [x: string]: DimItem }[] = []; // For each item, replace all the others overlapping it with the next best thing for (const item of overlappingItems) { const option = copy(items); const otherItems = overlappingItems.filter((i) => i !== item); let optionValid = true; for (const otherItem of otherItems) { // Note: we could look for items that just don't have the *same* equippingLabel but // that may fail if there are ever mutual-exclusion items beyond exotics. const nonExotics = itemsByType[otherItem.type].filter((i) => !i.equippingLabel); if (nonExotics.length) { option[otherItem.type] = _.maxBy(nonExotics, bestItemFn)!; } else { // this option isn't usable because we couldn't swap this exotic for any non-exotic optionValid = false; } } if (optionValid) { options.push(option); } } // Pick the option where the optimizer function adds up to the biggest number, again favoring equipped stuff if (options.length > 0) { const bestOption = _.maxBy(options, (opt) => _.sumBy(Object.values(opt), bestItemFn))!; items = bestOption; } });
function filterLoadoutToEquipped(loadout: Loadout) { const filteredLoadout = copy(loadout); filteredLoadout.items = _.mapValues(filteredLoadout.items, (items) => { return items.filter((i) => i.equipped); }); return filteredLoadout; }
return _.flatMap(Object.values(_.groupBy(items, (t) => t.hash)), (items) => { if (items[0].maxStackSize > 1) { const item = copy(items[0]); item.amount = _.sumBy(items, (i) => i.amount); return [item]; } else { return items; } });
async function getAndCacheFromAdapters(): Promise<DimData> { try { const value = await getFromAdapters(); cached = value || {}; return copy(cached); } finally { _getPromise = undefined; } }
consolidateHashes.map((hash) => { const ret = copy( D1StoresService.getItemAcrossStores({ hash }) ); if (ret) { ret.amount = _.sumBy(D1StoresService.getStores(), (s) => s.amountOfItem(ret)); } return ret; })
function hydratev3d0(loadoutPrimitive: DehydratedLoadout): Loadout { const result: Loadout = { id: loadoutPrimitive.id, name: loadoutPrimitive.name, platform: loadoutPrimitive.platform, destinyVersion: loadoutPrimitive.destinyVersion, classType: _.isUndefined(loadoutPrimitive.classType) ? -1 : loadoutPrimitive.classType, items: { unknown: [] } }; for (const itemPrimitive of loadoutPrimitive.items) { const item = copy( getStoresService(result.destinyVersion).getItemAcrossStores({ id: itemPrimitive.id, hash: itemPrimitive.hash }) ); if (item) { const discriminator = item.type.toLowerCase(); item.equipped = itemPrimitive.equipped; item.amount = itemPrimitive.amount; result.items[discriminator] = result.items[discriminator] || []; result.items[discriminator].push(item); } else { const loadoutItem = { id: itemPrimitive.id, hash: itemPrimitive.hash, amount: itemPrimitive.amount, equipped: itemPrimitive.equipped }; result.items.unknown.push(loadoutItem as DimItem); } } return result; }
function mergeVendors([firstVendor, ...otherVendors]: Vendor[]) { const mergedVendor = copy(firstVendor); otherVendors.forEach((vendor) => { Object.assign(firstVendor.cacheKeys, vendor.cacheKeys); vendor.categories.forEach((category) => { const existingCategory = _.find(mergedVendor.categories, { title: category.title }); if (existingCategory) { mergeCategory(existingCategory, category); } else { mergedVendor.categories.push(category); } }); }); mergedVendor.allItems = _.flatten(mergedVendor.categories.map((i) => i.saleItems)); return mergedVendor; }
const doLoadout = async () => { if (allowUndo && !store.isVault) { reduxStore.dispatch( actions.savePreviousLoadout({ storeId: store.id, loadoutId: loadout.id, previousLoadout: store.loadoutFromCurrentlyEquipped( t('Loadouts.Before', { name: loadout.name }) ) }) ); } let items: DimItem[] = copy(_.flatten(Object.values(loadout.items))); const loadoutItemIds = items.map((i) => { return { id: i.id, hash: i.hash }; }); // Only select stuff that needs to change state let totalItems = items.length; items = items.filter((pseudoItem) => { const item = getLoadoutItem(pseudoItem, store); // provide a more accurate count of total items if (!item) { totalItems--; return true; } const notAlreadyThere = item.owner !== store.id || item.location.inPostmaster || // Needs to be equipped. Stuff not marked "equip" doesn't // necessarily mean to de-equip it. (pseudoItem.equipped && !item.equipped) || pseudoItem.amount > 1; return notAlreadyThere; }); // only try to equip subclasses that are equippable, since we allow multiple in a loadout items = items.filter((item) => { const ok = item.type !== 'Class' || !item.equipped || item.canBeEquippedBy(store); if (!ok) { totalItems--; } return ok; }); // vault can't equip if (store.isVault) { items.forEach((i) => { i.equipped = false; }); } // We'll equip these all in one go! let itemsToEquip = items.filter((i) => i.equipped); if (itemsToEquip.length > 1) { // we'll use the equipItems function itemsToEquip.forEach((i) => { i.equipped = false; }); } // Stuff that's equipped on another character. We can bulk-dequip these const itemsToDequip = items.filter((pseudoItem) => { const item = storeService.getItemAcrossStores(pseudoItem); return item && item.owner !== store.id && item.equipped; }); const scope = { failed: 0, total: totalItems, successfulItems: [] as DimItem[] }; if (itemsToDequip.length > 1) { const realItemsToDequip = _.compact( itemsToDequip.map((i) => storeService.getItemAcrossStores(i)) ); const dequips = _.map( _.groupBy(realItemsToDequip, (i) => i.owner), (dequipItems, owner) => { const equipItems = _.compact( dequipItems.map((i) => dimItemService.getSimilarItem(i, loadoutItemIds)) ); return dimItemService.equipItems(storeService.getStore(owner)!, equipItems); } ); await Promise.all(dequips); } await applyLoadoutItems(store, items, loadoutItemIds, scope); let equippedItems: DimItem[]; if (itemsToEquip.length > 1) { // Use the bulk equipAll API to equip all at once. itemsToEquip = itemsToEquip.filter((i) => scope.successfulItems.find((si) => si.id === i.id) ); const realItemsToEquip = _.compact(itemsToEquip.map((i) => getLoadoutItem(i, store))); equippedItems = await dimItemService.equipItems(store, realItemsToEquip); } else { equippedItems = itemsToEquip; } if (equippedItems.length < itemsToEquip.length) { const failedItems = itemsToEquip.filter((i) => { return !equippedItems.find((it) => it.id === i.id); }); failedItems.forEach((item) => { scope.failed++; toaster.pop('error', loadout.name, t('Loadouts.CouldNotEquip', { itemname: item.name })); }); } // We need to do this until https://github.com/DestinyItemManager/DIM/issues/323 // is fixed on Bungie's end. When that happens, just remove this call. if (scope.successfulItems.length > 0) { await storeService.updateCharacters(); } let value = 'success'; let message = t('Loadouts.Applied', { count: scope.total, store: store.name, gender: store.gender }); if (scope.failed > 0) { if (scope.failed === scope.total) { value = 'error'; message = t('Loadouts.AppliedError'); } else { value = 'warning'; message = t('Loadouts.AppliedWarn', { failed: scope.failed, total: scope.total }); } } toaster.pop(value, loadout.name, message); };
/** * Update our item and store models after an item has been moved (or equipped/dequipped). * @return the new or updated item (it may create a new item!) */ function updateItemModel( item: DimItem, source: DimStore, target: DimStore, equip: boolean, amount: number = item.amount ) { // Refresh all the items - they may have been reloaded! const storeService = item.getStoresService(); source = storeService.getStore(source.id)!; target = storeService.getStore(target.id)!; // We really shouldn't do this! item = storeService.getItemAcrossStores(item) || item; // If we've moved to a new place if (source.id !== target.id || item.location.inPostmaster) { // We handle moving stackable and nonstackable items almost exactly the same! const stackable = item.maxStackSize > 1; // Items to be decremented const sourceItems = stackable ? _.sortBy( source.buckets[item.location.id].filter((i) => { return i.hash === item.hash && i.id === item.id && !i.notransfer; }), (i) => i.amount ) : [item]; // Items to be incremented. There's really only ever at most one of these, but // it's easier to deal with as a list. const targetItems = stackable ? _.sortBy( target.buckets[item.bucket.id].filter((i) => { return ( i.hash === item.hash && i.id === item.id && // Don't consider full stacks as targets i.amount !== i.maxStackSize && !i.notransfer ); }), (i) => i.amount ) : []; // moveAmount could be more than maxStackSize if there is more than one stack on a character! const moveAmount = amount || item.amount || 1; let addAmount = moveAmount; let removeAmount = moveAmount; let removedSourceItem = false; // Remove inventory from the source while (removeAmount > 0) { let sourceItem = sourceItems.shift(); if (!sourceItem) { throw new Error(t('ItemService.TooMuch')); } const amountToRemove = Math.min(removeAmount, sourceItem.amount); if (amountToRemove === sourceItem.amount) { // Completely remove the source item if (source.removeItem(sourceItem)) { removedSourceItem = sourceItem.index === item.index; } } else { // Perf hack: by replacing the item entirely with a cloned // item that has an adjusted index, we force the ng-repeat // to refresh its view of the item, updating the // amount. This is because we've switched to bind-once for // the amount since it rarely changes. source.removeItem(sourceItem); sourceItem = copy(sourceItem); sourceItem.amount -= amountToRemove; sourceItem.index = createItemIndex(sourceItem); source.addItem(sourceItem); } removeAmount -= amountToRemove; } // Add inventory to the target (destination) let targetItem: DimItem; while (addAmount > 0) { targetItem = targetItems.shift()!; if (!targetItem) { targetItem = item; if (!removedSourceItem) { targetItem = copy(item); targetItem.index = createItemIndex(targetItem); } removedSourceItem = false; // only move without cloning once targetItem.amount = 0; // We'll increment amount below if (targetItem.location.inPostmaster) { targetItem.location = targetItem.bucket; } target.addItem(targetItem); } const amountToAdd = Math.min(addAmount, targetItem.maxStackSize - targetItem.amount); // Perf hack: by replacing the item entirely with a cloned // item that has an adjusted index, we force the ng-repeat to // refresh its view of the item, updating the amount. This is // because we've switched to bind-once for the amount since it // rarely changes. target.removeItem(targetItem); targetItem = shallowCopy(targetItem); targetItem.amount += amountToAdd; targetItem.index = createItemIndex(targetItem); target.addItem(targetItem); addAmount -= amountToAdd; } item = targetItem!; // The item we're operating on switches to the last target } if (equip) { target.buckets[item.bucket.id] = target.buckets[item.bucket.id].map((i) => { // TODO: this state needs to be moved out i.equipped = i.index === item.index; return i; }); } storeService.touch(); return item; }
getDefinitions().then((defs) => { Object.assign(vm, { active: 'titan', i18nClassNames: _.zipObject( ['titan', 'hunter', 'warlock'], _.sortBy(Object.values(defs.Class), (classDef) => classDef.classType).map( (c: any) => c.className ) ), i18nItemNames: _.zipObject( ['Helmet', 'Gauntlets', 'Chest', 'Leg', 'ClassItem', 'Artifact', 'Ghost'], [45, 46, 47, 48, 49, 38, 39].map((key) => defs.ItemCategory.get(key).title) ), activesets: '5/5/2', type: 'Helmet', scaleType: 'scaled', progress: 0, fullMode: false, includeVendors: false, showBlues: false, showExotics: true, showYear1: false, allSetTiers: [], hasSets: true, highestsets: {}, activeHighestSets: [], ranked: {}, activePerks: {}, excludeditems: [], collapsedConfigs: [false, false, false, false, false, false, false, false, false, false], lockeditems: { Helmet: null, Gauntlets: null, Chest: null, Leg: null, ClassItem: null, Artifact: null, Ghost: null }, lockedperks: { Helmet: {}, Gauntlets: {}, Chest: {}, Leg: {}, ClassItem: {}, Artifact: {}, Ghost: {} }, setOrderValues: ['-str_val', '-dis_val', '-int_val'], lockedItemsValid(droppedId: string, droppedType: ArmorTypes) { droppedId = getId(droppedId); if (alreadyExists(vm.excludeditems, droppedId)) { return false; } const item = getItemById(droppedId, droppedType)!; const startCount: number = item.isExotic && item.type !== 'ClassItem' ? 1 : 0; return ( startCount + (droppedType !== 'Helmet' && vm.lockeditems.Helmet && vm.lockeditems.Helmet.isExotic ? 1 : 0) + (droppedType !== 'Gauntlets' && vm.lockeditems.Gauntlets && vm.lockeditems.Gauntlets.isExotic ? 1 : 0) + (droppedType !== 'Chest' && vm.lockeditems.Chest && vm.lockeditems.Chest.isExotic ? 1 : 0) + (droppedType !== 'Leg' && vm.lockeditems.Leg && vm.lockeditems.Leg.isExotic ? 1 : 0) < 2 ); }, excludedItemsValid(droppedId: string, droppedType: ArmorTypes) { const lockedItem = vm.lockeditems[droppedType]; return !(lockedItem && alreadyExists([lockedItem], droppedId)); }, onSelectedChange(prevIdx, selectedIdx) { if (vm.activeCharacters[prevIdx].class !== vm.activeCharacters[selectedIdx].class) { const classType = vm.activeCharacters[selectedIdx].class; if (classType !== 'vault') { vm.active = classType; } vm.onCharacterChange(); vm.selectedCharacter = selectedIdx; } }, onCharacterChange() { vm.ranked = getActiveBuckets( buckets[vm.active], vendorBuckets[vm.active], vm.includeVendors ); vm.activeCharacters = D1StoresService.getStores().filter((s) => !s.isVault); const activeStore = D1StoresService.getActiveStore()!; vm.selectedCharacter = _.findIndex( vm.activeCharacters, (char) => char.id === activeStore.id ); vm.activePerks = getActiveBuckets( perks[vm.active], vendorPerks[vm.active], vm.includeVendors ); vm.lockeditems = { Helmet: null, Gauntlets: null, Chest: null, Leg: null, ClassItem: null, Artifact: null, Ghost: null }; vm.lockedperks = { Helmet: {}, Gauntlets: {}, Chest: {}, Leg: {}, ClassItem: {}, Artifact: {}, Ghost: {} }; vm.excludeditems = vm.excludeditems.filter((item: D1Item) => item.hash === 2672107540); vm.activesets = ''; vm.highestsets = vm.getSetBucketsStep(vm.active); }, onActiveSetsChange() { vm.activeHighestSets = getActiveHighestSets(vm.highestsets, vm.activesets); }, onModeChange() { vm.highestsets = vm.getSetBucketsStep(vm.active); }, onIncludeVendorsChange() { vm.activePerks = getActiveBuckets( perks[vm.active], vendorPerks[vm.active], vm.includeVendors ); if (vm.includeVendors) { vm.ranked = mergeBuckets(buckets[vm.active], vendorBuckets[vm.active]); } else { vm.ranked = buckets[vm.active]; // Filter any vendor items from locked or excluded items _.each(vm.lockeditems, (item, type) => { if (item && item.isVendorItem) { vm.lockeditems[type] = null; } }); vm.excludeditems = _.filter(vm.excludeditems, (item) => { return !item.isVendorItem; }); // Filter any vendor perks from locked perks _.each(vm.lockedperks, (perkMap, type) => { vm.lockedperks[type] = _.omitBy(perkMap, (_perk, perkHash) => { return _.find(vendorPerks[vm.active][type], { hash: Number(perkHash) }); }); }); } vm.highestsets = vm.getSetBucketsStep(vm.active); }, onPerkLocked(perk: D1GridNode, type: ArmorTypes, $event) { const lockedPerk = vm.lockedperks[type][perk.hash]; const activeType = $event.shiftKey ? lockedPerk && lockedPerk.lockType === 'and' ? 'none' : 'and' : lockedPerk && lockedPerk.lockType === 'or' ? 'none' : 'or'; if (activeType === 'none') { delete vm.lockedperks[type][perk.hash]; } else { vm.lockedperks[type][perk.hash] = { icon: perk.icon, description: perk.description, lockType: activeType }; } vm.highestsets = vm.getSetBucketsStep(vm.active); if (vm.progress < 1) { vm.perkschanged = true; } }, onDrop(droppedId, type) { droppedId = getId(droppedId); if (vm.lockeditems[type] && alreadyExists([vm.lockeditems[type]], droppedId)) { return; } const item = getItemById(droppedId, type); vm.lockeditems[type] = item; vm.highestsets = vm.getSetBucketsStep(vm.active); if (vm.progress < 1) { vm.lockedchanged = true; } }, onRemove(removedType) { vm.lockeditems[removedType] = null; vm.highestsets = vm.getSetBucketsStep(vm.active); if (vm.progress < 1) { vm.lockedchanged = true; } }, excludeItem(item: D1Item) { vm.onExcludedDrop(item.index, item.type); }, onExcludedDrop(droppedId, type) { droppedId = getId(droppedId); if ( alreadyExists(vm.excludeditems, droppedId) || (vm.lockeditems[type] && alreadyExists([vm.lockeditems[type]], droppedId)) ) { return; } const item = getItemById(droppedId, type)!; vm.excludeditems.push(item); vm.highestsets = vm.getSetBucketsStep(vm.active); if (vm.progress < 1) { vm.excludedchanged = true; } }, onExcludedRemove(removedIndex) { vm.excludeditems = vm.excludeditems.filter( (excludeditem) => excludeditem.index !== removedIndex ); vm.highestsets = vm.getSetBucketsStep(vm.active); if (vm.progress < 1) { vm.excludedchanged = true; } }, lockEquipped() { const store = vm.activeCharacters[vm.selectedCharacter]; const loadout = filterLoadoutToEquipped(store.loadoutFromCurrentlyEquipped('')); const items = _.pick( loadout.items, 'helmet', 'gauntlets', 'chest', 'leg', 'classitem', 'artifact', 'ghost' ); // Do not lock items with no stats vm.lockeditems.Helmet = items.helmet[0].stats ? (items.helmet[0] as D1Item) : null; vm.lockeditems.Gauntlets = items.gauntlets[0].stats ? (items.gauntlets[0] as D1Item) : null; vm.lockeditems.Chest = items.chest[0].stats ? (items.chest[0] as D1Item) : null; vm.lockeditems.Leg = items.leg[0].stats ? (items.leg[0] as D1Item) : null; vm.lockeditems.ClassItem = items.classitem[0].stats ? (items.classitem[0] as D1Item) : null; vm.lockeditems.Artifact = items.artifact[0].stats ? (items.artifact[0] as D1Item) : null; vm.lockeditems.Ghost = items.ghost[0].stats ? (items.ghost[0] as D1Item) : null; vm.highestsets = vm.getSetBucketsStep(vm.active); if (vm.progress < 1) { vm.lockedchanged = true; } }, clearLocked() { vm.lockeditems = { Helmet: null, Gauntlets: null, Chest: null, Leg: null, ClassItem: null, Artifact: null, Ghost: null }; vm.activesets = ''; vm.highestsets = vm.getSetBucketsStep(vm.active); if (vm.progress < 1) { vm.lockedchanged = true; } }, newLoadout(set) { ngDialog.closeAll(); const loadout = newLoadout('', {}); loadout.classType = LoadoutClass[vm.active]; const items = _.pick( set.armor, 'Helmet', 'Chest', 'Gauntlets', 'Leg', 'ClassItem', 'Ghost', 'Artifact' ); _.each(items, (itemContainer: any, itemType) => { loadout.items[itemType.toString().toLowerCase()] = [itemContainer.item]; }); dimLoadoutService.editLoadout(loadout, { equipAll: true, showClass: false }); }, equipItems(set) { ngDialog.closeAll(); let loadout: Loadout = newLoadout($i18next.t('Loadouts.AppliedAuto'), {}); loadout.classType = LoadoutClass[vm.active]; const items = _.pick( set.armor, 'Helmet', 'Chest', 'Gauntlets', 'Leg', 'ClassItem', 'Ghost', 'Artifact' ); loadout.items.helmet = [items.Helmet.item]; loadout.items.chest = [items.Chest.item]; loadout.items.gauntlets = [items.Gauntlets.item]; loadout.items.leg = [items.Leg.item]; loadout.items.classitem = [items.ClassItem.item]; loadout.items.ghost = [items.Ghost.item]; loadout.items.artifact = [items.Artifact.item]; loadout = copy(loadout); _.each(loadout.items, (val) => { val[0].equipped = true; }); return dimLoadoutService.applyLoadout( vm.activeCharacters[vm.selectedCharacter], loadout, true ); }, getSetBucketsStep(activeGuardian: string): typeof this.highestsets | null { const bestArmor: any = getBestArmor( buckets[activeGuardian], vendorBuckets[activeGuardian], vm.lockeditems, vm.excludeditems, vm.lockedperks ); const helms = bestArmor.Helmet || []; const gaunts = bestArmor.Gauntlets || []; const chests = bestArmor.Chest || []; const legs = bestArmor.Leg || []; const classItems = bestArmor.ClassItem || []; const ghosts = bestArmor.Ghost || []; const artifacts = bestArmor.Artifact || []; const setMap = {}; const tiersSet = new Set<string>(); const combos = helms.length * gaunts.length * chests.length * legs.length * classItems.length * ghosts.length * artifacts.length; if (combos === 0) { return null; } vm.hasSets = false; function step(activeGuardian, h, g, c, l, ci, gh, ar, processedCount) { for (; h < helms.length; ++h) { for (; g < gaunts.length; ++g) { for (; c < chests.length; ++c) { for (; l < legs.length; ++l) { for (; ci < classItems.length; ++ci) { for (; gh < ghosts.length; ++gh) { for (; ar < artifacts.length; ++ar) { const validSet = Number(helms[h].item.isExotic) + Number(gaunts[g].item.isExotic) + Number(chests[c].item.isExotic) + Number(legs[l].item.isExotic) < 2; if (validSet) { const set: ArmorSet = { armor: { Helmet: helms[h], Gauntlets: gaunts[g], Chest: chests[c], Leg: legs[l], ClassItem: classItems[ci], Artifact: artifacts[ar], Ghost: ghosts[gh] }, stats: { STAT_INTELLECT: { value: 0, tier: 0, name: 'Intellect', icon: intellectIcon }, STAT_DISCIPLINE: { value: 0, tier: 0, name: 'Discipline', icon: disciplineIcon }, STAT_STRENGTH: { value: 0, tier: 0, name: 'Strength', icon: strengthIcon } }, setHash: '', includesVendorItems: false }; vm.hasSets = true; const pieces = Object.values(set.armor); set.setHash = genSetHash(pieces); calcArmorStats(pieces, set.stats); const tiersString = `${set.stats.STAT_INTELLECT.tier}/${ set.stats.STAT_DISCIPLINE.tier }/${set.stats.STAT_STRENGTH.tier}`; tiersSet.add(tiersString); // Build a map of all sets but only keep one copy of armor // so we reduce memory usage if (setMap[set.setHash]) { if (setMap[set.setHash].tiers[tiersString]) { setMap[set.setHash].tiers[tiersString].configs.push( getBonusConfig(set.armor) ); } else { setMap[set.setHash].tiers[tiersString] = { stats: set.stats, configs: [getBonusConfig(set.armor)] }; } } else { setMap[set.setHash] = { set, tiers: {} }; setMap[set.setHash].tiers[tiersString] = { stats: set.stats, configs: [getBonusConfig(set.armor)] }; } set.includesVendorItems = pieces.some( (armor: any) => armor.item.isVendorItem ); } processedCount++; if (processedCount % 50000 === 0) { // do this so the page doesn't lock up if ( vm.active !== activeGuardian || vm.lockedchanged || vm.excludedchanged || vm.perkschanged || !vm.transition.router.stateService.is('destiny1.loadout-builder') ) { // If active guardian or page is changed then stop processing combinations vm.lockedchanged = false; vm.excludedchanged = false; vm.perkschanged = false; return; } vm.progress = processedCount / combos; $timeout( step, 0, true, activeGuardian, h, g, c, l, ci, gh, ar, processedCount ); return; } } ar = 0; } gh = 0; } ci = 0; } l = 0; } c = 0; } g = 0; } const tiers = _.each( _.groupBy(Array.from(tiersSet.keys()), (tierString: string) => { return _.sumBy(tierString.split('/'), (num) => parseInt(num, 10)); }), (tier) => { tier.sort().reverse(); } ); vm.allSetTiers = []; const tierKeys = Object.keys(tiers); for (let t = tierKeys.length; t > tierKeys.length - 3; t--) { if (tierKeys[t]) { vm.allSetTiers.push(`- Tier ${tierKeys[t]} -`); _.each(tiers[tierKeys[t]], (set) => { vm.allSetTiers.push(set); }); } } if (!vm.allSetTiers.includes(vm.activesets)) { vm.activesets = vm.allSetTiers[1]; } vm.activeHighestSets = getActiveHighestSets(setMap, vm.activesets); vm.collapsedConfigs = [ false, false, false, false, false, false, false, false, false, false ]; // Finish progress vm.progress = processedCount / combos; console.log('processed', combos, 'combinations.'); } vm.lockedchanged = false; vm.excludedchanged = false; vm.perkschanged = false; $timeout(step, 0, true, activeGuardian, 0, 0, 0, 0, 0, 0, 0, 0); return setMap; }, getBonus, getStore: D1StoresService.getStore, getItems() { const stores = D1StoresService.getStores(); vm.stores = stores; if (stores.length === 0) { vm.transition.router.stateService.go('destiny1.inventory'); return; } function filterPerks(perks: D1GridNode[], item: D1Item) { // ['Infuse', 'Twist Fate', 'Reforge Artifact', 'Reforge Shell', 'Increase Intellect', 'Increase Discipline', 'Increase Strength', 'Deactivate Chroma'] const unwantedPerkHashes = [ 1270552711, 217480046, 191086989, 913963685, 1034209669, 1263323987, 193091484, 2133116599 ]; return _.uniqBy(perks.concat(item.talentGrid!.nodes), (node: any) => node.hash).filter( (node: any) => !unwantedPerkHashes.includes(node.hash) ); } function filterItems(items: D1Item[]) { return items.filter((item) => { return ( item.primStat && item.primStat.statHash === 3897883278 && // has defense hash item.talentGrid && item.talentGrid.nodes && ((vm.showBlues && item.tier === 'Rare') || item.tier === 'Legendary' || (vm.showExotics && item.isExotic)) && // is legendary or exotic item.stats ); }); } vm.selectedCharacter = D1StoresService.getActiveStore(); vm.active = vm.selectedCharacter.class.toLowerCase() || 'warlock'; vm.activeCharacters = _.reject(D1StoresService.getStores(), (s) => s.isVault); vm.selectedCharacter = _.findIndex( vm.activeCharacters, (char) => char.id === vm.selectedCharacter.id ); let allItems: D1Item[] = []; let vendorItems: D1Item[] = []; _.each(stores, (store) => { const items = filterItems(store.items); // Exclude felwinters if we have them const felwinters = items.filter((i) => i.hash === 2672107540); if (felwinters.length) { vm.excludeditems.push(...felwinters); vm.excludeditems = _.uniqBy(vm.excludeditems, (i) => i.id); } allItems = allItems.concat(items); // Build a map of perks _.each(items, (item) => { if (item.classType === 3) { _.each(['warlock', 'titan', 'hunter'], (classType) => { perks[classType][item.type] = filterPerks(perks[classType][item.type], item); }); } else { perks[item.classTypeName][item.type] = filterPerks( perks[item.classTypeName][item.type], item ); } }); }); // Process vendors here _.each(dimVendorService.vendors, (vendor: any) => { const vendItems = filterItems( vendor.allItems .map((i) => i.item) .filter( (item) => item.bucket.sort === 'Armor' || item.type === 'Artifact' || item.type === 'Ghost' ) ); vendorItems = vendorItems.concat(vendItems); // Exclude felwinters if we have them const felwinters = vendorItems.filter((i) => i.hash === 2672107540); if (felwinters.length) { vm.excludeditems.push(...felwinters); vm.excludeditems = _.uniqBy(vm.excludeditems, (i) => i.id); } // Build a map of perks _.each(vendItems, (item) => { if (item.classType === 3) { _.each(['warlock', 'titan', 'hunter'], (classType) => { vendorPerks[classType][item.type] = filterPerks( vendorPerks[classType][item.type], item ); }); } else { vendorPerks[item.classTypeName][item.type] = filterPerks( vendorPerks[item.classTypeName][item.type], item ); } }); }); // Remove overlapping perks in allPerks from vendorPerks _.each(vendorPerks, (perksWithType, classType) => { _.each(perksWithType, (perkArr, type) => { vendorPerks[classType][type] = _.reject(perkArr, (perk: any) => perks[classType][type].map((i) => i.hash).includes(perk.hash) ); }); }); function initBuckets() { function normalizeStats(item: D1ItemWithNormalStats) { item.normalStats = {}; _.each(item.stats!, (stat: any) => { item.normalStats![stat.statHash] = { statHash: stat.statHash, base: stat.base, scaled: stat.scaled ? stat.scaled.min : 0, bonus: stat.bonus, split: stat.split, qualityPercentage: stat.qualityPercentage ? stat.qualityPercentage.min : 0 }; }); return item; } function getBuckets(items: D1Item[]): ItemBucket { return { Helmet: items .filter((item) => { return item.type === 'Helmet'; }) .map(normalizeStats), Gauntlets: items .filter((item) => { return item.type === 'Gauntlets'; }) .map(normalizeStats), Chest: items .filter((item) => { return item.type === 'Chest'; }) .map(normalizeStats), Leg: items .filter((item) => { return item.type === 'Leg'; }) .map(normalizeStats), ClassItem: items .filter((item) => { return item.type === 'ClassItem'; }) .map(normalizeStats), Artifact: items .filter((item) => { return item.type === 'Artifact'; }) .map(normalizeStats), Ghost: items .filter((item) => { return item.type === 'Ghost'; }) .map(normalizeStats) }; } function loadBucket(classType: DestinyClass, useVendorItems = false): ItemBucket { const items = useVendorItems ? vendorItems : allItems; return getBuckets( items.filter((item) => { return ( (item.classType === classType || item.classType === 3) && item.stats && item.stats.length ); }) ); } buckets = { titan: loadBucket(0), hunter: loadBucket(1), warlock: loadBucket(2) }; vendorBuckets = { titan: loadBucket(0, true), hunter: loadBucket(1, true), warlock: loadBucket(2, true) }; } initBuckets(); // Get items vm.onCharacterChange(); // Start processing } }); // Entry point of builder this get stores and starts processing vm.getItems(); });