piclets / src /lib /components /Piclets /AbilityDisplay.svelte
Fraser's picture
rm legacy
1f2c086
<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);
// Handle target (most effects have target, but not all)
if ('target' in effect && effect.target !== 'self') {
desc += ` (${effect.target})`;
}
// Handle condition (common to many effects)
if ('condition' in effect && effect.condition) {
desc += ` when ${effect.condition}`;
}
// Handle type-specific properties
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>