Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/app/components/authenticated-v2/results/results.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,31 @@ <h3>You cancelled the calculation process</h3>
<mat-icon>search_off</mat-icon>
<h3>No results found</h3>
<p>Try adjusting your filters or search criteria.</p>

<div *ngIf="slotsWithNoLegendaryItems > 1">
<div class="no-items-warning">
<mat-icon color="warn">warning</mat-icon>
<strong>Warning:</strong> There is not enough legendary armor available for your current
settings.
</div>
</div>

<ng-container *ngIf="slotsWithNoLegendaryItems < 2">
<div *ngFor="let slot of itemCountPerSlot | keyvalue; let i = index">
<ng-container
*ngIf="
slot.value[0] + slot.value[1] === 0 ||
(slot.value[0] === 0 && _selectedExoticSlot !== 0 && i + 1 !== _selectedExoticSlot)
">
<div class="no-items-warning">
<mat-icon color="warn">warning</mat-icon>
<strong>{{ getArmorSlotName(slot.key) }}:</strong> No items available for this slot
due to your current settings.
</div>
</ng-container>
</div>
</ng-container>

<div id="common-issues">
<p>Common issues:</p>
<ul>
Expand Down
15 changes: 15 additions & 0 deletions src/app/components/authenticated-v2/results/results.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,21 @@ tr.result-detail-row {
text-align: center;
color: var(--mdc-theme-on-surface);

.no-items-warning {
display: flex;
align-items: center;
gap: 8px;
color: #ff9800;
background: rgba(255, 152, 0, 0.08);
border: 1px solid #ff9800;
border-radius: 4px;
padding: 6px 12px;
margin: 8px 0;
font-size: 14px;
font-weight: 500;
width: fit-content;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.04);
}
mat-icon {
font-size: 4rem;
width: 4rem;
Expand Down
131 changes: 90 additions & 41 deletions src/app/components/authenticated-v2/results/results.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import { MatSort } from "@angular/material/sort";
import { StatusProviderService } from "../../../services/status-provider.service";
import { animate, state, style, transition, trigger } from "@angular/animations";
import { DestinyClass } from "bungie-api-ts/destiny2";
import { ArmorSlot } from "../../../data/enum/armor-slot";
import { ArmorSlot, ArmorSlotNames } from "../../../data/enum/armor-slot";
import { FixableSelection } from "../../../data/buildConfiguration";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { InventoryArmorSource } from "src/app/data/types/IInventoryArmor";
import { AvailableItemsService } from "src/app/services/available-items.service";

export interface ResultDefinition {
exotic:
Expand Down Expand Up @@ -103,6 +104,7 @@ export class ResultsComponent implements OnInit, OnDestroy {

_config_maximumStatMods: number = 5;
_config_selectedExotics: number[] = [];
_selectedExoticSlot: ArmorSlot = ArmorSlot.ArmorSlotNone; // Used to filter results by selected exotic slots
_config_tryLimitWastedStats: boolean = false;
_config_onlyUseMasterworkedExotics: Boolean = false;
_config_onlyUseMasterworkedLegendaries: Boolean = false;
Expand Down Expand Up @@ -145,10 +147,21 @@ export class ResultsComponent implements OnInit, OnDestroy {
initializing: boolean = true; // Flag to indicate if the page is still initializing
cancelledCalculation: boolean = false;

// Count of items per armor slot, [legendary, exotic]
slotsWithNoLegendaryItems: number = 0;
itemCountPerSlot: { [key in Exclude<ArmorSlot, ArmorSlot.ArmorSlotNone>]: [number, number] } = {
[ArmorSlot.ArmorSlotHelmet]: [0, 0],
[ArmorSlot.ArmorSlotGauntlet]: [0, 0],
[ArmorSlot.ArmorSlotChest]: [0, 0],
[ArmorSlot.ArmorSlotLegs]: [0, 0],
[ArmorSlot.ArmorSlotClass]: [0, 0],
};

constructor(
private inventory: InventoryService,
public configService: ConfigurationService,
public status: StatusProviderService
public status: StatusProviderService,
public availableItems: AvailableItemsService
) {
// Load saved view mode from localStorage
const savedViewMode = localStorage.getItem("d2ap-view-mode") as "table" | "cards";
Expand All @@ -158,6 +171,29 @@ export class ResultsComponent implements OnInit, OnDestroy {
}

ngOnInit(): void {
// Check for item counts per slot so that we can show a warning if no items are available for a slot
for (let slot of Object.values(ArmorSlot) as ArmorSlot[]) {
if (slot === ArmorSlot.ArmorSlotNone) continue; // Skip the 'none' slot
if (this.itemCountPerSlot[slot] === undefined || this.itemCountPerSlot[slot] === null)
continue;
this.itemCountPerSlot[slot] = [0, 0];
const sll = slot;
this.availableItems
.getItemsForSlot$(slot)
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((items) => {
const exoticCount = items.filter((i) => i.isExotic).length;
this.itemCountPerSlot[sll] = [
items.length - exoticCount, // Legendary count
exoticCount, // Exotic count
];

this.slotsWithNoLegendaryItems = Object.values(this.itemCountPerSlot).filter(
(count) => count[0] === 0
).length;
});
}

this.status.status.pipe(takeUntil(this.ngUnsubscribe)).subscribe((s) => {
this.isCalculatingPermutations = s.calculatingPermutations || s.calculatingResults;

Expand All @@ -171,45 +207,54 @@ export class ResultsComponent implements OnInit, OnDestroy {
this.computationProgress = progress;
});
//
this.configService.configuration.pipe(takeUntil(this.ngUnsubscribe)).subscribe((c: any) => {
this.selectedClass = c.characterClass;
this._config_assumeLegendariesMasterworked = c.assumeLegendariesMasterworked;
this._config_assumeExoticsMasterworked = c.assumeExoticsMasterworked;
this._config_tryLimitWastedStats = c.tryLimitWastedStats;

this._config_maximumStatMods = c.maximumStatMods;
this._config_legacyArmor = c.allowLegacyArmor;
this._config_onlyUseMasterworkedExotics = c.onlyUseMasterworkedExotics;
this._config_onlyUseMasterworkedLegendaries = c.onlyUseMasterworkedLegendaries;
this._config_includeCollectionRolls = c.includeCollectionRolls;
this._config_includeVendorRolls = c.includeVendorRolls;
this._config_onlyShowResultsWithNoWastedStats = c.onlyShowResultsWithNoWastedStats;
this._config_assumeEveryLegendaryIsArtifice = c.assumeEveryLegendaryIsArtifice;
this._config_assumeEveryExoticIsArtifice = c.assumeEveryExoticIsArtifice;
this._config_enforceFeaturedArmor = c.enforceFeaturedArmor;
this._config_selectedExotics = c.selectedExotics;
this._config_armorPerkLimitation = Object.entries(c.armorPerks)
.filter((v: any) => v[1].value != ArmorPerkOrSlot.Any)
.map((k: any) => k[1]);
this._config_modslotLimitation = Object.entries(c.maximumModSlots)
.filter((v: any) => v[1].value < 5)
.map((k: any) => k[1]);

let columns = [
"exotic",
"health",
"melee",
"grenade",
"super",
"class",
"weapon",
"total",
"mods",
];
if (c.includeVendorRolls || c.includeCollectionRolls) columns.push("source");
columns.push("dropdown");
this.shownColumns = columns;
});
this.configService.configuration
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(async (c: any) => {
this.selectedClass = c.characterClass;
this._config_assumeLegendariesMasterworked = c.assumeLegendariesMasterworked;
this._config_assumeExoticsMasterworked = c.assumeExoticsMasterworked;
this._config_tryLimitWastedStats = c.tryLimitWastedStats;

this._config_maximumStatMods = c.maximumStatMods;
this._config_legacyArmor = c.allowLegacyArmor;
this._config_onlyUseMasterworkedExotics = c.onlyUseMasterworkedExotics;
this._config_onlyUseMasterworkedLegendaries = c.onlyUseMasterworkedLegendaries;
this._config_includeCollectionRolls = c.includeCollectionRolls;
this._config_includeVendorRolls = c.includeVendorRolls;
this._config_onlyShowResultsWithNoWastedStats = c.onlyShowResultsWithNoWastedStats;
this._config_assumeEveryLegendaryIsArtifice = c.assumeEveryLegendaryIsArtifice;
this._config_assumeEveryExoticIsArtifice = c.assumeEveryExoticIsArtifice;
this._config_enforceFeaturedArmor = c.enforceFeaturedArmor;
this._config_selectedExotics = c.selectedExotics;

if (c.selectedExotics.length > 0 && c.selectedExotics[0] > 0) {
this._selectedExoticSlot = await this.inventory.getSlotByItemHash(c.selectedExotics[0]);
} else {
this._selectedExoticSlot = ArmorSlot.ArmorSlotNone;
}

this._config_armorPerkLimitation = Object.entries(c.armorPerks)
.filter((v: any) => v[1].value != ArmorPerkOrSlot.Any)
.map((k: any) => k[1]);
this._config_modslotLimitation = Object.entries(c.maximumModSlots)
.filter((v: any) => v[1].value < 5)
.map((k: any) => k[1]);

let columns = [
"exotic",
"health",
"melee",
"grenade",
"super",
"class",
"weapon",
"total",
"mods",
];
if (c.includeVendorRolls || c.includeCollectionRolls) columns.push("source");
columns.push("dropdown");
this.shownColumns = columns;
});

this.inventory.armorResults.pipe(takeUntil(this.ngUnsubscribe)).subscribe(async (value) => {
this._results = value.results;
Expand Down Expand Up @@ -254,6 +299,10 @@ export class ResultsComponent implements OnInit, OnDestroy {
};
}

getArmorSlotName(slot: ArmorSlot | string): string {
return ArmorSlotNames[slot as ArmorSlot] || "Unknown Slot";
}

cancelCalculation() {
this.inventory.cancelCalculation();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ import {
} from "../../../../../data/enum/armor-stat";
import { DestinyClass } from "bungie-api-ts/destiny2";
import { InventoryService } from "../../../../../services/inventory.service";
import { DatabaseService } from "../../../../../services/database.service";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { environment } from "../../../../../../environments/environment";
import { ItemIconServiceService } from "src/app/services/item-icon-service.service";
import { ModUrl } from "../../../results/table-mod-display/table-mod-display.component";
import { ArmorSystem } from "src/app/data/types/IManifestArmor";
import { AvailableItemsService } from "../../../../../services/available-items.service";

@Component({
selector: "app-slot-limitation-selection",
Expand All @@ -54,7 +53,6 @@ export class SlotLimitationSelectionComponent implements OnInit, OnDestroy {
@Output()
possible: EventEmitter<boolean> = new EventEmitter<boolean>();

fixedExoticInThisSlot: boolean = false;
isPossible: boolean = true;
configSelectedClass: DestinyClass = DestinyClass.Unknown;
configAssumeLegendaryIsArtifice: boolean = false;
Expand Down Expand Up @@ -102,76 +100,9 @@ export class SlotLimitationSelectionComponent implements OnInit, OnDestroy {
public config: ConfigurationService,
public inventory: InventoryService,
private iconService: ItemIconServiceService,
private db: DatabaseService
private availableItems: AvailableItemsService
) {}

public async runPossibilityCheck() {
const mustCheckArmorPerk = this.armorPerkLock && this.armorPerk != ArmorPerkOrSlot.Any;

let results = 0;
if (mustCheckArmorPerk) {
// check if the current slot is locked to a specific exotic
if (this.fixedExoticInThisSlot) {
this.configSelectedExotic.forEach(async (exoticHash) => {
var exotics = await this.db.inventoryArmor
.where("clazz")
.equals(this.configSelectedClass)
.and(
(f) =>
f.perk == this.armorPerk ||
(this.armorPerk == ArmorPerkOrSlot.SlotArtifice &&
this.configAssumeExoticIsArtifice &&
f.isExotic &&
f.armorSystem == ArmorSystem.Armor2) ||
(this.armorPerk == ArmorPerkOrSlot.SlotArtifice &&
this.configAssumeLegendaryIsArtifice &&
!f.isExotic &&
f.armorSystem == ArmorSystem.Armor2) ||
(this.armorPerk == ArmorPerkOrSlot.SlotArtifice &&
this.configAssumeClassItemIsArtifice &&
f.slot == ArmorSlot.ArmorSlotClass &&
!f.isExotic &&
f.armorSystem === ArmorSystem.Armor2)
)
.and((f) => f.hash == exoticHash)
.and((f) => f.isExotic == 1)
.count();
results += exotics;
this.isPossible = results > 0;
this.possible.next(this.isPossible);
});
} else {
results += await this.db.inventoryArmor
.where("clazz")
.equals(this.configSelectedClass)
.and((f) => this.configSelectedExoticSum == 0 || !f.isExotic)
.and((f) => f.slot == this.slot)
.and(
(f) =>
f.perk == this.armorPerk ||
(this.armorPerk == ArmorPerkOrSlot.SlotArtifice &&
this.configAssumeExoticIsArtifice &&
f.isExotic &&
f.armorSystem == ArmorSystem.Armor2) ||
(this.armorPerk == ArmorPerkOrSlot.SlotArtifice &&
this.configAssumeLegendaryIsArtifice &&
!f.isExotic &&
f.armorSystem == ArmorSystem.Armor2) ||
(this.armorPerk == ArmorPerkOrSlot.SlotArtifice &&
this.configAssumeClassItemIsArtifice &&
f.slot == ArmorSlot.ArmorSlotClass &&
!f.isExotic &&
f.armorSystem === ArmorSystem.Armor2)
)
.count();
this.isPossible = results > 0;
}
} else {
this.isPossible = true;
}
this.possible.next(this.isPossible);
}

get slotName(): string {
switch (this.slot) {
case ArmorSlot.ArmorSlotHelmet:
Expand All @@ -190,20 +121,17 @@ export class SlotLimitationSelectionComponent implements OnInit, OnDestroy {
}

ngOnInit(): void {
this.availableItems
.getItemsForSlot$(this.slot)
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((items) => {
this.isPossible = items.length > 0;
this.possible.next(this.isPossible);
});

this.config.configuration.pipe(takeUntil(this.ngUnsubscribe)).subscribe(async (c) => {
const newExoticSum = c.selectedExotics.reduce((acc, x) => acc + x, 0);

var mustRunPossibilityCheck =
this.configSelectedClass != c.characterClass ||
this.configAssumeLegendaryIsArtifice != c.assumeEveryLegendaryIsArtifice ||
this.configAssumeExoticIsArtifice != c.assumeEveryExoticIsArtifice ||
this.configAssumeClassItemIsArtifice != c.assumeClassItemIsArtifice ||
this.selection != c.maximumModSlots[this.slot].value ||
this.armorPerk != c.armorPerks[this.slot].value ||
this.armorPerkLock != c.armorPerks[this.slot].fixed ||
this.configSelectedExoticSum != newExoticSum ||
this.maximumModSlots != c.maximumModSlots[this.slot].value;

this.configAssumeLegendaryIsArtifice = c.assumeEveryLegendaryIsArtifice;
this.configAssumeExoticIsArtifice = c.assumeEveryExoticIsArtifice;
this.configAssumeClassItemIsArtifice = c.assumeClassItemIsArtifice;
Expand All @@ -214,15 +142,6 @@ export class SlotLimitationSelectionComponent implements OnInit, OnDestroy {
this.maximumModSlots = c.maximumModSlots[this.slot].value;
this.configSelectedExoticSum = newExoticSum;
this.configSelectedExotic = c.selectedExotics;

this.fixedExoticInThisSlot =
(await this.inventory.getExoticsForClass(c.characterClass))
// TODO LOOK AT THIS
.filter((x) => c.selectedExotics.indexOf(x.items[0].hash) > -1)
.map((e) => e.items[0].slot)
.indexOf(this.slot) > -1;

if (mustRunPossibilityCheck) await this.runPossibilityCheck();
});
}

Expand Down Expand Up @@ -258,4 +177,14 @@ export class SlotLimitationSelectionComponent implements OnInit, OnDestroy {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}

// Helper method to check if items are available for a specific slot
hasItemsForSlot(slot: ArmorSlot): boolean {
return this.availableItems.hasItemsForSlot(slot);
}

// Helper method to get item count for a specific slot
getItemCountForSlot(slot: ArmorSlot): number {
return this.availableItems.getItemsForSlot(slot).length;
}
}
Loading