Skip to content

Commit

Permalink
Merge pull request #10706 from DestinyItemManager/perksdupe
Browse files Browse the repository at this point in the history
RFC: is:perksdupe
  • Loading branch information
bhollis committed Sep 6, 2024
2 parents 4288947 + f117c7f commit 5a47089
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 3 deletions.
3 changes: 2 additions & 1 deletion config/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@
"Dupe": "Shows duplicate items, including reissues",
"DupeCount": "Items that have the specified number of duplicates.",
"DupeLower": "Duplicate items, including reissues, that are not the highest power dupe. Only one duplicate is chosen as the highest, and the rest are considered lower.",
"DupePerks": "Shows items whose perks are either a duplicate of, or a subset of, another item of the same type.",
"Energy": "Shows items that have energy capacity (Armor 2.0).",
"Engrams": "Shows engrams.",
"EnhancedPerk": "Shows weapons that have the specified number of enhanced perks.",
Expand Down Expand Up @@ -321,7 +322,7 @@
"Stackable": "Shows items that can stack (ammo synths, strange coin, etc)",
"StackFull": "Show items which are at-capacity for their stack (Enhancement Cores, Strange Coins, Gunsmith Materials etc)",
"StatLower": "Shows armor whose stats are strictly lower than another of the same type of armor.",
"CustomStatLower": "Shows armor whose stats are strictly lower than another of the same type of armor, only taking into account stats in that class' custom stat total list.",
"CustomStatLower": "Shows armor whose stats are strictly lower than another of the same type of armor, only taking into account stats that are in any of that class' custom stat total list.",
"Stats": "Shows items based on a specific stat value. $t(Filter.StatsExtras)",
"StatsBase": "Filters armor based on its base stat value, not including attached mods or masterworking. $t(Filter.StatsExtras)",
"StatsExtras": "Supports stat addition by connecting multiple stat names with the + or & symbol. There are also special keywords highest, secondhighest, thirdhighest, etc. which match stats based on their rank within an item's stats. Each custom stats also has its own search term, shown in the Custom Stats settings.",
Expand Down
1 change: 1 addition & 0 deletions src/app/search/__snapshots__/search-config.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ exports[`buildSearchConfig generates a reasonable filter map: is filters 1`] = `
"deepsight",
"dupe",
"dupelower",
"dupeperks",
"emblems",
"emotes",
"energy",
Expand Down
26 changes: 25 additions & 1 deletion src/app/search/items/search-filters/dupes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { stripAdept } from 'app/compare/compare-utils';
import { tl } from 'app/i18next-t';
import { TagValue } from 'app/inventory/dim-item-info';
import { DimItem } from 'app/inventory/item-types';
import { StatsSet } from 'app/loadout-builder/process-worker/stats-set';
import { DEFAULT_SHADER, armorStats } from 'app/search/d2-known-values';
import { chainComparator, compareBy, reverseComparator } from 'app/utils/comparators';
import { isArtifice } from 'app/utils/item-utils';
import { DestinyClass } from 'bungie-api-ts/destiny2';
import { BucketHashes } from 'data/d2/generated-enums';
import { ItemFilterDefinition } from '../item-filter-types';
import { PerksSet } from './perks-set';
import { StatsSet } from './stats-set';

const notableTags = ['favorite', 'keep'];

Expand Down Expand Up @@ -203,6 +204,24 @@ const dupeFilters: ItemFilterDefinition[] = [
};
},
},
{
keywords: ['dupeperks'],
description: tl('Filter.DupePerks'),
filter: ({ allItems }) => {
const duplicates = new Map<string, PerksSet>();
for (const i of allItems) {
if (i.sockets?.allSockets.some((s) => s.isPerk && s.socketDefinition.defaultVisible)) {
if (!duplicates.has(i.typeName)) {
duplicates.set(i.typeName, new PerksSet());
}
duplicates.get(i.typeName)!.insert(i);
}
}
return (item) =>
item.sockets?.allSockets.some((s) => s.isPerk && s.socketDefinition.defaultVisible) &&
Boolean(duplicates.get(item.typeName)?.hasPerkDupes(item));
},
},
];

export default dupeFilters;
Expand All @@ -221,6 +240,11 @@ export function checkIfIsDupe(
);
}

/**
* Compute a set of items that are "stat lower" dupes. These are items for which
* there exists another item with strictly better stats (i.e. better in at least
* one stat and not worse in any stat).
*/
function computeStatDupeLower(allItems: DimItem[], relevantStatHashes: number[] = armorStats) {
// disregard no-class armor
const armor = allItems.filter((i) => i.bucket.inArmor && i.classType !== DestinyClass.Classified);
Expand Down
40 changes: 40 additions & 0 deletions src/app/search/items/search-filters/perks-set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { DimItem } from 'app/inventory/item-types';

/**
* A Perks can be populated with a bunch of items, and can then answer questions
* such as:
* 1. Are there any items that have (at least) all the same perks (in the same
* columns) as the input item? This covers both exactly-identical perk sets,
* as well as items that are perk-subsets of the input item (e.g. there may
* be another item that has all the same perks, plus some extra options in
* some columns).
*/
export class PerksSet {
// A map from item ID to a list of columns, each of which has a set of perkHashes
mapping = new Map<string, Set<number>[]>();

insert(item: DimItem) {
this.mapping.set(item.id, makePerksSet(item));
}

hasPerkDupes(item: DimItem) {
const perksSet = makePerksSet(item);

for (const [id, set] of this.mapping) {
if (id === item.id) {
continue;
}

if (perksSet.every((column) => set.some((otherColumn) => column.isSubsetOf(otherColumn)))) {
return true;
}
}
return false;
}
}

function makePerksSet(item: DimItem) {
return item
.sockets!.allSockets.filter((s) => s.isPerk && s.socketDefinition.defaultVisible)
.map((s) => new Set(s.plugOptions.map((p) => p.plugDef.hash)));
}
3 changes: 2 additions & 1 deletion src/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@
"CraftedDupe": "Shows duplicate weapons where at least one of the duplicates is crafted.",
"Curated": "Shows items that are a curated roll.",
"CurrentClass": "Shows items that are equippable on the currently logged in guardian.",
"CustomStatLower": "Shows armor whose stats are strictly lower than another of the same type of armor, only taking into account stats in that class' custom stat total list.",
"CustomStatLower": "Shows armor whose stats are strictly lower than another of the same type of armor, only taking into account stats that are in any of that class' custom stat total list.",
"DamageType": "Shows items based on their damage type.",
"Deepsight": "Shows weapons with Deepsight Resonance, which can have their pattern extracted, or which can have Deepsight Resonance enabled using a Deepsight Harmonizer.",
"Deprecated": "This filter is no longer supported.",
Expand All @@ -229,6 +229,7 @@
"Dupe": "Shows duplicate items, including reissues",
"DupeCount": "Items that have the specified number of duplicates.",
"DupeLower": "Duplicate items, including reissues, that are not the highest power dupe. Only one duplicate is chosen as the highest, and the rest are considered lower.",
"DupePerks": "Shows items whose perks are either a duplicate of, or a subset of, another item of the same type.",
"Energy": "Shows items that have energy capacity (Armor 2.0).",
"Engrams": "Shows engrams.",
"Enhanceable": "Shows weapons that can be enhanced.",
Expand Down

0 comments on commit 5a47089

Please sign in to comment.