|
<script lang="ts"> |
|
import { onMount } from 'svelte'; |
|
import { fade } from 'svelte/transition'; |
|
import type { PicletInstance, BattleMove } from '$lib/db/schema'; |
|
import BattleField from '../Battle/BattleField.svelte'; |
|
import BattleControls from '../Battle/BattleControls.svelte'; |
|
import { BattleService } from '$lib/db/battleService'; |
|
import { getEffectivenessText, getEffectivenessColor } from '$lib/types/picletTypes'; |
|
|
|
export let playerPiclet: PicletInstance; |
|
export let enemyPiclet: PicletInstance; |
|
export let isWildBattle: boolean = true; |
|
export let onBattleEnd: (result: any) => void = () => {}; |
|
export let rosterPiclets: PicletInstance[] = []; |
|
|
|
|
|
let currentMessage = isWildBattle |
|
? `A wild ${enemyPiclet.nickname} appeared!` |
|
: `Trainer wants to battle!`; |
|
let battlePhase: 'intro' | 'main' | 'moveSelect' | 'picletSelect' | 'ended' = 'intro'; |
|
let processingTurn = false; |
|
let battleEnded = false; |
|
|
|
|
|
let playerHpPercentage = playerPiclet.currentHp / playerPiclet.maxHp; |
|
let enemyHpPercentage = enemyPiclet.currentHp / enemyPiclet.maxHp; |
|
|
|
onMount(() => { |
|
|
|
setTimeout(() => { |
|
currentMessage = `Go, ${playerPiclet.nickname}!`; |
|
setTimeout(() => { |
|
currentMessage = `What will ${playerPiclet.nickname} do?`; |
|
battlePhase = 'main'; |
|
}, 1500); |
|
}, 2000); |
|
}); |
|
|
|
function handleAction(action: string) { |
|
if (processingTurn || battleEnded) return; |
|
|
|
switch (action) { |
|
case 'catch': |
|
if (isWildBattle) { |
|
processingTurn = true; |
|
currentMessage = 'You threw a Piclet Ball!'; |
|
setTimeout(() => { |
|
currentMessage = 'The wild piclet broke free!'; |
|
processingTurn = false; |
|
}, 2000); |
|
} |
|
break; |
|
case 'run': |
|
if (isWildBattle) { |
|
currentMessage = 'Got away safely!'; |
|
battleEnded = true; |
|
setTimeout(() => onBattleEnd(false), 1500); |
|
} else { |
|
currentMessage = "You can't run from a trainer battle!"; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
function handleMoveSelect(move: BattleMove) { |
|
battlePhase = 'main'; |
|
processingTurn = true; |
|
currentMessage = `${playerPiclet.nickname} used ${move.name}!`; |
|
|
|
setTimeout(() => { |
|
if (!BattleService.doesMoveHit(move.accuracy)) { |
|
currentMessage = `${playerPiclet.nickname}'s attack missed!`; |
|
setTimeout(() => enemyTurn(), 1500); |
|
return; |
|
} |
|
|
|
|
|
const { damage, effectiveness } = BattleService.calculateDamage(playerPiclet, enemyPiclet, move); |
|
|
|
|
|
const newHp = Math.max(0, enemyPiclet.currentHp - damage); |
|
enemyPiclet.currentHp = newHp; |
|
enemyHpPercentage = newHp / enemyPiclet.maxHp; |
|
|
|
|
|
const effectivenessMessage = getEffectivenessText(effectiveness); |
|
if (effectivenessMessage) { |
|
setTimeout(() => { |
|
currentMessage = effectivenessMessage; |
|
}, 1000); |
|
} |
|
|
|
setTimeout(() => { |
|
if (enemyHpPercentage <= 0) { |
|
currentMessage = `${enemyPiclet.nickname} fainted!`; |
|
battleEnded = true; |
|
setTimeout(() => onBattleEnd(true), 2000); |
|
} else { |
|
enemyTurn(); |
|
} |
|
}, effectivenessMessage ? 2500 : 1500); |
|
}, 1500); |
|
} |
|
|
|
function enemyTurn() { |
|
const enemyMove = enemyPiclet.moves[Math.floor(Math.random() * enemyPiclet.moves.length)]; |
|
currentMessage = `${enemyPiclet.nickname} used ${enemyMove.name}!`; |
|
|
|
setTimeout(() => { |
|
if (!BattleService.doesMoveHit(enemyMove.accuracy)) { |
|
currentMessage = `${enemyPiclet.nickname}'s attack missed!`; |
|
setTimeout(() => { |
|
currentMessage = `What will ${playerPiclet.nickname} do?`; |
|
processingTurn = false; |
|
}, 1500); |
|
return; |
|
} |
|
|
|
const { damage, effectiveness } = BattleService.calculateDamage(enemyPiclet, playerPiclet, enemyMove); |
|
|
|
|
|
const newHp = Math.max(0, playerPiclet.currentHp - damage); |
|
playerPiclet.currentHp = newHp; |
|
playerHpPercentage = newHp / playerPiclet.maxHp; |
|
|
|
|
|
const effectivenessMessage = getEffectivenessText(effectiveness); |
|
if (effectivenessMessage) { |
|
setTimeout(() => { |
|
currentMessage = effectivenessMessage; |
|
}, 1000); |
|
} |
|
|
|
setTimeout(() => { |
|
if (playerHpPercentage <= 0) { |
|
currentMessage = `${playerPiclet.nickname} fainted!`; |
|
battleEnded = true; |
|
setTimeout(() => onBattleEnd(false), 2000); |
|
} else { |
|
currentMessage = `What will ${playerPiclet.nickname} do?`; |
|
processingTurn = false; |
|
} |
|
}, effectivenessMessage ? 2500 : 1500); |
|
}, 1500); |
|
} |
|
|
|
function handlePicletSelect(piclet: PicletInstance) { |
|
battlePhase = 'main'; |
|
currentMessage = `Come back, ${playerPiclet.nickname}!`; |
|
setTimeout(() => { |
|
playerPiclet = piclet; |
|
playerHpPercentage = piclet.currentHp / piclet.maxHp; |
|
currentMessage = `Go, ${piclet.nickname}!`; |
|
setTimeout(() => { |
|
currentMessage = `What will ${piclet.nickname} do?`; |
|
}, 1500); |
|
}, 1500); |
|
} |
|
|
|
function handleBack() { |
|
battlePhase = 'main'; |
|
} |
|
</script> |
|
|
|
<div class="battle-page" transition:fade={{ duration: 300 }}> |
|
<nav class="battle-nav"> |
|
<button class="back-button" on:click={() => onBattleEnd('cancelled')} style="display: none;"> |
|
← Back |
|
</button> |
|
<h1>{isWildBattle ? 'Wild Battle' : 'Battle'}</h1> |
|
<div class="nav-spacer"></div> |
|
</nav> |
|
|
|
<div class="battle-content"> |
|
<BattleField |
|
{playerPiclet} |
|
{enemyPiclet} |
|
{playerHpPercentage} |
|
{enemyHpPercentage} |
|
showIntro={battlePhase === 'intro'} |
|
/> |
|
|
|
<BattleControls |
|
{currentMessage} |
|
{battlePhase} |
|
{processingTurn} |
|
{battleEnded} |
|
{isWildBattle} |
|
{playerPiclet} |
|
{enemyPiclet} |
|
{rosterPiclets} |
|
onAction={handleAction} |
|
onMoveSelect={handleMoveSelect} |
|
onPicletSelect={handlePicletSelect} |
|
onBack={handleBack} |
|
/> |
|
</div> |
|
</div> |
|
|
|
<style> |
|
.battle-page { |
|
position: fixed; |
|
inset: 0; |
|
z-index: 1000; |
|
height: 100vh; |
|
display: flex; |
|
flex-direction: column; |
|
background: #f8f9fa; |
|
overflow: hidden; |
|
padding-top: env(safe-area-inset-top); |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.battle-page { |
|
background: white; |
|
} |
|
|
|
.battle-page::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
height: env(safe-area-inset-top); |
|
background: white; |
|
z-index: 1; |
|
} |
|
} |
|
|
|
.battle-nav { |
|
display: none; |
|
} |
|
|
|
.back-button { |
|
background: none; |
|
border: none; |
|
color: #007bff; |
|
font-size: 1rem; |
|
cursor: pointer; |
|
padding: 0.5rem; |
|
} |
|
|
|
.battle-nav h1 { |
|
margin: 0; |
|
font-size: 1.25rem; |
|
font-weight: 600; |
|
color: #1a1a1a; |
|
position: absolute; |
|
left: 50%; |
|
transform: translateX(-50%); |
|
} |
|
|
|
.nav-spacer { |
|
width: 60px; |
|
} |
|
|
|
.battle-content { |
|
flex: 1; |
|
display: flex; |
|
flex-direction: column; |
|
overflow: hidden; |
|
position: relative; |
|
background: #f8f9fa; |
|
} |
|
</style> |