export function optimalLoadout(applicableItems: DimItem[], bestItemFn: (item: DimItem) => number, name: string): Loadout { const itemsByType = _.groupBy(applicableItems, 'type'); // Pick the best item let items = _.mapObject(itemsByType, (items) => _.max(items, bestItemFn)); // Solve for the case where our optimizer decided to equip two exotics const getLabel = (i) => i.equippingLabel; // All items that share an equipping label, grouped by label const overlaps: _.Dictionary<DimItem[]> = _.groupBy(Object.values(items).filter(getLabel), getLabel); _.each(overlaps, (overlappingItems) => { if (overlappingItems.length <= 1) { return; } const options: _.Dictionary<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] = _.max(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 = _.max(options, (opt) => sum(Object.values(opt), bestItemFn)); items = bestOption; } }); // Copy the items and mark them equipped and put them in arrays, so they look like a loadout const finalItems: { [type: string]: DimItem[] } = {}; _.each(items, (item, type) => { const itemCopy = copy(item); itemCopy.equipped = true; finalItems[type.toLowerCase()] = [itemCopy]; }); return { classType: -1, name, items: finalItems }; }
export function getSetTiers(armorSets: ArmorSet[]): string[] { const tiersSet = new Set<string>(); armorSets.forEach((set: ArmorSet) => { set.tiers.forEach((tier: string) => { tiersSet.add(tier); }); }); const tiers = _.each( _.groupBy(Array.from(tiersSet.keys()), (tierString: string) => { return sum(tierString.split('/'), (num) => parseInt(num, 10)); }), (tier) => { tier.sort().reverse(); } ); const tierKeys = Object.keys(tiers); const setTiers: string[] = []; for (let tier = tierKeys.length; tier > tierKeys.length - 3; tier--) { if (tierKeys[tier]) { setTiers.push(t('LoadoutBuilder.SelectTierHeader', { tier: tierKeys[tier] })); tiers[tierKeys[tier]].forEach((set) => { setTiers.push(set); }); } } return setTiers; }
cloudThis: function (objectList, idProp, keyProp, textProp, options) { let exclude = options.exclude || []; let topN = options.topN || 50; let minCount = options.minCount || 1; let descFinder = options.descriptions || function () { return null; }; var plaqueLists = _.groupBy(objectList, (o) => { return o[keyProp]; }); let toplist = _.map(Object.keys(plaqueLists), function (key) { var tags = analyseArray(plaqueLists[key]); let words = _(Object.keys(tags)) .filter((w) => exclude.indexOf(w) === -1) .map((w) => { return { word: w, plaques: _.uniq(tags[w], (t) => t.id) }; }) .filter((w) => w.plaques.length > minCount); let sorted = _.sortBy(words, (w) => w.plaques.length * -1); let top10 = _.first(sorted, topN); return { key: key, words: top10, description: descFinder(key) }; }); return toplist; }
export function gatherTokensLoadout(storeService: StoreServiceType): Loadout { let tokens = storeService.getAllItems().filter((i) => { return REP_TOKENS.has(i.hash) && !i.notransfer; }); if (tokens.length === 0) { throw new Error(t('Loadouts.NoTokens')); } tokens = addUpStackables(tokens); const itemsByType = _.groupBy(tokens, 'type'); // Copy the items and put them in arrays, so they look like a loadout const finalItems = {}; _.each(itemsByType, (items, type) => { if (items) { finalItems[type.toLowerCase()] = items; } }); return { classType: -1, name: t('Loadouts.GatherTokens'), items: finalItems }; }
export function searchLoadout(storeService: StoreServiceType, store: DimStore): Loadout { let items = storeService.getAllItems().filter((i) => { return i.visible && !i.location.inPostmaster && !i.notransfer; }); items = addUpStackables(items); const itemsByType = _.mapObject(_.groupBy(items, 'type'), (items) => limitToBucketSize(items, store.isVault)); // Copy the items and mark them equipped and put them in arrays, so they look like a loadout const finalItems = {}; _.each(itemsByType, (items, type) => { if (items) { finalItems[type.toLowerCase()] = items.map((i) => { const copiedItem = copy(i); copiedItem.equipped = false; return copiedItem; }); } }); return { classType: -1, name: t('Loadouts.FilteredItems'), items: finalItems }; }
).then((items) => { store.items = items; // by type-bucket store.buckets = _.groupBy(items, (i) => i.location.id); store.vaultCounts = {}; // Fill in any missing buckets Object.values(buckets.byType).forEach((bucket) => { if (!store.buckets[bucket.id]) { store.buckets[bucket.id] = []; } if (bucket.vaultBucket) { const vaultBucketId = bucket.vaultBucket.id; store.vaultCounts[vaultBucketId] = store.vaultCounts[vaultBucketId] || { count: 0, bucket: bucket.accountWide ? bucket : bucket.vaultBucket }; store.vaultCounts[vaultBucketId].count += store.buckets[bucket.id].length; } }); return store; });
constructor(private navCtrl: NavController) { this.titles = []; const g = groupBy(TITLE_DEFAULT, (t) => t.classify); for (const c in g) { if (c in g) { this.titles.push([c, g[c]]); } } }
// Add up stackable items so we don't have duplicates. This helps us actually move them, see // https://github.com/DestinyItemManager/DIM/issues/2691#issuecomment-373970255 function addUpStackables(items: DimItem[]) { return flatMap(Object.values(_.groupBy(items, (t) => t.hash)), (items) => { if (items[0].maxStackSize > 1) { const item = copy(items[0]); item.amount = sum(items, (i) => i.amount); return [item]; } else { return items; } }); }
).then((items) => { store.items = items; // by type-bucket store.buckets = _.groupBy(items, (i) => i.location.id); // Fill in any missing buckets Object.values(buckets.byType).forEach((bucket) => { if (!store.buckets[bucket.id]) { store.buckets[bucket.id] = []; } }); return store; });
return processItems(store, items, itemComponents, previousItems, newItems, itemInfoService).then((items) => { store.items = items; // by type-bucket store.buckets = _.groupBy(items, (i) => { return i.location.id; }); // Fill in any missing buckets Object.values(buckets.byType).forEach((bucket) => { if (!store.buckets[bucket.id]) { store.buckets[bucket.id] = []; } }); return store; });