From 1a13ea6eb47237acd07074f1fffe436ca919eae1 Mon Sep 17 00:00:00 2001 From: nznaza Date: Tue, 28 May 2024 22:15:56 -0600 Subject: [PATCH 01/17] feat: enable multiple exotic selection --- package-lock.json | 2 +- .../expanded-result-content.component.html | 8 +-- .../expanded-result-content.component.ts | 15 +++--- .../results/results.component.html | 49 ++++++++++++++----- .../results/results.component.ts | 14 +++--- .../desired-exotic-selection.component.ts | 16 ++++-- src/app/data/enum/modifierType.ts | 12 +++++ src/app/services/inventory.service.ts | 40 ++++++++------- src/app/services/results-builder.worker.ts | 46 +++++++---------- tsconfig.json | 2 +- tsconfig.worker.json | 2 +- 11 files changed, 122 insertions(+), 84 deletions(-) diff --git a/package-lock.json b/package-lock.json index 47d35557..303915c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "d2-armor-picker", - "version": "2.5.4", + "version": "2.6.0", "dependencies": { "@angular/animations": "^14.1.3", "@angular/cdk": "^14.1.3", diff --git a/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.html b/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.html index c5e8ed0d..60d35964 100644 --- a/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.html +++ b/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.html @@ -681,13 +681,13 @@ - + Exotic: - {{ exotic[0].name }} + {{ exotic.name }}
- - + +
diff --git a/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.ts b/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.ts index cdf55ef9..dfb15480 100644 --- a/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.ts +++ b/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.ts @@ -39,7 +39,7 @@ import { MatSnackBar } from "@angular/material/snack-bar"; import { BungieApiService } from "../../../../services/bungie-api.service"; import { ModOrAbility } from "../../../../data/enum/modOrAbility"; import { DestinyEnergyType, DestinyClass } from "bungie-api-ts/destiny2"; -import { ModifierType } from "../../../../data/enum/modifierType"; +import { ModifierNames, ModifierType } from "../../../../data/enum/modifierType"; import { BuildConfiguration } from "../../../../data/buildConfiguration"; import { takeUntil } from "rxjs/operators"; import { Subject } from "rxjs"; @@ -110,7 +110,7 @@ export class ExpandedResultContentComponent implements OnInit, OnDestroy { ngOnInit(): void { this.config.configuration.pipe(takeUntil(this.ngUnsubscribe)).subscribe((c) => { - this.config_characterClass = c.characterClass as unknown as DestinyClass; + this.config_characterClass = c.characterClass; this.config_assumeLegendariesMasterworked = c.assumeLegendariesMasterworked; this.config_assumeExoticsMasterworked = c.assumeExoticsMasterworked; this.config_assumeClassItemMasterworked = c.assumeClassItemMasterworked; @@ -307,16 +307,15 @@ export class ExpandedResultContentComponent implements OnInit, OnDestroy { if (c.selectedExotics.length == 1) { data.exoticArmorHash = c.selectedExotics[0]; } else { - var exos = this.element?.exotic; - if (exos && exos.length == 1) { - var exoticHash = exos[0].hash; - if (!!exoticHash) data.exoticArmorHash = parseInt(exoticHash, 10); - } + var exoticHash = this.element?.exotic?.hash; + if (!!exoticHash) data.exoticArmorHash = exoticHash; } const loadout: Loadout = { id: "d2ap", // this doesn't matter and will be replaced - name: "D2ArmorPicker Loadout", + name: `${ModifierNames[c.selectedModElement]} ${ + this.element?.exotic?.name + } D2ArmorPicker Loadout `, classType: c.characterClass as number, parameters: data, equipped: (this.element?.items || []).map(([i]) => ({ diff --git a/src/app/components/authenticated-v2/results/results.component.html b/src/app/components/authenticated-v2/results/results.component.html index 2506744a..e265168c 100644 --- a/src/app/components/authenticated-v2/results/results.component.html +++ b/src/app/components/authenticated-v2/results/results.component.html @@ -48,7 +48,7 @@ matTooltip="Note: To speed up the whole process, only {{ this.parsedResults | number }} results are listed in this table. - If you need more entries, disable the limitation in the advanced settings." + If you need more entries, disable the limitation in the advanced settings." >report_problem report_problem @@ -217,7 +217,7 @@ matSortDirection="asc" multiTemplateDataRows> + The actual rendered columns are set as a property on the row definition" --> + + + + + + # Build Shares + + {{ element.nonExoticsSetCount }} + Exotic - - - - - + + diff --git a/src/app/components/authenticated-v2/results/results.component.ts b/src/app/components/authenticated-v2/results/results.component.ts index 4b1d083c..635e7935 100644 --- a/src/app/components/authenticated-v2/results/results.component.ts +++ b/src/app/components/authenticated-v2/results/results.component.ts @@ -37,13 +37,12 @@ import { InventoryArmorSource } from "src/app/data/types/IInventoryArmor"; export interface ResultDefinition { exotic: | undefined - | [ - { - icon: string; - name: string; - hash: string; - } - ]; + | { + icon: string; + watermark: string; + name: string; + hash: number; + }; artifice: number[]; classItem: { perk: ArmorPerkOrSlot; @@ -53,6 +52,7 @@ export interface ResultDefinition { statsNoMods: number[]; items: ResultItem[][]; tiers: number; + maxTiers: number; waste: number; modCost: number; modCount: number; diff --git a/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts b/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts index a6b5d970..f0732258 100644 --- a/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts +++ b/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts @@ -20,7 +20,7 @@ import { ClassExoticInfo, InventoryService } from "../../../../services/inventor import { ConfigurationService } from "../../../../services/configuration.service"; import { animate, query, stagger, style, transition, trigger } from "@angular/animations"; import { ArmorSlot } from "../../../../data/enum/armor-slot"; -import { FORCE_USE_NO_EXOTIC } from "../../../../data/constants"; +import { FORCE_USE_ANY_EXOTIC, FORCE_USE_NO_EXOTIC } from "../../../../data/constants"; import { debounceTime, takeUntil } from "rxjs/operators"; import { Subject } from "rxjs"; import { DestinyClass } from "bungie-api-ts/destiny2"; @@ -125,11 +125,21 @@ export class DesiredExoticSelectionComponent implements OnInit, OnDestroy { if (index > -1) { // Always delete an item if it is already in the list this.selectedExotics.splice(index, 1); - } else if (hash == FORCE_USE_NO_EXOTIC) { + } else if ( + hash == FORCE_USE_NO_EXOTIC || + (this.selectedExotics.indexOf(FORCE_USE_NO_EXOTIC) != -1 && $event.shiftKey) + ) { this.selectedExotics = [FORCE_USE_NO_EXOTIC]; + } else if ( + hash == FORCE_USE_ANY_EXOTIC || + (this.selectedExotics.indexOf(FORCE_USE_ANY_EXOTIC) != -1 && $event.shiftKey) + ) { + this.selectedExotics = [FORCE_USE_ANY_EXOTIC]; } else if (this.selectedExotics.length == 0 || !$event.shiftKey) { - // if length is 0 or shift is NOT pressed, add the exotic + // if length is 0 or shift is NOT pressed, replace the selected exotic this.selectedExotics = [hash]; + } else { + this.selectedExotics.push(hash); } this.config.modifyConfiguration((c) => { c.selectedExotics = this.selectedExotics; diff --git a/src/app/data/enum/modifierType.ts b/src/app/data/enum/modifierType.ts index 59d0e0c9..46438209 100644 --- a/src/app/data/enum/modifierType.ts +++ b/src/app/data/enum/modifierType.ts @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +import { EnumDictionary } from "../types/EnumDictionary"; + export enum ModifierType { CombatStyleMod, Stasis, @@ -24,3 +26,13 @@ export enum ModifierType { Strand, RetrofitMods, // Artifact Retrofit mods } + +export const ModifierNames: EnumDictionary = { + [ModifierType.Arc]: "Arc", + [ModifierType.Solar]: "Solar", + [ModifierType.Void]: "Void", + [ModifierType.Stasis]: "Stasis", + [ModifierType.Strand]: "Strand", + [ModifierType.CombatStyleMod]: "", + [ModifierType.RetrofitMods]: "", +}; diff --git a/src/app/services/inventory.service.ts b/src/app/services/inventory.service.ts index a6f468b2..3cdc7860 100644 --- a/src/app/services/inventory.service.ts +++ b/src/app/services/inventory.service.ts @@ -35,11 +35,11 @@ import { isEqualItem, totalStats, } from "../data/types/IInventoryArmor"; -import { DestinyClass, ItemBindStatus, TierType } from "bungie-api-ts/destiny2"; +import { DestinyClass, TierType } from "bungie-api-ts/destiny2"; import { IPermutatorArmorSet } from "../data/types/IPermutatorArmorSet"; -import { getSkillTier, getStatSum, getWaste } from "./results-builder.worker"; +import { getSkillTier, getWaste } from "./results-builder.worker"; import { IPermutatorArmor } from "../data/types/IPermutatorArmor"; -import { FORCE_USE_NO_EXOTIC } from "../data/constants"; +import { FORCE_USE_ANY_EXOTIC, FORCE_USE_NO_EXOTIC } from "../data/constants"; import { VendorsService } from "./vendors.service"; type info = { @@ -258,7 +258,11 @@ export class InventoryService { }) // filter the selected exotic right here .filter( - (item) => config.selectedExotics.indexOf(FORCE_USE_NO_EXOTIC) == -1 || !item.isExotic + (item) => + !item.isExotic || + (config.selectedExotics.indexOf(FORCE_USE_NO_EXOTIC) == -1 && + config.selectedExotics.indexOf(item.hash) != -1) || + config.selectedExotics.indexOf(FORCE_USE_ANY_EXOTIC) != -1 ) .filter( (item) => @@ -363,18 +367,19 @@ export class InventoryService { ) as IInventoryArmor[]; let exotic = items.find((x) => x.isExotic); //let stats = getStatSum(items); + let tiers = getSkillTier(armorSet.statsWithMods); let v = { + loaded: false, exotic: exotic == null - ? [] - : [ - { - icon: exotic?.icon, - watermark: exotic?.watermarkIcon, - name: exotic?.name, - hash: exotic?.hash, - }, - ], + ? undefined + : { + icon: exotic?.icon, + watermark: exotic?.watermarkIcon, + name: exotic?.name, + hash: exotic?.hash, + }, + artifice: armorSet.usedArtifice, modCount: armorSet.usedMods.length, modCost: armorSet.usedMods.reduce( @@ -384,7 +389,8 @@ export class InventoryService { mods: armorSet.usedMods, stats: armorSet.statsWithMods, statsNoMods: armorSet.statsWithoutMods, - tiers: getSkillTier(armorSet.statsWithMods), + tiers: tiers, + maxTiers: 10 * (tiers + (5 - armorSet.usedMods.length)), waste: getWaste(armorSet.statsWithMods), items: items.reduce( (p: any, instance) => { @@ -413,12 +419,12 @@ export class InventoryService { }, [[], [], [], []] ), - classItem: armorSet.classItemPerk, + classItem: { perk: armorSet.classItemPerk }, usesCollectionRoll: items.some( (y) => y.source === InventoryArmorSource.Collections ), usesVendorRoll: items.some((y) => y.source === InventoryArmorSource.Vendor), - } as unknown as ResultDefinition; + } as ResultDefinition; endResults.push(v); } @@ -460,6 +466,7 @@ export class InventoryService { worker.terminate(); }; worker.postMessage({ + type: "builderRequest", currentClass: this.currentClass, config: this._config, threadSplit: { @@ -467,7 +474,6 @@ export class InventoryService { current: n, }, items, - selectedExotics, }); } } finally { diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 19796ecb..961bc075 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -19,7 +19,7 @@ import { BuildConfiguration } from "../data/buildConfiguration"; import { IDestinyArmor } from "../data/types/IInventoryArmor"; import { Database } from "../data/database"; import { ArmorSlot } from "../data/enum/armor-slot"; -import { FORCE_USE_ANY_EXOTIC } from "../data/constants"; +import { FORCE_USE_ANY_EXOTIC, FORCE_USE_NO_EXOTIC } from "../data/constants"; import { ModInformation } from "../data/ModInformation"; import { ArmorPerkOrSlot, @@ -50,31 +50,30 @@ function checkSlots( chest: IPermutatorArmor, leg: IPermutatorArmor ) { - var exoticId = config.selectedExotics[0] || 0; let requirements = constantModslotRequirement.slice(); if ( - (exoticId <= 0 || helmet.hash != exoticId) && + !helmet.isExotic && config.armorPerks[ArmorSlot.ArmorSlotHelmet].fixed && config.armorPerks[ArmorSlot.ArmorSlotHelmet].value != ArmorPerkOrSlot.None && config.armorPerks[ArmorSlot.ArmorSlotHelmet].value != helmet.perk ) return { valid: false }; if ( - (exoticId <= 0 || gauntlet.hash != exoticId) && + !gauntlet.isExotic && config.armorPerks[ArmorSlot.ArmorSlotGauntlet].fixed && config.armorPerks[ArmorSlot.ArmorSlotGauntlet].value != ArmorPerkOrSlot.None && config.armorPerks[ArmorSlot.ArmorSlotGauntlet].value != gauntlet.perk ) return { valid: false }; if ( - (exoticId <= 0 || chest.hash != exoticId) && + !chest.isExotic && config.armorPerks[ArmorSlot.ArmorSlotChest].fixed && config.armorPerks[ArmorSlot.ArmorSlotChest].value != ArmorPerkOrSlot.None && config.armorPerks[ArmorSlot.ArmorSlotChest].value != chest.perk ) return { valid: false }; if ( - (exoticId <= 0 || leg.hash != exoticId) && + !leg.isExotic && config.armorPerks[ArmorSlot.ArmorSlotLegs].fixed && config.armorPerks[ArmorSlot.ArmorSlotLegs].value != ArmorPerkOrSlot.None && config.armorPerks[ArmorSlot.ArmorSlotLegs].value != leg.perk @@ -93,13 +92,11 @@ function checkSlots( requirements[chest.perk]--; requirements[leg.perk]--; - // ignore exotic selection - if (exoticId > 0) { - if (helmet.hash == exoticId) requirements[config.armorPerks[helmet.slot].value]--; - else if (gauntlet.hash == exoticId) requirements[config.armorPerks[gauntlet.slot].value]--; - else if (chest.hash == exoticId) requirements[config.armorPerks[chest.slot].value]--; - else if (leg.hash == exoticId) requirements[config.armorPerks[leg.slot].value]--; - } + // ignore perk requirement in exotic + if (helmet.isExotic) requirements[config.armorPerks[helmet.slot].value]--; + else if (gauntlet.isExotic) requirements[config.armorPerks[gauntlet.slot].value]--; + else if (chest.isExotic) requirements[config.armorPerks[chest.slot].value]--; + else if (leg.isExotic) requirements[config.armorPerks[leg.slot].value]--; let bad = 0; for (let n = 1; n < ArmorPerkOrSlot.COUNT; n++) bad += Math.max(0, requirements[n]); @@ -171,28 +168,20 @@ function* generateArmorCombinations( gauntlets: IPermutatorArmor[], chests: IPermutatorArmor[], legs: IPermutatorArmor[], - constHasOneExoticLength: boolean, requiresAtLeastOneExotic: boolean ) { for (let helmet of helmets) { for (let gauntlet of gauntlets) { - if (constHasOneExoticLength && helmet.isExotic && gauntlet.isExotic) continue; + if (helmet.isExotic && gauntlet.isExotic) continue; for (let chest of chests) { - if (constHasOneExoticLength && (helmet.isExotic || gauntlet.isExotic) && chest.isExotic) - continue; + if ((helmet.isExotic || gauntlet.isExotic) && chest.isExotic) continue; for (let leg of legs) { - if ( - constHasOneExoticLength && - (helmet.isExotic || gauntlet.isExotic || chest.isExotic) && - leg.isExotic - ) - continue; + if ((helmet.isExotic || gauntlet.isExotic || chest.isExotic) && leg.isExotic) continue; if ( requiresAtLeastOneExotic && !(helmet.isExotic || gauntlet.isExotic || chest.isExotic || leg.isExotic) ) continue; - yield [helmet, gauntlet, chest, leg]; } } @@ -201,8 +190,9 @@ function* generateArmorCombinations( } addEventListener("message", async ({ data }) => { - const threadSplit = data.threadSplit as { count: number; current: number }; + if (data.type != "builderRequest") return; + const threadSplit = data.threadSplit as { count: number; current: number }; const startTime = Date.now(); console.debug("START RESULTS BUILDER 2"); console.time(`total #${threadSplit.current}`); @@ -220,7 +210,6 @@ addEventListener("message", async ({ data }) => { } console.log("Using config", data.config); - let selectedExotics = data.selectedExotics; let items = data.items as IPermutatorArmor[]; let helmets = items @@ -286,9 +275,9 @@ addEventListener("message", async ({ data }) => { const constantBonus = prepareConstantStatBonus(config); const constantModslotRequirement = prepareConstantModslotRequirement(config); const constantAvailableModslots = prepareConstantAvailableModslots(config); - const constHasOneExoticLength = selectedExotics.length <= 1; const hasArtificeClassItem = availableClassItemPerkTypes.has(ArmorPerkOrSlot.SlotArtifice); - const requiresAtLeastOneExotic = config.selectedExotics.indexOf(FORCE_USE_ANY_EXOTIC) > -1; + const requiresAtLeastOneExotic = + config.selectedExotics.indexOf(FORCE_USE_NO_EXOTIC) == -1 && config.selectedExotics.length > 0; console.log("hasArtificeClassItem", hasArtificeClassItem); @@ -306,7 +295,6 @@ addEventListener("message", async ({ data }) => { gauntlets, chests, legs, - constHasOneExoticLength, requiresAtLeastOneExotic )) { /** diff --git a/tsconfig.json b/tsconfig.json index 1271922c..82ab8ae4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,7 @@ "importHelpers": true, "target": "es2020", "module": "es2020", - "lib": ["es2019", "dom"] + "lib": ["es2020", "dom"] }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, diff --git a/tsconfig.worker.json b/tsconfig.worker.json index 34278ca2..7490f6f5 100644 --- a/tsconfig.worker.json +++ b/tsconfig.worker.json @@ -3,7 +3,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/worker", - "lib": ["es2019", "webworker"], + "lib": ["es2020", "webworker"], "types": [] }, "include": ["src/**/*.worker.ts"] From 2c435fc1ad128a8b5583bbbb92babb3888d13d12 Mon Sep 17 00:00:00 2001 From: nznaza Date: Tue, 28 May 2024 23:13:59 -0600 Subject: [PATCH 02/17] feat: similar build clustering --- .../results/results.component.html | 2 +- .../results/results.component.ts | 11 ++++- src/app/services/inventory.service.ts | 47 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/app/components/authenticated-v2/results/results.component.html b/src/app/components/authenticated-v2/results/results.component.html index e265168c..6b310ea0 100644 --- a/src/app/components/authenticated-v2/results/results.component.html +++ b/src/app/components/authenticated-v2/results/results.component.html @@ -362,7 +362,7 @@ *matHeaderCellDef mat-header-cell mat-sort-header="HashCount" - matTooltip="Number of builds generated with the selected settings that use the same non-exotic armor." + matTooltip="Number of builds generated that use the same non-exotic armor, when the builds use 2 different armor slot exotics, shows builds with at least 2 shared pieces." ># Build Shares {{ element.nonExoticsSetCount }} diff --git a/src/app/components/authenticated-v2/results/results.component.ts b/src/app/components/authenticated-v2/results/results.component.ts index 635e7935..f0ec3da8 100644 --- a/src/app/components/authenticated-v2/results/results.component.ts +++ b/src/app/components/authenticated-v2/results/results.component.ts @@ -59,6 +59,8 @@ export interface ResultDefinition { loaded: boolean; usesCollectionRoll?: boolean; usesVendorRoll?: boolean; + nonExoticsSetHash: bigint; + nonExoticsSetCount: number; } export enum ResultItemMoveState { @@ -193,6 +195,7 @@ export class ResultsComponent implements OnInit, OnDestroy { "mods", ]; if (c.showWastedStatsColumn) columns.push("waste"); + columns.push("nonExoticsSetCount"); if (c.includeVendorRolls || c.includeCollectionRolls) columns.push("source"); columns.push("dropdown"); this.shownColumns = columns; @@ -281,7 +284,13 @@ export class ResultsComponent implements OnInit, OnDestroy { // download the file let a = document.createElement("a"); a.download = "builds.json"; - const url = window.URL.createObjectURL(new Blob([JSON.stringify(jsonData, null, 2)])); + const url = window.URL.createObjectURL( + new Blob([ + JSON.stringify(jsonData, (key, value) => + typeof value === "bigint" ? value.toString() : value + ), + ]) + ); const link = document.createElement("a"); link.href = url; link.setAttribute("download", "d2ap_results.json"); diff --git a/src/app/services/inventory.service.ts b/src/app/services/inventory.service.ts index 3cdc7860..dddb0bc7 100644 --- a/src/app/services/inventory.service.ts +++ b/src/app/services/inventory.service.ts @@ -360,12 +360,42 @@ export class InventoryService { this.updatingResults = false; let endResults = []; + let permutationHashes = new Map(); + let permutationSlots = new Set( + results + .flatMap((a) => a.armor.map((x) => itemz.find((y) => y.id == x))) + .filter((a) => a !== undefined) + .filter((a) => a!.isExotic) + .map((a) => a!.slot) + ); for (let armorSet of results) { let items = armorSet.armor.map((x) => itemz.find((y) => y.id == x) ) as IInventoryArmor[]; let exotic = items.find((x) => x.isExotic); + // if the exotics are in 1 slot use the non exotic armor to allow "hotswappability" + // if the exotics are in 2 different slots, generate the Hash with the other 2 armor pieces, to allow "hotswappability" to cluster near + // if the exotics are in neither slot we don't care, use all armor + // if the exotics are in 3 or 4 slots there's no good strategy, cluster for it's non exotic parts + let legendaryArmor = + permutationSlots.size == 2 + ? items.filter((x) => !permutationSlots.has(x.slot)) + : items.filter((x) => !x.isExotic); + + let R = 0x9e3779b97f4a7c13n; //64bit golden ratio + let permHash = + legendaryArmor.reduce( + (previousValue, currentValue) => + BigInt(previousValue) * + (R + (BigInt((currentValue.itemInstanceId as any) | 0) << 1n)), + 1n + ) / 2n; + let permutationHash = permHash; + permutationHashes.set( + permutationHash, + (permutationHashes.get(permutationHash) ?? 0) + 1 + ); //let stats = getStatSum(items); let tiers = getSkillTier(armorSet.statsWithMods); let v = { @@ -424,10 +454,27 @@ export class InventoryService { (y) => y.source === InventoryArmorSource.Collections ), usesVendorRoll: items.some((y) => y.source === InventoryArmorSource.Vendor), + nonExoticsSetHash: permutationHash, + nonExoticsSetCount: 1, } as ResultDefinition; endResults.push(v); } + //Sort to keep sets with same legendary pieces together + endResults.sort((ob1, ob2) => ob2.tiers - ob1.tiers); + endResults.forEach( + (item) => (item.nonExoticsSetCount = permutationHashes.get(item.nonExoticsSetHash)!) + ); + endResults.sort((ob1, ob2) => { + if (ob1.nonExoticsSetHash > ob2.nonExoticsSetHash) { + return 1; + } else if (ob1.nonExoticsSetHash < ob2.nonExoticsSetHash) { + return -1; + } + return 0; + }); + endResults.sort((ob1, ob2) => ob2.nonExoticsSetCount - ob1.nonExoticsSetCount); + console.debug("endResults", endResults); this._armorResults.next({ From 9c3020237ca139074dc30267ac6b0c0fa63913fe Mon Sep 17 00:00:00 2001 From: nznaza Date: Wed, 29 May 2024 00:05:24 -0600 Subject: [PATCH 03/17] fix: fixed results break when no exotic was present --- .../components/authenticated-v2/results/results.component.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/components/authenticated-v2/results/results.component.html b/src/app/components/authenticated-v2/results/results.component.html index 6b310ea0..a36c9462 100644 --- a/src/app/components/authenticated-v2/results/results.component.html +++ b/src/app/components/authenticated-v2/results/results.component.html @@ -372,11 +372,13 @@ Exotic Date: Thu, 30 May 2024 20:24:57 -0600 Subject: [PATCH 04/17] feat: indicator if not all builds exotics can reach the same max stats --- .../desired-stat-selection.component.html | 1 + .../desired-stat-selection.component.ts | 5 ++ .../stat-tier-selection.component.html | 1 + .../stat-tier-selection.component.scss | 5 ++ .../stat-tier-selection.component.ts | 5 ++ src/app/services/inventory.service.ts | 88 +++++++++++++------ src/app/services/results-builder.worker.ts | 24 ++++- 7 files changed, 97 insertions(+), 32 deletions(-) diff --git a/src/app/components/authenticated-v2/settings/desired-stat-selection/desired-stat-selection.component.html b/src/app/components/authenticated-v2/settings/desired-stat-selection/desired-stat-selection.component.html index 6b6e7cb0..8a403727 100644 --- a/src/app/components/authenticated-v2/settings/desired-stat-selection/desired-stat-selection.component.html +++ b/src/app/components/authenticated-v2/settings/desired-stat-selection/desired-stat-selection.component.html @@ -57,6 +57,7 @@ [stat]="enum.value" [allowExactStats]="config_allowExactStats" [statsByMods]="statsByMods[enum.value]" + [minimumMaximumExoticAvailableTier]="minimumMaximumExoticAvailableTier[enum.value]" [maximumAvailableTier]="maximumPossibleTiers[enum.value]" [selectedTier]="minimumStatTiers[enum.value].value" [locked]="minimumStatTiers[enum.value].fixed" diff --git a/src/app/components/authenticated-v2/settings/desired-stat-selection/desired-stat-selection.component.ts b/src/app/components/authenticated-v2/settings/desired-stat-selection/desired-stat-selection.component.ts index 1ce98380..ec497f47 100644 --- a/src/app/components/authenticated-v2/settings/desired-stat-selection/desired-stat-selection.component.ts +++ b/src/app/components/authenticated-v2/settings/desired-stat-selection/desired-stat-selection.component.ts @@ -42,6 +42,7 @@ function calcScore(d: number[]) { export class DesiredStatSelectionComponent implements OnInit, OnDestroy { readonly stats: { name: string; value: ArmorStat }[]; minimumStatTiers: EnumDictionary> = getDefaultStatDict(1); + minimumMaximumExoticAvailableTier: number[] = [10, 10, 10, 10, 10, 10]; maximumPossibleTiers: number[] = [10, 10, 10, 10, 10, 10]; statsByMods: number[] = [0, 0, 0, 0, 0, 0]; _statCombo4x100: ArmorStat[][] = []; @@ -83,6 +84,10 @@ export class DesiredStatSelectionComponent implements OnInit, OnDestroy { this.maximumPossibleTiers = tiers; } + this.minimumMaximumExoticAvailableTier = d.minimumMaximumExoticPossibleTiers || [ + 10, 10, 10, 10, 10, 10, + ]; + this._statCombo3x100 = (d.statCombo3x100 || []).sort((a, b) => calcScore(b) - calcScore(a)); this._statCombo4x100 = d.statCombo4x100 || []; }); diff --git a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.html b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.html index f13bd75d..06b7fc6e 100644 --- a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.html +++ b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.html @@ -22,6 +22,7 @@ *ngFor="let tier of TierRange; let i = index" [checked]="selectedTier === i" [class.isFromMod]="isAddedByConfigMods(i)" + [class.isFromExotic]="isAddedByMaxExotic(i)" [class.mat-button-toggle-checked]="selectedTier >= i" [class.not-checked]="selectedTier < i" [disabled]="maximumAvailableTier < i" diff --git a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.scss b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.scss index f2dad949..0c196cdc 100644 --- a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.scss +++ b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.scss @@ -36,6 +36,11 @@ background-color: rgba(63, 110, 181, 0.15); } +.not-checked.isFromExotic { + background-color: rgba(63, 4, 5, 0.15); + //color: rgba(63, 4, 5, 0.7); +} + .lock-locked { color: #f44336; } diff --git a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.ts b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.ts index f3b36a58..7f75f3d5 100644 --- a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.ts +++ b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.ts @@ -29,6 +29,7 @@ export class StatTierSelectionComponent { @Input() stat: ArmorStat = ArmorStat.Mobility; @Input() statsByMods: number = 0; @Input() maximumAvailableTier: number = 10; + @Input() minimumMaximumExoticAvailableTier: number = 10; @Input() selectedTier: number = 0; @Input() locked: boolean = false; @Output() selectedTierChange = new EventEmitter(); @@ -58,6 +59,10 @@ export class StatTierSelectionComponent { ); } + isAddedByMaxExotic(index: number) { + return index > this.minimumMaximumExoticAvailableTier && index <= this.maximumAvailableTier; + } + toggleLockState() { this.locked = !this.locked; this.lockedChange.emit(this.locked); diff --git a/src/app/services/inventory.service.ts b/src/app/services/inventory.service.ts index 51c8c1de..93c557e7 100644 --- a/src/app/services/inventory.service.ts +++ b/src/app/services/inventory.service.ts @@ -46,6 +46,7 @@ import { ModOptimizationStrategy } from "../data/enum/mod-optimization-strategy" type info = { results: ResultDefinition[]; totalResults: number; + minimumMaximumExoticPossibleTiers: number[]; maximumPossibleTiers: number[]; statCombo3x100: ArmorStat[][]; statCombo4x100: ArmorStat[][]; @@ -91,11 +92,12 @@ export class InventoryService { private results: IPermutatorArmorSet[] = []; private totalPermutationCount = 0; private resultMaximumTiers: number[][] = []; + private resultMaximumExoticPossibleTiers: Map[] = []; private resultStatCombo3x100 = new Set(); private resultStatCombo4x100 = new Set(); private selectedExotics: IManifestArmor[] = []; - private itemz: IInventoryArmor[] = []; - private items: IPermutatorArmor[] = []; + private InventoryArmorItems: IInventoryArmor[] = []; + private PermutatorArmorItems: IPermutatorArmor[] = []; private endResults: ResultDefinition[] = []; constructor( @@ -163,6 +165,7 @@ export class InventoryService { totalResults: 0, totalTime: 0, itemCount: 0, + minimumMaximumExoticPossibleTiers: [0, 0, 0, 0, 0, 0], maximumPossibleTiers: [0, 0, 0, 0, 0, 0], statCombo3x100: [], statCombo4x100: [], @@ -263,7 +266,9 @@ export class InventoryService { this.results = []; this.totalPermutationCount = 0; + this.resultMaximumExoticPossibleTiers = []; this.resultMaximumTiers = []; + this.resultMaximumExoticPossibleTiers = []; this.resultStatCombo3x100 = new Set(); this.resultStatCombo4x100 = new Set(); const startTime = Date.now(); @@ -279,13 +284,13 @@ export class InventoryService { ); this.selectedExotics = this.selectedExotics.filter((i) => !!i); - this.itemz = (await this.db.inventoryArmor + this.InventoryArmorItems = (await this.db.inventoryArmor .where("clazz") .equals(config.characterClass) .distinct() .toArray()) as IInventoryArmor[]; - this.itemz = this.itemz + this.InventoryArmorItems = this.InventoryArmorItems // only armor :) .filter((item) => item.slot != ArmorSlot.ArmorSlotNone) // filter disabled items @@ -305,9 +310,9 @@ export class InventoryService { .filter( (item) => !item.isExotic || - (config.selectedExotics.indexOf(FORCE_USE_NO_EXOTIC) == -1 && - config.selectedExotics.indexOf(item.hash) != -1) || - config.selectedExotics.indexOf(FORCE_USE_ANY_EXOTIC) != -1 + config.selectedExotics.indexOf(item.hash) != -1 || + config.selectedExotics.indexOf(FORCE_USE_ANY_EXOTIC) != -1 || + config.selectedExotics.length == 0 ) .filter( (item) => @@ -351,10 +356,10 @@ export class InventoryService { // console.log(items.map(d => "id:'"+d.itemInstanceId+"'").join(" or ")) // Remove collection items if they are in inventory - this.itemz = this.itemz.filter((item) => { + this.InventoryArmorItems = this.InventoryArmorItems.filter((item) => { if (item.source === InventoryArmorSource.Inventory) return true; - const purchasedItemInstance = this.itemz.find( + const purchasedItemInstance = this.InventoryArmorItems.find( (rhs) => rhs.source === InventoryArmorSource.Inventory && isEqualItem(item, rhs) ); @@ -363,7 +368,7 @@ export class InventoryService { return purchasedItemInstance === undefined; }); - this.items = this.itemz.map((armor) => { + this.PermutatorArmorItems = this.InventoryArmorItems.map((armor) => { return { id: armor.id, hash: armor.hash, @@ -396,7 +401,9 @@ export class InventoryService { // Improve per thread performance by shuffling the inventory // sorting is a naive aproach that can be optimized // in my test is better than the default order from the db - this.items = this.items.sort((a, b) => totalStats(b) - totalStats(a)); + this.PermutatorArmorItems = this.PermutatorArmorItems.sort( + (a, b) => totalStats(b) - totalStats(a) + ); this._calculationProgress.next(0); for (let n = 0; n < nthreads; n++) { @@ -428,6 +435,7 @@ export class InventoryService { doneWorkerCount++; this.totalPermutationCount += data.stats.permutationCount; this.resultMaximumTiers.push(data.runtime.maximumPossibleTiers); + this.resultMaximumExoticPossibleTiers.push(data.runtime.maximumExoticPossibleTiers); for (let elem of data.runtime.statCombo3x100) this.resultStatCombo3x100.add(elem); for (let elem of data.runtime.statCombo4x100) this.resultStatCombo4x100.add(elem); } @@ -438,8 +446,8 @@ export class InventoryService { this.endResults = []; let permutationHashes = new Map(); let permutationSlots = new Set( - results - .flatMap((a) => a.armor.map((x) => itemz.find((y) => y.id == x))) + this.results + .flatMap((a) => a.armor.map((x) => this.InventoryArmorItems.find((y) => y.id == x))) .filter((a) => a !== undefined) .filter((a) => a!.isExotic) .map((a) => a!.slot) @@ -447,7 +455,7 @@ export class InventoryService { for (let armorSet of this.results) { let items = armorSet.armor.map((x) => - this.itemz.find((y) => y.id == x) + this.InventoryArmorItems.find((y) => y.id == x) ) as IInventoryArmor[]; let exotic = items.find((x) => x.isExotic); // if the exotics are in 1 slot use the non exotic armor to allow "hotswappability" @@ -474,7 +482,7 @@ export class InventoryService { ); //let stats = getStatSum(items); let tiers = getSkillTier(armorSet.statsWithMods); - + let v = { loaded: false, exotic: @@ -538,11 +546,11 @@ export class InventoryService { } //Sort to keep sets with same legendary pieces together - endResults.sort((ob1, ob2) => ob2.tiers - ob1.tiers); - endResults.forEach( + this.endResults.sort((ob1, ob2) => ob2.tiers - ob1.tiers); + this.endResults.forEach( (item) => (item.nonExoticsSetCount = permutationHashes.get(item.nonExoticsSetHash)!) ); - endResults.sort((ob1, ob2) => { + this.endResults.sort((ob1, ob2) => { if (ob1.nonExoticsSetHash > ob2.nonExoticsSetHash) { return 1; } else if (ob1.nonExoticsSetHash < ob2.nonExoticsSetHash) { @@ -550,15 +558,38 @@ export class InventoryService { } return 0; }); - endResults.sort((ob1, ob2) => ob2.nonExoticsSetCount - ob1.nonExoticsSetCount); + this.endResults.sort((ob1, ob2) => ob2.nonExoticsSetCount - ob1.nonExoticsSetCount); + + console.debug("endResults", this.endResults); + let minimumMaximumExoticPossibleTiers = [10, 10, 10, 10, 10, 10]; + let maximumExoticPossibleTiersMap = new Map(); + this.resultMaximumExoticPossibleTiers.forEach((m) => { + m.forEach((v, k) => { + let knownMaxForExotic = maximumExoticPossibleTiersMap.get(k) || [0, 0, 0, 0, 0, 0]; + for (let stat = 0; stat < 6; stat++) { + knownMaxForExotic[stat] = Math.max(knownMaxForExotic[stat], v[stat]); + } + maximumExoticPossibleTiersMap.set(k, knownMaxForExotic); + }); + }); + + maximumExoticPossibleTiersMap.forEach((values, key) => { + for (let stat = 0; stat < 6; stat++) { + minimumMaximumExoticPossibleTiers[stat] = Math.min( + minimumMaximumExoticPossibleTiers[stat], + Math.floor(Math.min(100, values[stat]) / 10) + ); + } + }); - console.debug("endResults", endResults); + console.log(`Values for ${minimumMaximumExoticPossibleTiers}`); this._armorResults.next({ results: this.endResults, totalResults: this.totalPermutationCount, // Total amount of results, differs from the real amount if the memory save setting is active itemCount: data.stats.itemCount, totalTime: Date.now() - startTime, + minimumMaximumExoticPossibleTiers: minimumMaximumExoticPossibleTiers, maximumPossibleTiers: this.resultMaximumTiers .reduce( (p, v) => { @@ -569,13 +600,13 @@ export class InventoryService { ) .map((k) => Math.floor(Math.min(100, k) / 10)), statCombo3x100: - Array.from(this.resultStatCombo3x100 as Set).map((d: number) => { + Array.from(this.resultStatCombo3x100).map((d: number) => { let r: ArmorStat[] = []; for (let n = 0; n < 6; n++) if ((d & (1 << n)) > 0) r.push(n); return r; }) || [], statCombo4x100: - Array.from(this.resultStatCombo4x100 as Set).map((d: number) => { + Array.from(this.resultStatCombo4x100).map((d: number) => { let r = []; for (let n = 0; n < 6; n++) if ((d & (1 << n)) > 0) r.push(n); return r; @@ -596,8 +627,7 @@ export class InventoryService { count: nthreads, current: n, }, - - items: this.items, + items: this.PermutatorArmorItems, selectedExotics: this.selectedExotics, }); } @@ -606,10 +636,12 @@ export class InventoryService { } estimateRequiredThreads(): number { - const helmets = this.items.filter((d) => d.slot == ArmorSlot.ArmorSlotHelmet); - const gauntlets = this.items.filter((d) => d.slot == ArmorSlot.ArmorSlotGauntlet); - const chests = this.items.filter((d) => d.slot == ArmorSlot.ArmorSlotChest); - const legs = this.items.filter((d) => d.slot == ArmorSlot.ArmorSlotLegs); + const helmets = this.PermutatorArmorItems.filter((d) => d.slot == ArmorSlot.ArmorSlotHelmet); + const gauntlets = this.PermutatorArmorItems.filter( + (d) => d.slot == ArmorSlot.ArmorSlotGauntlet + ); + const chests = this.PermutatorArmorItems.filter((d) => d.slot == ArmorSlot.ArmorSlotChest); + const legs = this.PermutatorArmorItems.filter((d) => d.slot == ArmorSlot.ArmorSlotLegs); const estimatedCalculations = this.estimateCombinationsToBeChecked( helmets, gauntlets, diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 9aa16e43..d049602b 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -291,9 +291,10 @@ addEventListener("message", async ({ data }) => { // runtime variables const runtime = { + maximumExoticPossibleTiers: new Map(), maximumPossibleTiers: [0, 0, 0, 0, 0, 0], - statCombo3x100: new Set(), - statCombo4x100: new Set(), + statCombo3x100: new Set(), + statCombo4x100: new Set(), }; const constantBonus = prepareConstantStatBonus(config); const constantModslotRequirement = prepareConstantModslotRequirement(config); @@ -315,7 +316,7 @@ addEventListener("message", async ({ data }) => { let estimatedCalculations = estimateCombinationsToBeChecked(helmets, gauntlets, chests, legs); let checkedCalculations = 0; let lastProgressReportTime = 0; - console.log("estimatedCalculations", estimatedCalculations); + console.log(`estimatedCalculations for thread #${threadSplit.current}`, estimatedCalculations); // define the delay; it can be 75ms if the estimated calculations are low // if the estimated calculations >= 1e6, then we will use 125ms @@ -424,7 +425,12 @@ export function getStatSum( } export function handlePermutation( - runtime: any, + runtime: { + maximumExoticPossibleTiers: Map; + maximumPossibleTiers: number[]; + statCombo3x100: Set; + statCombo4x100: Set; + }, config: BuildConfiguration, helmet: IPermutatorArmor, gauntlet: IPermutatorArmor, @@ -627,11 +633,19 @@ export function handlePermutation( //################################################################################# //* let n = 0; + let exoticHash = items.find((x) => x.isExotic)?.hash ?? 0; + let exoticmaximumPossibleTiers = runtime.maximumExoticPossibleTiers.get(exoticHash) ?? [ + 0, 0, 0, 0, 0, 0, + ]; for (let stat = 0; stat < 6; stat++) { if (runtime.maximumPossibleTiers[stat] < stats[stat]) { runtime.maximumPossibleTiers[stat] = stats[stat]; } + if (exoticmaximumPossibleTiers[stat] < stats[stat]) { + exoticmaximumPossibleTiers[stat] = stats[stat]; + } + const oldDistance = distances[stat]; for ( let tier = 10; @@ -654,11 +668,13 @@ export function handlePermutation( //const mods = null; if (mods != null) { runtime.maximumPossibleTiers[stat] = tier * 10; + exoticmaximumPossibleTiers[stat] = tier * 10; break; } } distances[stat] = oldDistance; } + runtime.maximumExoticPossibleTiers.set(exoticHash, exoticmaximumPossibleTiers); //console.debug("b "+runtime.maximumPossibleTiers,n) //console.warn(n) //*/ From 998cd6af4ff69ad48a8dfee63338f1b519bc6cb9 Mon Sep 17 00:00:00 2001 From: nznaza Date: Thu, 30 May 2024 20:58:00 -0600 Subject: [PATCH 05/17] feat: added show shared build column as an extra option --- .../authenticated-v2/results/results.component.html | 4 ++-- .../authenticated-v2/results/results.component.ts | 6 ++++-- .../advanced-settings/advanced-settings.component.ts | 9 +++++++++ src/app/data/buildConfiguration.ts | 2 ++ src/app/services/inventory.service.ts | 3 ++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/app/components/authenticated-v2/results/results.component.html b/src/app/components/authenticated-v2/results/results.component.html index a36c9462..6e15ce62 100644 --- a/src/app/components/authenticated-v2/results/results.component.html +++ b/src/app/components/authenticated-v2/results/results.component.html @@ -362,8 +362,8 @@ *matHeaderCellDef mat-header-cell mat-sort-header="HashCount" - matTooltip="Number of builds generated that use the same non-exotic armor, when the builds use 2 different armor slot exotics, shows builds with at least 2 shared pieces." - ># Build Shares + matTooltip="Number of builds generated that use the same non-exotic armor, when the builds use 2 different armor slot exotics, shows builds with at least 2 shared pieces."> + # Swapable builds {{ element.nonExoticsSetCount }} diff --git a/src/app/components/authenticated-v2/results/results.component.ts b/src/app/components/authenticated-v2/results/results.component.ts index f0ec3da8..67bb37b9 100644 --- a/src/app/components/authenticated-v2/results/results.component.ts +++ b/src/app/components/authenticated-v2/results/results.component.ts @@ -195,7 +195,7 @@ export class ResultsComponent implements OnInit, OnDestroy { "mods", ]; if (c.showWastedStatsColumn) columns.push("waste"); - columns.push("nonExoticsSetCount"); + if (c.includeLegendaryShareColumn) columns.push("nonExoticsSetCount"); if (c.includeVendorRolls || c.includeCollectionRolls) columns.push("source"); columns.push("dropdown"); this.shownColumns = columns; @@ -232,7 +232,7 @@ export class ResultsComponent implements OnInit, OnDestroy { case "Tiers": return data.tiers; case "Max Tiers": - return 10 * (data.tiers + (5 - data.modCount)); + return data.maxTiers; case "Waste": return data.waste; case "Mods": @@ -241,6 +241,8 @@ export class ResultsComponent implements OnInit, OnDestroy { //+ 40 * data.artifice.length data.modCost ); + case "HashCount": + return data.nonExoticsSetCount; } return 0; }; diff --git a/src/app/components/authenticated-v2/settings/advanced-settings/advanced-settings.component.ts b/src/app/components/authenticated-v2/settings/advanced-settings/advanced-settings.component.ts index 17ebb9df..c67b2f04 100644 --- a/src/app/components/authenticated-v2/settings/advanced-settings/advanced-settings.component.ts +++ b/src/app/components/authenticated-v2/settings/advanced-settings/advanced-settings.component.ts @@ -159,6 +159,15 @@ export class AdvancedSettingsComponent implements OnInit, OnDestroy { impactsResultCount: false, help: "Shows an additional column in the table that shows how many stats are wasted in a build.", }, + { + name: "Show the count of builds generated sharing non-exotic armor pieces.", + cp: (v: boolean) => + this.config.modifyConfiguration((c) => (c.includeLegendaryShareColumn = v)), + value: c.includeLegendaryShareColumn, + disabled: false, + impactsResultCount: false, + help: "Shows an additional column in the table with the count of how many builds generated use the same non-exotic armor pieces, or when 2 exotic slots are selected, the builds that share the other two slots", + }, ], "Wasted Stats": [ { diff --git a/src/app/data/buildConfiguration.ts b/src/app/data/buildConfiguration.ts index c9b138cf..a6a26c8b 100644 --- a/src/app/data/buildConfiguration.ts +++ b/src/app/data/buildConfiguration.ts @@ -85,6 +85,7 @@ export class BuildConfiguration { ignoreSunsetArmor = false; includeVendorRolls = false; includeCollectionRolls = false; + includeLegendaryShareColumn = false; assumeLegendariesMasterworked = true; assumeExoticsMasterworked = true; assumeClassItemMasterworked = true; @@ -125,6 +126,7 @@ export class BuildConfiguration { onlyUseMasterworkedExotics: false, onlyUseMasterworkedLegendaries: false, ignoreSunsetArmor: false, + includeLegendaryShareColumn: false, includeCollectionRolls: false, includeVendorRolls: false, allowBlueArmorPieces: true, diff --git a/src/app/services/inventory.service.ts b/src/app/services/inventory.service.ts index 93c557e7..ee1082cd 100644 --- a/src/app/services/inventory.service.ts +++ b/src/app/services/inventory.service.ts @@ -546,7 +546,8 @@ export class InventoryService { } //Sort to keep sets with same legendary pieces together - this.endResults.sort((ob1, ob2) => ob2.tiers - ob1.tiers); + //this.endResults.sort((ob1, ob2) => ob2.tiers - ob1.tiers); + this.endResults.sort((ob1, ob2) => (ob2.exotic?.hash ?? 0) - (ob1.exotic?.hash ?? 0)); this.endResults.forEach( (item) => (item.nonExoticsSetCount = permutationHashes.get(item.nonExoticsSetHash)!) ); From 618d063d26fd2eb4b6e0abe822a90252e19f162c Mon Sep 17 00:00:00 2001 From: nznaza Date: Thu, 30 May 2024 21:05:18 -0600 Subject: [PATCH 06/17] docs: improve help text for hotswap added columns --- .../authenticated-v2/results/results.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/authenticated-v2/results/results.component.html b/src/app/components/authenticated-v2/results/results.component.html index 6e15ce62..0f4bf79b 100644 --- a/src/app/components/authenticated-v2/results/results.component.html +++ b/src/app/components/authenticated-v2/results/results.component.html @@ -362,8 +362,8 @@ *matHeaderCellDef mat-header-cell mat-sort-header="HashCount" - matTooltip="Number of builds generated that use the same non-exotic armor, when the builds use 2 different armor slot exotics, shows builds with at least 2 shared pieces."> - # Swapable builds + matTooltip="Number of builds generated that use the same non-exotic armor, when 2 different armor slot exotics are generated, shows number of builds generated with at least 2 shared pieces."> + # Easy Swap builds {{ element.nonExoticsSetCount }}
From 13537445e2b92f11f098f5224b118761bcde34d0 Mon Sep 17 00:00:00 2001 From: nznaza Date: Thu, 30 May 2024 21:27:00 -0600 Subject: [PATCH 07/17] docs: add helper text for multiple exotic selection --- .../desired-exotic-selection.component.ts | 10 ++++++---- .../authenticated-v2/settings/settings.component.html | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts b/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts index f0732258..099216e1 100644 --- a/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts +++ b/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts @@ -127,16 +127,18 @@ export class DesiredExoticSelectionComponent implements OnInit, OnDestroy { this.selectedExotics.splice(index, 1); } else if ( hash == FORCE_USE_NO_EXOTIC || - (this.selectedExotics.indexOf(FORCE_USE_NO_EXOTIC) != -1 && $event.shiftKey) + (this.selectedExotics.indexOf(FORCE_USE_NO_EXOTIC) != -1 && + ($event.shiftKey || $event.ctrlKey)) ) { this.selectedExotics = [FORCE_USE_NO_EXOTIC]; } else if ( hash == FORCE_USE_ANY_EXOTIC || - (this.selectedExotics.indexOf(FORCE_USE_ANY_EXOTIC) != -1 && $event.shiftKey) + (this.selectedExotics.indexOf(FORCE_USE_ANY_EXOTIC) != -1 && + ($event.shiftKey || $event.ctrlKey)) ) { this.selectedExotics = [FORCE_USE_ANY_EXOTIC]; - } else if (this.selectedExotics.length == 0 || !$event.shiftKey) { - // if length is 0 or shift is NOT pressed, replace the selected exotic + } else if (this.selectedExotics.length == 0 || !($event.shiftKey || $event.ctrlKey)) { + // if length is 0 or shift or control is NOT pressed, replace the selected exotic this.selectedExotics = [hash]; } else { this.selectedExotics.push(hash); diff --git a/src/app/components/authenticated-v2/settings/settings.component.html b/src/app/components/authenticated-v2/settings/settings.component.html index aef23882..52664fb4 100644 --- a/src/app/components/authenticated-v2/settings/settings.component.html +++ b/src/app/components/authenticated-v2/settings/settings.component.html @@ -45,6 +45,7 @@ This section allows you to limit the armor pieces that are used in the buildcrafting process.
If you want, limit all results to one exotic.
+ Keep the Shift or Control key pressed to select multiple exotics
Exotics you do not have in the inventory or vault are grayed out. From d09ec38bd785655f417f24c49699cc32e2c363e9 Mon Sep 17 00:00:00 2001 From: nznaza Date: Fri, 31 May 2024 16:33:44 -0600 Subject: [PATCH 08/17] docs: add stat selection tooltips for unreachable stats --- .../stat-cooldown-tooltip.component.html | 6 ++++++ .../stat-cooldown-tooltip.component.ts | 8 ++++++++ .../stat-cooldown-tooltip.directive.ts | 7 ++++++- .../stat-tier-selection.component.html | 2 ++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.html b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.html index be81bb1f..aadd580e 100644 --- a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.html +++ b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.html @@ -21,6 +21,12 @@ {{ ArmorStatNames[stat] }} at Tier {{ tier }} + + Only achievable with some of your exotics + Not achievable with the current settings + Stat Tier {{ tier }} diff --git a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.ts b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.ts index 39f50534..d479a436 100644 --- a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.ts +++ b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.ts @@ -48,6 +48,8 @@ export class StatCooldownTooltipComponent implements OnInit { public ArmorStatNames = ArmorStatNames; @Input() tier: number = 0; + @Input() minimumMaximumExoticAvailableTier: number = 0; + @Input() maximumAvailableTier: number = 0; @Input() differenceTier: number = 0; // the tier we use to show a difference for @Input() stat: ArmorStat = ArmorStat.Mobility; @@ -104,4 +106,10 @@ export class StatCooldownTooltipComponent implements OnInit { getPercentageDifference(v1: number, v2: number) { return (v1 - v2) / Math.max(1, v2); } + + getIsAddedByExotic() { + return ( + this.tier > this.minimumMaximumExoticAvailableTier && this.tier <= this.maximumAvailableTier + ); + } } diff --git a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.directive.ts b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.directive.ts index 491847b9..61429884 100644 --- a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.directive.ts +++ b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.directive.ts @@ -40,7 +40,8 @@ export class StatCooldownTooltipDirective implements OnInit, OnDestroy { * This can be used to show the tooltip conditionally */ @Input() showToolTip: boolean = true; - + @Input() minimumMaximumExoticAvailableTier: number = 0; + @Input() maximumAvailableTier: number = 0; @Input() tooltipTier: number = 0; @Input() tooltipStat: ArmorStat = ArmorStat.Mobility; @Input() tooltipDifferenceTier: number = 0; // the tier we use to show a difference for @@ -102,6 +103,10 @@ export class StatCooldownTooltipDirective implements OnInit, OnDestroy { tooltipRef.instance.tier = this.tooltipTier; tooltipRef.instance.differenceTier = this.tooltipDifferenceTier; tooltipRef.instance.stat = this.tooltipStat; + tooltipRef.instance.maximumAvailableTier = this.maximumAvailableTier; + tooltipRef.instance.minimumMaximumExoticAvailableTier = + this.minimumMaximumExoticAvailableTier; + tooltipRef.instance.stat = this.tooltipStat; } } diff --git a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.html b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.html index 06b7fc6e..6b64b42d 100644 --- a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.html +++ b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.html @@ -31,6 +31,8 @@
From 51516b449753fcac78b1d0fbbac4eb593ceaa8a8 Mon Sep 17 00:00:00 2001 From: nznaza Date: Fri, 31 May 2024 19:45:01 -0600 Subject: [PATCH 09/17] feat: added early return max stats --- .../stat-cooldown-tooltip.component.html | 4 +- .../stat-tier-selection.component.scss | 7 ++- src/app/services/inventory.service.ts | 6 ++- src/app/services/results-builder.worker.ts | 45 +++++++++++++++---- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.html b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.html index aadd580e..feebef77 100644 --- a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.html +++ b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.html @@ -22,7 +22,9 @@ {{ ArmorStatNames[stat] }} at Tier {{ tier }} - Only achievable with some of your exotics + Not achievable by all selected exotics with the current settings Not achievable with the current settings diff --git a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.scss b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.scss index 0c196cdc..4f0ed418 100644 --- a/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.scss +++ b/src/app/components/authenticated-v2/settings/desired-stat-selection/stat-tier-selection/stat-tier-selection.component.scss @@ -33,12 +33,15 @@ } .not-checked.isFromMod { - background-color: rgba(63, 110, 181, 0.15); + background-color: rgba(63, 110, 181, 0.5); +} + +.mat-button-toggle-checked.isFromExotic { + background-color: rgba(63, 4, 5, 1); } .not-checked.isFromExotic { background-color: rgba(63, 4, 5, 0.15); - //color: rgba(63, 4, 5, 0.7); } .lock-locked { diff --git a/src/app/services/inventory.service.ts b/src/app/services/inventory.service.ts index ee1082cd..9a362734 100644 --- a/src/app/services/inventory.service.ts +++ b/src/app/services/inventory.service.ts @@ -165,7 +165,7 @@ export class InventoryService { totalResults: 0, totalTime: 0, itemCount: 0, - minimumMaximumExoticPossibleTiers: [0, 0, 0, 0, 0, 0], + minimumMaximumExoticPossibleTiers: [10, 10, 10, 10, 10, 10], maximumPossibleTiers: [0, 0, 0, 0, 0, 0], statCombo3x100: [], statCombo4x100: [], @@ -566,7 +566,9 @@ export class InventoryService { let maximumExoticPossibleTiersMap = new Map(); this.resultMaximumExoticPossibleTiers.forEach((m) => { m.forEach((v, k) => { - let knownMaxForExotic = maximumExoticPossibleTiersMap.get(k) || [0, 0, 0, 0, 0, 0]; + let knownMaxForExotic = maximumExoticPossibleTiersMap.get(k) || [ + -1, -1, -1, -1, -1, -1, + ]; for (let stat = 0; stat < 6; stat++) { knownMaxForExotic[stat] = Math.max(knownMaxForExotic[stat], v[stat]); } diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index d049602b..2fe3fc95 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -505,6 +505,10 @@ export function handlePermutation( } } + let exoticHash = items.find((x) => x.isExotic)?.hash ?? 0; + let exoticmaximumPossibleTiers = runtime.maximumExoticPossibleTiers.get(exoticHash) ?? [ + -1, -1, -1, -1, -1, -1, + ]; // distances required to reduce wasted stat points :) const optionalDistances = [0, 0, 0, 0, 0, 0]; if (config.tryLimitWastedStats) @@ -518,12 +522,42 @@ export function handlePermutation( optionalDistances[stat] = 10 - (stats[stat] % 10); } } + const totalOptionalDistances = optionalDistances.reduce((a, b) => a + b, 0); // if the sum of distances is > (10*5)+(3*artificeCount), we can abort here //const distanceSum = distances.reduce((a, b) => a + b, 0); const distanceSum = distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; - if (distanceSum > 10 * 5 + 3 * availableArtificeCount) return null; + if (distanceSum > 10 * 5 + 3 * availableArtificeCount) { + for (let stat = 0; stat < 6; stat++) { + const oldDistance = distances[stat]; + for (let tier = 10; tier >= 0; tier--) { + if (stats[stat] < tier * 10) { + const v = 10 - (stats[stat] % 10); + distances[stat] = Math.max(v < 10 ? v : 0, tier * 10 - stats[stat]); + const mods = get_mods_precalc( + config, + distances, + [0, 0, 0, 0, 0, 0], + availableArtificeCount, + availableModCost, + ModOptimizationStrategy.None + ); + distances[stat] = oldDistance; + //const mods = null; + if (mods != null) { + if (exoticmaximumPossibleTiers[stat] < tier * 10) { + exoticmaximumPossibleTiers[stat] = tier * 10; + break; + } + } + } + } + } + if (exoticHash != 0) + runtime.maximumExoticPossibleTiers.set(exoticHash, exoticmaximumPossibleTiers); + return null; + } let result: StatModifier[] | null; if (distanceSum == 0 && totalOptionalDistances == 0) result = []; @@ -632,11 +666,6 @@ export function handlePermutation( // Tier Availability Testing //################################################################################# //* - let n = 0; - let exoticHash = items.find((x) => x.isExotic)?.hash ?? 0; - let exoticmaximumPossibleTiers = runtime.maximumExoticPossibleTiers.get(exoticHash) ?? [ - 0, 0, 0, 0, 0, 0, - ]; for (let stat = 0; stat < 6; stat++) { if (runtime.maximumPossibleTiers[stat] < stats[stat]) { runtime.maximumPossibleTiers[stat] = stats[stat]; @@ -656,7 +685,6 @@ export function handlePermutation( if (stats[stat] >= tier * 10) break; const v = 10 - (stats[stat] % 10); distances[stat] = Math.max(v < 10 ? v : 0, tier * 10 - stats[stat]); - n++; const mods = get_mods_precalc( config, distances, @@ -674,7 +702,8 @@ export function handlePermutation( } distances[stat] = oldDistance; } - runtime.maximumExoticPossibleTiers.set(exoticHash, exoticmaximumPossibleTiers); + if (exoticHash != 0) + runtime.maximumExoticPossibleTiers.set(exoticHash, exoticmaximumPossibleTiers); //console.debug("b "+runtime.maximumPossibleTiers,n) //console.warn(n) //*/ From 1f32ad03a7c8cfddd5e62cc8f2632b4e3aa83e50 Mon Sep 17 00:00:00 2001 From: nznaza Date: Fri, 31 May 2024 20:59:14 -0600 Subject: [PATCH 10/17] fix: fixed max exotic tooltip not showing true max --- src/app/services/results-builder.worker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 2fe3fc95..16988ab4 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -530,6 +530,9 @@ export function handlePermutation( distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; if (distanceSum > 10 * 5 + 3 * availableArtificeCount) { for (let stat = 0; stat < 6; stat++) { + if (exoticmaximumPossibleTiers[stat] < stats[stat]) { + exoticmaximumPossibleTiers[stat] = stats[stat]; + } const oldDistance = distances[stat]; for (let tier = 10; tier >= 0; tier--) { if (stats[stat] < tier * 10) { From da519dcdfaf521a65cb13433ee173204a876c0dd Mon Sep 17 00:00:00 2001 From: nznaza Date: Fri, 31 May 2024 21:32:13 -0600 Subject: [PATCH 11/17] fix: fixed maximum exotic tier saving invalid combinations --- src/app/services/results-builder.worker.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 16988ab4..bff3320d 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -526,11 +526,14 @@ export function handlePermutation( const totalOptionalDistances = optionalDistances.reduce((a, b) => a + b, 0); // if the sum of distances is > (10*5)+(3*artificeCount), we can abort here //const distanceSum = distances.reduce((a, b) => a + b, 0); - const distanceSum = + let distanceSum = distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; if (distanceSum > 10 * 5 + 3 * availableArtificeCount) { for (let stat = 0; stat < 6; stat++) { - if (exoticmaximumPossibleTiers[stat] < stats[stat]) { + if ( + exoticmaximumPossibleTiers[stat] < stats[stat] && + distanceSum > 10 * 5 + 3 * availableArtificeCount + ) { exoticmaximumPossibleTiers[stat] = stats[stat]; } const oldDistance = distances[stat]; @@ -538,6 +541,8 @@ export function handlePermutation( if (stats[stat] < tier * 10) { const v = 10 - (stats[stat] % 10); distances[stat] = Math.max(v < 10 ? v : 0, tier * 10 - stats[stat]); + distanceSum = + distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; const mods = get_mods_precalc( config, distances, @@ -549,7 +554,10 @@ export function handlePermutation( distances[stat] = oldDistance; //const mods = null; if (mods != null) { - if (exoticmaximumPossibleTiers[stat] < tier * 10) { + if ( + exoticmaximumPossibleTiers[stat] < tier * 10 && + distanceSum > 10 * 5 + 3 * availableArtificeCount + ) { exoticmaximumPossibleTiers[stat] = tier * 10; break; } From 1c7eec2e6dc3809695004b598e43a50cf0e0b491 Mon Sep 17 00:00:00 2001 From: nznaza Date: Fri, 31 May 2024 22:55:04 -0600 Subject: [PATCH 12/17] revert: use naive approach to get max stats in unreachable exotic builds --- src/app/services/results-builder.worker.ts | 46 ++++++---------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index bff3320d..16d9b297 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -442,6 +442,10 @@ export function handlePermutation( hasArtificeClassItem = false ): never[] | IPermutatorArmorSet | null { const items = [helmet, gauntlet, chest, leg]; + let exoticHash = items.find((x) => x.isExotic)?.hash ?? 0; + let exoticmaximumPossibleTiers = runtime.maximumExoticPossibleTiers.get(exoticHash) ?? [ + -1, -1, -1, -1, -1, -1, + ]; var totalStatBonus = config.assumeClassItemMasterworked ? 2 : 0; for (let i = 0; i < items.length; i++) { let item = items[i]; // add masterworked value, if necessary @@ -505,10 +509,6 @@ export function handlePermutation( } } - let exoticHash = items.find((x) => x.isExotic)?.hash ?? 0; - let exoticmaximumPossibleTiers = runtime.maximumExoticPossibleTiers.get(exoticHash) ?? [ - -1, -1, -1, -1, -1, -1, - ]; // distances required to reduce wasted stat points :) const optionalDistances = [0, 0, 0, 0, 0, 0]; if (config.tryLimitWastedStats) @@ -530,37 +530,17 @@ export function handlePermutation( distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; if (distanceSum > 10 * 5 + 3 * availableArtificeCount) { for (let stat = 0; stat < 6; stat++) { - if ( - exoticmaximumPossibleTiers[stat] < stats[stat] && - distanceSum > 10 * 5 + 3 * availableArtificeCount - ) { - exoticmaximumPossibleTiers[stat] = stats[stat]; - } const oldDistance = distances[stat]; for (let tier = 10; tier >= 0; tier--) { - if (stats[stat] < tier * 10) { - const v = 10 - (stats[stat] % 10); - distances[stat] = Math.max(v < 10 ? v : 0, tier * 10 - stats[stat]); - distanceSum = - distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; - const mods = get_mods_precalc( - config, - distances, - [0, 0, 0, 0, 0, 0], - availableArtificeCount, - availableModCost, - ModOptimizationStrategy.None - ); - distances[stat] = oldDistance; - //const mods = null; - if (mods != null) { - if ( - exoticmaximumPossibleTiers[stat] < tier * 10 && - distanceSum > 10 * 5 + 3 * availableArtificeCount - ) { - exoticmaximumPossibleTiers[stat] = tier * 10; - break; - } + const v = 10 - (stats[stat] % 10); + distances[stat] = Math.max(v < 10 ? v : 0, tier * 10 - stats[stat]); + distanceSum = + distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; + distances[stat] = oldDistance; + if (distanceSum <= 10 * 5 + 3 * availableArtificeCount) { + if (exoticmaximumPossibleTiers[stat] < tier * 10) { + exoticmaximumPossibleTiers[stat] = tier * 10; + break; } } } From 7c894b20c8b43d51e79140b20ec7348c899380ef Mon Sep 17 00:00:00 2001 From: nznaza Date: Sat, 1 Jun 2024 05:30:57 -0600 Subject: [PATCH 13/17] feat: use available mod slots to determine max stats for exotics --- src/app/services/results-builder.worker.ts | 38 +++++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 16d9b297..0ac41f27 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -531,17 +531,15 @@ export function handlePermutation( if (distanceSum > 10 * 5 + 3 * availableArtificeCount) { for (let stat = 0; stat < 6; stat++) { const oldDistance = distances[stat]; - for (let tier = 10; tier >= 0; tier--) { + for (let tier = 10; tier >= exoticmaximumPossibleTiers[stat]; tier--) { const v = 10 - (stats[stat] % 10); distances[stat] = Math.max(v < 10 ? v : 0, tier * 10 - stats[stat]); distanceSum = distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; distances[stat] = oldDistance; - if (distanceSum <= 10 * 5 + 3 * availableArtificeCount) { - if (exoticmaximumPossibleTiers[stat] < tier * 10) { - exoticmaximumPossibleTiers[stat] = tier * 10; - break; - } + if (distanceSum <= getAvailableStatsByMods(availableArtificeCount, availableModCost)) { + exoticmaximumPossibleTiers[stat] = tier * 10; + break; } } } @@ -670,7 +668,7 @@ export function handlePermutation( for ( let tier = 10; tier >= config.minimumStatTiers[stat as ArmorStat].value && - tier > runtime.maximumPossibleTiers[stat] / 10; + tier > exoticmaximumPossibleTiers[stat] / 10; tier-- ) { if (stats[stat] >= tier * 10) break; @@ -686,7 +684,8 @@ export function handlePermutation( ); //const mods = null; if (mods != null) { - runtime.maximumPossibleTiers[stat] = tier * 10; + if (runtime.maximumPossibleTiers[stat] < tier * 100) + runtime.maximumPossibleTiers[stat] = tier * 10; exoticmaximumPossibleTiers[stat] = tier * 10; break; } @@ -737,7 +736,10 @@ function get_mods_precalc( optimize: ModOptimizationStrategy = ModOptimizationStrategy.None ): StatModifier[] | null { // check distances <= 65 - if (distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5] > 65) + if ( + distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5] > + getAvailableStatsByMods(availableArtificeCount, availableModCost) + ) return null; const modCombinations = config.onlyShowResultsWithNoWastedStats @@ -921,6 +923,24 @@ function get_mods_precalc( return usedMods; } +function getAvailableStatsByMods( + availableArtificeCount: number, + availableModCost: number[] +): number { + let LargeMajorMod = availableModCost.filter((x) => x >= 4).length; + let MediumMajorMod = availableModCost.filter((x) => x >= 3).length - LargeMajorMod; + let LargeMinorMod = + availableModCost.filter((x) => x >= 2).length - LargeMajorMod - MediumMajorMod; + let MediumMinorMod = + availableModCost.filter((x) => x >= 1).length - LargeMajorMod - MediumMajorMod - LargeMinorMod; + + return ( + (LargeMajorMod + MediumMajorMod) * 10 + + (LargeMinorMod + MediumMinorMod) * 5 + + availableArtificeCount * 3 + ); +} + export function getSkillTier(stats: number[]) { return ( Math.floor(Math.min(100, stats[ArmorStat.Mobility]) / 10) + From d4031e09029f030b0646782abd61239084613b69 Mon Sep 17 00:00:00 2001 From: nznaza Date: Sat, 1 Jun 2024 14:51:24 -0600 Subject: [PATCH 14/17] feat: default to first character instead of titan --- .../expanded-result-content.component.ts | 2 +- .../desired-exotic-selection.component.ts | 3 ++- .../slot-limitation-selection.component.ts | 2 +- src/app/data/buildConfiguration.ts | 4 ++-- src/app/services/results-builder.worker.spec.ts | 2 +- src/app/services/userdata.service.ts | 5 +++++ 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.ts b/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.ts index dfb15480..afa4fd85 100644 --- a/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.ts +++ b/src/app/components/authenticated-v2/results/expanded-result-content/expanded-result-content.component.ts @@ -64,7 +64,7 @@ export class ExpandedResultContentComponent implements OnInit, OnDestroy { public ArmorStatIconUrls = ArmorStatIconUrls; public ArmorStat = ArmorStat; public StatModifier = StatModifier; - public config_characterClass = DestinyClass.Titan; + public config_characterClass = DestinyClass.Unknown; public config_assumeLegendariesMasterworked = false; public config_assumeExoticsMasterworked = false; public config_assumeClassItemMasterworked = false; diff --git a/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts b/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts index 099216e1..718ffb7b 100644 --- a/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts +++ b/src/app/components/authenticated-v2/settings/desired-exotic-selection/desired-exotic-selection.component.ts @@ -47,7 +47,7 @@ export class DesiredExoticSelectionComponent implements OnInit, OnDestroy { includeVendorRolls = false; ignoreSunsetArmor = false; allowBlueArmorPieces = false; - currentClass: DestinyClass = DestinyClass.Titan; + currentClass: DestinyClass = DestinyClass.Unknown; exotics: ClassExoticInfo[][] = []; constructor(public inventory: InventoryService, public config: ConfigurationService) {} @@ -89,6 +89,7 @@ export class DesiredExoticSelectionComponent implements OnInit, OnDestroy { } this.exotics = [ + [], uniq(armors.filter((a) => a.item.slot == ArmorSlot.ArmorSlotHelmet)), uniq(armors.filter((a) => a.item.slot == ArmorSlot.ArmorSlotGauntlet)), uniq(armors.filter((a) => a.item.slot == ArmorSlot.ArmorSlotChest)), diff --git a/src/app/components/authenticated-v2/settings/desired-mod-limit-selection/slot-limitation-selection/slot-limitation-selection.component.ts b/src/app/components/authenticated-v2/settings/desired-mod-limit-selection/slot-limitation-selection/slot-limitation-selection.component.ts index c5b26fb9..f3d95a14 100644 --- a/src/app/components/authenticated-v2/settings/desired-mod-limit-selection/slot-limitation-selection/slot-limitation-selection.component.ts +++ b/src/app/components/authenticated-v2/settings/desired-mod-limit-selection/slot-limitation-selection/slot-limitation-selection.component.ts @@ -54,7 +54,7 @@ export class SlotLimitationSelectionComponent implements OnInit, OnDestroy, Afte possible: EventEmitter = new EventEmitter(); isPossible: boolean = true; - configSelectedClass: DestinyClass = DestinyClass.Titan; + configSelectedClass: DestinyClass = DestinyClass.Unknown; configAssumeLegendaryIsArtifice: boolean = false; configAssumeClassItemIsArtifice: boolean = false; armorPerk: ArmorPerkOrSlot = ArmorPerkOrSlot.None; diff --git a/src/app/data/buildConfiguration.ts b/src/app/data/buildConfiguration.ts index a6a26c8b..2f9b60ac 100644 --- a/src/app/data/buildConfiguration.ts +++ b/src/app/data/buildConfiguration.ts @@ -43,7 +43,7 @@ export interface FixableSelection { } export class BuildConfiguration { - characterClass: DestinyClass = DestinyClass.Titan; + characterClass: DestinyClass = DestinyClass.Unknown; // Add constant +1 strength addConstent1Resilience = false; @@ -139,7 +139,7 @@ export class BuildConfiguration { onlyShowResultsWithNoWastedStats: false, showWastedStatsColumn: false, showPotentialTierColumn: false, - characterClass: DestinyClass.Titan, + characterClass: DestinyClass.Unknown, selectedModElement: ModifierType.Stasis, selectedExotics: [], maximumModSlots: { diff --git a/src/app/services/results-builder.worker.spec.ts b/src/app/services/results-builder.worker.spec.ts index 30ac9fd0..2d3dbc90 100644 --- a/src/app/services/results-builder.worker.spec.ts +++ b/src/app/services/results-builder.worker.spec.ts @@ -125,7 +125,7 @@ function buildTestItem( return { name: "item_" + slot, armor2: true, - clazz: DestinyClass.Titan, + clazz: DestinyClass.Unknown, source: InventoryArmorSource.Inventory, description: "", slot: slot, diff --git a/src/app/services/userdata.service.ts b/src/app/services/userdata.service.ts index 67f53876..76ee381d 100644 --- a/src/app/services/userdata.service.ts +++ b/src/app/services/userdata.service.ts @@ -20,6 +20,7 @@ import { AuthService } from "./auth.service"; import { InventoryService } from "./inventory.service"; import { DestinyClass } from "bungie-api-ts/destiny2/interfaces"; import { MembershipService } from "./membership.service"; +import { ConfigurationService } from "./configuration.service"; @Injectable({ providedIn: "root", @@ -31,6 +32,7 @@ export class UserdataService { constructor( private auth: AuthService, + private config: ConfigurationService, private membership: MembershipService, private inventory: InventoryService ) { @@ -55,6 +57,9 @@ export class UserdataService { private async updateCharacterData() { this.characters = await this.membership.getCharacters(); + this.config.modifyConfiguration((d) => { + if (d.characterClass == DestinyClass.Unknown) d.characterClass = this.characters[0].clazz; + }); localStorage.setItem("cachedCharacters", JSON.stringify(this.characters)); } } From ed563f1eb69b8ce63feeeaa4d0afcd01e1cf3002 Mon Sep 17 00:00:00 2001 From: nznaza Date: Sat, 1 Jun 2024 16:25:56 -0600 Subject: [PATCH 15/17] fix: max tier not saving correctly --- .../desired-class-selection.component.ts | 12 ++---------- src/app/services/results-builder.worker.ts | 10 +++++++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/app/components/authenticated-v2/settings/desired-class-selection/desired-class-selection.component.ts b/src/app/components/authenticated-v2/settings/desired-class-selection/desired-class-selection.component.ts index a62054c9..9877203d 100644 --- a/src/app/components/authenticated-v2/settings/desired-class-selection/desired-class-selection.component.ts +++ b/src/app/components/authenticated-v2/settings/desired-class-selection/desired-class-selection.component.ts @@ -31,11 +31,7 @@ export class DesiredClassSelectionComponent implements OnInit, OnDestroy { itemCounts: (null | number)[] = [null, null, null]; selectedClass = -1; public storedMaterials: { - "3853748946": number; - "4257549985": number; - "4257549984": number; - "3159615086": number; - "3467984096": number; + [Key: string]: number; } | null = null; constructor( @@ -86,11 +82,7 @@ export class DesiredClassSelectionComponent implements OnInit, OnDestroy { private async loadStoredMaterials() { var k: { - "3853748946": number; - "4257549985": number; - "4257549984": number; - "3159615086": number; - "3467984096": number; + [Key: string]: number; } = JSON.parse(localStorage.getItem("stored-materials") || "{}"); if (!("3853748946" in k)) k["3853748946"] = 0; if (!("4257549984" in k)) k["4257549984"] = 0; diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 0ac41f27..0a9ed36a 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -668,7 +668,10 @@ export function handlePermutation( for ( let tier = 10; tier >= config.minimumStatTiers[stat as ArmorStat].value && - tier > exoticmaximumPossibleTiers[stat] / 10; + tier > + Math.floor( + Math.min(exoticmaximumPossibleTiers[stat], runtime.maximumPossibleTiers[stat]) / 10 + ); tier-- ) { if (stats[stat] >= tier * 10) break; @@ -684,9 +687,10 @@ export function handlePermutation( ); //const mods = null; if (mods != null) { - if (runtime.maximumPossibleTiers[stat] < tier * 100) + if (runtime.maximumPossibleTiers[stat] < tier * 10) runtime.maximumPossibleTiers[stat] = tier * 10; - exoticmaximumPossibleTiers[stat] = tier * 10; + if (exoticmaximumPossibleTiers[stat] < tier * 10) + exoticmaximumPossibleTiers[stat] = tier * 10; break; } } From 758a789db0ecc4d63f2021b4c10dbc6eb37e8fb0 Mon Sep 17 00:00:00 2001 From: nznaza Date: Sat, 1 Jun 2024 16:32:59 -0600 Subject: [PATCH 16/17] fix: merge optimizations from beta branch --- src/app/services/results-builder.worker.ts | 27 ++++++++++------------ 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index 0a9ed36a..b10cfb28 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -740,10 +740,9 @@ function get_mods_precalc( optimize: ModOptimizationStrategy = ModOptimizationStrategy.None ): StatModifier[] | null { // check distances <= 65 - if ( - distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5] > - getAvailableStatsByMods(availableArtificeCount, availableModCost) - ) + const totalDistance = + distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; + if (totalDistance > getAvailableStatsByMods(availableArtificeCount, availableModCost)) return null; const modCombinations = config.onlyShowResultsWithNoWastedStats @@ -777,7 +776,13 @@ function get_mods_precalc( const limit = 3; for (let i = 0; i < optionalDistances.length; i++) { if (optionalDistances[i] > 0) { - const additionalCombosA = modCombinations[optionalDistances[i]]; + const additionalCombosA = modCombinations[optionalDistances[i]].filter( + (d) => + d[2] == 0 && // disallow major mods + d[3] % 10 > 0 && // we do not want to add exact stat tiers + (optionalDistances[i] + d[3]) % 10 < optionalDistances[i] // and the changes must have less waste than before + ); + //(d) => d[3] % 10 > 0); if (additionalCombosA != null) { precalculatedMods[i] = additionalCombosA.slice(0, limit).concat(precalculatedMods[i]); } @@ -861,7 +866,7 @@ function get_mods_precalc( return true; } - const mustExecuteOptimization = optimize != ModOptimizationStrategy.None; + const mustExecuteOptimization = totalDistance > 0 && optimize != ModOptimizationStrategy.None; root: for (let mobility of precalculatedMods[0]) { if (!validate([mobility])) continue; for (let resilience of precalculatedMods[1]) { @@ -889,15 +894,7 @@ function get_mods_precalc( if (!validate(mods, true)) continue; - const sum = mods.reduce( - (a, b, i) => [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3] - distances[i]], - [0, 0, 0, 0] - ); - - if (sum[3] < 0) continue; // did not reach the target - if (sum[0] > availableArtificeCount) continue; - if (sum[0] == 0 && sum[1] == 0 && sum[2] == 0 && sum[3] == 0) continue; - + // Fill optional distances for (let m = 0; m < 6; m++) if (optionalDistances[m] > 0 && mods[m][3] == 0 && bestMods != null) continue inner; From a1586e64ba5a11e7f0bda3ece5727f8cb18821fd Mon Sep 17 00:00:00 2001 From: nznaza Date: Sat, 1 Jun 2024 16:46:20 -0600 Subject: [PATCH 17/17] feat: use optimized mod calc for max exotic stats --- src/app/services/results-builder.worker.ts | 28 +++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/app/services/results-builder.worker.ts b/src/app/services/results-builder.worker.ts index b10cfb28..904f4feb 100644 --- a/src/app/services/results-builder.worker.ts +++ b/src/app/services/results-builder.worker.ts @@ -528,17 +528,33 @@ export function handlePermutation( //const distanceSum = distances.reduce((a, b) => a + b, 0); let distanceSum = distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; - if (distanceSum > 10 * 5 + 3 * availableArtificeCount) { + if (distanceSum > getAvailableStatsByMods(availableArtificeCount, availableModCost)) { for (let stat = 0; stat < 6; stat++) { const oldDistance = distances[stat]; - for (let tier = 10; tier >= exoticmaximumPossibleTiers[stat]; tier--) { + for ( + let tier = 10; + tier >= + Math.floor( + Math.min(exoticmaximumPossibleTiers[stat], runtime.maximumPossibleTiers[stat]) / 10 + ); + tier-- + ) { const v = 10 - (stats[stat] % 10); distances[stat] = Math.max(v < 10 ? v : 0, tier * 10 - stats[stat]); - distanceSum = - distances[0] + distances[1] + distances[2] + distances[3] + distances[4] + distances[5]; + const mods = get_mods_precalc( + config, + distances, + [0, 0, 0, 0, 0, 0], + availableArtificeCount, + availableModCost, + ModOptimizationStrategy.None + ); + //const mods = null; distances[stat] = oldDistance; - if (distanceSum <= getAvailableStatsByMods(availableArtificeCount, availableModCost)) { - exoticmaximumPossibleTiers[stat] = tier * 10; + + if (mods != null) { + if (exoticmaximumPossibleTiers[stat] < tier * 10) + exoticmaximumPossibleTiers[stat] = tier * 10; break; } }