132 lines
3.1 KiB
TypeScript
132 lines
3.1 KiB
TypeScript
import { memo } from 'react'
|
|
import type { ControllerIconStyle } from '../input'
|
|
import type { Spell } from '../game'
|
|
import { ControllerBindingLabel } from './ControllerIcons'
|
|
|
|
export type SpellSlot = (Spell & {
|
|
cost: number
|
|
remaining: number
|
|
slotIndex: number
|
|
}) | null
|
|
|
|
export const ResourceBar = memo(function ResourceBar({
|
|
resource,
|
|
maxResource,
|
|
resourceName,
|
|
speedMultiplier,
|
|
unavailableText,
|
|
}: {
|
|
resource: number
|
|
maxResource: number
|
|
resourceName: string
|
|
speedMultiplier?: 1 | 2
|
|
unavailableText?: string
|
|
}) {
|
|
return (
|
|
<div className="mana-wrap party-mana-wrap">
|
|
<span>
|
|
{unavailableText ?? `${resourceName} ${Math.floor(resource)} / ${maxResource}`}
|
|
</span>
|
|
{speedMultiplier === 2 && <strong className="speed-badge">2x speed</strong>}
|
|
<div className="bar mana-bar"><span style={{ width: `${(resource / maxResource) * 100}%` }} /></div>
|
|
</div>
|
|
)
|
|
})
|
|
|
|
export const SpellButton = memo(function SpellButton({
|
|
spell,
|
|
binding,
|
|
iconStyle,
|
|
resourceName,
|
|
disabled,
|
|
onCast,
|
|
emptyKeyPrefix = 'empty',
|
|
}: {
|
|
spell: SpellSlot
|
|
binding?: string
|
|
iconStyle: ControllerIconStyle
|
|
resourceName: string
|
|
disabled?: boolean
|
|
onCast: (spell: Spell) => void
|
|
emptyKeyPrefix?: string
|
|
}) {
|
|
if (!spell) {
|
|
return (
|
|
<div className="spell empty-spell" key={emptyKeyPrefix}>
|
|
<kbd>{emptyKeyPrefix}</kbd><strong>Empty</strong>
|
|
</div>
|
|
)
|
|
}
|
|
return (
|
|
<button
|
|
className="spell"
|
|
disabled={disabled}
|
|
key={spell.id}
|
|
onClick={() => onCast(spell)}
|
|
title={spell.description}
|
|
type="button"
|
|
>
|
|
<kbd>
|
|
{binding
|
|
? (
|
|
<ControllerBindingLabel
|
|
binding={binding}
|
|
compact
|
|
iconStyle={iconStyle}
|
|
/>
|
|
)
|
|
: spell.slotIndex + 1}
|
|
</kbd>
|
|
<span className={`spell-icon spell-${spell.kind}`}>{spell.glyph}</span>
|
|
<strong>{spell.name}</strong>
|
|
<small>{spell.cost} {resourceName}</small>
|
|
{spell.remaining > 0 && <i>{spell.remaining.toFixed(1)}</i>}
|
|
</button>
|
|
)
|
|
})
|
|
|
|
export const SpellBar = memo(function SpellBar({
|
|
spells,
|
|
bindings,
|
|
iconStyle,
|
|
resource,
|
|
resourceName,
|
|
canCast,
|
|
onCast,
|
|
className = 'spell-bar six-slots vertical-spell-bar',
|
|
}: {
|
|
spells: SpellSlot[]
|
|
bindings: Record<string, string>
|
|
iconStyle: ControllerIconStyle
|
|
resource: number
|
|
resourceName: string
|
|
canCast: boolean
|
|
onCast: (spell: Spell) => void
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<div className={className}>
|
|
{spells.map((spell, slotIndex) => {
|
|
if (!spell) {
|
|
return (
|
|
<div className="spell empty-spell" key={`empty-${slotIndex}`}>
|
|
<kbd>{slotIndex + 1}</kbd><strong>Empty</strong>
|
|
</div>
|
|
)
|
|
}
|
|
return (
|
|
<SpellButton
|
|
binding={bindings[`ability${slotIndex + 1}`]}
|
|
disabled={!canCast || resource < spell.cost || spell.remaining > 0}
|
|
iconStyle={iconStyle}
|
|
key={spell.id}
|
|
onCast={onCast}
|
|
resourceName={resourceName}
|
|
spell={spell}
|
|
/>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
})
|