|
<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(); |
|
|
|
|
|
const battleMove = $derived(enhancedMove?.move); |
|
const currentPp = $derived(enhancedMove?.currentPp ?? move.currentPp); |
|
|
|
|
|
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); |
|
|
|
|
|
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 '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> |