Files
healer-man/src/ui/BossLootWindow.tsx
T

122 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, type CSSProperties } from "react";
import { BOSS_CARD_RARITY_INFO } from "../game/bossCards";
import { useCombatStore } from "../game/combatStore";
import { useGameStore } from "../game/store";
import { classCanEquipItem, PLAYER_EQUIPMENT_OWNER_ID } from "../game/equipment";
import {
compareEquipmentUpgrade,
resolvedEquipmentLoadout,
} from "../game/heirloomEquipment";
import { gearProfileFor } from "../game/specializationCatalog";
export const BOSS_LOOT_AUTO_DISMISS_MS = 10_000;
export function scheduleBossLootAutoDismiss(
rewardId: number,
dismiss: (id: number) => void,
): () => void {
const timeoutId = setTimeout(() => dismiss(rewardId), BOSS_LOOT_AUTO_DISMISS_MS);
return () => clearTimeout(timeoutId);
}
function itemType(inventorySlot: string): string {
return inventorySlot
.split(/[-_]/)
.filter(Boolean)
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
.join(" ");
}
export function BossLootWindow() {
const reward = useCombatStore((state) => state.bossLootRewards[0] ?? null);
const classId = useCombatStore((state) => state.classId);
const secondaryClassId = useCombatStore((state) => state.secondaryClassId);
const specializationId = useCombatStore((state) => state.specializationId);
const role = useCombatStore((state) => state.gearRole);
const level = useCombatStore((state) => state.level);
const equipment = useCombatStore((state) => state.equipment);
const inventory = useCombatStore((state) => state.inventory);
const visibleRewardId = reward?.id;
useEffect(() => {
if (visibleRewardId === undefined) return;
return scheduleBossLootAutoDismiss(
visibleRewardId,
(rewardId) => useCombatStore.getState().dismissBossLootReward(rewardId),
);
}, [visibleRewardId]);
if (!reward) return null;
const profile = gearProfileFor(classId, specializationId, role);
const heirloomContext = {
ownerId: PLAYER_EQUIPMENT_OWNER_ID,
ownerKind: "player" as const,
classId,
secondaryClassId,
specializationId,
role,
level,
};
const loadout = resolvedEquipmentLoadout(equipment, inventory, heirloomContext);
const dismiss = () => useCombatStore.getState().dismissBossLootReward(reward.id);
const viewInventory = () => {
dismiss();
useGameStore.getState().openOverlay("inventory");
};
return (
<section
className="boss-loot-window"
role="dialog"
aria-modal="false"
aria-labelledby={`boss-loot-title-${reward.id}`}
onPointerDown={(event) => event.stopPropagation()}
>
<header>
<span aria-hidden="true">◆</span>
<p>
<small>Boss defeated</small>
<strong id={`boss-loot-title-${reward.id}`}>{reward.bossName}</strong>
</p>
<button type="button" onClick={dismiss} aria-label="Close loot window">×</button>
</header>
<div className="boss-loot-window__body">
<p className="boss-loot-window__eyebrow">Loot acquired automatically</p>
<div className="boss-loot-window__items">
{reward.cards?.rarities.map((rarity) => <article key={rarity} className="boss-loot-window__item" style={{ "--item-quality": BOSS_CARD_RARITY_INFO[rarity].color } as CSSProperties}>
<b aria-hidden="true">◆</b><p><strong>{BOSS_CARD_RARITY_INFO[rarity].label} Boss Card</strong><small>{reward.bossName} · Collection log</small></p><em>+1</em>
</article>)}
{reward.items.map((item) => {
const comparison = classCanEquipItem(classId, item, { secondaryClassId, level })
? compareEquipmentUpgrade(item, loadout, profile, heirloomContext)
: null;
return <article
key={item.instanceId}
className={`boss-loot-window__item equipment-quality--${item.quality}`}
>
<b aria-hidden="true">{item.name.slice(0, 2).toUpperCase()}</b>
<p>
<strong>{item.name}</strong>
<small>{item.quality} · Item level {item.itemLevel} · {itemType(item.inventorySlot)}</small>
{comparison && <small>{comparison.isUpgrade ? `Upgrade · +${comparison.scoreDelta.toFixed(1)} score` : `${comparison.scoreDelta.toFixed(1)} score`}</small>}
</p>
{item.quantity > 1 ? <em>×{item.quantity}</em> : null}
</article>;
})}
{!reward.items.length && !reward.cards?.rarities.length ? (
<p className="boss-loot-window__empty">No items dropped this time.</p>
) : null}
</div>
</div>
<footer>
<span>{reward.cards?.rarities.length ? `${reward.cards.rarities.length} card${reward.cards.rarities.length === 1 ? "" : "s"} added to collection${reward.items.length ? ` · ${reward.items.length} items acquired` : ""}` : `${reward.items.length} items added to shared inventory`}</span>
{reward.cards ? <button type="button" onClick={() => { dismiss(); useGameStore.getState().openOverlay("boss-cards"); }}>View collection</button> : <button type="button" onClick={viewInventory}>View inventory</button>}
</footer>
</section>
);
}