Пример #1
0
  _.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;
    }
  });
Пример #2
0
 function filterLoadoutToEquipped(loadout: Loadout) {
   const filteredLoadout = copy(loadout);
   filteredLoadout.items = _.mapValues(filteredLoadout.items, (items) => {
     return items.filter((i) => i.equipped);
   });
   return filteredLoadout;
 }
Пример #3
0
 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;
   }
 });
Пример #4
0
async function getAndCacheFromAdapters(): Promise<DimData> {
  try {
    const value = await getFromAdapters();
    cached = value || {};
    return copy(cached);
  } finally {
    _getPromise = undefined;
  }
}
Пример #5
0
 consolidateHashes.map((hash) => {
   const ret = copy(
     D1StoresService.getItemAcrossStores({
       hash
     })
   );
   if (ret) {
     ret.amount = _.sumBy(D1StoresService.getStores(), (s) => s.amountOfItem(ret));
   }
   return ret;
 })
Пример #6
0
  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;
  }
Пример #7
0
  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;
  }
Пример #8
0
    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);
    };
Пример #9
0
  /**
   * 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;
  }
Пример #10
0
  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();
  });