Updated battle loop. Started to fix attack animations.

This commit is contained in:
lightheel 2025-08-03 15:25:37 -04:00
parent 023af17b23
commit 3b762d6195

View File

@ -10,14 +10,14 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.Image //import androidx.compose.foundation.Image
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource //import androidx.compose.ui.res.painterResource
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -70,6 +70,10 @@ class ArenaBattleSystem {
private var _isAttackVisible by mutableStateOf(false) private var _isAttackVisible by mutableStateOf(false)
private var _critBarProgress by mutableStateOf(0) private var _critBarProgress by mutableStateOf(0)
private var _isAttackButtonEnabled by mutableStateOf(true) private var _isAttackButtonEnabled by mutableStateOf(true)
// Attack animation state
private var _attackIsHit by mutableStateOf(false)
private var _isPlayerAttacking by mutableStateOf(false)
// Exposed state for Compose // Exposed state for Compose
val playerCurrentHP: Float get() = _playerCurrentHP val playerCurrentHP: Float get() = _playerCurrentHP
@ -82,6 +86,10 @@ class ArenaBattleSystem {
val isAttackVisible: Boolean get() = _isAttackVisible val isAttackVisible: Boolean get() = _isAttackVisible
val critBarProgress: Int get() = _critBarProgress val critBarProgress: Int get() = _critBarProgress
val isAttackButtonEnabled: Boolean get() = _isAttackButtonEnabled val isAttackButtonEnabled: Boolean get() = _isAttackButtonEnabled
// Attack animation state
val attackIsHit: Boolean get() = _attackIsHit
val isPlayerAttacking: Boolean get() = _isPlayerAttacking
//Initialize battle with character data //Initialize battle with character data
fun initializeBattle( fun initializeBattle(
@ -110,6 +118,8 @@ class ArenaBattleSystem {
_currentView = 0 _currentView = 0
_isAttackVisible = true _isAttackVisible = true
_isAttackButtonEnabled = false _isAttackButtonEnabled = false
_isPlayerAttacking = true
_attackProgress = 0f
Log.d(TAG, "Player attack started") Log.d(TAG, "Player attack started")
} }
@ -118,6 +128,8 @@ class ArenaBattleSystem {
_isAttacking = true _isAttacking = true
_currentView = 1 _currentView = 1
_isAttackVisible = true _isAttackVisible = true
_isPlayerAttacking = false
_attackProgress = 0f
Log.d(TAG, "Opponent attack started") Log.d(TAG, "Opponent attack started")
} }
@ -127,12 +139,23 @@ class ArenaBattleSystem {
} }
//Complete attack animation //Complete attack animation
fun completeAttackAnimation() { fun completeAttackAnimation(playerDamage: Float = 0f, opponentDamage: Float = 0f) {
_isAttacking = false _isAttacking = false
_isAttackVisible = false _isAttackVisible = false
_attackProgress = 0f _attackProgress = 0f
_currentView = if (_currentView == 0) 1 else 0 _attackIsHit = false
_isAttackButtonEnabled = true
// Apply damage at end of animation
if (playerDamage > 0) {
applyDamage(true, playerDamage) // Player takes damage
println("Player took $playerDamage damage at end of animation")
}
if (opponentDamage > 0) {
applyDamage(false, opponentDamage) // Opponent takes damage
println("Opponent took $opponentDamage damage at end of animation")
}
// Don't enable attack button here - it will be enabled when we switch back to player view
Log.d(TAG, "Attack animation completed") Log.d(TAG, "Attack animation completed")
} }
@ -206,6 +229,18 @@ class ArenaBattleSystem {
_isAttackButtonEnabled = false _isAttackButtonEnabled = false
Log.d(TAG, "Attack button disabled") Log.d(TAG, "Attack button disabled")
} }
// Set attack hit/miss state
fun setAttackHitState(isHit: Boolean) {
_attackIsHit = isHit
Log.d(TAG, "Attack hit state set to: $isHit")
}
// Switch to specific view
fun switchToView(view: Int) {
_currentView = view
Log.d(TAG, "Switched to view: $view")
}
} }
@Composable @Composable
@ -221,6 +256,9 @@ fun BattleScreen(
context: android.content.Context? = null context: android.content.Context? = null
) { ) {
var animationProgress by remember { mutableStateOf(0f) } var animationProgress by remember { mutableStateOf(0f) }
var pendingPlayerDamage by remember { mutableStateOf(0f) }
var pendingOpponentDamage by remember { mutableStateOf(0f) }
var currentAttackType by remember { mutableStateOf("") } // Track if this is player or enemy attack
// Critical bar timer // Critical bar timer
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -232,10 +270,19 @@ fun BattleScreen(
} }
} }
// Attack animation // Player attack animation
LaunchedEffect(battleSystem.isAttacking) { LaunchedEffect(battleSystem.isAttacking, battleSystem.isPlayerAttacking) {
if (battleSystem.isAttacking) { if (battleSystem.isAttacking && battleSystem.isPlayerAttacking) {
animationProgress = 0f animationProgress = 0f
println("=== Starting PLAYER attack animation ===")
println("Current view: ${battleSystem.currentView}")
println("Is player attacking: ${battleSystem.isPlayerAttacking}")
// Store the current attack type
currentAttackType = "player"
println("Attack type: $currentAttackType")
// Complete attack animation across the full screen
animate( animate(
initialValue = 0f, initialValue = 0f,
targetValue = 1f, targetValue = 1f,
@ -243,26 +290,83 @@ fun BattleScreen(
) { value, _ -> ) { value, _ ->
animationProgress = value animationProgress = value
battleSystem.updateAttackAnimation(value) battleSystem.updateAttackAnimation(value)
println("Animation progress: $value")
} }
battleSystem.completeAttackAnimation()
println("=== PLAYER Animation completed ===")
println("Final animation progress: $animationProgress")
// Complete the attack animation
battleSystem.completeAttackAnimation(pendingPlayerDamage, pendingOpponentDamage)
// Reset pending damage
pendingPlayerDamage = 0f
pendingOpponentDamage = 0f
// Player attack just completed - switch to enemy view
println("Player attack completed, switching to enemy view")
battleSystem.switchToView(1)
// Check if battle is over // Check if battle is over
if (battleSystem.isBattleOver()) { if (battleSystem.isBattleOver()) {
onBattleComplete(battleSystem.getWinner()) onBattleComplete(battleSystem.getWinner())
} }
} }
} }
// Opponent AI - automatically attack back after player attack // Enemy attack trigger (separate from animation)
LaunchedEffect(battleSystem.currentView) { LaunchedEffect(currentAttackType) {
if (battleSystem.currentView == 1 && !battleSystem.isAttacking) { if (currentAttackType == "player") {
// Wait a bit before opponent attacks // Player attack just completed - trigger enemy attack after delay
delay(1000) delay(500) // Wait before enemy attacks back
if (battleSystem.currentView == 1 && !battleSystem.isBattleOver()) { println("Starting enemy attack")
println("Opponent attacking back!") battleSystem.startOpponentAttack()
battleSystem.startOpponentAttack() println("Enemy attack triggered - LaunchedEffect should re-trigger")
// Apply damage to player }
battleSystem.applyDamage(true, 15f) // Player takes damage }
// Enemy attack animation
LaunchedEffect(battleSystem.isAttacking, battleSystem.isPlayerAttacking) {
if (battleSystem.isAttacking && !battleSystem.isPlayerAttacking) {
animationProgress = 0f
println("=== Starting ENEMY attack animation ===")
println("Current view: ${battleSystem.currentView}")
println("Is player attacking: ${battleSystem.isPlayerAttacking}")
// Store the current attack type
currentAttackType = "enemy"
println("Attack type: $currentAttackType")
// Complete attack animation across the full screen
animate(
initialValue = 0f,
targetValue = 1f,
animationSpec = tween(ArenaBattleSystem.ANIMATION_DURATION.toInt())
) { value, _ ->
animationProgress = value
battleSystem.updateAttackAnimation(value)
println("Animation progress: $value")
}
println("=== ENEMY Animation completed ===")
println("Final animation progress: $animationProgress")
// Complete the attack animation
battleSystem.completeAttackAnimation(pendingPlayerDamage, pendingOpponentDamage)
// Reset pending damage
pendingPlayerDamage = 0f
pendingOpponentDamage = 0f
// Enemy attack just completed - switch back to player view
println("Enemy attack completed, switching back to player view")
battleSystem.switchToView(0)
// Enable attack button when we're back on player view
battleSystem.enableAttackButton()
// Check if battle is over
if (battleSystem.isBattleOver()) {
onBattleComplete(battleSystem.getWinner())
} }
} }
} }
@ -283,12 +387,15 @@ fun BattleScreen(
attackAnimationProgress = animationProgress, attackAnimationProgress = animationProgress,
onAttackClick = { onAttackClick = {
battleSystem.startPlayerAttack() battleSystem.startPlayerAttack()
// Apply damage after animation // Damage will be applied at end of animation via pending damage system
battleSystem.applyDamage(false, 20f) // Opponent takes damage
}, },
activeCharacter = activeCharacter, activeCharacter = activeCharacter,
context = context, context = context,
opponent = opponentCharacter opponent = opponentCharacter,
onSetPendingDamage = { playerDamage, opponentDamage ->
pendingPlayerDamage = playerDamage
pendingOpponentDamage = opponentDamage
}
) )
} }
1 -> { 1 -> {
@ -325,7 +432,8 @@ fun PlayerBattleView(
onAttackClick: () -> Unit, onAttackClick: () -> Unit,
activeCharacter: APIBattleCharacter? = null, activeCharacter: APIBattleCharacter? = null,
context: android.content.Context? = null, context: android.content.Context? = null,
opponent: APIBattleCharacter? = null opponent: APIBattleCharacter? = null,
onSetPendingDamage: (Float, Float) -> Unit
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@ -390,51 +498,37 @@ fun PlayerBattleView(
.scale(-1f, 1f), // Flip horizontally .scale(-1f, 1f), // Flip horizontally
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
/*
Image(
painter = painterResource(
when (stage) {
"rookie" -> R.drawable.agumon
"champion" -> R.drawable.greymon
"ultimate" -> R.drawable.doruguremon
"mega" -> R.drawable.machinedramon
else -> R.drawable.agumon
}
),
contentDescription = "Player Character",
modifier = Modifier
.size(120.dp)
.scale(2f),
contentScale = ContentScale.Fit
)
*/
// Attack animation overlay // Attack animation overlay
if (attackAnimationProgress > 0) { if (attackAnimationProgress > 0) {
AttackSpriteImage( // Show player attack on both player and enemy screens
characterId = activeCharacter?.charaId ?: "dim011_mon01", // Show enemy attack on both enemy and player screens
isLarge = true, val shouldShowAttack = true // Always show attack during animation
modifier = Modifier
.size(60.dp) if (shouldShowAttack) {
.offset( val xOffset = if (battleSystem.isPlayerAttacking) {
x = (attackAnimationProgress * 200 - 100).dp, // Player attack: start from player position (left side) and fly to right edge
y = 0.dp (attackAnimationProgress * 400 - 200).dp
), } else {
contentScale = ContentScale.Fit // Enemy attack: start from right edge and fly to left edge
) (-attackAnimationProgress * 400 + 200).dp
/* }
Image(
painter = painterResource(R.drawable.atk_l_00), println("Attack sprite - Progress: $attackAnimationProgress, IsPlayerAttacking: ${battleSystem.isPlayerAttacking}, X Offset: $xOffset")
contentDescription = "Attack Animation",
modifier = Modifier AttackSpriteImage(
.size(60.dp) characterId = activeCharacter?.charaId ?: "dim011_mon01",
.offset( isLarge = true,
x = (attackAnimationProgress * 200 - 100).dp, modifier = Modifier
y = 0.dp .size(60.dp)
), .offset(
contentScale = ContentScale.Fit x = xOffset,
) y = 0.dp
*/ )
.scale(if (battleSystem.isPlayerAttacking) -1f else 1f, 1f), // Flip player attacks
contentScale = ContentScale.Fit
)
}
} }
} }
@ -494,40 +588,37 @@ fun PlayerBattleView(
// Update HP based on API response // Update HP based on API response
when (apiResult.state) { when (apiResult.state) {
1 -> { 1 -> {
// Match is still ongoing - update HP and continue // Match is still ongoing - update HP and continue
println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}")
// Apply the actual damage from API // Handle damage timing based on hit/miss
if (apiResult.playerAttackHit) { if (apiResult.playerAttackHit) {
val playerDamage = apiResult.playerAttackDamage.toFloat() // Player attack hit - enemy takes damage at end of player animation
if (playerDamage > 0) { // Player will dodge enemy attack
battleSystem.applyDamage(false, playerDamage) // Opponent takes damage println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage")
println("Player attack hit! Damage: $playerDamage") onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage
} battleSystem.setAttackHitState(true)
} else { } else {
println("Player attack missed!") // Player attack missed - player takes damage at end of enemy animation
} println("Player attack missed! Player will take damage from enemy attack")
onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), 0f) // Player takes damage
if (apiResult.opponentAttackDamage > 0) { battleSystem.setAttackHitState(false)
val opponentDamage = apiResult.opponentAttackDamage.toFloat() }
battleSystem.applyDamage(true, opponentDamage) // Player takes damage
println("Opponent attack hit! Damage: $opponentDamage") // Don't update HP immediately - wait for animation to complete
} // battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat())
// Update HP to match API response // Keep attack button enabled for next round
battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) battleSystem.enableAttackButton()
}
// Keep attack button enabled for next round
battleSystem.enableAttackButton()
}
2 -> { 2 -> {
// Match is over - report winner and complete battle // Match is over - report winner and complete battle
println("Match over! Winner: ${apiResult.winner}") println("Match over! Winner: ${apiResult.winner}")
println("Final HP - Player: ${apiResult.playerHP}, Opponent: ${apiResult.opponentHP}") println("Final HP - Player: ${apiResult.playerHP}, Opponent: ${apiResult.opponentHP}")
// Update final HP // Don't update HP immediately - let animation complete first
battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) // battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat())
// Disable attack button since match is over // Disable attack button since match is over
battleSystem.disableAttackButton() battleSystem.disableAttackButton()
@ -637,38 +728,37 @@ fun OpponentBattleView(
.size(80.dp), .size(80.dp),
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
/*
Image(
painter = painterResource(
when (stage) {
"rookie" -> R.drawable.agumon
"champion" -> R.drawable.greymon
"ultimate" -> R.drawable.doruguremon
"mega" -> R.drawable.machinedramon
else -> R.drawable.agumon
}
),
contentDescription = "Opponent Character",
modifier = Modifier
.size(120.dp)
.scale(-2f, 2f), // Flip horizontally
contentScale = ContentScale.Fit
)
*/
// Attack animation overlay // Attack animation overlay
if (attackAnimationProgress > 0) { if (attackAnimationProgress > 0) {
AttackSpriteImage( // Show player attack on both player and enemy screens
characterId = activeCharacter?.charaId ?: "dim011_mon01", // Show enemy attack on both enemy and player screens
isLarge = true, val shouldShowAttack = true // Always show attack during animation
modifier = Modifier
.size(60.dp) if (shouldShowAttack) {
.offset( val xOffset = if (battleSystem.isPlayerAttacking) {
x = (-attackAnimationProgress * 200 + 100).dp, // Player attack: start from left edge and fly to right edge
y = 0.dp (attackAnimationProgress * 400 - 200).dp
), } else {
contentScale = ContentScale.Fit // Enemy attack: start from enemy position (right side) and fly to left edge
) (-attackAnimationProgress * 400 + 200).dp
}
println("OpponentBattleView - Attack sprite - Progress: $attackAnimationProgress, IsPlayerAttacking: ${battleSystem.isPlayerAttacking}, X Offset: $xOffset")
AttackSpriteImage(
characterId = activeCharacter?.charaId ?: "dim011_mon01",
isLarge = true,
modifier = Modifier
.size(60.dp)
.offset(
x = xOffset,
y = 0.dp
)
.scale(if (battleSystem.isPlayerAttacking) -1f else 1f, 1f), // Flip player attacks
contentScale = ContentScale.Fit
)
}
} }
} }