Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7161b4b
feat: initial tuning implementation; unfinished
Aug 7, 2025
357f509
fix: allow secondary and tertiary stats in tuning
Mijago Aug 7, 2025
fbdd7fa
feat: add support for tuning mods in expanded result content
Mijago Aug 7, 2025
dddfbad
fix: improve handling of fixed stat tiers in permutation calculations
Mijago Aug 7, 2025
1e49ea6
feat: retrieve tuningStatHash in BungieApiService
Aug 7, 2025
13c0bf1
feat: enhance tuning information structure in IPermutatorArmorSet
Aug 7, 2025
84b1c64
Merge branch 'feature/t5-tuning' of https://github.com/Mijago/D2Armor…
Aug 7, 2025
61a99b9
feat: enhance tuning stat handling
Aug 7, 2025
b453c55
fix: improve tuning stat assignment logic in BungieApiService
Aug 8, 2025
4f72261
fix: tier availability can now handle 0waste again
Aug 8, 2025
eaa565e
feat: refactor tuning stat handling to use ArmorStat
Aug 8, 2025
bc99659
fix: optimize tuning stat filtering - SLOW!!
Aug 8, 2025
922beb8
feat: add tuning affinity display to result table
Aug 8, 2025
e5766c9
fix: exotic artifice slot
Aug 8, 2025
eea199c
fix: class items can have tunignAffinity too
Aug 8, 2025
f5924ff
fix: removed incorrect speedup methods
Aug 9, 2025
796548d
feat: add tier column to armor items table
Aug 9, 2025
5b8c920
docs: update version to 2.9.7
Aug 9, 2025
6c97741
fix: update test configurations and remove unused code
Aug 9, 2025
46e7317
feat: add tuning stats display to results card view
Aug 9, 2025
cb95c8c
feat: enhance tuning system with new tuningHashPlacement
Aug 9, 2025
a2438f0
fix: allow the use of negative stats
Aug 10, 2025
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
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "d2-armor-picker",
"version": "2.9.6",
"version": "2.9.7",
"scripts": {
"ng": "ng",
"config": "ts-node set-env.ts",
Expand All @@ -11,7 +11,9 @@
"deploy-canary": "npm run config && ng deploy D2ArmorPicker --build-target D2ArmorPicker:build:canary --repo=https://github.com/Mijago/D2ArmorPicker-Canary.git --base-href=/ --cname=canary.d2armorpicker.com ",
"deploy-nznaza": "npm run config && ng deploy D2ArmorPicker --build-target D2ArmorPicker:build:beta --no-silent --repo=https://github.com/nznaza/D2ArmorPicker.git --base-href=/D2ArmorPicker/ --cname=nznaza.github.io/D2ArmorPicker/",
"watch": "npm run config && ng build --watch --configuration development",
"test": "ng test",
"test": "ng test",
"test:ci": "ng test --browsers=ChromeHeadlessCI --watch=false --code-coverage=false",
"test:coverage": "ng test --browsers=ChromeHeadlessCI --watch=false --code-coverage=true",
"prepare": "husky install",
"lint": "ng lint --fix --max-warnings=0",
"format": "prettier . --write",
Expand Down
2 changes: 1 addition & 1 deletion set-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const production = process.env["PRODUCTION"] === "1";
const beta_branch = process.env["BETA"] === "1";
const canary_branch = process.env["CANARY"] === "1";

const version = "2.9.6";
const version = "2.9.7";

// Configure Angular `environment.ts` file path
const targetPath = production
Expand Down
10 changes: 9 additions & 1 deletion src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@
import { TestBed } from "@angular/core/testing";
import { AppComponent } from "./app.component";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { HttpClientModule } from "@angular/common/http";
import { LoggerTestingModule } from "ngx-logger/testing";
import { CommonMaterialModule } from "./modules/common-material/common-material.module";

describe("AppComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [BrowserAnimationsModule],
imports: [
BrowserAnimationsModule,
HttpClientModule,
LoggerTestingModule,
CommonMaterialModule,
],
}).compileComponents();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
<th class="ability-column">{{ ArmorStatNames[ArmorStat.StatWeapon] }}</th>
<th></th>
<th></th>
<th>Tier</th>
<th>MW</th>
<th></th>
<th></th>
</tr>
</thead>
<!-- Items -->
Expand Down Expand Up @@ -94,6 +96,14 @@
<app-armor-perk-icon [perk]="i.perk || 0"></app-armor-perk-icon>
</div>
</td>

<!-- Tier-->
<td style="text-align: center">
<span class="tuning-affinity" *ngIf="i.tier !== undefined">
{{ i.tier }}
</span>
</td>

<td style="text-align: center">
<ng-container *ngIf="i.armorSystem === 2; else showLevel">
<mat-icon aria-label="Masterworked" *ngIf="i.masterworked">check_circle</mat-icon>
Expand All @@ -102,6 +112,14 @@
<span *ngIf="i.masterworkLevel !== undefined">{{ i.masterworkLevel }}</span>
</ng-template>
</td>

