piclets / src /lib /components /Piclets /MoveDisplay.svelte
Fraser's picture
show act type
ea83c78
<script lang="ts">
import type { BattleMove as DBBattleMove } from '$lib/db/schema';
import type { Move, BattleEffect } from '$lib/battle-engine/types';
interface Props {
move: DBBattleMove;
enhancedMove?: { move: Move; currentPp: number };
expanded?: boolean;
showPP?: boolean;
}
let { move, enhancedMove, expanded = false, showPP = true }: Props = $props();
// Use enhanced move data if available, otherwise fall back to basic move
const battleMove = $derived(enhancedMove?.move);
const currentPp = $derived(enhancedMove?.currentPp ?? move.currentPp);
// Helper function to get type logo path
function getTypeLogo(type: string): string {
return `/classes/${type}.png`;
}
function getTypeColor(type: string): string {
switch (type) {
case 'normal': return '#a8a878';
case 'beast': return '#c03028';
case 'bug': return '#a8b820';
case 'aquatic': return '#6890f0';
case 'flora': return '#78c850';
case 'mineral': return '#b8a038';
case 'space': return '#705898';
case 'machina': return '#b8b8d0';
case 'structure': return '#b8a058';
case 'culture': return '#f85888';
case 'cuisine': return '#f08030';
default: return '#68a090';
}
}
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 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 'counter':
desc += ` (${effect.strength})`;
break;
case 'removeStatus':
desc += ` - ${effect.status}`;
break;
case 'mechanicOverride':
desc += ` - ${effect.mechanic}`;
break;
}
return desc;
}
const isOutOfPP = $derived(currentPp <= 0);
const typeColor = $derived(getTypeColor(move.type));
</script>
<div class="move-display" class:disabled={isOutOfPP}>
<div class="move-header">
<div class="move-title-section">
<img
src={getTypeLogo(move.type)}
alt={move.type}
class="type-logo"
/>
<div class="move-info">
<div class="move-name-row">
<span class="move-name">{move.name}</span>
{#if battleMove?.power && battleMove.power > 0}
<span class="power-badge">⚑{battleMove.power}</span>
{:else if move.power > 0}
<span class="power-badge">⚑{move.power}</span>
{/if}
</div>
<div class="move-type-badge" style="background-color: {typeColor}">
{move.type.toUpperCase()}
</div>
</div>
</div>
<div class="move-stats">
{#if showPP}
<div class="pp-display" class:out-of-pp={isOutOfPP}>
<span class="pp-label">PP</span>
<span class="pp-value">{currentPp}/{move.pp}</span>
</div>
{/if}
{#if battleMove?.accuracy}
<div class="accuracy-display">
<span class="accuracy-label">ACC</span>
<span class="accuracy-value">{battleMove.accuracy}%</span>
</div>
{:else if move.accuracy}
<div class="accuracy-display">
<span class="accuracy-label">ACC</span>
<span class="accuracy-value">{move.accuracy}%</span>
</div>
{/if}
</div>
</div>
<div class="move-description">
{move.description}
</div>
{#if battleMove?.flags && battleMove.flags.length > 0}
<div class="move-flags">
{#each battleMove.flags as flag}
<span class="flag-badge">{flag}</span>
{/each}
</div>
{/if}
{#if expanded && battleMove?.effects && battleMove.effects.length > 0}
<div class="move-effects">
<h4 class="effects-title">
<span class="effects-icon">🎯</span>
Move Effects ({battleMove.effects.length})
</h4>
<div class="effects-list">
{#each battleMove.effects as effect}
<div class="effect-item">
<span
class="effect-icon"
style="color: {getEffectColor(effect.type)}"
>
{getEffectIcon(effect.type)}
</span>
<div class="effect-details">
<span class="effect-type">{effect.type}</span>
<span class="effect-description">{formatEffectDescription(effect)}</span>
</div>
</div>
{/each}
</div>
</div>
{/if}
{#if battleMove?.priority && battleMove.priority !== 0}
<div class="priority-indicator">
<span class="priority-icon">
{battleMove.priority > 0 ? '⏫' : '⏬'}
</span>
<span class="priority-text">
Priority: {battleMove.priority > 0 ? '+' : ''}{battleMove.priority}
</span>
</div>
{/if}
</div>
<style>
.move-display {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border: 1px solid #dee2e6;
border-radius: 12px;
padding: 14px;
margin: 8px 0;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.move-display.disabled {
opacity: 0.6;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
.move-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
margin-bottom: 8px;
}
.move-title-section {
display: flex;
align-items: flex-start;
gap: 12px;
flex: 1;
}
.type-logo {
width: 24px;
height: 24px;
object-fit: contain;
margin-top: 2px;
}
.move-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.move-name-row {
display: flex;
align-items: center;
gap: 8px;
}
.move-name {
font-size: 16px;
font-weight: 600;
color: #495057;
}
.power-badge {
font-size: 12px;
padding: 2px 6px;
background: #ff6b35;
color: white;
border-radius: 8px;
font-weight: 600;
}
.move-type-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
color: white;
font-size: 10px;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
align-self: flex-start;
}
.move-stats {
display: flex;
flex-direction: column;
gap: 4px;
align-items: flex-end;
}
.pp-display,
.accuracy-display {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
padding: 3px 8px;
border-radius: 8px;
background: rgba(0, 0, 0, 0.05);
}
.pp-display.out-of-pp {
background: #fee;
color: #d63031;
}
.pp-label,
.accuracy-label {
font-weight: 600;
color: #6c757d;
}
.pp-value,
.accuracy-value {
font-weight: 500;
color: #495057;
}
.move-description {
font-size: 14px;
color: #6c757d;
line-height: 1.4;
margin-bottom: 8px;
}
.move-flags {
display: flex;
gap: 4px;
flex-wrap: wrap;
margin-bottom: 8px;
}
.flag-badge {
font-size: 10px;
padding: 2px 6px;
background: #e3f2fd;
color: #1976d2;
border-radius: 8px;
font-weight: 500;
}
.move-effects {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #e9ecef;
}
.effects-title {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 600;
color: #495057;
margin: 0 0 8px 0;
}
.effects-icon {
font-size: 14px;
}
.effects-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.effect-item {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 6px 10px;
background: rgba(0, 0, 0, 0.02);
border-radius: 6px;
}
.effect-icon {
font-size: 14px;
margin-top: 1px;
}
.effect-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 1px;
}
.effect-type {
font-size: 11px;
font-weight: 600;
color: #495057;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.effect-description {
font-size: 12px;
color: #6c757d;
line-height: 1.3;
}
.priority-indicator {
display: flex;
align-items: center;
gap: 6px;
margin-top: 8px;
padding: 4px 8px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
font-size: 12px;
}
.priority-icon {
font-size: 14px;
}
.priority-text {
font-weight: 500;
color: #856404;
}
</style>