D2ArmorPicker Changelog for Version {{ changelog.changelogData[0].version }}
D2ArmorPicker Changelog for Version {{ changelog.changelogData[0].version }}
-
- Hi! There has been a new version of D2ArmorPicker! The following list shows all the relevant
- changes. Note that you can always look at the changelogs in the Help tab.
-
+
+ Hi! There has been a new version of D2ArmorPicker! The following list shows all the relevant
+ changes. Note that you can always look at the changelogs in the Help tab.
+
diff --git a/src/app/components/authenticated-v2/overlays/armor-tooltip-component/armor-tooltip.component.ts b/src/app/components/authenticated-v2/overlays/armor-tooltip-component/armor-tooltip.component.ts
index 837043ef..7df375ce 100644
--- a/src/app/components/authenticated-v2/overlays/armor-tooltip-component/armor-tooltip.component.ts
+++ b/src/app/components/authenticated-v2/overlays/armor-tooltip-component/armor-tooltip.component.ts
@@ -3,20 +3,20 @@ import { ResultItem } from "../../results/results.component";
import { ArmorStat, ArmorStatNames } from "../../../../data/enum/armor-stat";
@Component({
- selector: "app-armor-tooltip-component",
- templateUrl: "./armor-tooltip.component.html",
- styleUrls: ["./armor-tooltip.component.css"],
+ selector: "app-armor-tooltip-component",
+ templateUrl: "./armor-tooltip.component.html",
+ styleUrls: ["./armor-tooltip.component.css"],
})
export class ArmorTooltipComponent {
- @Input() itemTooltip: ResultItem | undefined;
+ @Input() itemTooltip: ResultItem | undefined;
- getArmorStatName(i: number) {
- return ArmorStatNames[i as ArmorStat];
- }
+ getArmorStatName(i: number) {
+ return ArmorStatNames[i as ArmorStat];
+ }
- getWidth(stat: number) {
- return Math.min(100, (stat / 32) * 100) + "%";
- }
+ getWidth(stat: number) {
+ return Math.min(100, (stat / 32) * 100) + "%";
+ }
- constructor() {}
+ constructor() {}
}
diff --git a/src/app/components/authenticated-v2/overlays/armor-tooltip-component/item-tooltip-renderer.directive.ts b/src/app/components/authenticated-v2/overlays/armor-tooltip-component/item-tooltip-renderer.directive.ts
index a1d84cb3..69cfbe44 100644
--- a/src/app/components/authenticated-v2/overlays/armor-tooltip-component/item-tooltip-renderer.directive.ts
+++ b/src/app/components/authenticated-v2/overlays/armor-tooltip-component/item-tooltip-renderer.directive.ts
@@ -1,12 +1,12 @@
import {
- Directive,
- Input,
- TemplateRef,
- ElementRef,
- OnInit,
- HostListener,
- ComponentRef,
- OnDestroy,
+ Directive,
+ Input,
+ TemplateRef,
+ ElementRef,
+ OnInit,
+ HostListener,
+ ComponentRef,
+ OnDestroy,
} from "@angular/core";
import { Overlay, OverlayPositionBuilder, OverlayRef } from "@angular/cdk/overlay";
import { ComponentPortal } from "@angular/cdk/portal";
@@ -14,103 +14,103 @@ import { ArmorTooltipComponent } from "./armor-tooltip.component";
import { ResultItem } from "../../results/results.component";
@Directive({
- selector: "[itemTooltip]",
+ selector: "[itemTooltip]",
})
export class ItemTooltipRendererDirective implements OnInit, OnDestroy {
- /**
- * This will be used to show tooltip or not
- * This can be used to show the tooltip conditionally
- */
- @Input() showToolTip: boolean = true;
+ /**
+ * This will be used to show tooltip or not
+ * This can be used to show the tooltip conditionally
+ */
+ @Input() showToolTip: boolean = true;
- //If this is specified then specified text will be showin in the tooltip
- @Input() itemTooltip: ResultItem | undefined;
+ //If this is specified then specified text will be showin in the tooltip
+ @Input() itemTooltip: ResultItem | undefined;
- //If this is specified then specified template will be rendered in the tooltip
- @Input() contentTemplate: TemplateRef | undefined;
+ //If this is specified then specified template will be rendered in the tooltip
+ @Input() contentTemplate: TemplateRef | undefined;
- private _overlayRef: OverlayRef | undefined;
+ private _overlayRef: OverlayRef | undefined;
- constructor(
- private _overlay: Overlay,
- private _overlayPositionBuilder: OverlayPositionBuilder,
- private _elementRef: ElementRef
- ) {}
+ constructor(
+ private _overlay: Overlay,
+ private _overlayPositionBuilder: OverlayPositionBuilder,
+ private _elementRef: ElementRef
+ ) {}
- /**
- * Init life cycle event handler
- */
- ngOnInit() {
- if (!this.showToolTip) {
- return;
- }
+ /**
+ * Init life cycle event handler
+ */
+ ngOnInit() {
+ if (!this.showToolTip) {
+ return;
+ }
- const positionStrategy = this._overlayPositionBuilder
- .flexibleConnectedTo(this._elementRef)
- .withPositions([
- {
- originX: "center",
- originY: "bottom",
- overlayX: "center",
- overlayY: "top",
- offsetY: 5,
- },
- {
- originX: "center",
- originY: "top",
- overlayX: "center",
- overlayY: "bottom",
- offsetY: -5,
- },
- ]);
+ const positionStrategy = this._overlayPositionBuilder
+ .flexibleConnectedTo(this._elementRef)
+ .withPositions([
+ {
+ originX: "center",
+ originY: "bottom",
+ overlayX: "center",
+ overlayY: "top",
+ offsetY: 5,
+ },
+ {
+ originX: "center",
+ originY: "top",
+ overlayX: "center",
+ overlayY: "bottom",
+ offsetY: -5,
+ },
+ ]);
- this._overlayRef = this._overlay.create({ positionStrategy });
- this._overlayRef.addPanelClass("overlay-no-pointer-event");
- }
+ this._overlayRef = this._overlay.create({ positionStrategy });
+ this._overlayRef.addPanelClass("overlay-no-pointer-event");
+ }
- /**
- * This method will be called whenever mouse enters in the Host element
- * i.e. where this directive is applied
- * This method will show the tooltip by instantiating the McToolTipComponent and attaching to the overlay
- */
- @HostListener("mouseenter")
- show() {
- //attach the component if it has not already attached to the overlay
- if (this._overlayRef && !this._overlayRef.hasAttached()) {
- const tooltipRef: ComponentRef = this._overlayRef.attach(
- new ComponentPortal(ArmorTooltipComponent)
- );
- tooltipRef.instance.itemTooltip = this.itemTooltip;
- }
+ /**
+ * This method will be called whenever mouse enters in the Host element
+ * i.e. where this directive is applied
+ * This method will show the tooltip by instantiating the McToolTipComponent and attaching to the overlay
+ */
+ @HostListener("mouseenter")
+ show() {
+ //attach the component if it has not already attached to the overlay
+ if (this._overlayRef && !this._overlayRef.hasAttached()) {
+ const tooltipRef: ComponentRef = this._overlayRef.attach(
+ new ComponentPortal(ArmorTooltipComponent)
+ );
+ tooltipRef.instance.itemTooltip = this.itemTooltip;
}
+ }
- /**
- * This method will be called when mouse goes out of the host element
- * i.e. where this directive is applied
- * This method will close the tooltip by detaching the overlay from the view
- */
- @HostListener("mouseleave")
- hide() {
- this.closeToolTip();
- }
+ /**
+ * This method will be called when mouse goes out of the host element
+ * i.e. where this directive is applied
+ * This method will close the tooltip by detaching the overlay from the view
+ */
+ @HostListener("mouseleave")
+ hide() {
+ this.closeToolTip();
+ }
- /**
- * Destroy lifecycle event handler
- * This method will make sure to close the tooltip
- * It will be needed in case when app is navigating to different page
- * and user is still seeing the tooltip; In that case we do not want to hang around the
- * tooltip after the page [on which tooltip visible] is destroyed
- */
- ngOnDestroy() {
- this.closeToolTip();
- }
+ /**
+ * Destroy lifecycle event handler
+ * This method will make sure to close the tooltip
+ * It will be needed in case when app is navigating to different page
+ * and user is still seeing the tooltip; In that case we do not want to hang around the
+ * tooltip after the page [on which tooltip visible] is destroyed
+ */
+ ngOnDestroy() {
+ this.closeToolTip();
+ }
- /**
- * This method will close the tooltip by detaching the component from the overlay
- */
- private closeToolTip() {
- if (this._overlayRef) {
- this._overlayRef.detach();
- }
+ /**
+ * This method will close the tooltip by detaching the component from the overlay
+ */
+ private closeToolTip() {
+ if (this._overlayRef) {
+ this._overlayRef.detach();
}
+ }
}
diff --git a/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.css b/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.css
index 9d2d29e4..fe108100 100644
--- a/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.css
+++ b/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.css
@@ -1,21 +1,21 @@
:host {
- width: 350px;
+ width: 350px;
}
.tooltip-container {
- border: 1px solid gray;
- padding: 1px 5px 4px 5px;
- background: linear-gradient(#484848 0px, #2c2c2c 100%);
+ border: 1px solid gray;
+ padding: 1px 5px 4px 5px;
+ background: linear-gradient(#484848 0px, #2c2c2c 100%);
}
.perk-icon {
- width: 50px;
- height: 50px;
+ width: 50px;
+ height: 50px;
}
.perk-icon-col {
- width: 55px;
+ width: 55px;
}
.exotic-name {
- color: #eedb9e;
+ color: #eedb9e;
}
diff --git a/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.html b/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.html
index a0119de1..ed7340b2 100644
--- a/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.html
+++ b/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.html
@@ -1,32 +1,32 @@
-
-
-
-
-
-
-
{{ armor?.name }}
-
-
-
Loading exotic perk..
-
-
-
No (fixed) exotic perk available.
-
-
-
- {{ exoticPerk.name }}
-
-
-
-
- {{ exoticPerk.description }}
-
-
-
-
+
+
+
+
+
+
+
{{ armor?.name }}
+
+
+
Loading exotic perk..
+
+
+
No (fixed) exotic perk available.
+
+
+
+ {{ exoticPerk.name }}
+
+
+
+
+ {{ exoticPerk.description }}
+
+
+
+
diff --git a/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.ts b/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.ts
index efb1bf25..38f00f91 100644
--- a/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.ts
+++ b/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-perk-tooltip.component.ts
@@ -6,19 +6,19 @@ import { InventoryService } from "../../../../services/inventory.service";
import { ItemIconServiceService } from "../../../../services/item-icon-service.service";
@Component({
- selector: "app-exotic-perk-tooltip",
- templateUrl: "./exotic-perk-tooltip.component.html",
- styleUrls: ["./exotic-perk-tooltip.component.css"],
+ selector: "app-exotic-perk-tooltip",
+ templateUrl: "./exotic-perk-tooltip.component.html",
+ styleUrls: ["./exotic-perk-tooltip.component.css"],
})
export class ExoticPerkTooltipComponent implements OnInit {
- @Input() armor: IManifestArmor | undefined;
- exoticPerk: IManifestArmor | undefined;
- exoticPerkNotThere: boolean = false;
+ @Input() armor: IManifestArmor | undefined;
+ exoticPerk: IManifestArmor | undefined;
+ exoticPerkNotThere: boolean = false;
- constructor(public inv: InventoryService, public iconService: ItemIconServiceService) {}
+ constructor(public inv: InventoryService, public iconService: ItemIconServiceService) {}
- async ngOnInit() {
- this.exoticPerk = await this.iconService.getItemCached(this.armor?.exoticPerkHash ?? 0);
- this.exoticPerkNotThere = this.exoticPerk == null;
- }
+ async ngOnInit() {
+ this.exoticPerk = await this.iconService.getItemCached(this.armor?.exoticPerkHash ?? 0);
+ this.exoticPerkNotThere = this.exoticPerk == null;
+ }
}
diff --git a/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-tooltip.directive.ts b/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-tooltip.directive.ts
index 0803cf16..873f1069 100644
--- a/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-tooltip.directive.ts
+++ b/src/app/components/authenticated-v2/overlays/exotic-perk-tooltip/exotic-tooltip.directive.ts
@@ -1,12 +1,12 @@
import {
- Directive,
- Input,
- TemplateRef,
- ElementRef,
- OnInit,
- HostListener,
- ComponentRef,
- OnDestroy,
+ Directive,
+ Input,
+ TemplateRef,
+ ElementRef,
+ OnInit,
+ HostListener,
+ ComponentRef,
+ OnDestroy,
} from "@angular/core";
import { Overlay, OverlayPositionBuilder, OverlayRef } from "@angular/cdk/overlay";
import { ComponentPortal } from "@angular/cdk/portal";
@@ -14,103 +14,103 @@ import { ExoticPerkTooltipComponent } from "./exotic-perk-tooltip.component";
import { IManifestArmor } from "../../../../data/types/IManifestArmor";
@Directive({
- selector: "[exoticTooltip]",
+ selector: "[exoticTooltip]",
})
export class ExoticTooltipDirective implements OnInit, OnDestroy {
- /**
- * This will be used to show tooltip or not
- * This can be used to show the tooltip conditionally
- */
- @Input() showToolTip: boolean = true;
+ /**
+ * This will be used to show tooltip or not
+ * This can be used to show the tooltip conditionally
+ */
+ @Input() showToolTip: boolean = true;
- //If this is specified then specified text will be show in in the tooltip
- @Input() exoticTooltip: IManifestArmor | undefined;
+ //If this is specified then specified text will be show in in the tooltip
+ @Input() exoticTooltip: IManifestArmor | undefined;
- //If this is specified then specified template will be rendered in the tooltip
- @Input() contentTemplate: TemplateRef | undefined;
+ //If this is specified then specified template will be rendered in the tooltip
+ @Input() contentTemplate: TemplateRef | undefined;
- private _overlayRef: OverlayRef | undefined;
+ private _overlayRef: OverlayRef | undefined;
- constructor(
- private _overlay: Overlay,
- private _overlayPositionBuilder: OverlayPositionBuilder,
- private _elementRef: ElementRef
- ) {}
+ constructor(
+ private _overlay: Overlay,
+ private _overlayPositionBuilder: OverlayPositionBuilder,
+ private _elementRef: ElementRef
+ ) {}
- /**
- * Init life cycle event handler
- */
- ngOnInit() {
- if (!this.showToolTip) {
- return;
- }
+ /**
+ * Init life cycle event handler
+ */
+ ngOnInit() {
+ if (!this.showToolTip) {
+ return;
+ }
- const positionStrategy = this._overlayPositionBuilder
- .flexibleConnectedTo(this._elementRef)
- .withPositions([
- {
- originX: "center",
- originY: "bottom",
- overlayX: "center",
- overlayY: "top",
- offsetY: 5,
- },
- {
- originX: "center",
- originY: "top",
- overlayX: "center",
- overlayY: "bottom",
- offsetY: -5,
- },
- ]);
+ const positionStrategy = this._overlayPositionBuilder
+ .flexibleConnectedTo(this._elementRef)
+ .withPositions([
+ {
+ originX: "center",
+ originY: "bottom",
+ overlayX: "center",
+ overlayY: "top",
+ offsetY: 5,
+ },
+ {
+ originX: "center",
+ originY: "top",
+ overlayX: "center",
+ overlayY: "bottom",
+ offsetY: -5,
+ },
+ ]);
- this._overlayRef = this._overlay.create({ positionStrategy });
- this._overlayRef.addPanelClass("overlay-no-pointer-event");
- }
+ this._overlayRef = this._overlay.create({ positionStrategy });
+ this._overlayRef.addPanelClass("overlay-no-pointer-event");
+ }
- /**
- * This method will be called whenever mouse enters in the Host element
- * i.e. where this directive is applied
- * This method will show the tooltip by instantiating the McToolTipComponent and attaching to the overlay
- */
- @HostListener("mouseenter")
- show() {
- //attach the component if it has not already attached to the overlay
- if (this._overlayRef && !this._overlayRef.hasAttached()) {
- const tooltipRef: ComponentRef = this._overlayRef.attach(
- new ComponentPortal(ExoticPerkTooltipComponent)
- );
- tooltipRef.instance.armor = this.exoticTooltip;
- }
+ /**
+ * This method will be called whenever mouse enters in the Host element
+ * i.e. where this directive is applied
+ * This method will show the tooltip by instantiating the McToolTipComponent and attaching to the overlay
+ */
+ @HostListener("mouseenter")
+ show() {
+ //attach the component if it has not already attached to the overlay
+ if (this._overlayRef && !this._overlayRef.hasAttached()) {
+ const tooltipRef: ComponentRef = this._overlayRef.attach(
+ new ComponentPortal(ExoticPerkTooltipComponent)
+ );
+ tooltipRef.instance.armor = this.exoticTooltip;
}
+ }
- /**
- * This method will be called when mouse goes out of the host element
- * i.e. where this directive is applied
- * This method will close the tooltip by detaching the overlay from the view
- */
- @HostListener("mouseleave")
- hide() {
- this.closeToolTip();
- }
+ /**
+ * This method will be called when mouse goes out of the host element
+ * i.e. where this directive is applied
+ * This method will close the tooltip by detaching the overlay from the view
+ */
+ @HostListener("mouseleave")
+ hide() {
+ this.closeToolTip();
+ }
- /**
- * Destroy lifecycle event handler
- * This method will make sure to close the tooltip
- * It will be needed in case when app is navigating to different page
- * and user is still seeing the tooltip; In that case we do not want to hang around the
- * tooltip after the page [on which tooltip visible] is destroyed
- */
- ngOnDestroy() {
- this.closeToolTip();
- }
+ /**
+ * Destroy lifecycle event handler
+ * This method will make sure to close the tooltip
+ * It will be needed in case when app is navigating to different page
+ * and user is still seeing the tooltip; In that case we do not want to hang around the
+ * tooltip after the page [on which tooltip visible] is destroyed
+ */
+ ngOnDestroy() {
+ this.closeToolTip();
+ }
- /**
- * This method will close the tooltip by detaching the component from the overlay
- */
- private closeToolTip() {
- if (this._overlayRef) {
- this._overlayRef.detach();
- }
+ /**
+ * This method will close the tooltip by detaching the component from the overlay
+ */
+ private closeToolTip() {
+ if (this._overlayRef) {
+ this._overlayRef.detach();
}
+ }
}
diff --git a/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.css b/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.css
index 9ae94853..2d82e23d 100644
--- a/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.css
+++ b/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.css
@@ -1,13 +1,13 @@
:host {
- width: 350px;
+ width: 350px;
}
.tooltip-container {
- border: 1px solid gray;
- padding: 1px 5px 4px 5px;
- background: linear-gradient(#484848 0px, #2c2c2c 100%);
+ border: 1px solid gray;
+ padding: 1px 5px 4px 5px;
+ background: linear-gradient(#484848 0px, #2c2c2c 100%);
}
.exotic-name {
- color: #eedb9e;
+ color: #eedb9e;
}
diff --git a/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.html b/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.html
index f90800c4..6c41460a 100644
--- a/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.html
+++ b/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.html
@@ -1,12 +1,12 @@
-
-
-
-
{{ mod?.name }}
-
-
-
{{ mod?.description }}
-
-
-
+
+
+
+
{{ mod?.name }}
+
+
+
{{ mod?.description }}
+
+
+
diff --git a/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.ts b/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.ts
index 9bf7323e..ee9f25a5 100644
--- a/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.ts
+++ b/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-description-tooltip.component.ts
@@ -2,12 +2,12 @@ import { Component, Input, OnInit } from "@angular/core";
import { Modifier } from "../../../../data/modifier";
@Component({
- selector: "app-mod-description-tooltip",
- templateUrl: "./mod-description-tooltip.component.html",
- styleUrls: ["./mod-description-tooltip.component.css"],
+ selector: "app-mod-description-tooltip",
+ templateUrl: "./mod-description-tooltip.component.html",
+ styleUrls: ["./mod-description-tooltip.component.css"],
})
export class ModDescriptionTooltipComponent {
- @Input() mod: Modifier | undefined;
+ @Input() mod: Modifier | undefined;
- constructor() {}
+ constructor() {}
}
diff --git a/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-tooltip.directive.ts b/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-tooltip.directive.ts
index c7311cd2..d4d87c8d 100644
--- a/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-tooltip.directive.ts
+++ b/src/app/components/authenticated-v2/overlays/mod-description-tooltip/mod-tooltip.directive.ts
@@ -1,12 +1,12 @@
import {
- Directive,
- Input,
- TemplateRef,
- ElementRef,
- OnInit,
- HostListener,
- ComponentRef,
- OnDestroy,
+ Directive,
+ Input,
+ TemplateRef,
+ ElementRef,
+ OnInit,
+ HostListener,
+ ComponentRef,
+ OnDestroy,
} from "@angular/core";
import { Overlay, OverlayPositionBuilder, OverlayRef } from "@angular/cdk/overlay";
import { ComponentPortal } from "@angular/cdk/portal";
@@ -15,89 +15,90 @@ import { Modifier } from "../../../../data/modifier";
import { ModDescriptionTooltipComponent } from "./mod-description-tooltip.component";
@Directive({
- selector: "[modTooltip]",
+ selector: "[modTooltip]",
})
export class ModTooltipDirective implements OnInit, OnDestroy {
- //If this is specified then specified text will be show in in the tooltip
- @Input(`modTooltip`) mod: Modifier | undefined;
+ //If this is specified then specified text will be show in in the tooltip
+ @Input(`modTooltip`) mod: Modifier | undefined;
- private _overlayRef: OverlayRef | undefined;
+ private _overlayRef: OverlayRef | undefined;
- constructor(
- private _overlay: Overlay,
- private _overlayPositionBuilder: OverlayPositionBuilder,
- private _elementRef: ElementRef
- ) {}
+ constructor(
+ private _overlay: Overlay,
+ private _overlayPositionBuilder: OverlayPositionBuilder,
+ private _elementRef: ElementRef
+ ) {}
- /**
- * Init life cycle event handler
- */
- ngOnInit() {
- const positionStrategy = this._overlayPositionBuilder
- .flexibleConnectedTo(this._elementRef)
- .withPositions([
- {
- originX: "center",
- originY: "bottom",
- overlayX: "center",
- overlayY: "top",
- offsetY: 5,
- },
- {
- originX: "center",
- originY: "top",
- overlayX: "center",
- overlayY: "bottom",
- offsetY: -5,
- },
- ]);
+ /**
+ * Init life cycle event handler
+ */
+ ngOnInit() {
+ const positionStrategy = this._overlayPositionBuilder
+ .flexibleConnectedTo(this._elementRef)
+ .withPositions([
+ {
+ originX: "center",
+ originY: "bottom",
+ overlayX: "center",
+ overlayY: "top",
+ offsetY: 5,
+ },
+ {
+ originX: "center",
+ originY: "top",
+ overlayX: "center",
+ overlayY: "bottom",
+ offsetY: -5,
+ },
+ ]);
- this._overlayRef = this._overlay.create({ positionStrategy });
- this._overlayRef.addPanelClass("overlay-no-pointer-event");
- }
+ this._overlayRef = this._overlay.create({ positionStrategy });
+ this._overlayRef.addPanelClass("overlay-no-pointer-event");
+ }
- /**
- * This method will be called whenever mouse enters in the Host element
- * i.e. where this directive is applied
- * This method will show the tooltip by instantiating the McToolTipComponent and attaching to the overlay
- */
- @HostListener("mouseenter")
- show() {
- //attach the component if it has not already attached to the overlay
- if (this._overlayRef && !this._overlayRef.hasAttached()) {
- const tooltipRef: ComponentRef =
- this._overlayRef.attach(new ComponentPortal(ModDescriptionTooltipComponent));
- tooltipRef.instance.mod = this.mod;
- }
+ /**
+ * This method will be called whenever mouse enters in the Host element
+ * i.e. where this directive is applied
+ * This method will show the tooltip by instantiating the McToolTipComponent and attaching to the overlay
+ */
+ @HostListener("mouseenter")
+ show() {
+ //attach the component if it has not already attached to the overlay
+ if (this._overlayRef && !this._overlayRef.hasAttached()) {
+ const tooltipRef: ComponentRef = this._overlayRef.attach(
+ new ComponentPortal(ModDescriptionTooltipComponent)
+ );
+ tooltipRef.instance.mod = this.mod;
}
+ }
- /**
- * This method will be called when mouse goes out of the host element
- * i.e. where this directive is applied
- * This method will close the tooltip by detaching the overlay from the view
- */
- @HostListener("mouseleave")
- hide() {
- this.closeToolTip();
- }
+ /**
+ * This method will be called when mouse goes out of the host element
+ * i.e. where this directive is applied
+ * This method will close the tooltip by detaching the overlay from the view
+ */
+ @HostListener("mouseleave")
+ hide() {
+ this.closeToolTip();
+ }
- /**
- * Destroy lifecycle event handler
- * This method will make sure to close the tooltip
- * It will be needed in case when app is navigating to different page
- * and user is still seeing the tooltip; In that case we do not want to hang around the
- * tooltip after the page [on which tooltip visible] is destroyed
- */
- ngOnDestroy() {
- this.closeToolTip();
- }
+ /**
+ * Destroy lifecycle event handler
+ * This method will make sure to close the tooltip
+ * It will be needed in case when app is navigating to different page
+ * and user is still seeing the tooltip; In that case we do not want to hang around the
+ * tooltip after the page [on which tooltip visible] is destroyed
+ */
+ ngOnDestroy() {
+ this.closeToolTip();
+ }
- /**
- * This method will close the tooltip by detaching the component from the overlay
- */
- private closeToolTip() {
- if (this._overlayRef) {
- this._overlayRef.detach();
- }
+ /**
+ * This method will close the tooltip by detaching the component from the overlay
+ */
+ private closeToolTip() {
+ if (this._overlayRef) {
+ this._overlayRef.detach();
}
+ }
}
diff --git a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.css b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.css
index a25ffc85..8ab5e50c 100644
--- a/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.css
+++ b/src/app/components/authenticated-v2/overlays/stat-cooldown-tooltip/stat-cooldown-tooltip.component.css
@@ -1,30 +1,30 @@
:host {
- max-width: 420px;
+ max-width: 420px;
}
.tooltip-container {
- border: 1px solid gray;
- padding: 1px 5px 4px 5px;
- background: linear-gradient(#484848 0px, #2c2c2c 100%);
+ border: 1px solid gray;
+ padding: 1px 5px 4px 5px;
+ background: linear-gradient(#484848 0px, #2c2c2c 100%);
}
.exotic-name {
- color: #eedb9e;
+ color: #eedb9e;
}
.good {
- color: #58b458;
+ color: #58b458;
}
.bad {
- color: #ff6b6b;
+ color: #ff6b6b;
}
table {
- border-collapse: separate;
- border-spacing: 7px 3px;
+ border-collapse: separate;
+ border-spacing: 7px 3px;
}
th {
- white-space: nowrap;
+ white-space: nowrap;
}
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 d79a02b9..e6b75071 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
@@ -1,62 +1,56 @@
-
- Warning: The stats of one or more items used for this build may be invalid.
- Please remove all negative/positive stat modifiers from the marked items.
-
+
+ Warning: The stats of one or more items used for this build may be invalid.
+ Please remove all negative/positive stat modifiers from the marked items.
+
- This table shows you the expected stats of any item:
-
-
-
-
+
-
- The following two buttons may not work for some at the moment:
-
-
+
+
+
+ The following two buttons may not work for some at the moment:
+
+
-
-
- What to do now?
-
- Detailed Instructions of to get your loadout ready.
-
-
-
-
-
- Move all related items into your inventory
-
- and use a masterworked class item.
-
-
-
-
0">
- Masterwork these items:
-
-
- {{ item1.name }}
-
-
-
-
+
+
+ What to do now?
+
+ Detailed Instructions of to get your loadout ready.
+
+
+
+
+
+ Move all related items into your inventory
+ and use a masterworked class item.
+
+
+
+
0">
+ Masterwork these items:
+
+
+ {{ item1.name }}
+
+
+
+
-
-
0 &&
- ($any(element?.mods) | count : StatModifier.NONE) < 5
- "
- >Equip the following stat mods:
-
-
-
-
-
0" class="positive">
- 0">{{ minorCount }} minor
- 0 && majorCount > 0">
- and
- 0"> {{ majorCount }} major
- Mobility Mod 1"
- >s
-
-
-
-
-
-
-
-
0" class="positive">
- 0">{{ minorCount }} minor
- 0 && majorCount > 0">
- and
- 0"> {{ majorCount }} major
- Resilience Mod 1"
- >s
-
-
-
-
-
-
-
-
0" class="positive">
- 0">{{ minorCount }} minor
- 0 && majorCount > 0">
- and
- 0"> {{ majorCount }} major
- Recovery Mod 1"
- >s
-
-
-
-
-
-
-
-
0" class="positive">
- 0">{{ minorCount }} minor
- 0 && majorCount > 0">
- and
- 0"> {{ majorCount }} major
- Discipline Mod 1"
- >s
-
-
-
-
-
-
-
-
0" class="positive">
- 0">{{ minorCount }} minor
- 0 && majorCount > 0">
- and
- 0"> {{ majorCount }} major
- Intellect Mod 1"
- >s
-
-
-
-
-
-
-
-
0" class="positive">
- 0">{{ minorCount }} minor
- 0 && majorCount > 0">
- and
- 0"> {{ majorCount }} major
- Strength Mod 1"
- >s
-
-
-
-
-
+
+
0 && ($any(element?.mods) | count : StatModifier.NONE) < 5
+ "
+ >Equip the following stat mods:
+
+
+
+
+
0" class="positive">
+ 0">{{ minorCount }} minor
+ 0 && majorCount > 0"> and
+ 0"> {{ majorCount }} major
+ Mobility Mod 1">s
-
0">
- Equip any mods and fragments that you enabled in the configuration:
-
- No combinations found.
- You should loosen your settings a bit.
-
-
-
-
-
-
- Items Used
-
-
-
- Combinations generated
-
+ Results
+
+ 0">
+ Click on a row to expand it and to see the list of the items required for this build.
+
+
+
+
+
+ No combinations found.
+ You should loosen your settings a bit.
+
+
+
+
-
- Summary of important configuration choices
-
-
- -1"
- disableRipple
- matTooltip="This setting enforces that all exotics are ignored."
- >No Exotic
-
- Exotic
-
- 1 &&
- _config_selectedExotics.indexOf(-1) === -1
- "
- disableRipple
- matTooltip="The tool will try to fulfill your settings for all the selected exotics. If you select exotics in different slots then it will search for legendary items to allow hotswapping with the same stats.">
- Multiple Exotics
-
- No stat mods
-
- Reduce wasted stats
-
- 0"
- disableRipple
- matTooltip="This setting limits available stat mod types, like major Intellect or Recovery."
- >Stat Mod Limitation
-
- 0"
- disableRipple
- matTooltip="This setting enforces an specific armor perk or modslot for a specific armor slot.">
- Armor Perk or Slot
-
- Zero Waste
-
-
- report_problem
- Assume everything is artifice
- report_problem
-
-
- Masterworked Only
-
-
- Masterwork Assumption
-
-
-
+
+ Summary of important configuration choices
+
+
+ -1"
+ disableRipple
+ matTooltip="This setting enforces that all exotics are ignored."
+ >No Exotic
+
+ Exotic
+
+ 1 && _config_selectedExotics.indexOf(-1) === -1"
+ disableRipple
+ matTooltip="The tool will try to fulfill your settings for all the selected exotics. If you select exotics in different slots then it will search for legendary items to allow hotswapping with the same stats.">
+ Multiple Exotics
+
+ No stat mods
+
+ Reduce wasted stats
+
+ 0"
+ disableRipple
+ matTooltip="This setting limits available stat mod types, like major Intellect or Recovery."
+ >Stat Mod Limitation
+
+ 0"
+ disableRipple
+ matTooltip="This setting enforces an specific armor perk or modslot for a specific armor slot.">
+ Armor Perk or Slot
+
+ Zero Waste
+
+
+ report_problem
+ Assume everything is artifice
+ report_problem
+
+
+ Masterworked Only
+
+
+ Masterwork Assumption
+
+
+
-
- info
- Note that you can change the sort column and order of the table by clicking on the
- headers.
-
+
+ info
+ Note that you can change the sort column and order of the table by clicking on the headers.
+
- The mod cost limitation is currently disabled. Modslot filtering still works.
+ color="warn"
+ class="mat-elevation-z4 impossible-container"
+ *ngIf="disabledSlotLimitation">
+ The mod cost limitation is currently disabled. Modslot filtering still works.
-
-
-
- info
- Explanation
-
- Detailed instructions for this section.
-
-
- This section allows you to further finetune the results to your requirements.
+
+
+
+ info
+ Explanation
+
+ Detailed instructions for this section.
+
+
+ This section allows you to further finetune the results to your requirements.
-
- Limitation of available stat mod types
- First off, you are able to
- select the kind and amount of stat mods you want. By
- filling the bars you
- specify how many modslots are already occupied and
- thus the tool knows how much space it is able to use for your builds. Hover over the
- bars, it will show you which mods are available after you changed your selection.
-
-
- Example Use Case: "On one item I can only use up to four recovery mods, no major
- intellect mod. One item can not hold any mods". In this scenario you would set one
- slot to 10 used slots, and the rest to 6.
-
-
- Filter for Elemental Affinity and Armor Perk/Slot
- This allows you to super-finetune your elemental and armor-perk requirements. You can
- change the settings for each armor slot. There are
- two modes per armor slot: Locked and
- Unlocked. Per default each entry is in the
- Locked mode.
-
- Locked means that
- this armor slot must have the selected element or
- perk.
-
- Unlocked means that the perk must exist, but
- the exact armor slot is irrelevant. That means that
- the tool can decide where it puts the desired element or perk.
-
-
- Example Use Case: "The Class Item and Gauntlet
- must be stasis. Oh, and I need two solar and one
- arc modslots, but the position does not matter". In this scenario, Class Item and
- Gauntlet would be Locked and the rest would be
- Unlocked.
-
-
- Legend:
-
-
-
-
-
-
Vault of Glass
-
-
-
-
Deep Stone Crypt
-
-
-
-
Garden of Salvation
-
-
-
-
-
-
Last Wish
-
-
-
-
Vow of the Disciple
-
-
-
-
Nightmare Modslot
-
-
-
-
-
-
Artifice Modslot
-
-
-
-
Iron Banner Perk
-
-
-
-
Uniformed Officer Perk
-
-
-
-
+
+ Limitation of available stat mod types
+ First off, you are able to
+ select the kind and amount of stat mods you want. By
+ filling the bars you
+ specify how many modslots are already occupied and thus
+ the tool knows how much space it is able to use for your builds. Hover over the bars, it will
+ show you which mods are available after you changed your selection.
+
+
+ Example Use Case: "On one item I can only use up to four recovery mods, no major intellect
+ mod. One item can not hold any mods". In this scenario you would set one slot to 10 used
+ slots, and the rest to 6.
+
+
+ Filter for Elemental Affinity and Armor Perk/Slot
+ This allows you to super-finetune your elemental and armor-perk requirements. You can change
+ the settings for each armor slot. There are two modes per
+ armor slot: Locked and
+ Unlocked. Per default each entry is in the
+ Locked mode.
+
+ Locked means that
+ this armor slot must have the selected element or perk.
+
+ Unlocked means that the perk must exist, but
+ the exact armor slot is irrelevant. That means that the
+ tool can decide where it puts the desired element or perk.
+
+
+ Example Use Case: "The Class Item and Gauntlet
+ must be stasis. Oh, and I need two solar and one arc
+ modslots, but the position does not matter". In this scenario, Class Item and Gauntlet would
+ be Locked and the rest would be
+ Unlocked.
+
-
- Character Selection
-
-
-
-
-
- Select the stats you want to achieve
- The heart of this tool. Select the stats you want!
-
-
-
-
-
- Exotic
-
- If you want, limit all results to an exotic. Use this to create a build around an
- exotic.
- Exotics you do not have in the inventory or vault are grayed out.
-
-
-
-
-
-
- Armor limitation
-
- This section allows you to further specify what kind of armor and mods you want.
-
-
-
-
-
-
- Stat-Boost Selection
- Select Mods and Skills that affect your overall stats.
- Please note that D2AP also allows theoretical, but impossible input.
- Only fragments that affect stats are shown.
-
-
-
-
-
- Advanced Settings
- More settings! Use them to fine-tune your buildcrafting process.
-
-
-
-
-
- Disabled Armor Pieces
-
- These armor pieces are disabled. Click them to enable them again.
- They won't show up in any results.
- You can disable items in the detailed result overview.
-
-
-
-
-
-
- Configuration Management
- Save and load your buildcrafting settings for another day!
-
-
-
-
+
+ Character Selection
+
+
+
+
+
+ Select the stats you want to achieve
+ The heart of this tool. Select the stats you want!
+
+
+
+
+
+ Exotic
+
+ If you want, limit all results to an exotic. Use this to create a build around an exotic.
+ Exotics you do not have in the inventory or vault are grayed out.
+
+
+
+
+
+
+ Armor limitation
+
+ This section allows you to further specify what kind of armor and mods you want.
+
+
+
+
+
+
+ Stat-Boost Selection
+ Select Mods and Skills that affect your overall stats.
+ Please note that D2AP also allows theoretical, but impossible input.
+ Only fragments that affect stats are shown.
+
+
+
+
+
+ Advanced Settings
+ More settings! Use them to fine-tune your buildcrafting process.
+
+
+
+
+
+ Disabled Armor Pieces
+
+ These armor pieces are disabled. Click them to enable them again.
+ They won't show up in any results.
+ You can disable items in the detailed result overview.
+
+
+
+
+
+
+ Configuration Management
+ Save and load your buildcrafting settings for another day!
+
+
+
+
-
- Return to D2ArmorPicker
-
-
- About this page
-
- This page can help you to sort out your vault a bit. It takes all your armor
- entries and tries to sort them into {{ clusterInformation.length }} clusters. It then
- gives you a DIM search query for each cluster. You can use this to look at similar rolls
- in your vault and to clean out some of them.
-
-
- Please note that it will cluster all armor for all characters. Make sure to use
- "is:titan", "is:warlock" or "is:hunter" in DIM.
-
-
- Please also note that this feature is new and may have some inaccuracy for some items.
-
+
+ Return to D2ArmorPicker
+
+
+ About this page
+
+ This page can help you to sort out your vault a bit. It takes all your armor entries
+ and tries to sort them into {{ clusterInformation.length }} clusters. It then gives you a DIM
+ search query for each cluster. You can use this to look at similar rolls in your vault and to
+ clean out some of them.
+
+
+ Please note that it will cluster all armor for all characters. Make sure to use "is:titan",
+ "is:warlock" or "is:hunter" in DIM.
+
+
+ Please also note that this feature is new and may have some inaccuracy for some items.
+
- How do I use this page?
-
-
-
Pick one of the clusters below.
-
Copy the DIM search query and insert it in the search bar in DIM.
-
- If you want, add one of the following to the search query: "is:titan",
- "is:warlock", "is:hunter" (without " of course);
- You can also use "not:masterwork", "not:exotic" or other filters.
-
-
Look at all the armor. Just look at it. If you see duplicates, you can look at
- them and decide which ones to keep.
-
-
-
-
+ How do I use this page?
+
+
+
Pick one of the clusters below.
+
Copy the DIM search query and insert it in the search bar in DIM.
+
+ If you want, add one of the following to the search query: "is:titan", "is:warlock",
+ "is:hunter" (without " of course);
+ You can also use "not:masterwork", "not:exotic" or other filters.
+
+
Look at all the armor. Just look at it. If you see duplicates, you can look at them and
+ decide which ones to keep.
+
+
+
+
-
- Options
- Additional options for the clustering results.
-
-
- Exotics Decide whether you want to see or hide exotics.
-
-
- visibility_off
- visibility
- All
-
-
-
-
- Masterwork Decide whether you want to see or hide masterworked
- armor.
-
-
- visibility_off
- visibility
- All
-
-
-
-
- Class Decide which class you want to see.
-
-
-
-
-
-
-
-
-
-
-
+
+ Options
+ Additional options for the clustering results.
+
+
+ Exotics Decide whether you want to see or hide exotics.
+
+
+ visibility_off
+ visibility
+ All
+
+
+
+
+ Masterwork Decide whether you want to see or hide masterworked armor.
+
+
+ visibility_off
+ visibility
+ All
+
+
+
+
+ Class Decide which class you want to see.
+
+
+
+
+
+
+
+
+
+
+
- All
-
-
-
- Please note that this feature is mainly targeted for data nerds and people interested in the
- basic stats of armor. It is just a data visualization. Mobile layout and a search function
- may happen in the future.
-
- Filters
- Some filters. Nothing fancy but it works. Sorry ;)
-
-
-
-
-
- Item Name
-
-
-
-
-
- Item Hash
-
-
-
-
-
- Item ID
-
-
-
-
-
-
Mobility:
-
{{ minMobility }}
-
-
-
+ Armor Investigation
+ A visualization tool for data scientists.
+
+ This part of the D2ArmorPicker lists all of your armor items. For each armor it shows how it is
+ generated. It shows every used "plug", as well as the intrinsic stats of exotics. For
+ more information about plugs, see
+ here. Make sure to give the author an upvote for the hard work!
+ Please note that this feature is mainly targeted for data nerds and people interested in the
+ basic stats of armor. It is just a data visualization. Mobile layout and a search function
+ may happen in the future.
+
+ Filters
+ Some filters. Nothing fancy but it works. Sorry ;)
+
+
-
- Return to D2ArmorPicker
-
+
+ Return to D2ArmorPicker
+
-
-
- About D2ArmorPicker by Mijago
-
-
-
-
-
-
-
-
-
-
-
-
- D2ArmorPicker (or short D2AP) is a small web-app to quickly find armor that fits your
- desired stat requirements. It uses the armor in your vault, inventory and postmaster,
- calculates every possible variation and shows only those that fulfill the given
- requirements.
-
-
- As I raided a lot and I mainly play meme builds, I wanted to be able to switch exotics
- and have decent stats without being a burden on my team. That's why I developed the
- first version of this tool
- in Python. After a few days I started to translate this tool
- into a webpage.
- My friends liked it, but it didn't have API access. Thus I created the first version of
- D2ArmorPicker, and after people started using and liking it, I started to completely
- rewrite it - that's Version 2!
-
-
- I keep updating and improving this tool, but beware that it takes a bit, as I work on it
- on my spare time. Feel free to follow me on Twitter
- , I occasionally post updates there. If you like my tools and want to support me, head
- to ko-fi.com and buy me a coffee!
- ❤
-
-
+
+
+ About D2ArmorPicker by Mijago
+
+
+
+
+
+
+
+
+
+
+
+
+ D2ArmorPicker (or short D2AP) is a small web-app to quickly find armor that fits your desired
+ stat requirements. It uses the armor in your vault, inventory and postmaster, calculates every
+ possible variation and shows only those that fulfill the given requirements.
+
+
+ As I raided a lot and I mainly play meme builds, I wanted to be able to switch exotics and
+ have decent stats without being a burden on my team. That's why I developed the
+ first version of this tool
+ in Python. After a few days I started to translate this tool
+ into a webpage. My
+ friends liked it, but it didn't have API access. Thus I created the first version of
+ D2ArmorPicker, and after people started using and liking it, I started to completely rewrite
+ it - that's Version 2!
+
+
+ I keep updating and improving this tool, but beware that it takes a bit, as I work on it on my
+ spare time. Feel free to follow me on Twitter
+ , I occasionally post updates there. If you like my tools and want to support me, head to
+ ko-fi.com and buy me a coffee! ❤
+
+
-
- How do I use D2ArmorPicker?
-
- Follow these few steps:
-
-
Login. You already did this!
-
- [Optional] Pick an exotic you want to use. The tool will only display results
- for this exotic. You can always undo this.
-
-
- [Optional] Select additional mods/fragments that give a bonus (or penalty).
- These are important if you want certain stat combinations even if you have
- penalties on your gear.
-
-
- Select the stat distribution you seek. This is the heart of D2ArmorPicker. You
- just click 100 recovery and it will display you builds that have 100 recovery.
- Note that it automatically adds stat mods if necessary.
-
-
- Now look to the result table. On a normal desktop it should be on the right, on
- smaller devices it might move below the configuration. The table lists multiple
- results and you can sort it by your requirements.
- Click on an entry to open look the detailed view:
-
-
-
- The example above shows a build that utilizes Dunemarchers and a
- zero-waste-build. To achieve it, I have to move all the items in my inventory
- and equip them. After this, I masterwork them and add the mods as
- the table displays them:
-
-
Discipline: One major mod, one minor mod;
-
Intellect: One major mod, one minor mod;
-
Strength: One major mod;
-
I also add Powerful Friends and Radiant Light, as I chose them in the
- configuration.
-
-
-
-
-
+
+ How do I use D2ArmorPicker?
+
+ Follow these few steps:
+
+
Login. You already did this!
+
+ [Optional] Pick an exotic you want to use. The tool will only display results for this
+ exotic. You can always undo this.
+
+
+ [Optional] Select additional mods/fragments that give a bonus (or penalty). These are
+ important if you want certain stat combinations even if you have penalties on your gear.
+
+
+ Select the stat distribution you seek. This is the heart of D2ArmorPicker. You just click
+ 100 recovery and it will display you builds that have 100 recovery.
+ Note that it automatically adds stat mods if necessary.
+
+
+ Now look to the result table. On a normal desktop it should be on the right, on smaller
+ devices it might move below the configuration. The table lists multiple results and you
+ can sort it by your requirements.
+ Click on an entry to open look the detailed view:
+
+
+
+ The example above shows a build that utilizes Dunemarchers and a zero-waste-build. To
+ achieve it, I have to move all the items in my inventory and equip them. After this, I
+ masterwork them and add the mods as the table displays them:
+
+
Discipline: One major mod, one minor mod;
+
Intellect: One major mod, one minor mod;
+
Strength: One major mod;
+
I also add Powerful Friends and Radiant Light, as I chose them in the
+ configuration.
+
+
+
+
+
-
- Where can I grind high stat armor?
-
- There are many spots that give you mediocre armor, but for the really good builds you
- need the correct armor. Here I provide a list of high-stat armor sources. It may not be
- complete, but a good starting point.
- Last updated February 8, 2022.
+
+ Where can I grind high stat armor?
+
+ There are many spots that give you mediocre armor, but for the really good builds you need the
+ correct armor. Here I provide a list of high-stat armor sources. It may not be complete, but a
+ good starting point.
+ Last updated February 8, 2022.
-
-
Farm legendary Dares!
-
- Clear Pit of Heresy Dungeon, the boss drop guarantees two stats to be 16 (or
- higher).
-
- Farm a bunch of Spoils, go into Master Vault of Glass and buy armor at the final
- chest.
-
The list will be extended sometime in Witch Queen!
-
- Some good armor rolls may look like these:
-
-
-
-
-
-
-
-
+
+
Farm legendary Dares!
+
+ Clear Pit of Heresy Dungeon, the boss drop guarantees two stats to be 16 (or higher).
+
+ Farm a bunch of Spoils, go into Master Vault of Glass and buy armor at the final
+ chest.
+
The list will be extended sometime in Witch Queen!
diff --git a/src/app/components/authenticated-v2/subpages/help-page/help-page.component.ts b/src/app/components/authenticated-v2/subpages/help-page/help-page.component.ts
index 97aae095..5e3175e6 100644
--- a/src/app/components/authenticated-v2/subpages/help-page/help-page.component.ts
+++ b/src/app/components/authenticated-v2/subpages/help-page/help-page.component.ts
@@ -2,17 +2,17 @@ import { Component, OnInit } from "@angular/core";
import { CHANGELOG_DATA } from "../../../../data/changelog";
@Component({
- selector: "app-help-page",
- templateUrl: "./help-page.component.html",
- styleUrls: ["./help-page.component.css"],
+ selector: "app-help-page",
+ templateUrl: "./help-page.component.html",
+ styleUrls: ["./help-page.component.css"],
})
export class HelpPageComponent {
- knownIssues: string[] = [
- "When you click buttons on the page too fast are able to select an invalid state with no results. Just undo your changed settings. And be patient - the calculation is an expensive task.",
- "Sometimes duplicate results are given. This happens when the inventory got updated twice (Race Condition). Only reported once, and not really a problem.",
- ];
+ knownIssues: string[] = [
+ "When you click buttons on the page too fast are able to select an invalid state with no results. Just undo your changed settings. And be patient - the calculation is an expensive task.",
+ "Sometimes duplicate results are given. This happens when the inventory got updated twice (Race Condition). Only reported once, and not really a problem.",
+ ];
- changelog = CHANGELOG_DATA;
+ changelog = CHANGELOG_DATA;
- constructor() {}
+ constructor() {}
}
diff --git a/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.html b/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.html
index 2b8b015e..8fc388b8 100644
--- a/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.html
+++ b/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.html
@@ -1,455 +1,441 @@
-
-
- Configuration
-
-
-
Required Stats
+
+
+ Configuration
+
+
+
Required Stats
-
-
-
- Mobility
-
-
-
-
-
- Resilience
-
-
-
-
-
- Recovery
-
-
-
-
-
- Discipline
-
-
-
-
-
- Intellect
-
-
-
-
-
- Strength
-
-
-
-
+
+
+
+ Mobility
+
+
+
+
+
+ Resilience
+
+
+
+
+
+ Recovery
+
+
+
+
+
+ Discipline
+
+
+
+
+
+ Intellect
+
+
+
+
+
+ Strength
+
+
+
+
- Stats must be met exactly
-
-
-
Armor Selection
-
Use armor:
-
- Both
- Own Armor
- Generated Armor
-
-
+ Stats must be met exactly
+
+
+
Armor Selection
+
Use armor:
+
+ Both
+ Own Armor
+ Generated Armor
+
+
-
- Class:
-
- Any
- Titan
- Hunter
- Warlock
-
-
+
+ Class:
+
+ Any
+ Titan
+ Hunter
+ Warlock
+
+
- Require an exotic armor piece
-
-
- Generate exotics with intrinsic stats
-
-
+ Require an exotic armor piece
+
+
+ Generate exotics with intrinsic stats
+
+
-
-
Fragment selection
- Enable fragment picking
-
-
- Subclass:
-
- Any
- Solar
- Arc
- Void
- Stasis
- Strand
-
+
+
Fragment selection
+ Enable fragment picking
+
+
+ Subclass:
+
+ Any
+ Solar
+ Arc
+ Void
+ Stasis
+ Strand
+
-
-
+
+
-
diff --git a/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.scss b/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.scss
index 083870cf..8383b8a0 100644
--- a/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.scss
+++ b/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.scss
@@ -1,87 +1,87 @@
#result-table tr.line-top td {
- border-top: 1px solid #e0e0e0;
+ border-top: 1px solid #e0e0e0;
}
#result-table tr.line-bottom td {
- border-bottom: 1px solid #e0e0e0;
+ border-bottom: 1px solid #e0e0e0;
}
#result-table tr td:last-of-type {
- border-left: 1px solid #e0e0e0;
+ border-left: 1px solid #e0e0e0;
}
#result-table tr td:first-of-type {
- border-right: 1px solid #e0e0e0;
+ border-right: 1px solid #e0e0e0;
}
#result-table tr.item-row td:nth-of-type(4) {
- border-right: 1px solid #e0e0e0;
+ border-right: 1px solid #e0e0e0;
}
#result-table tr:not(.item-row) td:nth-of-type(2) {
- border-right: 1px solid #e0e0e0;
+ border-right: 1px solid #e0e0e0;
}
.status-optimal {
- color: #4caf50;
+ color: #4caf50;
}
.status-acceptable {
- color: #ff9800;
+ color: #ff9800;
}
.status-infeasable {
- color: #f44336;
+ color: #f44336;
}
.status-undefined {
- color: #9e9e9e;
+ color: #9e9e9e;
}
#result-table td {
- /* text right align */
- text-align: right;
+ /* text right align */
+ text-align: right;
}
#container {
- margin-bottom: 20px;
- margin-top: 20px;
+ margin-bottom: 20px;
+ margin-top: 20px;
}
#slider-table mat-slider {
- width: 200px;
+ width: 200px;
}
.input-list {
- max-width: 450px;
+ max-width: 450px;
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
- .input-list-entry {
- flex-basis: 33.33333%;
- }
+ .input-list-entry {
+ flex-basis: 33.33333%;
+ }
}
.row-masterwork {
- td:not(:first-of-type) {
- color: goldenrod;
- }
+ td:not(:first-of-type) {
+ color: goldenrod;
+ }
}
.theoretic,
.intrinsic {
- td:not(:first-of-type) {
- color: #ecb5b5;
- }
+ td:not(:first-of-type) {
+ color: #ecb5b5;
+ }
}
.icon-item {
- height: 20px;
+ height: 20px;
}
.exotic {
- td:nth-of-type(2) {
- color: #e6d4b8;
- }
+ td:nth-of-type(2) {
+ color: #e6d4b8;
+ }
}
diff --git a/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.ts b/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.ts
index 1b0c96ae..3f9211aa 100644
--- a/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.ts
+++ b/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.component.ts
@@ -7,9 +7,9 @@ import { ArmorSlot } from "../../../../data/enum/armor-slot";
import { DestinyClass } from "bungie-api-ts/destiny2";
import { buildDb } from "../../../../data/database";
import {
- ArmorPerkOrSlot,
- ArmorPerkOrSlotIcons,
- ArmorPerkOrSlotNames,
+ ArmorPerkOrSlot,
+ ArmorPerkOrSlotIcons,
+ ArmorPerkOrSlotNames,
} from "../../../../data/enum/armor-stat";
import { Table } from "dexie";
import { IManifestArmor } from "../../../../data/types/IManifestArmor";
@@ -17,996 +17,989 @@ import { IManifestArmor } from "../../../../data/types/IManifestArmor";
const statNames = ["mobility", "resilience", "recovery", "discipline", "intellect", "strength"];
const intrinsicExoticArmorByClassAndSlot = {
- [DestinyClass.Titan]: {
- [ArmorSlot.ArmorSlotHelmet]: [
- { stats: [0, 1, 1], armor: [3216110440, 106575079, 2578771006] },
- { stats: [0, 2, 0], armor: [2808156426, 3883866764] },
- ],
- [ArmorSlot.ArmorSlotGauntlet]: [
- { stats: [0, 1, 1], armor: [1734844651, 241462141, 241462142] },
- { stats: [0, 2, 0], armor: [1734844650, 1848640623, 2240152949, 2563444729] },
- ],
- [ArmorSlot.ArmorSlotChest]: [
- { stats: [2, 1, 0], armor: [1192890598, 1341951177, 3874247549] },
- { stats: [1, 1, 1], armor: [1591207518, 1591207519] },
- { stats: [1, 2, 0], armor: [1654461647] },
- ],
- [ArmorSlot.ArmorSlotLegs]: [
- { stats: [1, 1, 0], armor: [3539357319, 2255796155, 136355432, 1160559849] },
- { stats: [1, 0, 1], armor: [2423243921] },
- { stats: [0, 2, 0], armor: [3539357318] },
- ],
- },
- [DestinyClass.Hunter]: {
- [ArmorSlot.ArmorSlotHelmet]: [
- { stats: [2, 0, 0], armor: [896224899] },
- { stats: [1, 1, 0], armor: [2757274117, 1053737370, 1321354572, 1321354573] },
- { stats: [1, 0, 1], armor: [3562696927, 2773056939] },
- ],
- [ArmorSlot.ArmorSlotGauntlet]: [
- { stats: [1, 1, 1], armor: [3942036043] },
- { stats: [0, 1, 1], armor: [475652357] },
- { stats: [1, 0, 1], armor: [691578978] },
- { stats: [1, 1, 0], armor: [691578979, 1688602431] },
- { stats: [2, 0, 0], armor: [193869523, 1734144409, 4165919945] },
- ],
- [ArmorSlot.ArmorSlotChest]: [
- { stats: [2, 0, 1], armor: [978537162] },
- { stats: [2, 1, 0], armor: [903984858, 1474735276, 2766109872] },
- { stats: [1, 1, 1], armor: [1474735277] },
- { stats: [1, 2, 0], armor: [2766109874, 3070555693] },
- ],
- [ArmorSlot.ArmorSlotLegs]: [
- { stats: [2, 0, 0], armor: [193869520, 609852545] },
- { stats: [1, 1, 0], armor: [193869522] },
- ],
- },
- [DestinyClass.Warlock]: {
- [ArmorSlot.ArmorSlotHelmet]: [
- { stats: [0, 1, 1], armor: [3381022971, 1030017949, 1096253259, 2384488862] },
- { stats: [0, 0, 2], armor: [3381022970, 2177524718, 2428181146] },
- { stats: [1, 0, 1], armor: [3381022969, 3948284065] },
- ],
- [ArmorSlot.ArmorSlotGauntlet]: [
- { stats: [0, 0, 2], armor: [1906093346, 3288917178, 3844826443] },
- { stats: [0, 2, 1], armor: [2950045886] },
- { stats: [0, 1, 1], armor: [3084282676, 3844826440] },
- { stats: [1, 0, 1], armor: [3627185503, 3787517196] },
- ],
- [ArmorSlot.ArmorSlotChest]: [
- { stats: [2, 0, 1], armor: [370930766, 4057299719] },
- { stats: [0, 2, 1], armor: [1725917554, 4057299718] },
- { stats: [0, 1, 2], armor: [2082483156] },
- ],
- [ArmorSlot.ArmorSlotLegs]: [
- { stats: [0, 1, 2], armor: [121305948] },
- { stats: [1, 0, 1], armor: [138282166] },
- { stats: [0, 1, 1], armor: [4136768282] },
- ],
- },
+ [DestinyClass.Titan]: {
+ [ArmorSlot.ArmorSlotHelmet]: [
+ { stats: [0, 1, 1], armor: [3216110440, 106575079, 2578771006] },
+ { stats: [0, 2, 0], armor: [2808156426, 3883866764] },
+ ],
+ [ArmorSlot.ArmorSlotGauntlet]: [
+ { stats: [0, 1, 1], armor: [1734844651, 241462141, 241462142] },
+ { stats: [0, 2, 0], armor: [1734844650, 1848640623, 2240152949, 2563444729] },
+ ],
+ [ArmorSlot.ArmorSlotChest]: [
+ { stats: [2, 1, 0], armor: [1192890598, 1341951177, 3874247549] },
+ { stats: [1, 1, 1], armor: [1591207518, 1591207519] },
+ { stats: [1, 2, 0], armor: [1654461647] },
+ ],
+ [ArmorSlot.ArmorSlotLegs]: [
+ { stats: [1, 1, 0], armor: [3539357319, 2255796155, 136355432, 1160559849] },
+ { stats: [1, 0, 1], armor: [2423243921] },
+ { stats: [0, 2, 0], armor: [3539357318] },
+ ],
+ },
+ [DestinyClass.Hunter]: {
+ [ArmorSlot.ArmorSlotHelmet]: [
+ { stats: [2, 0, 0], armor: [896224899] },
+ { stats: [1, 1, 0], armor: [2757274117, 1053737370, 1321354572, 1321354573] },
+ { stats: [1, 0, 1], armor: [3562696927, 2773056939] },
+ ],
+ [ArmorSlot.ArmorSlotGauntlet]: [
+ { stats: [1, 1, 1], armor: [3942036043] },
+ { stats: [0, 1, 1], armor: [475652357] },
+ { stats: [1, 0, 1], armor: [691578978] },
+ { stats: [1, 1, 0], armor: [691578979, 1688602431] },
+ { stats: [2, 0, 0], armor: [193869523, 1734144409, 4165919945] },
+ ],
+ [ArmorSlot.ArmorSlotChest]: [
+ { stats: [2, 0, 1], armor: [978537162] },
+ { stats: [2, 1, 0], armor: [903984858, 1474735276, 2766109872] },
+ { stats: [1, 1, 1], armor: [1474735277] },
+ { stats: [1, 2, 0], armor: [2766109874, 3070555693] },
+ ],
+ [ArmorSlot.ArmorSlotLegs]: [
+ { stats: [2, 0, 0], armor: [193869520, 609852545] },
+ { stats: [1, 1, 0], armor: [193869522] },
+ ],
+ },
+ [DestinyClass.Warlock]: {
+ [ArmorSlot.ArmorSlotHelmet]: [
+ { stats: [0, 1, 1], armor: [3381022971, 1030017949, 1096253259, 2384488862] },
+ { stats: [0, 0, 2], armor: [3381022970, 2177524718, 2428181146] },
+ { stats: [1, 0, 1], armor: [3381022969, 3948284065] },
+ ],
+ [ArmorSlot.ArmorSlotGauntlet]: [
+ { stats: [0, 0, 2], armor: [1906093346, 3288917178, 3844826443] },
+ { stats: [0, 2, 1], armor: [2950045886] },
+ { stats: [0, 1, 1], armor: [3084282676, 3844826440] },
+ { stats: [1, 0, 1], armor: [3627185503, 3787517196] },
+ ],
+ [ArmorSlot.ArmorSlotChest]: [
+ { stats: [2, 0, 1], armor: [370930766, 4057299719] },
+ { stats: [0, 2, 1], armor: [1725917554, 4057299718] },
+ { stats: [0, 1, 2], armor: [2082483156] },
+ ],
+ [ArmorSlot.ArmorSlotLegs]: [
+ { stats: [0, 1, 2], armor: [121305948] },
+ { stats: [1, 0, 1], armor: [138282166] },
+ { stats: [0, 1, 1], armor: [4136768282] },
+ ],
+ },
};
@Component({
- selector: "app-theorizer-page",
- templateUrl: "./theorizer-page.component.html",
- styleUrls: ["./theorizer-page.component.scss"],
+ selector: "app-theorizer-page",
+ templateUrl: "./theorizer-page.component.html",
+ styleUrls: ["./theorizer-page.component.scss"],
})
export class TheorizerPageComponent implements OnInit {
- ModifierType = ModifierType;
+ ModifierType = ModifierType;
- glpk: GLPK | null = null;
+ glpk: GLPK | null = null;
- calculating = false;
+ calculating = false;
- // options
- options = {
- solver: {
- timeout: 5,
- presolve: true,
- },
- armor: {
- // armorType, 1 = own, 2 = generated, 3 = both
- armorType: 3,
- requiresExotic: true,
- },
- stats: {
- desired: {
- mobility: 0,
- resilience: 0,
- recovery: 0,
- discipline: 0,
- intellect: 0,
- strength: 0,
- },
- constantBoost: {
- mobility: 0,
- resilience: 0,
- recovery: 0,
- discipline: 0,
- intellect: 0,
- strength: 0,
- },
- // if we must reach the EXACT stats and can not go over them
- statsAreFixed: false,
- maxValue: 109,
- minTiers: 0,
- minPoints: 100,
- maxWaste: 54,
- },
- fragments: {
- enableFragmentPicker: false,
- subclass: -1,
- class: DestinyClass.Unknown,
- },
- mods: {
- maxMods: 5,
- maxArtifice: 5,
- },
- generator: {
- generateExoticsWithIntrinsicStats: false,
- },
- availablePlugs: [
- [1, 1, 10],
- [1, 1, 11],
- [1, 1, 12],
- [1, 1, 13],
- [1, 1, 14],
- [1, 1, 15],
- [1, 5, 5],
- [1, 5, 6],
- [1, 5, 7],
- [1, 5, 8],
- [1, 5, 9],
- [1, 5, 10],
- [1, 5, 11],
- [1, 6, 5],
- [1, 6, 6],
- [1, 6, 7],
- [1, 6, 8],
- [1, 6, 9],
- [1, 7, 5],
- [1, 7, 6],
- [1, 7, 7],
- [1, 7, 8],
- [1, 8, 5],
- [1, 8, 6],
- [1, 8, 7],
- [1, 9, 5],
- [1, 9, 6],
- [1, 10, 1],
- [1, 10, 5],
- [1, 11, 1],
- [1, 11, 5],
- [1, 12, 1],
- [1, 13, 1],
- [1, 14, 1],
- [1, 15, 1],
- [5, 1, 5],
- [5, 1, 6],
- [5, 1, 7],
- [5, 1, 8],
- [5, 1, 9],
- [5, 1, 10],
- [5, 1, 11],
- [5, 5, 1],
- [5, 5, 5],
- [5, 6, 1],
- [5, 7, 1],
- [5, 8, 1],
- [5, 9, 1],
- [5, 10, 1],
- [5, 11, 1],
- [6, 1, 5],
- [6, 1, 6],
- [6, 1, 7],
- [6, 1, 8],
- [6, 1, 9],
- [6, 5, 1],
- [6, 6, 1],
- [6, 7, 1],
- [6, 8, 1],
- [6, 9, 1],
- [7, 1, 5],
- [7, 1, 6],
- [7, 1, 7],
- [7, 1, 8],
- [7, 5, 1],
- [7, 6, 1],
- [7, 7, 1],
- [7, 8, 1],
- [8, 1, 5],
- [8, 1, 6],
- [8, 1, 7],
- [8, 5, 1],
- [8, 6, 1],
- [8, 7, 1],
- [9, 1, 5],
- [9, 1, 6],
- [9, 5, 1],
- [9, 6, 1],
- [10, 1, 1],
- [10, 1, 5],
- [10, 5, 1],
- [11, 1, 1],
- [11, 1, 5],
- [11, 5, 1],
- [12, 1, 1],
- [13, 1, 1],
- [14, 1, 1],
- [15, 1, 1],
- ],
- };
- result: Result | null = null;
- result_items: any | null = null;
- time_progress = 0;
- private timerId: number = 0;
- lp: LP | null = null;
- private inventoryArmor: Table;
- private manifestArmor: Table;
-
- constructor() {
- const db = buildDb(async () => {});
- this.inventoryArmor = db.table("inventoryArmor");
- this.manifestArmor = db.table("manifestArmor");
+ // options
+ options = {
+ solver: {
+ timeout: 5,
+ presolve: true,
+ },
+ armor: {
+ // armorType, 1 = own, 2 = generated, 3 = both
+ armorType: 3,
+ requiresExotic: true,
+ },
+ stats: {
+ desired: {
+ mobility: 0,
+ resilience: 0,
+ recovery: 0,
+ discipline: 0,
+ intellect: 0,
+ strength: 0,
+ },
+ constantBoost: {
+ mobility: 0,
+ resilience: 0,
+ recovery: 0,
+ discipline: 0,
+ intellect: 0,
+ strength: 0,
+ },
+ // if we must reach the EXACT stats and can not go over them
+ statsAreFixed: false,
+ maxValue: 109,
+ minTiers: 0,
+ minPoints: 100,
+ maxWaste: 54,
+ },
+ fragments: {
+ enableFragmentPicker: false,
+ subclass: -1,
+ class: DestinyClass.Unknown,
+ },
+ mods: {
+ maxMods: 5,
+ maxArtifice: 5,
+ },
+ generator: {
+ generateExoticsWithIntrinsicStats: false,
+ },
+ availablePlugs: [
+ [1, 1, 10],
+ [1, 1, 11],
+ [1, 1, 12],
+ [1, 1, 13],
+ [1, 1, 14],
+ [1, 1, 15],
+ [1, 5, 5],
+ [1, 5, 6],
+ [1, 5, 7],
+ [1, 5, 8],
+ [1, 5, 9],
+ [1, 5, 10],
+ [1, 5, 11],
+ [1, 6, 5],
+ [1, 6, 6],
+ [1, 6, 7],
+ [1, 6, 8],
+ [1, 6, 9],
+ [1, 7, 5],
+ [1, 7, 6],
+ [1, 7, 7],
+ [1, 7, 8],
+ [1, 8, 5],
+ [1, 8, 6],
+ [1, 8, 7],
+ [1, 9, 5],
+ [1, 9, 6],
+ [1, 10, 1],
+ [1, 10, 5],
+ [1, 11, 1],
+ [1, 11, 5],
+ [1, 12, 1],
+ [1, 13, 1],
+ [1, 14, 1],
+ [1, 15, 1],
+ [5, 1, 5],
+ [5, 1, 6],
+ [5, 1, 7],
+ [5, 1, 8],
+ [5, 1, 9],
+ [5, 1, 10],
+ [5, 1, 11],
+ [5, 5, 1],
+ [5, 5, 5],
+ [5, 6, 1],
+ [5, 7, 1],
+ [5, 8, 1],
+ [5, 9, 1],
+ [5, 10, 1],
+ [5, 11, 1],
+ [6, 1, 5],
+ [6, 1, 6],
+ [6, 1, 7],
+ [6, 1, 8],
+ [6, 1, 9],
+ [6, 5, 1],
+ [6, 6, 1],
+ [6, 7, 1],
+ [6, 8, 1],
+ [6, 9, 1],
+ [7, 1, 5],
+ [7, 1, 6],
+ [7, 1, 7],
+ [7, 1, 8],
+ [7, 5, 1],
+ [7, 6, 1],
+ [7, 7, 1],
+ [7, 8, 1],
+ [8, 1, 5],
+ [8, 1, 6],
+ [8, 1, 7],
+ [8, 5, 1],
+ [8, 6, 1],
+ [8, 7, 1],
+ [9, 1, 5],
+ [9, 1, 6],
+ [9, 5, 1],
+ [9, 6, 1],
+ [10, 1, 1],
+ [10, 1, 5],
+ [10, 5, 1],
+ [11, 1, 1],
+ [11, 1, 5],
+ [11, 5, 1],
+ [12, 1, 1],
+ [13, 1, 1],
+ [14, 1, 1],
+ [15, 1, 1],
+ ],
+ };
+ result: Result | null = null;
+ result_items: any | null = null;
+ time_progress = 0;
+ private timerId: number = 0;
+ lp: LP | null = null;
+ private inventoryArmor: Table;
+ private manifestArmor: Table;
+
+ constructor() {
+ const db = buildDb(async () => {});
+ this.inventoryArmor = db.table("inventoryArmor");
+ this.manifestArmor = db.table("manifestArmor");
+ }
+
+ sum(l: number[]): number {
+ return l.reduce((a, b) => a + b, 0);
+ }
+
+ getPerkName(perk: number) {
+ return ArmorPerkOrSlotNames[perk as ArmorPerkOrSlot];
+ }
+
+ getPerkIconUrl(perk: number) {
+ return ArmorPerkOrSlotIcons[perk as ArmorPerkOrSlot];
+ }
+
+ slotNameByIndex(index: number): string {
+ switch (index) {
+ case 0:
+ return "Helmet";
+ case 1:
+ return "Gauntlets";
+ case 2:
+ return "Chest Armor";
+ case 3:
+ return "Leg Armor";
+ case 4:
+ return "Class Item";
+ default:
+ return "Unknown";
}
-
- sum(l: number[]): number {
- return l.reduce((a, b) => a + b, 0);
+ }
+
+ resultValueToText(value: number): string {
+ // this.GLP_UNDEF=1,this.GLP_FEAS=2,this.GLP_INFEAS=3,this.GLP_NOFEAS=4,this.GLP_OPT=5,this.GLP_UNBND=6,
+ switch (value) {
+ case 1:
+ return "Undefined. Might be unsolvable. Give it more time.";
+ case 2:
+ return "Feasible, but not optimal. Give it more time.";
+ case 3:
+ return "Infeasible. Your configuration is not possible.";
+ case 4:
+ return "No feasible solution found. Your configuration may not be possible.";
+ case 5:
+ return "Optimal solution found.";
+ case 6:
+ return "Unbounded. Your configuration is not possible (actually a Mijago skill issue).";
+ default:
+ return "Unknown result";
}
+ }
- getPerkName(perk: number) {
- return ArmorPerkOrSlotNames[perk as ArmorPerkOrSlot];
- }
+ async ngOnInit() {
+ this.glpk = await GLPKConstructor();
- getPerkIconUrl(perk: number) {
- return ArmorPerkOrSlotIcons[perk as ArmorPerkOrSlot];
- }
+ console.log(this.glpk);
+ }
- slotNameByIndex(index: number): string {
- switch (index) {
- case 0:
- return "Helmet";
- case 1:
- return "Gauntlets";
- case 2:
- return "Chest Armor";
- case 3:
- return "Leg Armor";
- case 4:
- return "Class Item";
- default:
- return "Unknown";
- }
- }
+ startTimer() {
+ this.time_progress = 0;
+ const interval = (this.options.solver.timeout / 100) * 1000;
- resultValueToText(value: number): string {
- // this.GLP_UNDEF=1,this.GLP_FEAS=2,this.GLP_INFEAS=3,this.GLP_NOFEAS=4,this.GLP_OPT=5,this.GLP_UNBND=6,
- switch (value) {
- case 1:
- return "Undefined. Might be unsolvable. Give it more time.";
- case 2:
- return "Feasible, but not optimal. Give it more time.";
- case 3:
- return "Infeasible. Your configuration is not possible.";
- case 4:
- return "No feasible solution found. Your configuration may not be possible.";
- case 5:
- return "Optimal solution found.";
- case 6:
- return "Unbounded. Your configuration is not possible (actually a Mijago skill issue).";
- default:
- return "Unknown result";
- }
+ this.timerId = setInterval(() => {
+ this.time_progress += 1;
+ if (this.time_progress >= 100) {
+ this.stopTimer();
+ }
+ }, interval) as unknown as number;
+ }
+
+ stopTimer() {
+ if (this.timerId) {
+ this.time_progress = 100;
+ clearInterval(this.timerId);
+ this.timerId = 0;
}
+ }
+
+ async run() {
+ this.result = this.result_items = null;
+ if (!this.glpk) throw new Error("GLPK not initialized yet");
+ this.calculating = true;
+
+ const lp = await this.buildFromConfiguration();
+ this.lp = lp;
+ this.startTimer();
+ const result = await this.glpk.solve(lp);
+ this.stopTimer();
+ this.result_items = await this.getItemsFromResult(result);
+ this.result = result;
+ this.calculating = false;
+ }
+
+ async getItemsFromResult(result: Result) {
+ const items = [
+ [0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0],
+ ];
+ // contains if items are generated or not, and if they are not, then the metadata
+ const itemMeta = [null, null, null, null, null];
+ const itemIntrinsics: (any | null)[] = [null, null, null, null, null];
+ const itemExotic: (boolean | null)[] = [null, null, null, null, null];
+ const itemArtifice: boolean[] = [false, false, false, false, false];
+ let artificeCount = 0;
+
+ const masterwork = [10, 10, 10, 10, 10, 10];
+ const constants = [0, 0, 0, 0, 0, 0];
+
+ const statMods = {
+ major: [0, 0, 0, 0, 0, 0],
+ minor: [0, 0, 0, 0, 0, 0],
+ };
+ const artificeMods = [0, 0, 0, 0, 0, 0];
- async ngOnInit() {
- this.glpk = await GLPKConstructor();
+ for (let kv in result!.result!.vars) {
+ if (!kv.startsWith("constant_")) continue;
+ if (result!.result!.vars[kv] == 0) continue;
- console.log(this.glpk);
+ const [_, stat] = kv.split("_");
+ constants[parseInt(stat)] += result!.result!.vars[kv] - 10;
}
-
- startTimer() {
- this.time_progress = 0;
- const interval = (this.options.solver.timeout / 100) * 1000;
-
- this.timerId = setInterval(() => {
- this.time_progress += 1;
- if (this.time_progress >= 100) {
- this.stopTimer();
- }
- }, interval) as unknown as number;
+ for (let kv in result!.result!.vars) {
+ if (!kv.startsWith("plug_")) continue;
+ if (result!.result!.vars[kv] == 0) continue;
+
+ const [_, slot, tier, plug] = kv.split("_");
+ const plugValues = this.options.availablePlugs[parseInt(plug)];
+ const is2ndHalf = parseInt(tier) >= 2 ? 1 : 0;
+ for (let stat = 0; stat < 3; stat++) {
+ items[parseInt(slot)][stat + 3 * is2ndHalf] += plugValues[stat];
+ }
+
+ // check if exotic_${slot} is 1
+ if (result!.result!.vars[`exotic_${slot}`] == 1) {
+ itemExotic[parseInt(slot)] = true;
+ }
}
+ const itemsToGrab = [];
+ for (let kv in result!.result!.vars) {
+ if (!kv.startsWith("item_")) continue;
+ if (result!.result!.vars[kv] == 0) continue;
- stopTimer() {
- if (this.timerId) {
- this.time_progress = 100;
- clearInterval(this.timerId);
- this.timerId = 0;
- }
+ const [_, slot, itemId] = kv.split("_");
+ itemsToGrab.push({ slot: parseInt(slot), itemId: itemId });
}
-
- async run() {
- this.result = this.result_items = null;
- if (!this.glpk) throw new Error("GLPK not initialized yet");
- this.calculating = true;
-
- const lp = await this.buildFromConfiguration();
- this.lp = lp;
- this.startTimer();
- const result = await this.glpk.solve(lp);
- this.stopTimer();
- this.result_items = await this.getItemsFromResult(result);
- this.result = result;
- this.calculating = false;
+ if (itemsToGrab.length > 0) {
+ const db = buildDb(async () => {});
+ const inventoryArmor = db.table("inventoryArmor");
+
+ for (let e of itemsToGrab) {
+ let dbitems = await inventoryArmor.where("itemInstanceId").equals(e.itemId).toArray();
+ if (dbitems.length == 0) continue;
+ const item = dbitems[0];
+ itemMeta[e.slot] = item;
+ items[e.slot][0] += item.mobility;
+ items[e.slot][1] += item.resilience;
+ items[e.slot][2] += item.recovery;
+ items[e.slot][3] += item.discipline;
+ items[e.slot][4] += item.intellect;
+ items[e.slot][5] += item.strength;
+
+ itemExotic[e.slot] = item.isExotic;
+ itemArtifice[e.slot] = item.perk == ArmorPerkOrSlot.SlotArtifice;
+ artificeCount += itemArtifice[e.slot] ? 1 : 0;
+ }
}
- async getItemsFromResult(result: Result) {
- const items = [
- [0, 0, 0, 0, 0, 0],
- [0, 0, 0, 0, 0, 0],
- [0, 0, 0, 0, 0, 0],
- [0, 0, 0, 0, 0, 0],
- [0, 0, 0, 0, 0, 0],
- ];
- // contains if items are generated or not, and if they are not, then the metadata
- const itemMeta = [null, null, null, null, null];
- const itemIntrinsics: (any | null)[] = [null, null, null, null, null];
- const itemExotic: (boolean | null)[] = [null, null, null, null, null];
- const itemArtifice: boolean[] = [false, false, false, false, false];
- let artificeCount = 0;
-
- const masterwork = [10, 10, 10, 10, 10, 10];
- const constants = [0, 0, 0, 0, 0, 0];
-
- const statMods = {
- major: [0, 0, 0, 0, 0, 0],
- minor: [0, 0, 0, 0, 0, 0],
- };
- const artificeMods = [0, 0, 0, 0, 0, 0];
+ // grab item bonus stats, if set
+ for (let kv in result!.result!.vars) {
+ // intrinsic_${slot}_${clazz}_${i}
+ if (!kv.startsWith("intrinsic_")) continue;
+ if (result!.result!.vars[kv] == 0) continue;
+
+ let [_, slot, clazz, entryId] = kv.split("_");
+ const entry = (intrinsicExoticArmorByClassAndSlot as any)[parseInt(clazz)][
+ parseInt(slot) + 1
+ ][parseInt(entryId)];
+
+ const entryArmor = await Promise.all(
+ entry.armor.map(async (k: number) => {
+ return await this.manifestArmor.where("hash").equals(k).first();
+ })
+ );
+
+ itemIntrinsics[parseInt(slot)] = {
+ entry: entry,
+ items: entryArmor,
+ };
+ }
- for (let kv in result!.result!.vars) {
- if (!kv.startsWith("constant_")) continue;
- if (result!.result!.vars[kv] == 0) continue;
+ /* STAT MODS */
+ for (let kv in result!.result!.vars) {
+ if (!kv.startsWith("mod_")) continue;
+ if (result!.result!.vars[kv] == 0) continue;
- const [_, stat] = kv.split("_");
- constants[parseInt(stat)] += result!.result!.vars[kv] - 10;
- }
- for (let kv in result!.result!.vars) {
- if (!kv.startsWith("plug_")) continue;
- if (result!.result!.vars[kv] == 0) continue;
-
- const [_, slot, tier, plug] = kv.split("_");
- const plugValues = this.options.availablePlugs[parseInt(plug)];
- const is2ndHalf = parseInt(tier) >= 2 ? 1 : 0;
- for (let stat = 0; stat < 3; stat++) {
- items[parseInt(slot)][stat + 3 * is2ndHalf] += plugValues[stat];
- }
-
- // check if exotic_${slot} is 1
- if (result!.result!.vars[`exotic_${slot}`] == 1) {
- itemExotic[parseInt(slot)] = true;
- }
- }
- const itemsToGrab = [];
- for (let kv in result!.result!.vars) {
- if (!kv.startsWith("item_")) continue;
- if (result!.result!.vars[kv] == 0) continue;
+ const [_, type, stat] = kv.split("_");
+ statMods[type == "1" ? "minor" : "major"][parseInt(stat)] += result!.result!.vars[kv];
+ }
- const [_, slot, itemId] = kv.split("_");
- itemsToGrab.push({ slot: parseInt(slot), itemId: itemId });
- }
- if (itemsToGrab.length > 0) {
- const db = buildDb(async () => {});
- const inventoryArmor = db.table("inventoryArmor");
-
- for (let e of itemsToGrab) {
- let dbitems = await inventoryArmor
- .where("itemInstanceId")
- .equals(e.itemId)
- .toArray();
- if (dbitems.length == 0) continue;
- const item = dbitems[0];
- itemMeta[e.slot] = item;
- items[e.slot][0] += item.mobility;
- items[e.slot][1] += item.resilience;
- items[e.slot][2] += item.recovery;
- items[e.slot][3] += item.discipline;
- items[e.slot][4] += item.intellect;
- items[e.slot][5] += item.strength;
-
- itemExotic[e.slot] = item.isExotic;
- itemArtifice[e.slot] = item.perk == ArmorPerkOrSlot.SlotArtifice;
- artificeCount += itemArtifice[e.slot] ? 1 : 0;
- }
- }
+ /* ARTIFICE */
+ let requiredArtificeArmor = 0;
+ for (let kv in result!.result!.vars) {
+ if (!kv.startsWith("artifice_")) continue;
+ if (result!.result!.vars[kv] == 0) continue;
- // grab item bonus stats, if set
- for (let kv in result!.result!.vars) {
- // intrinsic_${slot}_${clazz}_${i}
- if (!kv.startsWith("intrinsic_")) continue;
- if (result!.result!.vars[kv] == 0) continue;
-
- let [_, slot, clazz, entryId] = kv.split("_");
- const entry = (intrinsicExoticArmorByClassAndSlot as any)[parseInt(clazz)][
- parseInt(slot) + 1
- ][parseInt(entryId)];
-
- const entryArmor = await Promise.all(
- entry.armor.map(async (k: number) => {
- return await this.manifestArmor.where("hash").equals(k).first();
- })
- );
-
- itemIntrinsics[parseInt(slot)] = {
- entry: entry,
- items: entryArmor,
- };
- }
+ const [_, stat] = kv.split("_");
+ artificeMods[parseInt(stat)] += result!.result!.vars[kv];
+ requiredArtificeArmor += result!.result!.vars[kv];
+ }
- /* STAT MODS */
- for (let kv in result!.result!.vars) {
- if (!kv.startsWith("mod_")) continue;
- if (result!.result!.vars[kv] == 0) continue;
+ // class item is artifice too
+ if (artificeCount < requiredArtificeArmor) {
+ // set class item to be artifice
+ itemArtifice[4] = true;
+ artificeCount++;
+ }
- const [_, type, stat] = kv.split("_");
- statMods[type == "1" ? "minor" : "major"][parseInt(stat)] += result!.result!.vars[kv];
- }
+ for (let slot = 0; slot < 4 && artificeCount < requiredArtificeArmor; slot++) {
+ if (itemArtifice[slot]) continue;
+ if (itemExotic[slot] == false) continue;
+ if (itemMeta[slot] != null) continue;
+ itemArtifice[slot] = true;
+ artificeCount++;
+ }
- /* ARTIFICE */
- let requiredArtificeArmor = 0;
- for (let kv in result!.result!.vars) {
- if (!kv.startsWith("artifice_")) continue;
- if (result!.result!.vars[kv] == 0) continue;
+ // now sum every stat to get the final value
+ const total = [0, 0, 0, 0, 0, 0];
+ for (let stat = 0; stat < 6; stat++) {
+ for (let slot = 0; slot < 5; slot++) {
+ total[stat] += items[slot][stat];
- const [_, stat] = kv.split("_");
- artificeMods[parseInt(stat)] += result!.result!.vars[kv];
- requiredArtificeArmor += result!.result!.vars[kv];
+ if (stat < 3 && itemIntrinsics[slot] != null) {
+ total[stat] += itemIntrinsics[slot]["entry"]["stats"][stat];
}
+ }
+ total[stat] += constants[stat];
+ total[stat] += masterwork[stat];
+ console.log(
+ "artificeMods[stat]",
+ stat,
+ artificeMods[stat],
+ "|",
+ total[stat],
+ 10 * statMods.major[stat],
+ 5 * statMods.minor[stat],
+ 3 * artificeMods[stat]
+ );
+ total[stat] += 10 * statMods.major[stat] + 5 * statMods.minor[stat] + 3 * artificeMods[stat];
+ }
- // class item is artifice too
- if (artificeCount < requiredArtificeArmor) {
- // set class item to be artifice
- itemArtifice[4] = true;
- artificeCount++;
- }
+ // get the tiers for each stat
+ const tiers = total.map((k) => Math.floor(k / 10));
+ const waste = total.map((k) => k % 10);
+ const tierSum = tiers.reduce((a, b) => a + b, 0);
+
+ return {
+ items,
+ artificeMods,
+ statMods,
+ constants,
+ total,
+ waste,
+ tiers,
+ tierSum,
+ masterwork,
+ itemMeta,
+ itemIntrinsics,
+ itemExotic,
+ itemArtifice,
+ };
+ }
- for (let slot = 0; slot < 4 && artificeCount < requiredArtificeArmor; slot++) {
- if (itemArtifice[slot]) continue;
- if (itemExotic[slot] == false) continue;
- if (itemMeta[slot] != null) continue;
- itemArtifice[slot] = true;
- artificeCount++;
- }
+ async getItems(clazz?: DestinyClass): Promise {
+ let items = (await this.inventoryArmor
+ .where("slot")
+ .notEqual(ArmorSlot.ArmorSlotNone)
+ .distinct()
+ .toArray()) as IInventoryArmor[];
- // now sum every stat to get the final value
- const total = [0, 0, 0, 0, 0, 0];
- for (let stat = 0; stat < 6; stat++) {
- for (let slot = 0; slot < 5; slot++) {
- total[stat] += items[slot][stat];
+ if (clazz != undefined) {
+ items = items.filter((item) => item.clazz == clazz);
+ }
- if (stat < 3 && itemIntrinsics[slot] != null) {
- total[stat] += itemIntrinsics[slot]["entry"]["stats"][stat];
- }
- }
- total[stat] += constants[stat];
- total[stat] += masterwork[stat];
- console.log(
- "artificeMods[stat]",
- stat,
- artificeMods[stat],
- "|",
- total[stat],
- 10 * statMods.major[stat],
- 5 * statMods.minor[stat],
- 3 * artificeMods[stat]
- );
- total[stat] +=
- 10 * statMods.major[stat] + 5 * statMods.minor[stat] + 3 * artificeMods[stat];
- }
+ // items = items
+ // config.onlyUseMasterworkedItems - only keep masterworked items
+ //.filter(item => !config.onlyUseMasterworkedItems || item.masterworked)
+ // non-legendaries and non-exotics
+ //.filter(item => config.allowBlueArmorPieces || item.rarity == TierType.Exotic || item.rarity == TierType.Superior)
+ // sunset armor
+ //.filter(item => !config.ignoreSunsetArmor || !item.isSunset)
+ // armor perks
+
+ return items;
+ }
+
+ async buildFromConfiguration(): Promise {
+ if (!this.glpk) throw new Error("GLPK not initialized yet");
+
+ const lp = {
+ name: "d2ap_theorizer",
+ options: {
+ msgLev: this.glpk.GLP_MSG_ERR,
+ presolve: this.options.solver.presolve,
+ tmlim: this.options.solver.timeout,
+ },
+ objective: {
+ direction: this.glpk.GLP_MAX,
+ name: "objective",
+ vars: [],
+ },
+ subjectTo: [
+ {
+ name: "goal_mobility",
+ bnds: {
+ type: this.glpk.GLP_DB,
+ ub: this.options.stats.maxValue,
+ lb: this.options.stats.desired.mobility,
+ },
+ vars: [] as any[],
+ },
+ {
+ name: "goal_resilience",
+ bnds: {
+ type: this.glpk.GLP_DB,
+ ub: this.options.stats.maxValue,
+ lb: this.options.stats.desired.resilience,
+ },
+ vars: [] as any[],
+ },
+ {
+ name: "goal_recovery",
+ bnds: {
+ type: this.glpk.GLP_DB,
+ ub: this.options.stats.maxValue,
+ lb: this.options.stats.desired.recovery,
+ },
+ vars: [] as any[],
+ },
+ {
+ name: "goal_discipline",
+ bnds: {
+ type: this.glpk.GLP_DB,
+ ub: this.options.stats.maxValue,
+ lb: this.options.stats.desired.discipline,
+ },
+ vars: [] as any[],
+ },
+ {
+ name: "goal_intellect",
+ bnds: {
+ type: this.glpk.GLP_DB,
+ ub: this.options.stats.maxValue,
+ lb: this.options.stats.desired.intellect,
+ },
+ vars: [] as any[],
+ },
+ {
+ name: "goal_strength",
+ bnds: {
+ type: this.glpk.GLP_DB,
+ ub: this.options.stats.maxValue,
+ lb: this.options.stats.desired.strength,
+ },
+ vars: [] as any[],
+ },
+ ],
+ bounds: [],
+ binaries: [], // binary values
+ generals: [], // integers
+ } as LP;
+
+ // add MW and Const Values
+ for (let stat = 0; stat < 6; stat++) {
+ const val = (this.options.stats as any).constantBoost[statNames[stat]];
+ let constVal = 10 + val;
+ lp.bounds!.push({
+ name: `constant_${stat}`,
+ type: this.glpk.GLP_FX,
+ ub: constVal,
+ lb: constVal,
+ });
+ lp.subjectTo![stat].vars.push({ name: `constant_${stat}`, coef: 1 });
+ }
- // get the tiers for each stat
- const tiers = total.map((k) => Math.floor(k / 10));
- const waste = total.map((k) => k % 10);
- const tierSum = tiers.reduce((a, b) => a + b, 0);
-
- return {
- items,
- artificeMods,
- statMods,
- constants,
- total,
- waste,
- tiers,
- tierSum,
- masterwork,
- itemMeta,
- itemIntrinsics,
- itemExotic,
- itemArtifice,
- };
+ const withOwnArmor = (this.options.armor.armorType & 1) > 0;
+ const withGeneratedArmor = (this.options.armor.armorType & 2) > 0;
+ const withBothArmorSources = withOwnArmor && withGeneratedArmor;
+
+ const items = await this.getItems();
+ let helmets = items.filter((i) => i.slot == ArmorSlot.ArmorSlotHelmet);
+ let gauntlets = items.filter((i) => i.slot == ArmorSlot.ArmorSlotGauntlet);
+ let chests = items.filter((i) => i.slot == ArmorSlot.ArmorSlotChest);
+ let legs = items.filter((i) => i.slot == ArmorSlot.ArmorSlotLegs);
+
+ // check class setting
+ if (this.options.fragments.class != DestinyClass.Unknown) {
+ const clazz = this.options.fragments.class;
+ helmets = helmets.filter((i) => i.clazz == clazz);
+ gauntlets = gauntlets.filter((i) => i.clazz == clazz);
+ chests = chests.filter((i) => i.clazz == clazz);
+ legs = legs.filter((i) => i.clazz == clazz);
}
- async getItems(clazz?: DestinyClass): Promise {
- let items = (await this.inventoryArmor
- .where("slot")
- .notEqual(ArmorSlot.ArmorSlotNone)
- .distinct()
- .toArray()) as IInventoryArmor[];
+ let itemsBySlot = [helmets, gauntlets, chests, legs];
- if (clazz != undefined) {
- items = items.filter((item) => item.clazz == clazz);
- }
+ const classLimitSubject = {
+ name: `classlim`,
+ vars: [] as any[],
+ bnds: { type: this.glpk.GLP_UP, ub: 1, lb: 1 },
+ };
+ const classLimitSubjects = [];
+ // add a variable for each class. Only one of them may be > 0
+ for (let clazz = 0; clazz < 3; clazz++) {
+ const clazzVar = `class_${clazz}`;
+ lp.binaries!.push(clazzVar);
+ classLimitSubject.vars.push({ name: clazzVar, coef: 1 });
+
+ classLimitSubjects.push({
+ name: `classlim_${clazz}`,
+ vars: [{ name: clazzVar, coef: -4 }], // TODO I may have to add 0.25 for theoretical armor piece plugs
+ bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
+ });
+ }
- // items = items
- // config.onlyUseMasterworkedItems - only keep masterworked items
- //.filter(item => !config.onlyUseMasterworkedItems || item.masterworked)
- // non-legendaries and non-exotics
- //.filter(item => config.allowBlueArmorPieces || item.rarity == TierType.Exotic || item.rarity == TierType.Superior)
- // sunset armor
- //.filter(item => !config.ignoreSunsetArmor || !item.isSunset)
- // armor perks
+ // only allow ZERO or ONE exotic
+ const exoticLimitSubject = {
+ name: `exoticlim`,
+ vars: [] as any[],
+ bnds: { type: this.glpk.GLP_DB, ub: 1, lb: 0 },
+ };
- return items;
+ if (this.options.armor.requiresExotic) {
+ console.log("requiring exotic");
+ exoticLimitSubject.bnds = { type: this.glpk.GLP_FX, ub: 1, lb: 1 };
}
- async buildFromConfiguration(): Promise {
- if (!this.glpk) throw new Error("GLPK not initialized yet");
-
- const lp = {
- name: "d2ap_theorizer",
- options: {
- msgLev: this.glpk.GLP_MSG_ERR,
- presolve: this.options.solver.presolve,
- tmlim: this.options.solver.timeout,
- },
- objective: {
- direction: this.glpk.GLP_MAX,
- name: "objective",
- vars: [],
- },
- subjectTo: [
- {
- name: "goal_mobility",
- bnds: {
- type: this.glpk.GLP_DB,
- ub: this.options.stats.maxValue,
- lb: this.options.stats.desired.mobility,
- },
- vars: [] as any[],
- },
- {
- name: "goal_resilience",
- bnds: {
- type: this.glpk.GLP_DB,
- ub: this.options.stats.maxValue,
- lb: this.options.stats.desired.resilience,
- },
- vars: [] as any[],
- },
- {
- name: "goal_recovery",
- bnds: {
- type: this.glpk.GLP_DB,
- ub: this.options.stats.maxValue,
- lb: this.options.stats.desired.recovery,
- },
- vars: [] as any[],
- },
- {
- name: "goal_discipline",
- bnds: {
- type: this.glpk.GLP_DB,
- ub: this.options.stats.maxValue,
- lb: this.options.stats.desired.discipline,
- },
- vars: [] as any[],
- },
- {
- name: "goal_intellect",
- bnds: {
- type: this.glpk.GLP_DB,
- ub: this.options.stats.maxValue,
- lb: this.options.stats.desired.intellect,
- },
- vars: [] as any[],
- },
- {
- name: "goal_strength",
- bnds: {
- type: this.glpk.GLP_DB,
- ub: this.options.stats.maxValue,
- lb: this.options.stats.desired.strength,
- },
- vars: [] as any[],
- },
- ],
- bounds: [],
- binaries: [], // binary values
- generals: [], // integers
- } as LP;
-
- // add MW and Const Values
- for (let stat = 0; stat < 6; stat++) {
- const val = (this.options.stats as any).constantBoost[statNames[stat]];
- let constVal = 10 + val;
- lp.bounds!.push({
- name: `constant_${stat}`,
- type: this.glpk.GLP_FX,
- ub: constVal,
- lb: constVal,
- });
- lp.subjectTo![stat].vars.push({ name: `constant_${stat}`, coef: 1 });
- }
+ lp.subjectTo!.push(classLimitSubject);
+ lp.subjectTo!.push(...classLimitSubjects);
+ lp.subjectTo!.push(exoticLimitSubject);
+
+ const penalty = 20;
+ const artificeArmorPieces = [];
+ const artificeArmorPlugs = [];
+
+ // we have 4 slots
+ // we pick four plugs for each slot; a plug has three values
+ // the sum of first two plugs represents mob/res/rec
+ // the sum of second two plugs represents dis/int/str
+ // the sum of the first two plugs over all armor pieces represents the total base of mob/res/rec
+ // the sum of the second two plugs over all armor pieces represents the total base of dis/int/str
+ for (let slot = 0; slot < 4; slot++) {
+ const slotLimitSubject = {
+ name: `slotlim_${slot}`,
+ vars: [] as any[],
+ bnds: { type: this.glpk.GLP_FX, ub: 4, lb: 4 },
+ };
+ lp.subjectTo!.push(slotLimitSubject);
+
+ // introduce one binary variable for each plug in each slot
+ if (withGeneratedArmor) {
+ const intrinsicStatSelectionSubject = {
+ name: `allow_intrinsic_${slot}`,
+ vars: [] as any[],
+ bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
+ };
- const withOwnArmor = (this.options.armor.armorType & 1) > 0;
- const withGeneratedArmor = (this.options.armor.armorType & 2) > 0;
- const withBothArmorSources = withOwnArmor && withGeneratedArmor;
-
- const items = await this.getItems();
- let helmets = items.filter((i) => i.slot == ArmorSlot.ArmorSlotHelmet);
- let gauntlets = items.filter((i) => i.slot == ArmorSlot.ArmorSlotGauntlet);
- let chests = items.filter((i) => i.slot == ArmorSlot.ArmorSlotChest);
- let legs = items.filter((i) => i.slot == ArmorSlot.ArmorSlotLegs);
-
- // check class setting
- if (this.options.fragments.class != DestinyClass.Unknown) {
- const clazz = this.options.fragments.class;
- helmets = helmets.filter((i) => i.clazz == clazz);
- gauntlets = gauntlets.filter((i) => i.clazz == clazz);
- chests = chests.filter((i) => i.clazz == clazz);
- legs = legs.filter((i) => i.clazz == clazz);
+ lp.binaries!.push(`exotic_${slot}`);
+ exoticLimitSubject.vars.push({ name: `exotic_${slot}`, coef: 1 });
+
+ // generateExoticsWithIntrinsicStats
+ if (this.options.generator.generateExoticsWithIntrinsicStats) {
+ // add variables to see if this slot is generated and exotic
+ // only one slot is allowed to be exotic, so we can use this later
+
+ // add the subject that limits the usage of intrinsic stat plugs to only work when we select 4 plugs
+ lp.subjectTo!.push(intrinsicStatSelectionSubject);
+
+ // add a variable for categories in possibleBonusStats
+ for (let clazz = 0; clazz < 3; clazz++) {
+ const entries = (intrinsicExoticArmorByClassAndSlot as any)[clazz][slot + 1];
+ for (let i = 0; i < entries.length; i++) {
+ let entry = entries[i];
+ const name = `intrinsic_${slot}_${clazz}_${i}`;
+ lp.binaries!.push(name);
+ // add to intrinsicStatSelectionSubject
+ intrinsicStatSelectionSubject.vars.push({ name: name, coef: 1 });
+
+ // add a limit that it is <= selected class
+ lp.subjectTo!.push({
+ name: `intrinsic_${slot}_${clazz}_classlim`,
+ vars: [
+ { name: name, coef: 1 },
+ { name: `class_${clazz}`, coef: -1 },
+ ],
+ bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
+ });
+
+ // add a limit that it only added when no exotic is selected
+ lp.subjectTo!.push({
+ name: `intrinsic_${slot}_${clazz}_exoticlim`,
+ vars: [
+ { name: name, coef: 1 },
+ { name: `exotic_${slot}`, coef: -1 },
+ ],
+ bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
+ });
+
+ // add the stats
+ for (let statmrr = 0; statmrr < 3; statmrr++) {
+ if (entry.stats[statmrr] > 0)
+ lp.subjectTo![statmrr].vars.push({
+ name: name,
+ coef: entry.stats[statmrr],
+ });
+ }
+
+ // apply a penalty for using this
+ lp.objective!.vars.push({ name: name, coef: -100 });
+ }
+ }
+ // TODO make sure that we only add them if it is generated
}
- let itemsBySlot = [helmets, gauntlets, chests, legs];
-
- const classLimitSubject = {
- name: `classlim`,
- vars: [] as any[],
- bnds: { type: this.glpk.GLP_UP, ub: 1, lb: 1 },
+ // only allow this slot to be exotic if it is also generated and used
+ let exoticGenlimSlot = {
+ name: `exotic_${slot}_genlim`,
+ vars: [{ name: `exotic_${slot}`, coef: 1 }],
+ bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
};
- const classLimitSubjects = [];
- // add a variable for each class. Only one of them may be > 0
- for (let clazz = 0; clazz < 3; clazz++) {
- const clazzVar = `class_${clazz}`;
- lp.binaries!.push(clazzVar);
- classLimitSubject.vars.push({ name: clazzVar, coef: 1 });
-
- classLimitSubjects.push({
- name: `classlim_${clazz}`,
- vars: [{ name: clazzVar, coef: -4 }], // TODO I may have to add 0.25 for theoretical armor piece plugs
- bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
- });
- }
+ lp.subjectTo!.push(exoticGenlimSlot);
- // only allow ZERO or ONE exotic
- const exoticLimitSubject = {
- name: `exoticlim`,
+ for (let plugId = 0; plugId < 4; plugId++) {
+ const subject = {
+ name: `plug_${slot}_${plugId}`,
vars: [] as any[],
- bnds: { type: this.glpk.GLP_DB, ub: 1, lb: 0 },
- };
+ bnds: { type: this.glpk.GLP_FX, ub: 1, lb: 1 },
+ };
+ if (withBothArmorSources) subject.bnds = { type: this.glpk.GLP_DB, ub: 1, lb: 0 };
- if (this.options.armor.requiresExotic) {
- console.log("requiring exotic");
- exoticLimitSubject.bnds = { type: this.glpk.GLP_FX, ub: 1, lb: 1 };
- }
+ for (let plug = 0; plug < this.options.availablePlugs.length; plug++) {
+ const plugName = `plug_${slot}_${plugId}_${plug}`;
+ lp.binaries!.push(plugName);
+ subject.vars.push({ name: plugName, coef: 1 });
- lp.subjectTo!.push(classLimitSubject);
- lp.subjectTo!.push(...classLimitSubjects);
- lp.subjectTo!.push(exoticLimitSubject);
+ // add to intrinsicStatSelectionSubject
+ intrinsicStatSelectionSubject.vars.push({ name: plugName, coef: -0.25 });
+ exoticGenlimSlot.vars.push({ name: plugName, coef: -0.25 });
- const penalty = 20;
- const artificeArmorPieces = [];
- const artificeArmorPlugs = [];
+ artificeArmorPlugs.push(plugName);
- // we have 4 slots
- // we pick four plugs for each slot; a plug has three values
- // the sum of first two plugs represents mob/res/rec
- // the sum of second two plugs represents dis/int/str
- // the sum of the first two plugs over all armor pieces represents the total base of mob/res/rec
- // the sum of the second two plugs over all armor pieces represents the total base of dis/int/str
- for (let slot = 0; slot < 4; slot++) {
- const slotLimitSubject = {
- name: `slotlim_${slot}`,
- vars: [] as any[],
- bnds: { type: this.glpk.GLP_FX, ub: 4, lb: 4 },
- };
- lp.subjectTo!.push(slotLimitSubject);
-
- // introduce one binary variable for each plug in each slot
- if (withGeneratedArmor) {
- const intrinsicStatSelectionSubject = {
- name: `allow_intrinsic_${slot}`,
- vars: [] as any[],
- bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
- };
-
- lp.binaries!.push(`exotic_${slot}`);
- exoticLimitSubject.vars.push({ name: `exotic_${slot}`, coef: 1 });
-
- // generateExoticsWithIntrinsicStats
- if (this.options.generator.generateExoticsWithIntrinsicStats) {
- // add variables to see if this slot is generated and exotic
- // only one slot is allowed to be exotic, so we can use this later
-
- // add the subject that limits the usage of intrinsic stat plugs to only work when we select 4 plugs
- lp.subjectTo!.push(intrinsicStatSelectionSubject);
-
- // add a variable for categories in possibleBonusStats
- for (let clazz = 0; clazz < 3; clazz++) {
- const entries = (intrinsicExoticArmorByClassAndSlot as any)[clazz][
- slot + 1
- ];
- for (let i = 0; i < entries.length; i++) {
- let entry = entries[i];
- const name = `intrinsic_${slot}_${clazz}_${i}`;
- lp.binaries!.push(name);
- // add to intrinsicStatSelectionSubject
- intrinsicStatSelectionSubject.vars.push({ name: name, coef: 1 });
-
- // add a limit that it is <= selected class
- lp.subjectTo!.push({
- name: `intrinsic_${slot}_${clazz}_classlim`,
- vars: [
- { name: name, coef: 1 },
- { name: `class_${clazz}`, coef: -1 },
- ],
- bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
- });
-
- // add a limit that it only added when no exotic is selected
- lp.subjectTo!.push({
- name: `intrinsic_${slot}_${clazz}_exoticlim`,
- vars: [
- { name: name, coef: 1 },
- { name: `exotic_${slot}`, coef: -1 },
- ],
- bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
- });
-
- // add the stats
- for (let statmrr = 0; statmrr < 3; statmrr++) {
- if (entry.stats[statmrr] > 0)
- lp.subjectTo![statmrr].vars.push({
- name: name,
- coef: entry.stats[statmrr],
- });
- }
-
- // apply a penalty for using this
- lp.objective!.vars.push({ name: name, coef: -100 });
- }
- }
- // TODO make sure that we only add them if it is generated
- }
-
- // only allow this slot to be exotic if it is also generated and used
- let exoticGenlimSlot = {
- name: `exotic_${slot}_genlim`,
- vars: [{ name: `exotic_${slot}`, coef: 1 }],
- bnds: { type: this.glpk.GLP_UP, ub: 0, lb: 0 },
- };
- lp.subjectTo!.push(exoticGenlimSlot);
-
- for (let plugId = 0; plugId < 4; plugId++) {
- const subject = {
- name: `plug_${slot}_${plugId}`,
- vars: [] as any[],
- bnds: { type: this.glpk.GLP_FX, ub: 1, lb: 1 },
- };
- if (withBothArmorSources)
- subject.bnds = { type: this.glpk.GLP_DB, ub: 1, lb: 0 };
-
- for (let plug = 0; plug < this.options.availablePlugs.length; plug++) {
- const plugName = `plug_${slot}_${plugId}_${plug}`;
- lp.binaries!.push(plugName);
- subject.vars.push({ name: plugName, coef: 1 });
-
- // add to intrinsicStatSelectionSubject
- intrinsicStatSelectionSubject.vars.push({ name: plugName, coef: -0.25 });
- exoticGenlimSlot.vars.push({ name: plugName, coef: -0.25 });
-
- artificeArmorPlugs.push(plugName);
-
- // 4 plugs per item, so coeff 0.25 for each plug
- slotLimitSubject.vars.push({ name: plugName, coef: 1 });
-
- if (withBothArmorSources) {
- lp.objective!.vars.push({ name: plugName, coef: -100 });
- }
-
- // add the plug to the subject which manages the required stats
- for (let n = 0; n < 3; n++) {
- let cn = n;
- if (plugId > 1) cn += 3;
-
- lp.subjectTo![cn].vars.push({
- name: plugName,
- coef: this.options.availablePlugs[plug][n],
- });
- // add a penalty for every stat point. This means the solver will try to minimize the number of generated stat points
- if (penalty > 0)
- lp.objective!.vars.push({
- name: plugName,
- coef: -penalty * this.options.availablePlugs[plug][n],
- });
- }
- }
- lp.subjectTo!.push(subject);
- }
- }
+ // 4 plugs per item, so coeff 0.25 for each plug
+ slotLimitSubject.vars.push({ name: plugName, coef: 1 });
- if (withOwnArmor) {
- // add a variable for each item in each slot
- for (let item of itemsBySlot[slot]) {
- const item_id = item.itemInstanceId;
- const identifier = `item_${slot}_${item_id}`;
- lp.binaries!.push(identifier);
- //lp.bounds!.push({name: identifier, type: this.glpk.GLP_DB, ub: 1, lb: 0});
- lp.subjectTo![0].vars.push({ name: identifier, coef: item.mobility });
- lp.subjectTo![1].vars.push({ name: identifier, coef: item.resilience });
- lp.subjectTo![2].vars.push({ name: identifier, coef: item.recovery });
- lp.subjectTo![3].vars.push({ name: identifier, coef: item.discipline });
- lp.subjectTo![4].vars.push({ name: identifier, coef: item.intellect });
- lp.subjectTo![5].vars.push({ name: identifier, coef: item.strength });
- // limit the number of items per slot to 1
- slotLimitSubject.vars.push({ name: identifier, coef: 4 });
- // Add an objective for each item, which means we want to have as many of our own items as possible
- if (withBothArmorSources) {
- lp.objective!.vars.push({ name: identifier, coef: 100 });
- }
-
- // class limit subject
- classLimitSubjects[item.clazz].vars.push({ name: identifier, coef: 1 });
-
- // exotic limit
- if (item.isExotic) {
- exoticLimitSubject.vars.push({ name: identifier, coef: 1 });
- // also rate this one higher, so that we have more exotics in the results
- lp.objective!.vars.push({ name: identifier, coef: 40 });
- }
- if (item.perk == ArmorPerkOrSlot.SlotArtifice) {
- artificeArmorPieces.push(identifier);
- }
- }
+ if (withBothArmorSources) {
+ lp.objective!.vars.push({ name: plugName, coef: -100 });
}
- }
- if (this.options.mods.maxMods > 0) {
- const modSubject = {
- name: `limit_mods`,
- vars: [] as any[],
- bnds: {
- type: this.options.mods.maxMods > 0 ? this.glpk.GLP_DB : this.glpk.GLP_FX,
- ub: this.options.mods.maxMods,
- lb: 0,
- },
- };
- for (let stat = 0; stat < 6; stat++) {
- // 1 minor, 2 major; and then artifice
- lp.bounds!.push({ name: `mod_${1}_${stat}`, type: this.glpk.GLP_DB, ub: 5, lb: 0 });
- lp.bounds!.push({ name: `mod_${2}_${stat}`, type: this.glpk.GLP_DB, ub: 5, lb: 0 });
- lp.generals!.push(`mod_${1}_${stat}`);
- lp.generals!.push(`mod_${2}_${stat}`);
-
- lp.subjectTo![stat].vars.push({ name: `mod_${1}_${stat}`, coef: 5 });
- lp.subjectTo![stat].vars.push({ name: `mod_${2}_${stat}`, coef: 10 });
-
- // only allow a total of 5 mods and 3 artificer mods
- modSubject.vars.push({ name: `mod_${1}_${stat}`, coef: 1 });
- modSubject.vars.push({ name: `mod_${2}_${stat}`, coef: 1 });
+ // add the plug to the subject which manages the required stats
+ for (let n = 0; n < 3; n++) {
+ let cn = n;
+ if (plugId > 1) cn += 3;
+
+ lp.subjectTo![cn].vars.push({
+ name: plugName,
+ coef: this.options.availablePlugs[plug][n],
+ });
+ // add a penalty for every stat point. This means the solver will try to minimize the number of generated stat points
+ if (penalty > 0)
+ lp.objective!.vars.push({
+ name: plugName,
+ coef: -penalty * this.options.availablePlugs[plug][n],
+ });
}
- lp.subjectTo!.push(modSubject);
+ }
+ lp.subjectTo!.push(subject);
}
+ }
+
+ if (withOwnArmor) {
+ // add a variable for each item in each slot
+ for (let item of itemsBySlot[slot]) {
+ const item_id = item.itemInstanceId;
+ const identifier = `item_${slot}_${item_id}`;
+ lp.binaries!.push(identifier);
+ //lp.bounds!.push({name: identifier, type: this.glpk.GLP_DB, ub: 1, lb: 0});
+ lp.subjectTo![0].vars.push({ name: identifier, coef: item.mobility });
+ lp.subjectTo![1].vars.push({ name: identifier, coef: item.resilience });
+ lp.subjectTo![2].vars.push({ name: identifier, coef: item.recovery });
+ lp.subjectTo![3].vars.push({ name: identifier, coef: item.discipline });
+ lp.subjectTo![4].vars.push({ name: identifier, coef: item.intellect });
+ lp.subjectTo![5].vars.push({ name: identifier, coef: item.strength });
+ // limit the number of items per slot to 1
+ slotLimitSubject.vars.push({ name: identifier, coef: 4 });
+ // Add an objective for each item, which means we want to have as many of our own items as possible
+ if (withBothArmorSources) {
+ lp.objective!.vars.push({ name: identifier, coef: 100 });
+ }
+
+ // class limit subject
+ classLimitSubjects[item.clazz].vars.push({ name: identifier, coef: 1 });
+
+ // exotic limit
+ if (item.isExotic) {
+ exoticLimitSubject.vars.push({ name: identifier, coef: 1 });
+ // also rate this one higher, so that we have more exotics in the results
+ lp.objective!.vars.push({ name: identifier, coef: 40 });
+ }
+ if (item.perk == ArmorPerkOrSlot.SlotArtifice) {
+ artificeArmorPieces.push(identifier);
+ }
+ }
+ }
+ }
- if (this.options.mods.maxArtifice > 0) {
- const artifMaxSubject = {
- name: `limit_artif_max`,
- vars: [] as any[],
- bnds: {
- type: this.options.mods.maxArtifice > 0 ? this.glpk.GLP_DB : this.glpk.GLP_FX,
- ub: this.options.mods.maxArtifice,
- lb: 0,
- },
- };
- const artifSlotSubject = {
- name: `limit_artif_slot`,
- vars: [] as any[],
- bnds: { type: this.glpk.GLP_UP, ub: 1, lb: 0 }, // UB is 1 as we assume our class item is artifice !TODO make this a setting
- };
- // add all armor pieces which can be artificed
- for (let piece of artificeArmorPieces) {
- artifSlotSubject.vars.push({ name: piece, coef: -1 });
- }
- for (let piece of artificeArmorPlugs) {
- artifSlotSubject.vars.push({ name: piece, coef: -0.25 });
- }
- // add 1 in case we generated an exotic armor
- if (withGeneratedArmor) {
- for (let slot = 0; slot < 4; slot++) {
- artifSlotSubject.vars.push({ name: `exotic_${slot}`, coef: 1 });
- }
- }
-
- for (let stat = 0; stat < 6; stat++) {
- lp.subjectTo![stat].vars.push({ name: `artifice_${stat}`, coef: 3 });
- artifMaxSubject.vars.push({ name: `artifice_${stat}`, coef: 1 });
- artifSlotSubject.vars.push({ name: `artifice_${stat}`, coef: 1 });
+ if (this.options.mods.maxMods > 0) {
+ const modSubject = {
+ name: `limit_mods`,
+ vars: [] as any[],
+ bnds: {
+ type: this.options.mods.maxMods > 0 ? this.glpk.GLP_DB : this.glpk.GLP_FX,
+ ub: this.options.mods.maxMods,
+ lb: 0,
+ },
+ };
+ for (let stat = 0; stat < 6; stat++) {
+ // 1 minor, 2 major; and then artifice
+ lp.bounds!.push({ name: `mod_${1}_${stat}`, type: this.glpk.GLP_DB, ub: 5, lb: 0 });
+ lp.bounds!.push({ name: `mod_${2}_${stat}`, type: this.glpk.GLP_DB, ub: 5, lb: 0 });
+ lp.generals!.push(`mod_${1}_${stat}`);
+ lp.generals!.push(`mod_${2}_${stat}`);
+
+ lp.subjectTo![stat].vars.push({ name: `mod_${1}_${stat}`, coef: 5 });
+ lp.subjectTo![stat].vars.push({ name: `mod_${2}_${stat}`, coef: 10 });
+
+ // only allow a total of 5 mods and 3 artificer mods
+ modSubject.vars.push({ name: `mod_${1}_${stat}`, coef: 1 });
+ modSubject.vars.push({ name: `mod_${2}_${stat}`, coef: 1 });
+ }
+ lp.subjectTo!.push(modSubject);
+ }
- lp.bounds!.push({ name: `artifice_${stat}`, type: this.glpk.GLP_DB, ub: 5, lb: 0 });
- lp.generals!.push(`artifice_${stat}`);
- }
- lp.subjectTo!.push(artifMaxSubject);
- lp.subjectTo!.push(artifSlotSubject);
+ if (this.options.mods.maxArtifice > 0) {
+ const artifMaxSubject = {
+ name: `limit_artif_max`,
+ vars: [] as any[],
+ bnds: {
+ type: this.options.mods.maxArtifice > 0 ? this.glpk.GLP_DB : this.glpk.GLP_FX,
+ ub: this.options.mods.maxArtifice,
+ lb: 0,
+ },
+ };
+ const artifSlotSubject = {
+ name: `limit_artif_slot`,
+ vars: [] as any[],
+ bnds: { type: this.glpk.GLP_UP, ub: 1, lb: 0 }, // UB is 1 as we assume our class item is artifice !TODO make this a setting
+ };
+ // add all armor pieces which can be artificed
+ for (let piece of artificeArmorPieces) {
+ artifSlotSubject.vars.push({ name: piece, coef: -1 });
+ }
+ for (let piece of artificeArmorPlugs) {
+ artifSlotSubject.vars.push({ name: piece, coef: -0.25 });
+ }
+ // add 1 in case we generated an exotic armor
+ if (withGeneratedArmor) {
+ for (let slot = 0; slot < 4; slot++) {
+ artifSlotSubject.vars.push({ name: `exotic_${slot}`, coef: 1 });
}
+ }
+
+ for (let stat = 0; stat < 6; stat++) {
+ lp.subjectTo![stat].vars.push({ name: `artifice_${stat}`, coef: 3 });
+ artifMaxSubject.vars.push({ name: `artifice_${stat}`, coef: 1 });
+ artifSlotSubject.vars.push({ name: `artifice_${stat}`, coef: 1 });
+
+ lp.bounds!.push({ name: `artifice_${stat}`, type: this.glpk.GLP_DB, ub: 5, lb: 0 });
+ lp.generals!.push(`artifice_${stat}`);
+ }
+ lp.subjectTo!.push(artifMaxSubject);
+ lp.subjectTo!.push(artifSlotSubject);
+ }
- if (this.options.stats.minTiers > 0 || this.options.stats.maxWaste < 54) {
- // I want to have the TIERS of the armor stats
- // for this, I introduce two variables per stat:
- // - The first is the "waste", which is bound between 0 and 9
- // - The second is the "tier", which is bound between -5 and 20
- // We will set these variables as "mobility - waste - 10 tier = 0"
- for (let stat = 0; stat < 6; stat++) {
- lp.bounds!.push({ name: `waste_${stat}`, type: this.glpk.GLP_DB, ub: 9, lb: 0 });
- //lp.bounds!.push({name: `tier_${stat}`, type: this.glpk.GLP_DB, ub: 100, lb: -100});
- lp.generals!.push(`waste_${stat}`);
- lp.generals!.push(`tier_${stat}`);
-
- //lp.objective.vars.push({name: `tier_${stat}`, coef: 2})
- //lp.objective.vars.push({name: `waste_${stat}`, coef: -100})
-
- const setWasteAndTierSubject = {
- name: `set_waste_and_tier_${stat}`,
- vars: [
- { name: `waste_${stat}`, coef: -1 },
- { name: `tier_${stat}`, coef: -10 },
- ...lp.subjectTo![stat].vars,
- ],
- bnds: { type: this.glpk.GLP_FX, ub: 0, lb: 0 },
- };
-
- lp.subjectTo!.push(setWasteAndTierSubject);
- }
+ if (this.options.stats.minTiers > 0 || this.options.stats.maxWaste < 54) {
+ // I want to have the TIERS of the armor stats
+ // for this, I introduce two variables per stat:
+ // - The first is the "waste", which is bound between 0 and 9
+ // - The second is the "tier", which is bound between -5 and 20
+ // We will set these variables as "mobility - waste - 10 tier = 0"
+ for (let stat = 0; stat < 6; stat++) {
+ lp.bounds!.push({ name: `waste_${stat}`, type: this.glpk.GLP_DB, ub: 9, lb: 0 });
+ //lp.bounds!.push({name: `tier_${stat}`, type: this.glpk.GLP_DB, ub: 100, lb: -100});
+ lp.generals!.push(`waste_${stat}`);
+ lp.generals!.push(`tier_${stat}`);
+
+ //lp.objective.vars.push({name: `tier_${stat}`, coef: 2})
+ //lp.objective.vars.push({name: `waste_${stat}`, coef: -100})
+
+ const setWasteAndTierSubject = {
+ name: `set_waste_and_tier_${stat}`,
+ vars: [
+ { name: `waste_${stat}`, coef: -1 },
+ { name: `tier_${stat}`, coef: -10 },
+ ...lp.subjectTo![stat].vars,
+ ],
+ bnds: { type: this.glpk.GLP_FX, ub: 0, lb: 0 },
+ };
- // set minTiers <= the sum of the tiers
- if (this.options.stats.minTiers > 0) {
- const minTierSubject = {
- name: `require_tier_minimum`,
- vars: [] as any[],
- bnds: { type: this.glpk.GLP_LO, ub: 0, lb: this.options.stats.minTiers },
- };
- console.log("this.options.stats.minTiers", this.options.stats.minTiers);
- for (let stat = 0; stat < 6; stat++) {
- minTierSubject.vars.push({ name: `tier_${stat}`, coef: 1 });
- }
- lp.subjectTo!.push(minTierSubject);
- }
+ lp.subjectTo!.push(setWasteAndTierSubject);
+ }
- // Specify maxWaste
- if (this.options.stats.maxWaste < 54) {
- const maxWasteSubject = {
- name: `require_waste_maximum`,
- vars: [] as any[],
- bnds: {
- type: this.options.stats.maxWaste > 0 ? this.glpk.GLP_UP : this.glpk.GLP_FX,
- ub: this.options.stats.maxWaste,
- lb: 0,
- },
- };
- for (let stat = 0; stat < 6; stat++) {
- maxWasteSubject.vars.push({ name: `waste_${stat}`, coef: 1 });
- }
- lp.subjectTo!.push(maxWasteSubject);
- }
+ // set minTiers <= the sum of the tiers
+ if (this.options.stats.minTiers > 0) {
+ const minTierSubject = {
+ name: `require_tier_minimum`,
+ vars: [] as any[],
+ bnds: { type: this.glpk.GLP_LO, ub: 0, lb: this.options.stats.minTiers },
+ };
+ console.log("this.options.stats.minTiers", this.options.stats.minTiers);
+ for (let stat = 0; stat < 6; stat++) {
+ minTierSubject.vars.push({ name: `tier_${stat}`, coef: 1 });
}
-
- // Fix the stats if we enforce them
- if (this.options.stats.statsAreFixed) {
- for (let n = 0; n < 6; n++) {
- lp.subjectTo[n].bnds.ub = lp.subjectTo[n].bnds.lb;
- lp.subjectTo[n].bnds.type = this.glpk.GLP_FX;
- }
+ lp.subjectTo!.push(minTierSubject);
+ }
+
+ // Specify maxWaste
+ if (this.options.stats.maxWaste < 54) {
+ const maxWasteSubject = {
+ name: `require_waste_maximum`,
+ vars: [] as any[],
+ bnds: {
+ type: this.options.stats.maxWaste > 0 ? this.glpk.GLP_UP : this.glpk.GLP_FX,
+ ub: this.options.stats.maxWaste,
+ lb: 0,
+ },
+ };
+ for (let stat = 0; stat < 6; stat++) {
+ maxWasteSubject.vars.push({ name: `waste_${stat}`, coef: 1 });
}
+ lp.subjectTo!.push(maxWasteSubject);
+ }
+ }
- return lp;
+ // Fix the stats if we enforce them
+ if (this.options.stats.statsAreFixed) {
+ for (let n = 0; n < 6; n++) {
+ lp.subjectTo[n].bnds.ub = lp.subjectTo[n].bnds.lb;
+ lp.subjectTo[n].bnds.type = this.glpk.GLP_FX;
+ }
}
+
+ return lp;
+ }
}
diff --git a/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.module.ts b/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.module.ts
index fdcc9515..add67f67 100644
--- a/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.module.ts
+++ b/src/app/components/authenticated-v2/subpages/theorizer-page/theorizer-page.module.ts
@@ -6,15 +6,15 @@ import { CommonMaterialModule } from "../../../../modules/common-material/common
// router
const routes: Routes = [
- {
- path: "",
- component: TheorizerPageComponent,
- },
+ {
+ path: "",
+ component: TheorizerPageComponent,
+ },
];
@NgModule({
- declarations: [TheorizerPageComponent],
- exports: [TheorizerPageComponent],
- imports: [CommonModule, CommonMaterialModule, RouterModule.forChild(routes)],
+ declarations: [TheorizerPageComponent],
+ exports: [TheorizerPageComponent],
+ imports: [CommonModule, CommonMaterialModule, RouterModule.forChild(routes)],
})
export class TheorizerPageModule {}
diff --git a/src/app/components/handle-bungie-login/handle-bungie-login.component.ts b/src/app/components/handle-bungie-login/handle-bungie-login.component.ts
index 696be32d..c0a41c11 100644
--- a/src/app/components/handle-bungie-login/handle-bungie-login.component.ts
+++ b/src/app/components/handle-bungie-login/handle-bungie-login.component.ts
@@ -3,34 +3,33 @@ import { ActivatedRoute, Router } from "@angular/router";
import { AuthService } from "../../services/auth.service";
@Component({
- selector: "app-handle-bungie-login",
- templateUrl: "./handle-bungie-login.component.html",
- styleUrls: ["./handle-bungie-login.component.css"],
+ selector: "app-handle-bungie-login",
+ templateUrl: "./handle-bungie-login.component.html",
+ styleUrls: ["./handle-bungie-login.component.css"],
})
export class HandleBungieLoginComponent implements OnInit {
- constructor(
- private activatedRoute: ActivatedRoute,
- private router: Router,
- private loginService: AuthService
- ) {}
+ constructor(
+ private activatedRoute: ActivatedRoute,
+ private router: Router,
+ private loginService: AuthService
+ ) {}
- ngOnInit(): void {
- this.activatedRoute.queryParams.subscribe(async (params) => {
- let code = params["code"];
- if (window.location.search.indexOf("?code=") > -1)
- code = window.location.search.substr(6);
+ ngOnInit(): void {
+ this.activatedRoute.queryParams.subscribe(async (params) => {
+ let code = params["code"];
+ if (window.location.search.indexOf("?code=") > -1) code = window.location.search.substr(6);
- console.info({ code });
+ console.info({ code });
- if (!code) return;
+ if (!code) return;
- this.loginService.authCode = code;
+ this.loginService.authCode = code;
- console.info("Generate tokens with the new code");
- await this.loginService.generateTokens();
+ console.info("Generate tokens with the new code");
+ await this.loginService.generateTokens();
- console.info("Now navigate to /");
- await this.router.navigate(["/"]);
- });
- }
+ console.info("Now navigate to /");
+ await this.router.navigate(["/"]);
+ });
+ }
}
diff --git a/src/app/components/login/login.component.css b/src/app/components/login/login.component.css
index 6d04d6e3..08d830b8 100644
--- a/src/app/components/login/login.component.css
+++ b/src/app/components/login/login.component.css
@@ -1,34 +1,34 @@
mat-card {
- max-width: 600px;
- margin-bottom: 4px;
- margin-left: auto;
- margin-right: auto;
+ max-width: 600px;
+ margin-bottom: 4px;
+ margin-left: auto;
+ margin-right: auto;
}
button {
- position: absolute;
- right: 10px;
- bottom: 10px;
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
}
a {
- color: #99afe3;
+ color: #99afe3;
}
a:visited {
- color: #7294dc;
+ color: #7294dc;
}
.example-header-image {
- background-image: url("../../../assets/superGreatLogo.png");
- background-size: cover;
+ background-image: url("../../../assets/superGreatLogo.png");
+ background-size: cover;
}
.image-example {
- max-width: 100%;
+ max-width: 100%;
}
.armor-image-container {
- overflow: auto;
+ overflow: auto;
}
.armor-image-container > img {
- max-height: 300px;
+ max-height: 300px;
}
diff --git a/src/app/components/login/login.component.html b/src/app/components/login/login.component.html
index f63e1af4..2b99ee92 100644
--- a/src/app/components/login/login.component.html
+++ b/src/app/components/login/login.component.html
@@ -1,105 +1,100 @@
-
-
- Destiny 2 Armor Picker
- A simple armor min-max tool by Mijago.
-
-
-
- This tool allows you to select the optimal armor from your inventory and vault to
- satisfy your stat needs. It also tells you which stat mods you have to use!
-
-
- You have to log in to enable this tool to grab your armor from the API.
- If you like my tools and want to support me, head to
- ko-fi.com and buy me a coffee! ❤
-
- If you found a bug or have a feature request, please open an issue
- in the Github Repository!
-
-
- D2ArmorPicker uses the Bungie.net api and it's OAuth authorization. You log in on an
- official Bungie.net page and D2ArmorPicker receives a temporary login token from Bungie.
- That means it does not receive your credentials.
-
+ This tool allows you to select the optimal armor from your inventory and vault to satisfy your
+ stat needs. It also tells you which stat mods you have to use!
+
+
+ You have to log in to enable this tool to grab your armor from the API.
+ If you like my tools and want to support me, head to
+ ko-fi.com and buy me a coffee! ❤
+
+ If you found a bug or have a feature request, please open an issue
+ in the Github Repository!
+
+
+ D2ArmorPicker uses the Bungie.net api and it's OAuth authorization. You log in on an official
+ Bungie.net page and D2ArmorPicker receives a temporary login token from Bungie. That means it
+ does not receive your credentials.
+
+
+
+
+
- Examples
- Some examples to show what D2ArmorPicker can do.
-
-
-
-
- Stat Selection
- The heart of this tool.
-
-
- First, you are able to select the stats you want. The tool will automatically
- parse all your armor (including your vault and your postmaster), and then show
- you which stat selections are even possible with your current gear. The tool
- also shows you all possible loadouts with three or four stats at 100!
-
-
-
- There are also additional settings. You can add stasis fragments and negative
- and positive stat mods to your builds. Also, you can select 0-waste builds (so
- only flat numbers). D2ArmorPicker will also add stat mods to reduce wasted stats
- wherever possible!
-
-
+ Examples
+ Some examples to show what D2ArmorPicker can do.
+
+
+
+
+ Stat Selection
+ The heart of this tool.
+
+
+ First, you are able to select the stats you want. The tool will automatically parse all
+ your armor (including your vault and your postmaster), and then show you which stat
+ selections are even possible with your current gear. The tool also shows you all possible
+ loadouts with three or four stats at 100!
+
+
+
+ There are also additional settings. You can add stasis fragments and negative and positive
+ stat mods to your builds. Also, you can select 0-waste builds (so only flat numbers).
+ D2ArmorPicker will also add stat mods to reduce wasted stats wherever possible!
+
+
-
-
- Result Display
-
- What you will get from this tool.
-
-
-
- D2ArmorPicker will show you a table with many results. You can sort and filter
- it, and then you click on one row to see which items are needed for your desired
- stats.
-
-
-
- The page will also give you a detailed description of what you have to do in
- order to get to your desired build. The following screenshot shows one of the
- more detailed descriptions:
-
-
-
+
+
+ Result Display
+ What you will get from this tool.
+
+
+ D2ArmorPicker will show you a table with many results. You can sort and filter it, and
+ then you click on one row to see which items are needed for your desired stats.
+
+
+
+ The page will also give you a detailed description of what you have to do in order to get
+ to your desired build. The following screenshot shows one of the more detailed
+ descriptions:
+
+
+
-
-
- Armor Clustering
-
- For the time when your vault is overflowing.
-
-
-
- Since version 2.0.14, D2ArmorPicker has an armor clustering feature. This means
- that it takes all your armor and groups it in 25 groups, where all items in one
- group has very similar stats. This can help you to clear out your vault,
- especially from duplicated armor rolls.
-
-
-
- Take Cluster 1, for example. I copy the DIM-Query, enter it into DIM and see the
- following results:
-
-
-
-
-
-
-
-
-
+
+
+ Armor Clustering
+
+ For the time when your vault is overflowing.
+
+
+
+ Since version 2.0.14, D2ArmorPicker has an armor clustering feature. This means that it
+ takes all your armor and groups it in 25 groups, where all items in one group has very
+ similar stats. This can help you to clear out your vault, especially from duplicated armor
+ rolls.
+
+
+
+ Take Cluster 1, for example. I copy the DIM-Query, enter it into DIM and see the following
+ results:
+