Adds crafted items, weapon config, generally working
This commit is contained in:
@@ -4,7 +4,8 @@ import { Equipment } from "./components/Equipment";
|
|||||||
import { SourceList } from "./components/SourceList";
|
import { SourceList } from "./components/SourceList";
|
||||||
import { StateContext } from "./lib/context/StateContext";
|
import { StateContext } from "./lib/context/StateContext";
|
||||||
import { reducer, withLoadingAction } from "./lib/state";
|
import { reducer, withLoadingAction } from "./lib/state";
|
||||||
import type { ItemId } from "./lib/types";
|
import { WeaponConfig, type ItemId } from "./lib/types";
|
||||||
|
import { WeaponConfigPicker } from "./components/WeaponConfigPicker";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [state, dispatch] = useReducer(withLoadingAction(reducer), "loading");
|
const [state, dispatch] = useReducer(withLoadingAction(reducer), "loading");
|
||||||
@@ -25,6 +26,7 @@ function App() {
|
|||||||
state: {
|
state: {
|
||||||
equipedItems: [{ id: 219309 as ItemId, quality: "champion" }],
|
equipedItems: [{ id: 219309 as ItemId, quality: "champion" }],
|
||||||
bisList: [],
|
bisList: [],
|
||||||
|
weaponConfig: WeaponConfig.TwoHander,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -46,11 +48,15 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1>WoW Gear Finder</h1>
|
<h1>WoW Gear Finder</h1>
|
||||||
|
<h2>Config</h2>
|
||||||
|
<WeaponConfigPicker />
|
||||||
|
<h2>Equipment</h2>
|
||||||
<Equipment
|
<Equipment
|
||||||
state={state}
|
state={state}
|
||||||
onEquip={(item) => dispatch({ action: "equipItem", item })}
|
onEquip={(item) => dispatch({ action: "equipItem", item })}
|
||||||
onUnequip={(item) => dispatch({ action: "unequipItem", item })}
|
onUnequip={(item) => dispatch({ action: "unequipItem", item })}
|
||||||
/>
|
/>
|
||||||
|
<h2>Upgrades</h2>
|
||||||
<SourceList state={state} />
|
<SourceList state={state} />
|
||||||
</StateContext.Provider>
|
</StateContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ItemsById } from "../lib/items";
|
import { ItemsById, itemsPerSlot } from "../lib/items";
|
||||||
import type { State } from "../lib/state";
|
import type { State } from "../lib/state";
|
||||||
import { Slots, type Item, type Slot } from "../lib/types";
|
import { Slots, type Item, type Slot } from "../lib/types";
|
||||||
import { ItemLink } from "./ItemLink";
|
import { ItemLink } from "./ItemLink";
|
||||||
@@ -12,16 +12,28 @@ export type Props = {
|
|||||||
onUnequip: (item: Item) => void;
|
onUnequip: (item: Item) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Equipment = ({ state, onEquip, onUnequip }: Props) => (
|
export const Equipment = ({ state, onEquip, onUnequip }: Props) => {
|
||||||
<div>
|
return (
|
||||||
<ItemTypeahead value={null} onSelect={onEquip} />
|
<div>
|
||||||
<div className={styles.equipedItems}>
|
<ItemTypeahead value={null} onSelect={(item) => item && onEquip(item)} />
|
||||||
{Slots.map((slot) => (
|
<div className={styles.equipedItems}>
|
||||||
<Slot state={state} slot={slot} onUnequip={onUnequip} key={slot} />
|
{Slots.map((slot) => {
|
||||||
))}
|
if (itemsPerSlot(slot, state.weaponConfig) > 0) {
|
||||||
|
return (
|
||||||
|
<Slot
|
||||||
|
state={state}
|
||||||
|
slot={slot}
|
||||||
|
onUnequip={onUnequip}
|
||||||
|
key={slot}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
type SlotProps = {
|
type SlotProps = {
|
||||||
state: State;
|
state: State;
|
||||||
|
|||||||
7
src/components/ItemLink.module.css
Normal file
7
src/components/ItemLink.module.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.itemLink {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Item } from "../lib/types";
|
import type { Item } from "../lib/types";
|
||||||
|
import * as styles from "./ItemLink.module.css";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
item: Item;
|
item: Item;
|
||||||
@@ -9,7 +10,13 @@ export const ItemLink = ({ item }: Props) => (
|
|||||||
href={`https://www.wowhead.com/item=${item.id}`}
|
href={`https://www.wowhead.com/item=${item.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
className={styles.itemLink}
|
||||||
>
|
>
|
||||||
|
{item.icon && (
|
||||||
|
<img
|
||||||
|
src={`https://wow.zamimg.com/images/wow/icons/medium/${item.icon}.jpg`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{item.name}
|
{item.name}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import {
|
|||||||
ComboboxOptions,
|
ComboboxOptions,
|
||||||
} from "@headlessui/react";
|
} from "@headlessui/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { AllItems } from "../lib/items";
|
|
||||||
import type { Item } from "../lib/types";
|
import type { Item } from "../lib/types";
|
||||||
|
import { AllItems } from "../lib/drops";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
value: Item | null;
|
value: Item | null;
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ export const QualitySelector = ({ item }: Props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<select onChange={handleOnChange}>
|
<select onChange={handleOnChange} value={item.quality}>
|
||||||
{Object.values(Quality).map((quality) => (
|
{Object.values(Quality).map((quality) => (
|
||||||
<option value={quality} selected={item.quality === quality}>
|
<option value={quality}>
|
||||||
{quality.charAt(0).toUpperCase() + quality.slice(1)}
|
{quality.charAt(0).toUpperCase() + quality.slice(1)}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -3,3 +3,7 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
grid-template-columns: 1fr 6em 6em 6em;
|
grid-template-columns: 1fr 6em 6em 6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.itemList {
|
||||||
|
grid-column: 1 / span 4;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { useState } from "react";
|
||||||
import { getUpgrades, hasUpgradeType } from "../lib/items";
|
import { getUpgrades, hasUpgradeType } from "../lib/items";
|
||||||
import type { State } from "../lib/state";
|
import type { State } from "../lib/state";
|
||||||
import {
|
import {
|
||||||
@@ -7,6 +8,7 @@ import {
|
|||||||
type Source,
|
type Source,
|
||||||
type Upgrade,
|
type Upgrade,
|
||||||
} from "../lib/types";
|
} from "../lib/types";
|
||||||
|
import { ItemLink } from "./ItemLink";
|
||||||
import styles from "./SourceList.module.css";
|
import styles from "./SourceList.module.css";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
@@ -14,12 +16,12 @@ export type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SourceList = ({ state }: Props) => {
|
export const SourceList = ({ state }: Props) => {
|
||||||
const upgrades = getUpgrades(state.equipedItems);
|
const upgrades = getUpgrades(state.equipedItems, state.weaponConfig);
|
||||||
|
|
||||||
const upgradesBySource: Record<Source, Upgrade[]> = _.groupBy(
|
const upgradesBySource = _.groupBy(
|
||||||
upgrades,
|
upgrades,
|
||||||
(upgrade: { item: Item }) => upgrade.item.source,
|
(upgrade: { item: Item }) => upgrade.item.source,
|
||||||
);
|
) as Record<Source, Upgrade[]>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.sourceList}>
|
<div className={styles.sourceList}>
|
||||||
@@ -28,22 +30,74 @@ export const SourceList = ({ state }: Props) => {
|
|||||||
<div>Hero</div>
|
<div>Hero</div>
|
||||||
<div>Myth</div>
|
<div>Myth</div>
|
||||||
{Object.entries(upgradesBySource).map(
|
{Object.entries(upgradesBySource).map(
|
||||||
([source, items]: [string, Upgrade[]]) => {
|
([source, items]: [string, Upgrade[]]) => (
|
||||||
const champion = items.filter(
|
<SourceInfo source={source as Source} items={items} key={source} />
|
||||||
hasUpgradeType(UpgradeType.Champion),
|
),
|
||||||
).length;
|
|
||||||
const hero = items.filter(hasUpgradeType(UpgradeType.Hero)).length;
|
|
||||||
const myth = items.filter(hasUpgradeType(UpgradeType.Myth)).length;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div>{source}</div>
|
|
||||||
<div>{champion}</div>
|
|
||||||
<div>{hero}</div>
|
|
||||||
<div>{myth}</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SourceInfoProps = {
|
||||||
|
source: Source;
|
||||||
|
items: Upgrade[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const SourceInfo = ({ source, items }: SourceInfoProps) => {
|
||||||
|
const [upgradeTypeShown, setUpgradeTypeShown] = useState<UpgradeType | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const championUpgrades = items.filter(hasUpgradeType(UpgradeType.Champion));
|
||||||
|
const heroUpgrades = items.filter(hasUpgradeType(UpgradeType.Hero));
|
||||||
|
const mythUpgrades = items.filter(hasUpgradeType(UpgradeType.Myth));
|
||||||
|
|
||||||
|
const toggleIsOpen = (upgradeType: UpgradeType) => {
|
||||||
|
if (upgradeTypeShown === upgradeType) {
|
||||||
|
setUpgradeTypeShown(null);
|
||||||
|
}
|
||||||
|
setUpgradeTypeShown(upgradeType);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>{source}</div>
|
||||||
|
<div onClick={() => toggleIsOpen(UpgradeType.Champion)}>
|
||||||
|
{championUpgrades.length}
|
||||||
|
</div>
|
||||||
|
<div onClick={() => toggleIsOpen(UpgradeType.Hero)}>
|
||||||
|
{heroUpgrades.length}
|
||||||
|
</div>
|
||||||
|
<div onClick={() => toggleIsOpen(UpgradeType.Myth)}>
|
||||||
|
{mythUpgrades.length}
|
||||||
|
</div>
|
||||||
|
{upgradeTypeShown === UpgradeType.Champion && (
|
||||||
|
<div className={styles.itemList}>
|
||||||
|
{championUpgrades.map((upgrade) => (
|
||||||
|
<div>
|
||||||
|
<ItemLink item={upgrade.item} /> ({upgrade.item.slot})
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{upgradeTypeShown === UpgradeType.Hero && (
|
||||||
|
<div className={styles.itemList}>
|
||||||
|
{heroUpgrades.map((upgrade) => (
|
||||||
|
<div>
|
||||||
|
<ItemLink item={upgrade.item} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{upgradeTypeShown === UpgradeType.Myth && (
|
||||||
|
<div className={styles.itemList}>
|
||||||
|
{mythUpgrades.map((upgrade) => (
|
||||||
|
<div>
|
||||||
|
<ItemLink item={upgrade.item} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
28
src/components/WeaponConfigPicker.tsx
Normal file
28
src/components/WeaponConfigPicker.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { useAppState } from "../lib/context/StateContext";
|
||||||
|
import { WeaponConfig } from "../lib/types";
|
||||||
|
|
||||||
|
export const WeaponConfigPicker = () => {
|
||||||
|
const { state, dispatch } = useAppState();
|
||||||
|
|
||||||
|
const handleOnChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const value = event.target.value?.trim();
|
||||||
|
if (value && value.length > 0) {
|
||||||
|
dispatch({
|
||||||
|
action: "changeWeaponConfig",
|
||||||
|
weaponConfig: value as WeaponConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<select onChange={handleOnChange} value={state.weaponConfig}>
|
||||||
|
{Object.values(WeaponConfig).map((config) => (
|
||||||
|
<option key={config} value={config}>
|
||||||
|
{config}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -12,6 +12,7 @@ export const Aldani = [
|
|||||||
slot: "trinket",
|
slot: "trinket",
|
||||||
name: "Incorporeal Warpclaw",
|
name: "Incorporeal Warpclaw",
|
||||||
source: "aldani" as const,
|
source: "aldani" as const,
|
||||||
|
icon: "inv_misc_nightsaberclaw_mana",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 242481 as ItemId,
|
id: 242481 as ItemId,
|
||||||
|
|||||||
16
src/lib/drops/crafted.ts
Normal file
16
src/lib/drops/crafted.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { Item } from "../types";
|
||||||
|
|
||||||
|
export const Crafted: Item[] = [
|
||||||
|
{
|
||||||
|
id: 219334,
|
||||||
|
name: "Rune-Branded Armguards",
|
||||||
|
slot: "wrist",
|
||||||
|
source: "crafted",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 219502,
|
||||||
|
name: "Adrenal Surge Clasp",
|
||||||
|
slot: "waist",
|
||||||
|
source: "crafted",
|
||||||
|
},
|
||||||
|
] as Item[];
|
||||||
@@ -73,4 +73,18 @@ export const Dawnbreaker = [
|
|||||||
name: "Bludgeons of Blistering Wind",
|
name: "Bludgeons of Blistering Wind",
|
||||||
source: "dawnbreaker" as const,
|
source: "dawnbreaker" as const,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 221136,
|
||||||
|
slot: "finger",
|
||||||
|
name: "Devout Zealot's Ring",
|
||||||
|
source: "dawnbreaker" as const,
|
||||||
|
icon: "inv_11_0_nerubian_ring_01_color4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 221141,
|
||||||
|
slot: "finger",
|
||||||
|
name: "High Nerubian Signet",
|
||||||
|
source: "dawnbreaker" as const,
|
||||||
|
icon: "inv_11_0_nerubian_ring_02_color5",
|
||||||
|
},
|
||||||
] as Item[];
|
] as Item[];
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ export const Floodgate = [
|
|||||||
name: "Improvised Seaforium Pacemaker",
|
name: "Improvised Seaforium Pacemaker",
|
||||||
source: "floodgate" as const,
|
source: "floodgate" as const,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 232543 as ItemId,
|
||||||
|
slot: "trinket",
|
||||||
|
name: "Ringing Ritual Mud",
|
||||||
|
source: "floodgate" as const,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 234499 as ItemId,
|
id: 234499 as ItemId,
|
||||||
slot: "wrist",
|
slot: "wrist",
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
export { Aldani } from "./aldani";
|
import type { Item } from "../types";
|
||||||
export { AraKara } from "./ara-kara";
|
import { Aldani } from "./aldani";
|
||||||
export { Dawnbreaker } from "./dawnbreaker";
|
import { AraKara } from "./ara-kara";
|
||||||
export { Floodgate } from "./floodgate";
|
import { Crafted } from "./crafted";
|
||||||
export { HallsOfAtonement } from "./halls-of-atonement";
|
import { Dawnbreaker } from "./dawnbreaker";
|
||||||
export { PrioryOfTheSacredFlame } from "./priory";
|
import { Floodgate } from "./floodgate";
|
||||||
export { Streets, Gambit } from "./tazavesh";
|
import { HallsOfAtonement } from "./halls-of-atonement";
|
||||||
export { ManaforgeOmega } from "./manaforge-omega";
|
import { ManaforgeOmega } from "./manaforge-omega";
|
||||||
|
import { PrioryOfTheSacredFlame } from "./priory";
|
||||||
|
import { Streets, Gambit } from "./tazavesh";
|
||||||
|
|
||||||
|
export const AllItems: Item[] = [
|
||||||
|
...PrioryOfTheSacredFlame,
|
||||||
|
...Aldani,
|
||||||
|
...AraKara,
|
||||||
|
...Dawnbreaker,
|
||||||
|
...Floodgate,
|
||||||
|
...HallsOfAtonement,
|
||||||
|
...Streets,
|
||||||
|
...Gambit,
|
||||||
|
...ManaforgeOmega,
|
||||||
|
...Crafted,
|
||||||
|
];
|
||||||
|
|||||||
@@ -37,4 +37,167 @@ export const ManaforgeOmega = [
|
|||||||
slot: "back",
|
slot: "back",
|
||||||
source: "manaforge omega",
|
source: "manaforge omega",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 242395,
|
||||||
|
name: "Astral Antenna",
|
||||||
|
slot: "trinket",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 243306,
|
||||||
|
name: "Interloper's Reinforced Sandals",
|
||||||
|
slot: "feet",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 242397,
|
||||||
|
name: "Sigil of the Cosmic Hunt",
|
||||||
|
slot: "trinket",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 242394,
|
||||||
|
name: "Eradicating Arcanocore",
|
||||||
|
slot: "trinket",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237562,
|
||||||
|
name: "Time-Compressed Wristguards",
|
||||||
|
slot: "wrist",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237739,
|
||||||
|
name: "Obliteration Beamglaive",
|
||||||
|
slot: "2h-weapon",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 242391,
|
||||||
|
name: "Soulbinder's Embrace",
|
||||||
|
slot: "trinket",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237734,
|
||||||
|
name: "Oath-Breaker's Recompense",
|
||||||
|
slot: "1h-weapon",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237726,
|
||||||
|
name: "Marvel of Technomancy",
|
||||||
|
slot: "2h-weapon",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237533,
|
||||||
|
name: "Atomic Phasebelt",
|
||||||
|
slot: "waist",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237738,
|
||||||
|
name: "Unbound Training Claws",
|
||||||
|
slot: "1h-weapon",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 238027,
|
||||||
|
name: "Harvested Creephide Cord",
|
||||||
|
slot: "waist",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 238031,
|
||||||
|
name: "Veiled Manta Vest",
|
||||||
|
slot: "chest",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237813,
|
||||||
|
name: "Factory-Issue Plexhammer",
|
||||||
|
slot: "1h-weapon",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237565,
|
||||||
|
name: "Kinetic Dunerunners",
|
||||||
|
slot: "feet",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237525,
|
||||||
|
name: "Irradiated Impurity Filter",
|
||||||
|
slot: "head",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237541,
|
||||||
|
name: "Darksorrow's Corrupted Carapace",
|
||||||
|
slot: "chest",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237731,
|
||||||
|
name: "Ergospheric Cudgel",
|
||||||
|
slot: "1h-weapon",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237552,
|
||||||
|
name: "Deathbound Shoulderpads",
|
||||||
|
slot: "shoulder",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237546,
|
||||||
|
name: "Bindings of Lost Essence",
|
||||||
|
slot: "wrist",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237540,
|
||||||
|
name: "Winged Gamma Handlers",
|
||||||
|
slot: "hands",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237557,
|
||||||
|
name: "Reaper's Dreadbelt",
|
||||||
|
slot: "waist",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237553,
|
||||||
|
name: "Laboratory Test Slippers",
|
||||||
|
slot: "feet",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 237531,
|
||||||
|
name: "Elite Shadowguard Legwraps",
|
||||||
|
slot: "legs",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 230026,
|
||||||
|
name: "Scrapfield 9001",
|
||||||
|
slot: "trinket",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 228854,
|
||||||
|
name: "Bilgerat's Discarded Slacks",
|
||||||
|
slot: "legs",
|
||||||
|
source: "manaforge omega",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 242401,
|
||||||
|
name: "Brand of Ceaseless Ire",
|
||||||
|
slot: "trinket",
|
||||||
|
source: "manaforge omega",
|
||||||
|
icon: "inv_112_raidtrinkets_manaforgetanktrinket3",
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {
|
import {
|
||||||
UpgradeType,
|
UpgradeType,
|
||||||
|
WeaponConfig,
|
||||||
type EquipedItem,
|
type EquipedItem,
|
||||||
type Item,
|
type Item,
|
||||||
type ItemId,
|
type ItemId,
|
||||||
@@ -8,29 +9,7 @@ import {
|
|||||||
type Slot,
|
type Slot,
|
||||||
type Upgrade,
|
type Upgrade,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import {
|
import { AllItems } from "./drops";
|
||||||
Aldani,
|
|
||||||
AraKara,
|
|
||||||
Dawnbreaker,
|
|
||||||
Floodgate,
|
|
||||||
HallsOfAtonement,
|
|
||||||
PrioryOfTheSacredFlame,
|
|
||||||
Streets,
|
|
||||||
Gambit,
|
|
||||||
ManaforgeOmega,
|
|
||||||
} from "./drops";
|
|
||||||
|
|
||||||
export const AllItems: Item[] = [
|
|
||||||
...PrioryOfTheSacredFlame,
|
|
||||||
...Aldani,
|
|
||||||
...AraKara,
|
|
||||||
...Dawnbreaker,
|
|
||||||
...Floodgate,
|
|
||||||
...HallsOfAtonement,
|
|
||||||
...Streets,
|
|
||||||
...Gambit,
|
|
||||||
...ManaforgeOmega,
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ItemsById: Record<string, Item> = AllItems.reduce(
|
export const ItemsById: Record<string, Item> = AllItems.reduce(
|
||||||
(db, item) => {
|
(db, item) => {
|
||||||
@@ -87,7 +66,10 @@ export const upgradesForItem = ({ quality }: { quality: Quality }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUpgrades = (equipedItemIds: EquipedItem[]): Upgrade[] => {
|
export const getUpgrades = (
|
||||||
|
equipedItemIds: EquipedItem[],
|
||||||
|
weaponConfig: WeaponConfig,
|
||||||
|
): Upgrade[] => {
|
||||||
return Object.entries(ItemsBySlot).flatMap(([slot, items]) => {
|
return Object.entries(ItemsBySlot).flatMap(([slot, items]) => {
|
||||||
const equipedItems = equipedItemIds.flatMap(({ id, quality }) => ({
|
const equipedItems = equipedItemIds.flatMap(({ id, quality }) => ({
|
||||||
item: ItemsById[id],
|
item: ItemsById[id],
|
||||||
@@ -96,7 +78,16 @@ export const getUpgrades = (equipedItemIds: EquipedItem[]): Upgrade[] => {
|
|||||||
const equipedInSlot = equipedItems.filter(
|
const equipedInSlot = equipedItems.filter(
|
||||||
(item) => item.item.slot === slot,
|
(item) => item.item.slot === slot,
|
||||||
);
|
);
|
||||||
const itemsForSlot = itemsPerSlot(slot as Slot);
|
|
||||||
|
const itemsForSlot = itemsPerSlot(slot as Slot, weaponConfig);
|
||||||
|
|
||||||
|
if (itemsForSlot === 0) {
|
||||||
|
console.log(
|
||||||
|
`Skipping ${slot} because it has no items with ${weaponConfig}`,
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const numChampion = equipedInSlot.filter(qualityAtLeast("champion")).length;
|
const numChampion = equipedInSlot.filter(qualityAtLeast("champion")).length;
|
||||||
const numHero = equipedInSlot.filter(qualityAtLeast("hero")).length;
|
const numHero = equipedInSlot.filter(qualityAtLeast("hero")).length;
|
||||||
const numMyth = equipedInSlot.filter(qualityAtLeast("myth")).length;
|
const numMyth = equipedInSlot.filter(qualityAtLeast("myth")).length;
|
||||||
@@ -105,7 +96,6 @@ export const getUpgrades = (equipedItemIds: EquipedItem[]): Upgrade[] => {
|
|||||||
numHero >= itemsForSlot ? [] : [UpgradeType.Hero],
|
numHero >= itemsForSlot ? [] : [UpgradeType.Hero],
|
||||||
numMyth >= itemsForSlot ? [] : [UpgradeType.Myth],
|
numMyth >= itemsForSlot ? [] : [UpgradeType.Myth],
|
||||||
].flat();
|
].flat();
|
||||||
console.log(slot, numChampion, numHero, numMyth, upgradeTypes);
|
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
const equiped = equipedInSlot.find((i) => i.item.id === item.id);
|
const equiped = equipedInSlot.find((i) => i.item.id === item.id);
|
||||||
if (equiped) {
|
if (equiped) {
|
||||||
@@ -128,12 +118,27 @@ export const hasUpgradeType =
|
|||||||
(upgrade: Upgrade): boolean =>
|
(upgrade: Upgrade): boolean =>
|
||||||
upgrade.upgradeTypes.includes(type);
|
upgrade.upgradeTypes.includes(type);
|
||||||
|
|
||||||
export const itemsPerSlot = (slot: Slot): number => {
|
export const itemsPerSlot = (
|
||||||
|
slot: Slot,
|
||||||
|
weaponConfig: WeaponConfig,
|
||||||
|
): number => {
|
||||||
switch (slot) {
|
switch (slot) {
|
||||||
case "trinket":
|
case "trinket":
|
||||||
case "finger":
|
case "finger":
|
||||||
case "1h-weapon":
|
|
||||||
return 2;
|
return 2;
|
||||||
|
case "1h-weapon":
|
||||||
|
switch (weaponConfig) {
|
||||||
|
case WeaponConfig.DualWield:
|
||||||
|
return 2;
|
||||||
|
case WeaponConfig.TwoHander:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case "off-hand":
|
||||||
|
return weaponConfig === WeaponConfig.WeaponAndOffHand ? 1 : 0;
|
||||||
|
case "2h-weapon":
|
||||||
|
return weaponConfig === WeaponConfig.TwoHander ? 1 : 0;
|
||||||
default:
|
default:
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import type { EquipedItem, Item, ItemId, Quality } from "./types";
|
import type { EquipedItem, Item, ItemId, Quality } from "./types";
|
||||||
|
import { WeaponConfig } from "./types";
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
equipedItems: EquipedItem[];
|
equipedItems: EquipedItem[];
|
||||||
bisList: ItemId[];
|
bisList: ItemId[];
|
||||||
|
weaponConfig: WeaponConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyState: State = {
|
export const emptyState: State = {
|
||||||
equipedItems: [],
|
equipedItems: [],
|
||||||
bisList: [],
|
bisList: [],
|
||||||
|
weaponConfig: WeaponConfig.TwoHander, // Default weapon config
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
@@ -23,6 +26,10 @@ export type Action =
|
|||||||
action: "changeQuality";
|
action: "changeQuality";
|
||||||
itemId: ItemId;
|
itemId: ItemId;
|
||||||
quality: Quality;
|
quality: Quality;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
action: "changeWeaponConfig";
|
||||||
|
weaponConfig: WeaponConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reducer = (state: State, action: Action): State => {
|
export const reducer = (state: State, action: Action): State => {
|
||||||
@@ -58,6 +65,11 @@ export const reducer = (state: State, action: Action): State => {
|
|||||||
return item;
|
return item;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
case "changeWeaponConfig":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
weaponConfig: action.weaponConfig,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const Slots = [
|
|||||||
"trinket",
|
"trinket",
|
||||||
"1h-weapon",
|
"1h-weapon",
|
||||||
"2h-weapon",
|
"2h-weapon",
|
||||||
"shield",
|
"off-hand",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type Slot = (typeof Slots)[number];
|
export type Slot = (typeof Slots)[number];
|
||||||
@@ -39,13 +39,15 @@ export type Source =
|
|||||||
| "halls-of-atonement"
|
| "halls-of-atonement"
|
||||||
| "priory"
|
| "priory"
|
||||||
| "streets"
|
| "streets"
|
||||||
| "manaforge omega";
|
| "manaforge omega"
|
||||||
|
| "crafted";
|
||||||
|
|
||||||
export type Item = {
|
export type Item = {
|
||||||
id: ItemId;
|
id: ItemId;
|
||||||
slot: Slot;
|
slot: Slot;
|
||||||
name: string;
|
name: string;
|
||||||
source: Source;
|
source: Source;
|
||||||
|
icon?: string; // Wowhead icon name (not ID)
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EquipedItem = {
|
export type EquipedItem = {
|
||||||
@@ -64,3 +66,11 @@ export const UpgradeType = {
|
|||||||
export type UpgradeType = (typeof UpgradeType)[keyof typeof UpgradeType];
|
export type UpgradeType = (typeof UpgradeType)[keyof typeof UpgradeType];
|
||||||
|
|
||||||
export type Upgrade = { item: Item; upgradeTypes: UpgradeType[] };
|
export type Upgrade = { item: Item; upgradeTypes: UpgradeType[] };
|
||||||
|
|
||||||
|
export const WeaponConfig = {
|
||||||
|
WeaponAndOffHand: "weapon and off-hand",
|
||||||
|
DualWield: "dual wield",
|
||||||
|
TwoHander: "two-hander",
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WeaponConfig = (typeof WeaponConfig)[keyof typeof WeaponConfig];
|
||||||
|
|||||||
Reference in New Issue
Block a user