Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: is:perksdupe #10706

Merged
merged 3 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading