|
<script lang="ts"> |
|
import type { SpecialAbility, BattleEffect, Trigger } from '$lib/battle-engine/types'; |
|
|
|
interface Props { |
|
ability: SpecialAbility; |
|
expanded?: boolean; |
|
} |
|
|
|
let { ability, expanded = false }: Props = $props(); |
|
|
|
function getEffectIcon(effectType: string): string { |
|
switch (effectType) { |
|
case 'damage': return 'βοΈ'; |
|
case 'modifyStats': return 'π'; |
|
case 'applyStatus': return 'π«'; |
|
case 'heal': return 'π'; |
|
case 'manipulatePP': return 'β‘'; |
|
case 'fieldEffect': return 'π'; |
|
case 'counter': return 'π'; |
|
case 'priority': return 'β‘'; |
|
case 'removeStatus': return 'β¨'; |
|
case 'mechanicOverride': return 'βοΈ'; |
|
default: return 'β'; |
|
} |
|
} |
|
|
|
function getEffectColor(effectType: string): string { |
|
switch (effectType) { |
|
case 'damage': return '#ff6b6b'; |
|
case 'modifyStats': return '#4dabf7'; |
|
case 'applyStatus': return '#9775fa'; |
|
case 'heal': return '#51cf66'; |
|
case 'manipulatePP': return '#ffd43b'; |
|
case 'fieldEffect': return '#495057'; |
|
case 'counter': return '#fd7e14'; |
|
case 'priority': return '#20c997'; |
|
case 'removeStatus': return '#74c0fc'; |
|
case 'mechanicOverride': return '#868e96'; |
|
default: return '#adb5bd'; |
|
} |
|
} |
|
|
|
function getTriggerIcon(event: string): string { |
|
switch (event) { |
|
case 'onDamageTaken': return 'π‘οΈ'; |
|
case 'onDamageDealt': return 'βοΈ'; |
|
case 'onContactDamage': return 'π'; |
|
case 'onCriticalHit': return 'π₯'; |
|
case 'endOfTurn': return 'π'; |
|
case 'onLowHP': return 'β€οΈ'; |
|
case 'onStatusInflicted': return 'π«'; |
|
case 'onHPDrained': return 'π©Έ'; |
|
case 'onKO': return 'π'; |
|
case 'onSwitchIn': return 'β‘οΈ'; |
|
case 'onSwitchOut': return 'β¬
οΈ'; |
|
case 'beforeMoveUse': return 'β°'; |
|
case 'afterMoveUse': return 'β
'; |
|
case 'onFullHP': return 'π'; |
|
case 'onOpponentContactMove': return 'π€'; |
|
case 'onStatChange': return 'π'; |
|
case 'onTypeChange': return 'π'; |
|
default: return 'β‘'; |
|
} |
|
} |
|
|
|
function formatEffectDescription(effect: BattleEffect): string { |
|
let desc = effect.type.charAt(0).toUpperCase() + effect.type.slice(1); |
|
|
|
|
|
if ('target' in effect && effect.target !== 'self') { |
|
desc += ` (${effect.target})`; |
|
} |
|
|
|
|
|
if ('condition' in effect && effect.condition) { |
|
desc += ` when ${effect.condition}`; |
|
} |
|
|
|
|
|
switch (effect.type) { |
|
case 'damage': |
|
if (effect.amount) desc += ` - ${effect.amount}`; |
|
if (effect.formula) desc += ` (${effect.formula})`; |
|
break; |
|
case 'modifyStats': |
|
const statChanges = Object.entries(effect.stats).map(([stat, change]) => |
|
`${stat}: ${change}` |
|
).join(', '); |
|
desc += ` (${statChanges})`; |
|
break; |
|
case 'applyStatus': |
|
desc += ` - ${effect.status}`; |
|
if (effect.chance && effect.chance < 100) desc += ` (${effect.chance}%)`; |
|
break; |
|
case 'heal': |
|
if (effect.amount) desc += ` - ${effect.amount}`; |
|
break; |
|
case 'manipulatePP': |
|
desc += ` - ${effect.action}`; |
|
if (effect.amount) desc += ` ${effect.amount}`; |
|
break; |
|
case 'fieldEffect': |
|
desc += ` - ${effect.effect}`; |
|
if (effect.stackable) desc += ` (stackable)`; |
|
break; |
|
case 'counter': |
|
desc += `(${effect.strength})`; |
|
break; |
|
case 'removeStatus': |
|
desc += ` - ${effect.status}`; |
|
break; |
|
case 'mechanicOverride': |
|
desc += ` - ${effect.mechanic}`; |
|
break; |
|
} |
|
|
|
return desc; |
|
} |
|
</script> |
|
|
|
<div class="ability-display"> |
|
<div class="ability-header"> |
|
<div class="ability-name-section"> |
|
<span class="ability-icon">β¨</span> |
|
<div class="ability-info"> |
|
<h3 class="ability-name">{ability.name}</h3> |
|
<p class="ability-description">{ability.description}</p> |
|
</div> |
|
</div> |
|
|
|
</div> |
|
|
|
{#if expanded && ability.triggers?.length} |
|
{@const trigger = ability.triggers[0]} |
|
<div class="ability-details"> |
|
<div class="triggers-section"> |
|
<h4 class="section-title"> |
|
<span class="section-icon">β‘</span> |
|
Trigger |
|
</h4> |
|
<div class="trigger-item"> |
|
<div class="trigger-header"> |
|
<span class="trigger-icon">{getTriggerIcon(trigger.event)}</span> |
|
<div class="trigger-info"> |
|
<span class="trigger-event">{trigger.event}</span> |
|
{#if trigger.condition} |
|
<span class="trigger-condition">when {trigger.condition}</span> |
|
{/if} |
|
</div> |
|
</div> |
|
|
|
{#if trigger.effects?.length} |
|
<div class="trigger-effects"> |
|
{#each trigger.effects as effect} |
|
<div class="trigger-effect"> |
|
<span |
|
class="effect-icon small" |
|
style="color: {getEffectColor(effect.type)}" |
|
> |
|
{getEffectIcon(effect.type)} |
|
</span> |
|
<span class="effect-summary">{formatEffectDescription(effect)}</span> |
|
</div> |
|
{/each} |
|
</div> |
|
{/if} |
|
</div> |
|
</div> |
|
</div> |
|
{/if} |
|
</div> |
|
|
|
<style> |
|
.ability-display { |
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
|
border: 1px solid #dee2e6; |
|
border-radius: 12px; |
|
padding: 16px; |
|
margin: 8px 0; |
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|
} |
|
|
|
.ability-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: flex-start; |
|
gap: 12px; |
|
} |
|
|
|
.ability-name-section { |
|
display: flex; |
|
align-items: flex-start; |
|
gap: 12px; |
|
flex: 1; |
|
} |
|
|
|
.ability-icon { |
|
font-size: 24px; |
|
margin-top: 2px; |
|
} |
|
|
|
.ability-info { |
|
flex: 1; |
|
} |
|
|
|
.ability-name { |
|
font-size: 18px; |
|
font-weight: 600; |
|
color: #495057; |
|
margin: 0 0 4px 0; |
|
} |
|
|
|
.ability-description { |
|
font-size: 14px; |
|
color: #6c757d; |
|
margin: 0; |
|
line-height: 1.4; |
|
} |
|
|
|
|
|
.ability-details { |
|
margin-top: 16px; |
|
padding-top: 16px; |
|
border-top: 1px solid #e9ecef; |
|
} |
|
|
|
.section-title { |
|
display: flex; |
|
align-items: center; |
|
gap: 8px; |
|
font-size: 14px; |
|
font-weight: 600; |
|
color: #495057; |
|
margin: 0 0 12px 0; |
|
} |
|
|
|
.section-icon { |
|
font-size: 16px; |
|
} |
|
|
|
|
|
|
|
.trigger-item { |
|
background: rgba(255, 255, 255, 0.7); |
|
border: 1px solid rgba(0, 0, 0, 0.08); |
|
border-radius: 8px; |
|
padding: 12px; |
|
} |
|
|
|
.trigger-header { |
|
display: flex; |
|
align-items: flex-start; |
|
gap: 10px; |
|
margin-bottom: 8px; |
|
} |
|
|
|
.trigger-icon { |
|
font-size: 16px; |
|
margin-top: 1px; |
|
} |
|
|
|
.trigger-info { |
|
flex: 1; |
|
display: flex; |
|
flex-direction: column; |
|
gap: 2px; |
|
} |
|
|
|
.trigger-event { |
|
font-size: 13px; |
|
font-weight: 600; |
|
color: #495057; |
|
} |
|
|
|
.trigger-condition { |
|
font-size: 12px; |
|
color: #868e96; |
|
font-style: italic; |
|
} |
|
|
|
.trigger-effects { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 6px; |
|
padding-left: 26px; |
|
} |
|
|
|
.trigger-effect { |
|
display: flex; |
|
align-items: center; |
|
gap: 8px; |
|
} |
|
|
|
.effect-summary { |
|
font-size: 12px; |
|
color: #6c757d; |
|
line-height: 1.3; |
|
} |
|
</style> |