Adds quality selection, all dungeons
This commit is contained in:
7
package-lock.json
generated
7
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.7",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
@@ -1614,6 +1615,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.7",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
|
||||
12
src/App.tsx
12
src/App.tsx
@@ -1,9 +1,10 @@
|
||||
import { useEffect, useReducer } from "react";
|
||||
import "./App.css";
|
||||
import { Equipment } from "./components/Equipment";
|
||||
import { SourceList } from "./components/SourceList";
|
||||
import { StateContext } from "./lib/context/StateContext";
|
||||
import { reducer, withLoadingAction } from "./lib/state";
|
||||
import type { ItemId } from "./lib/types";
|
||||
import { SourceList } from "./components/SourceList";
|
||||
|
||||
function App() {
|
||||
const [state, dispatch] = useReducer(withLoadingAction(reducer), "loading");
|
||||
@@ -38,7 +39,12 @@ function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StateContext.Provider
|
||||
value={{
|
||||
state,
|
||||
dispatch,
|
||||
}}
|
||||
>
|
||||
<h1>WoW Gear Finder</h1>
|
||||
<Equipment
|
||||
state={state}
|
||||
@@ -46,7 +52,7 @@ function App() {
|
||||
onUnequip={(item) => dispatch({ action: "unequipItem", item })}
|
||||
/>
|
||||
<SourceList state={state} />
|
||||
</>
|
||||
</StateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.equipedItems {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: 200px 1fr;
|
||||
grid-template-columns: 10em 1fr 1fr 5em;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,17 @@
|
||||
}
|
||||
|
||||
.slot {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.items {
|
||||
.item {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.quality {
|
||||
grid-column: 3;
|
||||
}
|
||||
|
||||
.actions {
|
||||
grid-column: 4;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Slots, type Item, type Slot } from "../lib/types";
|
||||
import { ItemLink } from "./ItemLink";
|
||||
import { ItemTypeahead } from "./ItemTypeahead";
|
||||
import styles from "./Equipment.module.css";
|
||||
import { QualitySelector } from "./QualitySelector";
|
||||
|
||||
export type Props = {
|
||||
state: State;
|
||||
@@ -28,19 +29,37 @@ type SlotProps = {
|
||||
onUnequip: (item: Item) => void;
|
||||
};
|
||||
|
||||
const Slot = ({ state, slot, onUnequip }: SlotProps) => (
|
||||
const Slot = ({ state, slot, onUnequip }: SlotProps) => {
|
||||
const items = state.equipedItems
|
||||
.map((item) => ({ ...item, item: ItemsById[item.id] }))
|
||||
.filter((item) => item && item.item.slot === slot);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`${styles.slot} ${styles.gridItem}`}>{slot}</div>
|
||||
<div className={styles.gridItem}>
|
||||
{state.equipedItems
|
||||
.map((item) => ({ ...item, item: ItemsById[item.id] }))
|
||||
.filter((item) => item && item.item.slot === slot)
|
||||
.map((item) => (
|
||||
<div>
|
||||
<ItemLink item={item.item} />({item.quality})
|
||||
{
|
||||
// Show placeholder if there are no items
|
||||
items.length === 0 && (
|
||||
<>
|
||||
<div className={`${styles.gridItem} ${styles.item}`}></div>
|
||||
<div className={`${styles.gridItem} ${styles.quality}`}></div>
|
||||
<div className={`${styles.gridItem} ${styles.actions}`}></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{items.map((item) => (
|
||||
<>
|
||||
<div className={`${styles.gridItem} ${styles.item}`}>
|
||||
<ItemLink item={item.item} />
|
||||
</div>
|
||||
<div className={`${styles.gridItem} ${styles.quality}`}>
|
||||
<QualitySelector item={item} />
|
||||
</div>
|
||||
<div className={`${styles.gridItem} ${styles.actions}`}>
|
||||
<button onClick={() => onUnequip(item.item)}>X</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
27
src/components/QualitySelector.tsx
Normal file
27
src/components/QualitySelector.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useAppState } from "../lib/context/StateContext";
|
||||
import { Quality, type EquipedItem } from "../lib/types";
|
||||
|
||||
export type Props = {
|
||||
item: EquipedItem;
|
||||
};
|
||||
|
||||
export const QualitySelector = ({ item }: Props) => {
|
||||
const { dispatch } = useAppState();
|
||||
|
||||
const handleOnChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
dispatch({
|
||||
action: "changeQuality",
|
||||
itemId: item.id,
|
||||
quality: event.target.value as Quality,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<select onChange={handleOnChange}>
|
||||
{Object.values(Quality).map((quality) => (
|
||||
<option value={quality} selected={item.quality === quality}>
|
||||
{quality.charAt(0).toUpperCase() + quality.slice(1)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,12 @@
|
||||
import * as _ from "lodash";
|
||||
import { getUpgrades, qualityAtMost } from "../lib/items";
|
||||
import { getUpgrades, hasUpgradeType } from "../lib/items";
|
||||
import type { State } from "../lib/state";
|
||||
import type { Item } from "../lib/types";
|
||||
import {
|
||||
UpgradeType,
|
||||
type Item,
|
||||
type Source,
|
||||
type Upgrade,
|
||||
} from "../lib/types";
|
||||
import styles from "./SourceList.module.css";
|
||||
|
||||
export type Props = {
|
||||
@@ -11,7 +16,7 @@ export type Props = {
|
||||
export const SourceList = ({ state }: Props) => {
|
||||
const upgrades = getUpgrades(state.equipedItems);
|
||||
|
||||
const upgradesBySource = _.groupBy(
|
||||
const upgradesBySource: Record<Source, Upgrade[]> = _.groupBy(
|
||||
upgrades,
|
||||
(upgrade: { item: Item }) => upgrade.item.source,
|
||||
);
|
||||
@@ -22,10 +27,13 @@ export const SourceList = ({ state }: Props) => {
|
||||
<div>Champion</div>
|
||||
<div>Hero</div>
|
||||
<div>Myth</div>
|
||||
{Object.entries(upgradesBySource).map(([source, items]) => {
|
||||
const champion = items.filter(qualityAtMost("champion")).length;
|
||||
const hero = items.filter(qualityAtMost("hero")).length;
|
||||
const myth = items.filter(qualityAtMost("myth")).length;
|
||||
{Object.entries(upgradesBySource).map(
|
||||
([source, items]: [string, Upgrade[]]) => {
|
||||
const champion = items.filter(
|
||||
hasUpgradeType(UpgradeType.Champion),
|
||||
).length;
|
||||
const hero = items.filter(hasUpgradeType(UpgradeType.Hero)).length;
|
||||
const myth = items.filter(hasUpgradeType(UpgradeType.Myth)).length;
|
||||
return (
|
||||
<>
|
||||
<div>{source}</div>
|
||||
@@ -34,7 +42,8 @@ export const SourceList = ({ state }: Props) => {
|
||||
<div>{myth}</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
17
src/lib/context/StateContext.ts
Normal file
17
src/lib/context/StateContext.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createContext, useContext } from "react";
|
||||
import type { State, Action } from "../state";
|
||||
|
||||
export type StateContextType = {
|
||||
state: State;
|
||||
dispatch: React.ActionDispatch<[Action]>;
|
||||
};
|
||||
|
||||
export const StateContext = createContext<StateContextType | null>(null);
|
||||
|
||||
export function useAppState(): StateContextType {
|
||||
const value = useContext(StateContext);
|
||||
if (!value) {
|
||||
throw new Error("useState must be used within a StateContext.Provider");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
46
src/lib/drops/aldani.ts
Normal file
46
src/lib/drops/aldani.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { ItemId } from "../types";
|
||||
|
||||
export const Aldani = [
|
||||
{
|
||||
id: 242494 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Lily of the Eternal Weave",
|
||||
source: "aldani" as const,
|
||||
},
|
||||
{
|
||||
id: 242495 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Incorporeal Warpclaw",
|
||||
source: "aldani" as const,
|
||||
},
|
||||
{
|
||||
id: 242481 as ItemId,
|
||||
slot: "2h-weapon",
|
||||
name: "Spellstrike Warplance",
|
||||
source: "aldani" as const,
|
||||
},
|
||||
{
|
||||
id: 242473 as ItemId,
|
||||
slot: "legs",
|
||||
name: "Spittle-Stained Trousers",
|
||||
source: "aldani" as const,
|
||||
},
|
||||
{
|
||||
id: 242470 as ItemId,
|
||||
slot: "1h-weapon",
|
||||
name: "Mandibular Bonewhacker",
|
||||
source: "aldani" as const,
|
||||
},
|
||||
{
|
||||
id: 242486 as ItemId,
|
||||
slot: "shoulders",
|
||||
name: "Mantle of Wounded Fate",
|
||||
source: "aldani" as const,
|
||||
},
|
||||
{
|
||||
id: 242482 as ItemId,
|
||||
slot: "chest",
|
||||
name: "Reinforced Stalkerhide Vest",
|
||||
source: "aldani" as const,
|
||||
},
|
||||
] as const;
|
||||
52
src/lib/drops/ara-kara.ts
Normal file
52
src/lib/drops/ara-kara.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { ItemId } from "../types";
|
||||
|
||||
export const AraKara = [
|
||||
{
|
||||
id: 221159 as ItemId,
|
||||
slot: "2h-weapon",
|
||||
name: "Harvester's Interdiction",
|
||||
source: "ara-kara",
|
||||
},
|
||||
{
|
||||
id: 219317 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Harvester's Edict",
|
||||
source: "ara-kara",
|
||||
},
|
||||
{
|
||||
id: 219316 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Ceaseless Swarmgland",
|
||||
source: "ara-kara",
|
||||
},
|
||||
{
|
||||
id: 221157 as ItemId,
|
||||
slot: "wrist",
|
||||
name: "Unbreakable Beetlebane Bindings",
|
||||
source: "ara-kara",
|
||||
},
|
||||
{
|
||||
id: 221153 as ItemId,
|
||||
slot: "legs",
|
||||
name: "Gauzewoven Legguards",
|
||||
source: "ara-kara",
|
||||
},
|
||||
{
|
||||
id: 221154 as ItemId,
|
||||
slot: "back",
|
||||
name: "Swarmcaller's Shroud",
|
||||
source: "ara-kara",
|
||||
},
|
||||
{
|
||||
id: 221163 as ItemId,
|
||||
slot: "head",
|
||||
name: "Whispering Mask",
|
||||
source: "ara-kara",
|
||||
},
|
||||
{
|
||||
id: 219315 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Refracting Aggression Module",
|
||||
source: "ara-kara",
|
||||
},
|
||||
] as const;
|
||||
76
src/lib/drops/dawnbreaker.ts
Normal file
76
src/lib/drops/dawnbreaker.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { Item } from "../types";
|
||||
|
||||
export const Dawnbreaker = [
|
||||
{
|
||||
id: 219312,
|
||||
slot: "trinket",
|
||||
name: "Empowering Crystal of Anub'ikkaj",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 225574,
|
||||
slot: "back",
|
||||
name: "Wings of Shattered Sorrow",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 219311,
|
||||
slot: "trinket",
|
||||
name: "Void Pactstone",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 221142,
|
||||
slot: "wrist",
|
||||
name: "Scheming Assailer's Bands",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 221137,
|
||||
slot: "2h-weapon",
|
||||
name: "Black Shepherd's Guisarme",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 221144,
|
||||
slot: "1h-weapon",
|
||||
name: "Zephyrous Sail Carver",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 221134,
|
||||
slot: "waist",
|
||||
name: "Shadow Congregant's Belt",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 221145,
|
||||
slot: "1h-weapon",
|
||||
name: "Shipwrecker's Bludgeon",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 221148,
|
||||
slot: "shoulders",
|
||||
name: "Epaulets of the Clipped Wings",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 225583,
|
||||
slot: "waist",
|
||||
name: "Behemoth's Eroded Cinch",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 212453,
|
||||
slot: "trinket",
|
||||
name: "Skyterror's Corrosive Organ",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
{
|
||||
id: 212398,
|
||||
slot: "1h-weapon",
|
||||
name: "Bludgeons of Blistering Wind",
|
||||
source: "dawnbreaker" as const,
|
||||
},
|
||||
] as Item[];
|
||||
46
src/lib/drops/floodgate.ts
Normal file
46
src/lib/drops/floodgate.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { ItemId } from "../types";
|
||||
|
||||
export const Floodgate = [
|
||||
{
|
||||
id: 232541 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Improvised Seaforium Pacemaker",
|
||||
source: "floodgate" as const,
|
||||
},
|
||||
{
|
||||
id: 234499 as ItemId,
|
||||
slot: "wrist",
|
||||
name: "Disturbed Kelp Wraps",
|
||||
source: "floodgate" as const,
|
||||
},
|
||||
{
|
||||
id: 246274 as ItemId,
|
||||
slot: "feet",
|
||||
name: "Geezle's Zapstep Boots",
|
||||
source: "floodgate" as const,
|
||||
},
|
||||
{
|
||||
id: 234494 as ItemId,
|
||||
slot: "2h-weapon",
|
||||
name: "Gallytech Turbo-Tiller",
|
||||
source: "floodgate" as const,
|
||||
},
|
||||
{
|
||||
id: 234498 as ItemId,
|
||||
slot: "head",
|
||||
name: "Waterworks Filtration Mask",
|
||||
source: "floodgate" as const,
|
||||
},
|
||||
{
|
||||
id: 234507 as ItemId,
|
||||
slot: "back",
|
||||
name: "Electrician's Siphoning Filter",
|
||||
source: "floodgate" as const,
|
||||
},
|
||||
{
|
||||
id: 234500 as ItemId,
|
||||
slot: "shoulders",
|
||||
name: "Mechanized Junkpads",
|
||||
source: "floodgate" as const,
|
||||
},
|
||||
] as const;
|
||||
52
src/lib/drops/halls-of-atonement.ts
Normal file
52
src/lib/drops/halls-of-atonement.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { ItemId } from "../types";
|
||||
|
||||
export const HallsOfAtonement = [
|
||||
{
|
||||
id: 246344 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Cursed Stone Idol",
|
||||
source: "halls-of-atonement" as const,
|
||||
},
|
||||
{
|
||||
id: 178832 as ItemId,
|
||||
slot: "hands",
|
||||
name: "Gloves of Haunting Fixation",
|
||||
source: "halls-of-atonement" as const,
|
||||
},
|
||||
{
|
||||
id: 178819 as ItemId,
|
||||
slot: "legs",
|
||||
name: "Skyterror's Stonehide Leggings",
|
||||
source: "halls-of-atonement" as const,
|
||||
},
|
||||
{
|
||||
id: 246273 as ItemId,
|
||||
slot: "chest",
|
||||
name: "Vest of Refracted Shadows",
|
||||
source: "halls-of-atonement" as const,
|
||||
},
|
||||
{
|
||||
id: 178834 as ItemId,
|
||||
slot: "1h-weapon",
|
||||
name: "Stoneguardian's Morningstar",
|
||||
source: "halls-of-atonement" as const,
|
||||
},
|
||||
{
|
||||
id: 178823 as ItemId,
|
||||
slot: "waist",
|
||||
name: "Waistcord of Dark Devotion",
|
||||
source: "halls-of-atonement" as const,
|
||||
},
|
||||
{
|
||||
id: 178825 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Pulsating Stoneheart",
|
||||
source: "halls-of-atonement" as const,
|
||||
},
|
||||
{
|
||||
id: 178817 as ItemId,
|
||||
slot: "head",
|
||||
name: "Hood of Refracted Shadows",
|
||||
source: "halls-of-atonement" as const,
|
||||
},
|
||||
] as const;
|
||||
8
src/lib/drops/index.ts
Normal file
8
src/lib/drops/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export { Aldani } from "./aldani";
|
||||
export { AraKara } from "./ara-kara";
|
||||
export { Dawnbreaker } from "./dawnbreaker";
|
||||
export { Floodgate } from "./floodgate";
|
||||
export { HallsOfAtonement } from "./halls-of-atonement";
|
||||
export { PrioryOfTheSacredFlame } from "./priory";
|
||||
export { Streets, Gambit } from "./tazavesh";
|
||||
export { ManaforgeOmega } from "./manaforge-omega";
|
||||
40
src/lib/drops/manaforge-omega.ts
Normal file
40
src/lib/drops/manaforge-omega.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { ItemId } from "../types";
|
||||
|
||||
export const ManaforgeOmega = [
|
||||
{
|
||||
id: 237673 as ItemId,
|
||||
name: "Half-Mask of Fallen Storms",
|
||||
slot: "head",
|
||||
source: "manaforge omega",
|
||||
},
|
||||
{
|
||||
id: 237674 as ItemId,
|
||||
name: "Grasp of Fallen Storms",
|
||||
slot: "hands",
|
||||
source: "manaforge omega",
|
||||
},
|
||||
{
|
||||
id: 237676 as ItemId,
|
||||
name: "Gi of Fallen Storms",
|
||||
slot: "chest",
|
||||
source: "manaforge omega",
|
||||
},
|
||||
{
|
||||
id: 237672 as ItemId,
|
||||
name: "Legwraps of Fallen Storms",
|
||||
slot: "legs",
|
||||
source: "manaforge omega",
|
||||
},
|
||||
{
|
||||
id: 237671 as ItemId,
|
||||
name: "Glyphs of Fallen Storms",
|
||||
slot: "shoulders",
|
||||
source: "manaforge omega",
|
||||
},
|
||||
{
|
||||
id: 235799 as ItemId,
|
||||
name: "Reshii Wraps",
|
||||
slot: "back",
|
||||
source: "manaforge omega",
|
||||
},
|
||||
] as const;
|
||||
34
src/lib/drops/priory.ts
Normal file
34
src/lib/drops/priory.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { ItemId } from "../types";
|
||||
|
||||
export const PrioryOfTheSacredFlame = [
|
||||
{
|
||||
id: 219309 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Tome of Light's Devotion",
|
||||
source: "priory" as const,
|
||||
},
|
||||
{
|
||||
id: 219308 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "Signet of the Priory",
|
||||
source: "priory" as const,
|
||||
},
|
||||
{
|
||||
id: 252009 as ItemId,
|
||||
slot: "neck",
|
||||
name: "Bloodstained Memento",
|
||||
source: "priory" as const,
|
||||
},
|
||||
{
|
||||
id: 221200 as ItemId,
|
||||
slot: "finger",
|
||||
name: "Radiant Necromancer's Band",
|
||||
source: "priory" as const,
|
||||
},
|
||||
{
|
||||
id: 221125 as ItemId,
|
||||
slot: "head",
|
||||
name: "Helm of the Righteous Crusade",
|
||||
source: "priory" as const,
|
||||
},
|
||||
] as const;
|
||||
115
src/lib/drops/tazavesh.ts
Normal file
115
src/lib/drops/tazavesh.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type { ItemId } from "../types";
|
||||
|
||||
export const Gambit = [
|
||||
{
|
||||
id: 190958 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "So'leah's Secret Technique",
|
||||
source: "gambit" as const,
|
||||
},
|
||||
{
|
||||
id: 185818 as ItemId,
|
||||
slot: "trinket",
|
||||
name: "So'leah's Secret Technique",
|
||||
source: "gambit" as const,
|
||||
},
|
||||
{
|
||||
id: 185823 as ItemId,
|
||||
slot: "1h-weapon",
|
||||
name: "Fatebreaker, Destroyer of Futures",
|
||||
source: "gambit" as const,
|
||||
},
|
||||
{
|
||||
id: 246280 as ItemId,
|
||||
slot: "feet",
|
||||
name: "Boots of Titanic Deconversion",
|
||||
source: "gambit" as const,
|
||||
},
|
||||
{
|
||||
id: 185781 as ItemId,
|
||||
slot: "back",
|
||||
name: "Drape of Titanic Dreams",
|
||||
source: "gambit" as const,
|
||||
},
|
||||
{
|
||||
id: 185790 as ItemId,
|
||||
slot: "feet",
|
||||
name: "Treads of Titanic Deconversion",
|
||||
source: "gambit" as const,
|
||||
},
|
||||
{
|
||||
id: 185797 as ItemId,
|
||||
slot: "head",
|
||||
name: "Rakishly Tipped Tricorne",
|
||||
source: "gambit" as const,
|
||||
},
|
||||
{
|
||||
id: 185801 as ItemId,
|
||||
slot: "legs",
|
||||
name: "Anomalous Starlit Breeches",
|
||||
source: "gambit" as const,
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const Streets = [
|
||||
{
|
||||
id: 185780 as ItemId,
|
||||
slot: "1h-weapon",
|
||||
name: "Interrogator's Flensing Blade",
|
||||
source: "streets" as const,
|
||||
},
|
||||
{
|
||||
id: 185778 as ItemId,
|
||||
slot: "1h-weapon",
|
||||
name: "First Fist of the So Cartel",
|
||||
source: "streets" as const,
|
||||
},
|
||||
{
|
||||
id: 185824 as ItemId,
|
||||
slot: "1h-weapon",
|
||||
name: "Blade of Grievous Harm",
|
||||
source: "streets" as const,
|
||||
},
|
||||
{
|
||||
id: 185809 as ItemId,
|
||||
slot: "waist",
|
||||
name: "Venza's Powderbelt",
|
||||
source: "streets" as const,
|
||||
},
|
||||
{
|
||||
id: 185779 as ItemId,
|
||||
slot: "2h-weapon",
|
||||
name: "Spire of Expurgation",
|
||||
source: "streets" as const,
|
||||
},
|
||||
{
|
||||
id: 185802 as ItemId,
|
||||
slot: "shoulders",
|
||||
name: "Breakbeat Shoulderguards",
|
||||
source: "streets" as const,
|
||||
},
|
||||
{
|
||||
id: 185786 as ItemId,
|
||||
slot: "chest",
|
||||
name: "So'azmi's Fractal Vest",
|
||||
source: "streets" as const,
|
||||
},
|
||||
{
|
||||
id: 185791 as ItemId,
|
||||
slot: "hands",
|
||||
name: "Knuckle-Dusting Handwraps",
|
||||
source: "streets" as const,
|
||||
},
|
||||
{
|
||||
id: 185817 as ItemId,
|
||||
slot: "wrist",
|
||||
name: "Bracers of Autonomous Classification",
|
||||
source: "streets" as const,
|
||||
},
|
||||
{
|
||||
id: 185843 as ItemId,
|
||||
slot: "back",
|
||||
name: "Duplicating Drape",
|
||||
source: "streets" as const,
|
||||
},
|
||||
] as const;
|
||||
128
src/lib/items.ts
128
src/lib/items.ts
@@ -1,25 +1,35 @@
|
||||
import _ from "lodash";
|
||||
import {
|
||||
UpgradeType,
|
||||
type EquipedItem,
|
||||
type Item,
|
||||
type ItemId,
|
||||
type Quality,
|
||||
type Slot,
|
||||
type Upgrade,
|
||||
} from "./types";
|
||||
|
||||
export const PrioryOfTheSacredFlame = [
|
||||
{ id: 219309 as ItemId, slot: "trinket", name: "Tome of Light's Devotion" },
|
||||
{ id: 219308 as ItemId, slot: "trinket", name: "Signet of the Priory" },
|
||||
{ id: 252009 as ItemId, slot: "neck", name: "Bloodstained Memento" },
|
||||
{ id: 221200 as ItemId, slot: "finger", name: "Radiant Necromancer's Band" },
|
||||
{ id: 221125 as ItemId, slot: "head", name: "Helm of the Righteous Crusade" },
|
||||
] as const;
|
||||
import {
|
||||
Aldani,
|
||||
AraKara,
|
||||
Dawnbreaker,
|
||||
Floodgate,
|
||||
HallsOfAtonement,
|
||||
PrioryOfTheSacredFlame,
|
||||
Streets,
|
||||
Gambit,
|
||||
ManaforgeOmega,
|
||||
} from "./drops";
|
||||
|
||||
export const AllItems: Item[] = [
|
||||
...PrioryOfTheSacredFlame.map((item) => ({
|
||||
...item,
|
||||
source: "priory" as const,
|
||||
})),
|
||||
...PrioryOfTheSacredFlame,
|
||||
...Aldani,
|
||||
...AraKara,
|
||||
...Dawnbreaker,
|
||||
...Floodgate,
|
||||
...HallsOfAtonement,
|
||||
...Streets,
|
||||
...Gambit,
|
||||
...ManaforgeOmega,
|
||||
];
|
||||
|
||||
export const ItemsById: Record<string, Item> = AllItems.reduce(
|
||||
@@ -33,36 +43,27 @@ export const ItemsById: Record<string, Item> = AllItems.reduce(
|
||||
export const ItemsBySlot: Record<Slot, Item[]> = _.groupBy(
|
||||
AllItems,
|
||||
(item: Item) => item.slot,
|
||||
);
|
||||
) as Record<Slot, Item[]>;
|
||||
|
||||
export const itemsForSlot = (slot: Slot): Item[] => ItemsBySlot[slot] ?? [];
|
||||
|
||||
function qualityToNumber(quality: Quality): number {
|
||||
switch (quality) {
|
||||
case "champion":
|
||||
case "explorer":
|
||||
return 1;
|
||||
case "hero":
|
||||
case "veteran":
|
||||
return 2;
|
||||
case "myth":
|
||||
case "champion":
|
||||
return 3;
|
||||
case "hero":
|
||||
return 4;
|
||||
case "myth":
|
||||
return 5;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function numberToQuality(quality: number): Quality | null {
|
||||
switch (quality) {
|
||||
case 1:
|
||||
return "champion";
|
||||
case 2:
|
||||
return "hero";
|
||||
case 3:
|
||||
return "myth";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const qualityAtLeast =
|
||||
(desiredQuality: Quality) =>
|
||||
({ quality: actualQuality }: { quality: Quality }): boolean =>
|
||||
@@ -73,9 +74,20 @@ export const qualityAtMost =
|
||||
({ quality: actualQuality }: { quality: Quality }): boolean =>
|
||||
qualityToNumber(actualQuality) <= qualityToNumber(desiredQuality);
|
||||
|
||||
export const getUpgrades = (
|
||||
equipedItemIds: EquipedItem[],
|
||||
): { item: Item; quality: Quality }[] => {
|
||||
export const upgradesForItem = ({ quality }: { quality: Quality }) => {
|
||||
switch (quality) {
|
||||
case "myth":
|
||||
return [];
|
||||
case "hero":
|
||||
return [UpgradeType.Myth];
|
||||
case "champion":
|
||||
return [UpgradeType.Hero, UpgradeType.Myth];
|
||||
default:
|
||||
return [UpgradeType.Champion, UpgradeType.Hero, UpgradeType.Myth];
|
||||
}
|
||||
};
|
||||
|
||||
export const getUpgrades = (equipedItemIds: EquipedItem[]): Upgrade[] => {
|
||||
return Object.entries(ItemsBySlot).flatMap(([slot, items]) => {
|
||||
const equipedItems = equipedItemIds.flatMap(({ id, quality }) => ({
|
||||
item: ItemsById[id],
|
||||
@@ -84,21 +96,45 @@ export const getUpgrades = (
|
||||
const equipedInSlot = equipedItems.filter(
|
||||
(item) => item.item.slot === slot,
|
||||
);
|
||||
return items.flatMap((item) => {
|
||||
if (equipedInSlot.length === 0) {
|
||||
return [{ item, quality: "champion" as Quality }];
|
||||
const itemsForSlot = itemsPerSlot(slot as Slot);
|
||||
const numChampion = equipedInSlot.filter(qualityAtLeast("champion")).length;
|
||||
const numHero = equipedInSlot.filter(qualityAtLeast("hero")).length;
|
||||
const numMyth = equipedInSlot.filter(qualityAtLeast("myth")).length;
|
||||
const upgradeTypes = [
|
||||
numChampion >= itemsForSlot ? [] : [UpgradeType.Champion],
|
||||
numHero >= itemsForSlot ? [] : [UpgradeType.Hero],
|
||||
numMyth >= itemsForSlot ? [] : [UpgradeType.Myth],
|
||||
].flat();
|
||||
console.log(slot, numChampion, numHero, numMyth, upgradeTypes);
|
||||
return items.map((item) => {
|
||||
const equiped = equipedInSlot.find((i) => i.item.id === item.id);
|
||||
if (equiped) {
|
||||
const upgradesForThisItem = upgradesForItem(equiped);
|
||||
return {
|
||||
item,
|
||||
upgradeTypes: upgradeTypes.filter((u) =>
|
||||
upgradesForThisItem.includes(u),
|
||||
),
|
||||
};
|
||||
}
|
||||
// Change this to some sort of quality-plus-one logic
|
||||
if (equipedInSlot.every(qualityAtLeast("myth"))) {
|
||||
return [];
|
||||
}
|
||||
if (equipedInSlot.every(qualityAtLeast("hero"))) {
|
||||
return [{ item, quality: "myth" as Quality }];
|
||||
}
|
||||
if (equipedInSlot.every(qualityAtLeast("champion"))) {
|
||||
return [{ item, quality: "hero" as Quality }];
|
||||
}
|
||||
return [];
|
||||
|
||||
return { item, upgradeTypes };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const hasUpgradeType =
|
||||
(type: UpgradeType) =>
|
||||
(upgrade: Upgrade): boolean =>
|
||||
upgrade.upgradeTypes.includes(type);
|
||||
|
||||
export const itemsPerSlot = (slot: Slot): number => {
|
||||
switch (slot) {
|
||||
case "trinket":
|
||||
case "finger":
|
||||
case "1h-weapon":
|
||||
return 2;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import type { EquipedItem, Item, ItemId } from "./types";
|
||||
import type { EquipedItem, Item, ItemId, Quality } from "./types";
|
||||
|
||||
export type State = {
|
||||
equipedItems: EquipedItem[];
|
||||
bisList: ItemId[];
|
||||
};
|
||||
|
||||
export const emptyState: State = {
|
||||
equipedItems: [],
|
||||
bisList: [],
|
||||
};
|
||||
|
||||
export type Action =
|
||||
| {
|
||||
action: "equipItem";
|
||||
@@ -13,6 +18,11 @@ export type Action =
|
||||
| {
|
||||
action: "unequipItem";
|
||||
item: Item;
|
||||
}
|
||||
| {
|
||||
action: "changeQuality";
|
||||
itemId: ItemId;
|
||||
quality: Quality;
|
||||
};
|
||||
|
||||
export const reducer = (state: State, action: Action): State => {
|
||||
@@ -35,6 +45,19 @@ export const reducer = (state: State, action: Action): State => {
|
||||
(item) => item.id !== action.item.id,
|
||||
),
|
||||
};
|
||||
case "changeQuality":
|
||||
return {
|
||||
...state,
|
||||
equipedItems: state.equipedItems.map((item) => {
|
||||
if (item.id === action.itemId) {
|
||||
return {
|
||||
id: item.id,
|
||||
quality: action.quality,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -18,11 +18,28 @@ export const Slots = [
|
||||
|
||||
export type Slot = (typeof Slots)[number];
|
||||
|
||||
export type Quality = "champion" | "hero" | "myth";
|
||||
export const Quality = {
|
||||
Explorer: "explorer",
|
||||
Veteran: "veteran",
|
||||
Champion: "champion",
|
||||
Hero: "hero",
|
||||
Myth: "myth",
|
||||
};
|
||||
|
||||
export type Quality = (typeof Quality)[keyof typeof Quality];
|
||||
|
||||
export type ItemId = number & { __type: "ItemId" };
|
||||
|
||||
export type Source = "priory";
|
||||
export type Source =
|
||||
| "aldani"
|
||||
| "ara-kara"
|
||||
| "dawnbreaker"
|
||||
| "floodgate"
|
||||
| "gambit"
|
||||
| "halls-of-atonement"
|
||||
| "priory"
|
||||
| "streets"
|
||||
| "manaforge omega";
|
||||
|
||||
export type Item = {
|
||||
id: ItemId;
|
||||
@@ -35,3 +52,15 @@ export type EquipedItem = {
|
||||
id: ItemId;
|
||||
quality: Quality;
|
||||
};
|
||||
|
||||
export const UpgradeType = {
|
||||
ILvl: "ilvl",
|
||||
Champion: "champion",
|
||||
Hero: "hero",
|
||||
Myth: "myth",
|
||||
BiS: "bis",
|
||||
};
|
||||
|
||||
export type UpgradeType = (typeof UpgradeType)[keyof typeof UpgradeType];
|
||||
|
||||
export type Upgrade = { item: Item; upgradeTypes: UpgradeType[] };
|
||||
|
||||
Reference in New Issue
Block a user