<!-- Tuning Affinity-->
<td>
<span class="tuning-affinity" *ngIf="i.tuningStat !== undefined">
<app-stat-icon [stat]="i.tuningStat"></app-stat-icon>
</span>
</td>

<!-- Disable -->
<td class="icon-column">
<button
Expand Down Expand Up @@ -272,6 +290,7 @@
<span *ngIf="count > 0" class="positive">{{ count }}×10</span>
</td>
</tr>
<!-- Artifice Mods -->
<ng-container *ngVar="element?.artifice as mods">
<tr>
<td>Artifice Mods</td>
Expand All @@ -296,6 +315,21 @@
</tr>
</ng-container>

<!-- Tuning Mods -->
<ng-container *ngIf="element && element?.tuning && (element?.tuning)!.improvements.length > 0">
<tr>
<td>Tuning</td>
<td *ngFor="let stat of ArmorStatOrder; let i = index">
<span *ngIf="element!.tuning!.stats[stat] > 0" class="positive">{{
element!.tuning!.stats[stat]
}}</span>
<span *ngIf="element!.tuning!.stats[stat] < 0" class="negative">{{
element!.tuning!.stats[stat]
}}</span>
</td>
</tr>
</ng-container>

<!-- Final sum line -->
<tr *ngVar="element?.stats as stats" class="result-total-all result-total highlight-row">
<td>Total</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Component, Input, OnDestroy, OnInit } from "@angular/core";
import { NGXLogger } from "ngx-logger";
import {
ArmorStat,
ARMORSTAT_ORDER,
ArmorStatIconUrls,
ArmorStatNames,
SpecialArmorStat,
Expand Down Expand Up @@ -55,6 +56,8 @@ export class ExpandedResultContentComponent implements OnInit, OnDestroy {
public ArmorStatNames = ArmorStatNames;
public ArmorStatIconUrls = ArmorStatIconUrls;
public ArmorStat = ArmorStat;
public ArmorStatOrder = ARMORSTAT_ORDER;

public StatModifier = StatModifier;
public config_characterClass = DestinyClass.Unknown;
public config_assumeLegendariesMasterworked = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@
<span class="stat-value-compact">{{ result.stats[stat] }}</span>
</div>
</div>

<div class="mods-section">
<app-table-mod-display
[mods]="result.mods"
Expand Down Expand Up @@ -379,6 +378,20 @@ <h4>Armor Breakdown</h4>
</span>
</td>
</tr>
<!-- Tuning stat row -->
<tr class="tuning-row" *ngIf="result.tuning && isBreakdownExpanded(i)">
<td class="tuning-label">Tuning</td>
<td *ngFor="let stat of STAT_ORDER">
<span *ngIf="result.tuning.stats[stat] !== 0" class="tuning-value">
<span
[class.positive]="result.tuning.stats[stat] > 0"
[class.negative]="result.tuning.stats[stat] < 0">
{{ result.tuning.stats[stat] > 0 ? "+" : ""
}}{{ result.tuning.stats[stat] }}
</span>
</span>
</td>
</tr>

<!-- Final total row -->
<tr class="final-total-row">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,8 @@

.minor-mods-row,
.major-mods-row,
.artifice-mods-row {
.artifice-mods-row,
.tuning-row {
background: rgba(255, 193, 7, 0.1);
border: 1px solid rgba(255, 193, 7, 0.3);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { InventoryArmorSource } from "src/app/data/types/IInventoryArmor";
import { MAXIMUM_STAT_MOD_AMOUNT } from "src/app/data/constants";
import { Tuning } from "src/app/data/types/IPermutatorArmorSet";

export interface ResultDefinition {
exotic:
Expand All @@ -53,6 +54,8 @@ export interface ResultDefinition {
loaded: boolean;
usesCollectionRoll?: boolean;
usesVendorRoll?: boolean;
tuning?: Tuning;
tuningHashPlacement: Array<number | null>; // Array of hashes or nulls for tuning stat placements
}

export enum ResultItemMoveState {
Expand All @@ -70,6 +73,7 @@ export interface ResultItem {
tier: number; // 0 = exotic, 1-5 = legendary
name: string;
exotic: boolean;
tuningStat?: ArmorStat; // 0 = none, 1-6 = element affinity
masterworked: boolean;
armorSystem: number; // 2 = Armor 2.0, 3 = Armor 3.0
masterworkLevel: number; // 0-5, 5 = full masterwork
Expand Down
21 changes: 20 additions & 1 deletion src/app/data/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,33 @@ export const CHANGELOG_DATA: {
clearManifest?: boolean;
entries: ChangelogEntry[];
}[] = [
{
version: "2.9.7",
date: "August 9, 2025",
clearManifest: true,
entries: [
{
type: ChangelogEntryType.ADD,
text: "Introduced T5 Tuning: allows tuning of Tier 5 armor with (1/1/1) and (+5/-5) stat modifications for advanced optimization.",
},
{
type: ChangelogEntryType.ADD,
text: "Added UI and calculation logic to support T5 tuning, including new tuning options in the results and settings.",
},
{
type: ChangelogEntryType.MODIFIED,
text: "Updated the core logic to handle tuning scenarios and stat tradeoffs.",
},
],
},
{
version: "2.9.6",
date: "July 31, 2025",
clearManifest: true,
entries: [
{
type: ChangelogEntryType.ADD,
text: "Added gearset selection feature, allowing users to easily select and combine gearsets.",
text: "Added the gearset selection feature, allowing users to easily select and combine gearsets.",
issues: [],
},
{
Expand Down
4 changes: 2 additions & 2 deletions src/app/data/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export class Database extends Dexie {

constructor() {
super("d2armorpicker-v2");
this.version(31).stores({
manifestArmor: "id++, hash, isExotic",
this.version(32).stores({
manifestArmor: "id++, hash, isExotic, itemType",
inventoryArmor:
"id++, itemInstanceId, isExotic, hash, name, masterworked, clazz, slot, source, gearSetHash, perk, [clazz+gearSetHash]",
sandboxPerkDefinition: "id++, hash",
Expand Down
9 changes: 9 additions & 0 deletions src/app/data/enum/armor-stat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ export const ArmorStatHashes: EnumDictionary<ArmorStat, number> = {
[ArmorStat.StatMelee]: 4244567218,
};

export const ArmorStatFromHash: EnumDictionary<number, ArmorStat> = {
[ArmorStatHashes[ArmorStat.StatWeapon]]: ArmorStat.StatWeapon,
[ArmorStatHashes[ArmorStat.StatHealth]]: ArmorStat.StatHealth,
[ArmorStatHashes[ArmorStat.StatClass]]: ArmorStat.StatClass,
[ArmorStatHashes[ArmorStat.StatGrenade]]: ArmorStat.StatGrenade,
[ArmorStatHashes[ArmorStat.StatSuper]]: ArmorStat.StatSuper,
[ArmorStatHashes[ArmorStat.StatMelee]]: ArmorStat.StatMelee,
};

export const ArmorStatIconUrls: EnumDictionary<ArmorStat, string> = {
[ArmorStat.StatWeapon]:
"https://www.bungie.net/common/destiny2_content/icons/bc69675acdae9e6b9a68a02fb4d62e07.png",
Expand Down
1 change: 1 addition & 0 deletions src/app/data/types/IInventoryArmor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface IInventoryArmor
ITimestampedEntry {
// Note: this will be empty for vendor items
statPlugHashes?: (number | undefined)[];
tuningStat?: ArmorStat; // for armor 3.0, this is the tuning stat hash
// exoticPerkHash is now inherited as number[] from IManifestArmor
}

Expand Down
3 changes: 2 additions & 1 deletion src/app/data/types/IPermutatorArmor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DestinyClass, TierType } from "bungie-api-ts/destiny2";
import { ArmorPerkOrSlot } from "../enum/armor-stat";
import { ArmorPerkOrSlot, ArmorStat } from "../enum/armor-stat";
import { IDestinyArmor } from "./IInventoryArmor";

export interface IPermutatorArmor extends IDestinyArmor {
Expand All @@ -9,4 +9,5 @@ export interface IPermutatorArmor extends IDestinyArmor {
rarity: TierType;
isSunset: boolean;
exoticPerkHash: number[];
tuningStat?: ArmorStat; // for armor 3.0, this is the tuning stat hash
}
22 changes: 20 additions & 2 deletions src/app/data/types/IPermutatorArmorSet.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { StatModifier } from "../enum/armor-stat";
import { ArmorStat, StatModifier } from "../enum/armor-stat";
import { IPermutatorArmor } from "./IPermutatorArmor";

export interface PossibleTuningInformation {
tuningStat: ArmorStat;
archetypeStats: ArmorStat[];
}

export interface SelectedTuning extends PossibleTuningInformation {
// describes the stat that is reduced by the tuning; If it is null, no stat is but the 1/1/1 tuning is applied
reducedStat: ArmorStat | null;
}

export interface Tuning {
stats: number[];
improvements: SelectedTuning[];
}

export interface IPermutatorArmorSet {
armor: number[];
useExoticClassItem: boolean;
usedArtifice: StatModifier[];
usedMods: StatModifier[];
statsWithMods: number[];
statsWithoutMods: number[];
tuning?: Tuning;
}

export function createArmorSet(
Expand All @@ -19,7 +35,8 @@ export function createArmorSet(
usedArtifice: StatModifier[],
usedMods: StatModifier[],
statsWithMods: number[],
statsWithoutMods: number[]
statsWithoutMods: number[],
tuning: Tuning | undefined = undefined
): IPermutatorArmorSet {
return {
armor: [helmet.id, gauntlet.id, chest.id, leg.id, classItem.id],
Expand All @@ -28,6 +45,7 @@ export function createArmorSet(
usedMods,
statsWithMods,
statsWithoutMods,
tuning,
};
}

Expand Down
Loading