192 lines
4.7 KiB
TypeScript
192 lines
4.7 KiB
TypeScript
import type { CombatLogEntry } from '../game'
|
|
import type { RunBossCoinAward } from '../combat/rewardSummaries'
|
|
import type { DungeonReward, LootRoll } from '../profile'
|
|
|
|
type BonusItem = NonNullable<DungeonReward['bonusItem']>
|
|
|
|
export function LevelGain({
|
|
previousLevel,
|
|
newLevel,
|
|
talentPointsGained,
|
|
}: {
|
|
previousLevel: number | null
|
|
newLevel: number | null
|
|
talentPointsGained: number
|
|
}) {
|
|
if (!previousLevel || !newLevel || talentPointsGained <= 0) return null
|
|
return (
|
|
<p className="level-gain">
|
|
Level {previousLevel} to {newLevel}
|
|
<small>+{talentPointsGained} talent point</small>
|
|
</p>
|
|
)
|
|
}
|
|
|
|
export function AbilityUnlocks({
|
|
abilities,
|
|
}: {
|
|
abilities: DungeonReward['unlockedAbilities']
|
|
}) {
|
|
return (
|
|
<>
|
|
{abilities.map((ability) => (
|
|
<p className="ability-unlock" key={ability.id}>
|
|
<span>{ability.glyph}</span>
|
|
Ability Unlocked: {ability.name}
|
|
</p>
|
|
))}
|
|
</>
|
|
)
|
|
}
|
|
|
|
export function RewardXpSummary({
|
|
reward,
|
|
}: {
|
|
reward: {
|
|
experienceGained: number
|
|
previousLevel: number | null
|
|
newLevel: number | null
|
|
talentPointsGained: number
|
|
unlockedAbilities: DungeonReward['unlockedAbilities']
|
|
}
|
|
}) {
|
|
return (
|
|
<>
|
|
<p>+{reward.experienceGained} XP</p>
|
|
<LevelGain previousLevel={reward.previousLevel} newLevel={reward.newLevel} talentPointsGained={reward.talentPointsGained} />
|
|
<AbilityUnlocks abilities={reward.unlockedAbilities} />
|
|
</>
|
|
)
|
|
}
|
|
|
|
export function BonusItemReward({
|
|
item,
|
|
eyebrow,
|
|
compact = false,
|
|
}: {
|
|
item?: BonusItem | null
|
|
eyebrow?: string
|
|
compact?: boolean
|
|
}) {
|
|
if (!item) return null
|
|
if (compact) {
|
|
return (
|
|
<p className="ability-unlock">
|
|
<span>{item.glyph}</span>
|
|
{item.name} x{item.quantity}
|
|
{item.duplicate ? ` (owned x${item.quantityAfter})` : ''}
|
|
</p>
|
|
)
|
|
}
|
|
return (
|
|
<div className="bonus-item">
|
|
{eyebrow && <p className="eyebrow">{eyebrow}</p>}
|
|
<div className="bonus-item-detail">
|
|
<span>{item.glyph}</span>
|
|
<strong className={`rarity-${item.rarity}`}>{item.name}</strong>
|
|
<small>Item Level {item.itemLevel} x{item.quantity}</small>
|
|
{item.duplicate && <small> (owned x{item.quantityAfter})</small>}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function LootRollList({
|
|
rolls,
|
|
expectedRolls,
|
|
}: {
|
|
rolls: LootRoll[]
|
|
expectedRolls: number
|
|
}) {
|
|
return (
|
|
<div className="run-loot-rolls">
|
|
{rolls.map((roll) => (
|
|
<div className={roll.dropped || roll.petAwarded ? 'dropped' : 'empty'} key={roll.encounterId}>
|
|
<strong>{roll.encounterName}</strong>
|
|
<span>
|
|
{[
|
|
...roll.items.map((item) => `${item.glyph} ${item.name} x${item.quantity}${item.duplicate ? ` (owned x${item.quantityAfter})` : ''}`),
|
|
...(roll.petAwarded
|
|
? [`* ${roll.petAwarded.petName}${roll.petAwarded.duplicate ? ` (owned x${roll.petAwarded.quantityAfter})` : ''}`]
|
|
: []),
|
|
].join(', ') || 'No components dropped'}
|
|
</span>
|
|
</div>
|
|
))}
|
|
{rolls.length < expectedRolls && <small>Finishing loot rolls...</small>}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function PvpRunLootList({
|
|
loot,
|
|
emptyLabel = 'No boss coins awarded',
|
|
}: {
|
|
loot: RunBossCoinAward[]
|
|
emptyLabel?: string
|
|
}) {
|
|
return (
|
|
<div className="run-loot-rolls">
|
|
{loot.length > 0 ? loot.map((item, index) => (
|
|
<div className="dropped" key={`${item.id}-${index}`}>
|
|
<strong>{item.sourceLabel}</strong>
|
|
<span>
|
|
{item.glyph} {item.name} x{item.quantity}
|
|
{item.duplicate ? ` (owned x${item.quantityAfter})` : ''}
|
|
</span>
|
|
</div>
|
|
)) : (
|
|
<div>
|
|
<strong>Boss Coins</strong>
|
|
<span>{emptyLabel}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function ResultLogToggle({
|
|
log,
|
|
showEndLog,
|
|
onToggle,
|
|
}: {
|
|
log: CombatLogEntry[]
|
|
showEndLog: boolean
|
|
onToggle: () => void
|
|
}) {
|
|
if (log.length === 0) return null
|
|
return (
|
|
<>
|
|
<button className="secondary-result-button" onClick={onToggle} type="button">
|
|
{showEndLog ? 'Hide Combat Log' : 'View Combat Log'}
|
|
</button>
|
|
{showEndLog && (
|
|
<div className="result-log">
|
|
{log.slice().reverse().map((entry) => (
|
|
<div className={`log-entry ${entry.tone}`} key={entry.id}>{entry.text}</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
export function RematchControls({
|
|
requested,
|
|
message,
|
|
onRematch,
|
|
}: {
|
|
requested: boolean
|
|
message: string
|
|
onRematch: () => void
|
|
}) {
|
|
return (
|
|
<>
|
|
<button disabled={requested} onClick={onRematch} type="button">
|
|
{requested ? 'Waiting for Rematch' : 'Rematch'}
|
|
</button>
|
|
{message && <p>{message}</p>}
|
|
</>
|
|
)
|
|
}
|