From a011ae39a40442f8e3bfbc98e14931b09e2f4500 Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 1 Aug 2025 15:42:37 -0400 Subject: [PATCH 01/65] First push on branch. --- app/build.gradle.kts | 7 + app/src/main/AndroidManifest.xml | 2 + .../vbhelper/battle/APIBattleCharacter.kt | 13 + .../vbhelper/battle/OpponentService.kt | 13 + .../vbhelper/battle/OpponentsDataModel.kt | 5 + .../nacabaro/vbhelper/battle/PVPDataModel.kt | 13 + .../nacabaro/vbhelper/battle/PVPService.kt | 13 + .../vbhelper/battle/RetrofitHelper.kt | 179 ++++++++ .../vbhelper/screens/BattlesScreen.kt | 410 +++++++++++++++++- 9 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/APIBattleCharacter.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/OpponentService.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/OpponentsDataModel.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/PVPDataModel.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/PVPService.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/RetrofitHelper.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 753ad0e..00f6931 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -91,4 +91,11 @@ dependencies { implementation("com.google.android.material:material:1.2.0") implementation(libs.protobuf.javalite) implementation("androidx.compose.material:material") + + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.google.code.gson:gson:2.10.1") + + // HTTP request logging + implementation("com.squareup.okhttp3:logging-interceptor:4.11.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d3a5163..c28d47b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/OpponentsDataModel.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/OpponentsDataModel.kt new file mode 100644 index 0000000..8bff492 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/OpponentsDataModel.kt @@ -0,0 +1,5 @@ +package com.github.nacabaro.vbhelper.battle + +data class OpponentsDataModel ( + val opponentsList: ArrayList +):java.io.Serializable \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/PVPDataModel.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/PVPDataModel.kt new file mode 100644 index 0000000..e0b55f7 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/PVPDataModel.kt @@ -0,0 +1,13 @@ +package com.github.nacabaro.vbhelper.battle + +data class PVPDataModel ( + val status: String, + val state: Int, + val currentRound: Int, + val playerHP: Int, + val opponentHP: Int, + val playerAttackHit: Boolean, + val playerAttackDamage: Int, + val opponentAttackDamage: Int, + val winner: String +):java.io.Serializable \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/PVPService.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/PVPService.kt new file mode 100644 index 0000000..8bc6ba2 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/PVPService.kt @@ -0,0 +1,13 @@ +package com.github.nacabaro.vbhelper.battle + +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Query + +interface PVPService { + @GET("api/pvp") + // This method returns a Call object with a generic + // type of DataModel, which represents + // the data model for the response. + fun getwinner(@Query("apiStage") apiStage: Int, @Query("playerID") playerID: Int, @Query("playerDigi") playerDigi: String, @Query("playerStage") playerStage: Int, @Query("critBar") critBar: Int, @Query("opponentDigi") opponentDigi: String, @Query("opponentStage") opponentStage: Int): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/RetrofitHelper.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/RetrofitHelper.kt new file mode 100644 index 0000000..1cfca0e --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/RetrofitHelper.kt @@ -0,0 +1,179 @@ +package com.github.nacabaro.vbhelper.battle + +import android.content.Context +import retrofit2.Retrofit +import android.widget.Toast +import retrofit2.* +import retrofit2.converter.gson.GsonConverterFactory + +class RetrofitHelper { + + fun getOpponents(context: Context, stage: String, callback: (OpponentsDataModel) -> Unit) { + println("RetrofitHelper: Starting API call for stage: $stage") + + try { + // Create a Retrofit instance with the base URL and + // a GsonConverterFactory for parsing the response. + val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://192.168.0.230:8080/").addConverterFactory( + GsonConverterFactory.create()).build() + println("RetrofitHelper: Retrofit instance created") + + // Create an ApiService instance from the Retrofit instance. + val service: OpponentService = retrofit.create(OpponentService::class.java) + println("RetrofitHelper: Service created") + + // Call the getopponents() method of the ApiService + // to make an API request. + val call: Call = service.getopponents(stage) + println("RetrofitHelper: API call created, enqueueing...") + + // Use the enqueue() method of the Call object to + // make an asynchronous API request. + call.enqueue(object : Callback { + override fun onFailure(call: Call, t: Throwable) { + println("RetrofitHelper: API call failed: ${t.message}") + t.printStackTrace() + Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show() + } + + override fun onResponse(call: Call, response: Response) { + println("RetrofitHelper: API response received - Code: ${response.code()}") + println("RetrofitHelper: Response body: ${response.body()}") + + if(response.isSuccessful){ + println("RetrofitHelper: Response successful, calling callback") + val opponentsList: OpponentsDataModel = response.body() as OpponentsDataModel + callback(opponentsList) + } else { + println("RetrofitHelper: Response not successful - Error: ${response.errorBody()?.string()}") + } + } + }) + } catch (e: Exception) { + println("RetrofitHelper: Exception in getOpponents: ${e.message}") + e.printStackTrace() + } + } + + /* + fun getCombatWinner(context: Context, stage: String, callback: (CombatDataModel) -> Unit) { + + // Create a Retrofit instance with the base URL and + // a GsonConverterFactory for parsing the response. + val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://192.168.0.230:8080/").addConverterFactory( + GsonConverterFactory.create()).build() + + // Create an ApiService instance from the Retrofit instance. + val service: CombatService = retrofit.create(CombatService::class.java) + + // Call the getwinner() method of the ApiService + // to make an API request. + val call: Call = service.getwinner(stage) + + // Use the enqueue() method of the Call object to + // make an asynchronous API request. + call.enqueue(object : Callback { + // This is an anonymous inner class that implements the Callback interface. + + override fun onFailure(call: Call, t: Throwable) { + // This method is called when the API request fails. + Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show() + } + + override fun onResponse(call: Call, response: Response) { + // This method is called when the API response is received successfully. + + if(response.isSuccessful){ + // If the response is successful, parse the + // response body to a DataModel object. + val winner: CombatDataModel = response.body() as CombatDataModel + + // Call the callback function with the DataModel + // object as a parameter. + callback(winner) + } + } + }) + } + + fun getBattleWinner(context: Context, playerDigi: String, playerStage: Int, opponentDigi: String, opponentStage: Int, callback: (BattleDataModel) -> Unit) { + + // Create a Retrofit instance with the base URL and + // a GsonConverterFactory for parsing the response. + val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://192.168.0.230:8080/").addConverterFactory( + GsonConverterFactory.create()).build() + + // Create an ApiService instance from the Retrofit instance. + val service: BattleService = retrofit.create(BattleService::class.java) + + // Call the getwinner() method of the ApiService + // to make an API request. + val call: Call = service.getwinner(playerDigi, playerStage, opponentDigi, opponentStage) + + // Use the enqueue() method of the Call object to + // make an asynchronous API request. + call.enqueue(object : Callback { + // This is an anonymous inner class that implements the Callback interface. + + override fun onFailure(call: Call, t: Throwable) { + // This method is called when the API request fails. + Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show() + } + + override fun onResponse(call: Call, response: Response) { + // This method is called when the API response is received successfully. + + if(response.isSuccessful){ + // If the response is successful, parse the + // response body to a DataModel object. + val winner: BattleDataModel = response.body() as BattleDataModel + + // Call the callback function with the DataModel + // object as a parameter. + callback(winner) + } + } + }) + } + */ + + fun getPVPWinner(context: Context, apiStage: Int, playerID: Int, playerDigi: String, playerStage: Int, critBar: Int, opponentDigi: String, opponentStage: Int, callback: (PVPDataModel) -> Unit) { + + // Create a Retrofit instance with the base URL and + // a GsonConverterFactory for parsing the response. + val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://192.168.0.230:8080/").addConverterFactory( + GsonConverterFactory.create()).build() + + // Create an ApiService instance from the Retrofit instance. + val service: PVPService = retrofit.create(PVPService::class.java) + + // Call the getwinner() method of the ApiService + // to make an API request. + val call: Call = service.getwinner(apiStage, playerID, playerDigi, playerStage, critBar, opponentDigi, opponentStage) + + // Use the enqueue() method of the Call object to + // make an asynchronous API request. + call.enqueue(object : Callback { + // This is an anonymous inner class that implements the Callback interface. + + override fun onFailure(call: Call, t: Throwable) { + // This method is called when the API request fails. + Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show() + } + + override fun onResponse(call: Call, response: Response) { + // This method is called when the API response is received successfully. + + if(response.isSuccessful){ + // If the response is successful, parse the + // response body to a DataModel object. + val apiResults: PVPDataModel = response.body() as PVPDataModel + + // Call the callback function with the DataModel + // object as a parameter. + callback(apiResults) + } + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index b50ef12..7a334be 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -9,10 +9,269 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.material3.Button +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.unit.dp +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.foundation.layout.Box +import androidx.compose.ui.platform.LocalContext +import androidx.compose.material3.ExperimentalMaterial3Api +import com.github.nacabaro.vbhelper.battle.APIBattleCharacter import com.github.nacabaro.vbhelper.components.TopBanner +import com.github.nacabaro.vbhelper.battle.RetrofitHelper +@OptIn(ExperimentalMaterial3Api::class) @Composable fun BattlesScreen() { + var currentView by remember { mutableStateOf("main") } + + var opponentsList by remember { mutableStateOf(ArrayList()) } + + var activeCharacter by remember { mutableStateOf(null) } + + var expanded by remember { mutableStateOf(false) } + var selectedStage by remember { mutableStateOf("") } + + val context = LocalContext.current + + val rookieButton = @Composable { + Button( + onClick = { + println("Rookie button clicked - starting API call") + try { + RetrofitHelper().getOpponents(context, "rookie") { opponents -> + println("API call completed successfully") + try { + println("Received opponents data: $opponents") + println("Opponents list size: ${opponents.opponentsList.size}") + + // For loop to check opponents and print their names + for (opponent in opponents.opponentsList) { + println("Opponent: ${opponent.name}") + } + + // Store the opponents in your ArrayList + opponentsList.clear() + opponentsList.addAll(opponents.opponentsList) + + println("Updated opponentsList size: ${opponentsList.size}") + println("About to change view to rookie") + currentView = "rookie" + println("View changed to rookie") + } catch (e: Exception) { + println("Error processing opponents data: ${e.message}") + e.printStackTrace() + } + } + } catch (e: Exception) { + println("Error calling getOpponents: ${e.message}") + e.printStackTrace() + } + } + ) { + Text("Rookie Battles") + } + } + + val championButton = @Composable { + Button( + onClick = { + println("Champion button clicked - starting API call") + try { + RetrofitHelper().getOpponents(context, "champion") { opponents -> + println("API call completed successfully") + try { + println("Received opponents data: $opponents") + println("Opponents list size: ${opponents.opponentsList.size}") + + // For loop to check opponents and print their names + for (opponent in opponents.opponentsList) { + println("Opponent: ${opponent.name}") + } + + // Store the opponents in your ArrayList + opponentsList.clear() + opponentsList.addAll(opponents.opponentsList) + + println("Updated opponentsList size: ${opponentsList.size}") + println("About to change view to champion") + currentView = "champion" + println("View changed to champion") + } catch (e: Exception) { + println("Error processing opponents data: ${e.message}") + e.printStackTrace() + } + } + } catch (e: Exception) { + println("Error calling getOpponents: ${e.message}") + e.printStackTrace() + } + } + ) { + Text("Champion Battles") + } + } + + val ultimateButton = @Composable { + Button( + onClick = { + println("Ultimate button clicked - starting API call") + try { + RetrofitHelper().getOpponents(context, "ultimate") { opponents -> + println("API call completed successfully") + try { + println("Received opponents data: $opponents") + println("Opponents list size: ${opponents.opponentsList.size}") + + // For loop to check opponents and print their names + for (opponent in opponents.opponentsList) { + println("Opponent: ${opponent.name}") + } + + // Store the opponents in your ArrayList + opponentsList.clear() + opponentsList.addAll(opponents.opponentsList) + + println("Updated opponentsList size: ${opponentsList.size}") + println("About to change view to ultimate") + currentView = "ultimate" + println("View changed to ultimate") + } catch (e: Exception) { + println("Error processing opponents data: ${e.message}") + e.printStackTrace() + } + } + } catch (e: Exception) { + println("Error calling getOpponents: ${e.message}") + e.printStackTrace() + } + } + ) { + Text("Ultimate Battles") + } + } + + val megaButton = @Composable { + Button( + onClick = { + println("Mega button clicked - starting API call") + try { + RetrofitHelper().getOpponents(context, "mega") { opponents -> + println("API call completed successfully") + try { + println("Received opponents data: $opponents") + println("Opponents list size: ${opponents.opponentsList.size}") + + // For loop to check opponents and print their names + for (opponent in opponents.opponentsList) { + println("Opponent: ${opponent.name}") + } + + // Store the opponents in your ArrayList + opponentsList.clear() + opponentsList.addAll(opponents.opponentsList) + + println("Updated opponentsList size: ${opponentsList.size}") + println("About to change view to mega") + currentView = "mega" + println("View changed to mega") + } catch (e: Exception) { + println("Error processing opponents data: ${e.message}") + e.printStackTrace() + } + } + } catch (e: Exception) { + println("Error calling getOpponents: ${e.message}") + e.printStackTrace() + } + } + ) { + Text("Mega Battles") + } + } + + val backButton = @Composable { + Button( + onClick = { + currentView = "main" + } + ) { + Text("Back") + } + } + + val characterDropdown = @Composable { currentStage: String -> + // Create hardcoded character lists for each stage + val rookieCharacters = listOf( + APIBattleCharacter("AGUMON", "degimon_name_Dim012_003", "dim012_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), + APIBattleCharacter("PULSEMON", "degimon_name_Dim000_003", "dim000_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), + APIBattleCharacter("DORUMON", "degimon_name_dim137_mon03", "dim137_mon03", 0, 1, 3000, 3000, 5100.0f, 1050.0f) + ) + + val championCharacters = listOf( + APIBattleCharacter("GREYMON","degimon_name_Dim012_004","dim012_mon04",1,1,2000, 2000, 3000.0f,900.0f), + APIBattleCharacter("TYRANNOMON","degimon_name_Dim008_006","dim008_mon06",1,3,2000, 2000, 2400.0f,600.0f), + APIBattleCharacter("DORUGAMON","degimon_name_dim137_mon05","dim137_mon05",1,3,3500, 3500, 5200.0f,1200.0f) + ) + + val ultimateCharacters = listOf( + APIBattleCharacter("METALGREYMON (VIRUS)","degimon_name_Dim014_005","dim014_mon05",2,2,2640, 2640, 2450.0f,800.0f), + APIBattleCharacter("MAMEMON", "degimon_name_Dim000_005", "dim000_mon05", 2, 1, 3000, 3000, 4000.0f, 1000.0f), + APIBattleCharacter("DORUGREYMON","degimon_name_dim137_mon09","dim137_mon09",2,3,5000, 5000, 6400.0f,1400.0f) + ) + + val megaCharacters = listOf( + APIBattleCharacter("WARGREYMON","degimon_name_Dim012_014","dim012_mon14",3,1,3080, 3080, 3825.0f,800.0f), + APIBattleCharacter("SLAYERDRAMON","degimon_name_dim129_mon15","dim129_mon15",3,1,4800, 4800, 6300.0f,1950.0f), + APIBattleCharacter("BREAKDRAMON","degimon_name_dim129_mon17","dim129_mon17",3,2,6000, 6000, 4000.0f,1980.0f) + ) + + // Get the appropriate character list based on current stage + val characterList = when (currentStage.lowercase()) { + "rookie" -> rookieCharacters + "champion" -> championCharacters + "ultimate" -> ultimateCharacters + "mega" -> megaCharacters + else -> rookieCharacters + } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded } + ) { + OutlinedTextField( + value = selectedStage.ifEmpty { "Select Character" }, + onValueChange = {}, + readOnly = true, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + modifier = Modifier.menuAnchor() + ) + + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + characterList.forEach { character -> + DropdownMenuItem( + text = { Text(character.name) }, + onClick = { + selectedStage = character.name + activeCharacter = character + expanded = false + println("Selected character: ${character.name}") + } + ) + } + } + } + } + Scaffold ( topBar = { TopBanner( @@ -27,7 +286,156 @@ fun BattlesScreen() { .padding(top = contentPadding.calculateTopPadding()) .fillMaxSize() ) { - Text("Coming soon") + when (currentView) { + "main" -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + rookieButton() + championButton() + ultimateButton() + megaButton() + } + } + + + "rookie" -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Rookie Battle View") + + // Add character selection dropdown + characterDropdown("rookie") + + // Display buttons for each opponent + opponentsList.forEach { opponent -> + Button( + onClick = { + activeCharacter = opponent + println("Selected character: ${opponent.name}") + // You can add battle logic here + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text("Battle ${opponent.name}") + } + } + + // Show selected character info + activeCharacter?.let { character -> + Text("Active Character: ${character.name}") + Text("HP: ${character.currentHp}/${character.baseHp}") + Text("BP: ${character.baseBp}") + Text("AP: ${character.baseAp}") + } + + backButton() + } + } + + "champion" -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Champion Battle View") + + // Add character selection dropdown + characterDropdown("champion") + + // Display buttons for each opponent + opponentsList.forEach { opponent -> + Button( + onClick = { + activeCharacter = opponent + println("Selected character: ${opponent.name}") + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text("Battle ${opponent.name}") + } + } + + // Show selected character info + activeCharacter?.let { character -> + Text("Active Character: ${character.name}") + Text("HP: ${character.currentHp}/${character.baseHp}") + Text("BP: ${character.baseBp}") + Text("AP: ${character.baseAp}") + } + + backButton() + } + } + + "ultimate" -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Ultimate Battle View") + + // Add character selection dropdown + characterDropdown("ultimate") + + // Display buttons for each opponent + opponentsList.forEach { opponent -> + Button( + onClick = { + activeCharacter = opponent + println("Selected character: ${opponent.name}") + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text("Battle ${opponent.name}") + } + } + + // Show selected character info + activeCharacter?.let { character -> + Text("Active Character: ${character.name}") + Text("HP: ${character.currentHp}/${character.baseHp}") + Text("BP: ${character.baseBp}") + Text("AP: ${character.baseAp}") + } + + backButton() + } + } + + "mega" -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Mega Battle View") + + // Add character selection dropdown + characterDropdown("mega") + + // Display buttons for each opponent + opponentsList.forEach { opponent -> + Button( + onClick = { + activeCharacter = opponent + println("Selected character: ${opponent.name}") + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text("Battle ${opponent.name}") + } + } + + // Show selected character info + activeCharacter?.let { character -> + Text("Active Character: ${character.name}") + Text("HP: ${character.currentHp}/${character.baseHp}") + Text("BP: ${character.baseBp}") + Text("AP: ${character.baseAp}") + } + + backButton() + } + } + } } } } From bd0cc4639818b9853a29d1f3b95c48c8a4f9767c Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 1 Aug 2025 15:53:40 -0400 Subject: [PATCH 02/65] Setup API calls for stage 0 on Digimon name button click. --- .../vbhelper/screens/BattlesScreen.kt | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 7a334be..5f7d810 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -312,9 +312,10 @@ fun BattlesScreen() { opponentsList.forEach { opponent -> Button( onClick = { - activeCharacter = opponent - println("Selected character: ${opponent.name}") - // You can add battle logic here + activeCharacter?.let { + RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 0, 0, opponent.name, 0) { apiResult -> + } + } }, modifier = Modifier.padding(vertical = 4.dp) ) { @@ -347,8 +348,10 @@ fun BattlesScreen() { opponentsList.forEach { opponent -> Button( onClick = { - activeCharacter = opponent - println("Selected character: ${opponent.name}") + activeCharacter?.let { + RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 1, 0, opponent.name, 1) { apiResult -> + } + } }, modifier = Modifier.padding(vertical = 4.dp) ) { @@ -381,8 +384,10 @@ fun BattlesScreen() { opponentsList.forEach { opponent -> Button( onClick = { - activeCharacter = opponent - println("Selected character: ${opponent.name}") + activeCharacter?.let { + RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 2, 0, opponent.name, 2) { apiResult -> + } + } }, modifier = Modifier.padding(vertical = 4.dp) ) { @@ -415,8 +420,10 @@ fun BattlesScreen() { opponentsList.forEach { opponent -> Button( onClick = { - activeCharacter = opponent - println("Selected character: ${opponent.name}") + activeCharacter?.let { + RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 3, 0, opponent.name, 3) { apiResult -> + } + } }, modifier = Modifier.padding(vertical = 4.dp) ) { From a72718273c01a29ae0c56ca22861b283022ebdca Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 1 Aug 2025 17:04:35 -0400 Subject: [PATCH 03/65] Added imports to remove build errors. Started added battle screens, logic, and animations. --- .../vbhelper/screens/BattlesScreen.kt | 562 ++++++++++++++++-- 1 file changed, 514 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 5f7d810..c270d2f 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -4,31 +4,481 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.Image +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.material3.Button import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.draw.scale +import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.dp +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.DropdownMenuItem import androidx.compose.foundation.layout.Box import androidx.compose.ui.platform.LocalContext +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.animation.core.animate +import androidx.compose.animation.core.tween +//import androidx.compose.animation.core.animateFloatAsState +import kotlinx.coroutines.delay +import androidx.compose.foundation.background +import androidx.compose.ui.graphics.Color +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import com.github.nacabaro.vbhelper.battle.APIBattleCharacter +//import com.github.nacabaro.vbhelper.battle.BattleSpriteManager +import android.util.Log import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.battle.RetrofitHelper +class ArenaBattleSystem { + companion object { + private const val TAG = "VBArenaBattleSystem" + const val ANIMATION_DURATION = 1500L + } + + // Battle state + private var _playerCurrentHP by mutableStateOf(100f) + private var _playerMaxHP by mutableStateOf(100f) + private var _opponentCurrentHP by mutableStateOf(100f) + private var _opponentMaxHP by mutableStateOf(100f) + private var _isAttacking by mutableStateOf(false) + private var _attackProgress by mutableStateOf(0f) + private var _currentView by mutableStateOf(0) // 0 = player, 1 = opponent + private var _isAttackVisible by mutableStateOf(false) + private var _critBarProgress by mutableStateOf(0) + private var _isAttackButtonEnabled by mutableStateOf(true) + + // Exposed state for Compose + val playerCurrentHP: Float get() = _playerCurrentHP + val playerMaxHP: Float get() = _playerMaxHP + val opponentCurrentHP: Float get() = _opponentCurrentHP + val opponentMaxHP: Float get() = _opponentMaxHP + val isAttacking: Boolean get() = _isAttacking + val attackProgress: Float get() = _attackProgress + val currentView: Int get() = _currentView + val isAttackVisible: Boolean get() = _isAttackVisible + val critBarProgress: Int get() = _critBarProgress + val isAttackButtonEnabled: Boolean get() = _isAttackButtonEnabled + + //Initialize battle with character data + fun initializeBattle( + playerHP: Float = 100f, + opponentHP: Float = 100f, + playerMaxHP: Float = 100f, + opponentMaxHP: Float = 100f + ) { + _playerCurrentHP = playerHP + _playerMaxHP = playerMaxHP + _opponentCurrentHP = opponentHP + _opponentMaxHP = opponentMaxHP + _currentView = 0 + _isAttacking = false + _attackProgress = 0f + _isAttackVisible = false + _critBarProgress = 0 + _isAttackButtonEnabled = true + + Log.d(TAG, "Battle initialized: Player HP $playerHP/$playerMaxHP, Opponent HP $opponentHP/$opponentMaxHP") + } + + //Start player attack + fun startPlayerAttack() { + _isAttacking = true + _currentView = 0 + _isAttackVisible = true + _isAttackButtonEnabled = false + Log.d(TAG, "Player attack started") + } + + //Start opponent attack + fun startOpponentAttack() { + _isAttacking = true + _currentView = 1 + _isAttackVisible = true + Log.d(TAG, "Opponent attack started") + } + + //Update attack animation progress + fun updateAttackAnimation(progress: Float) { + _attackProgress = progress + } + + //Complete attack animation + fun completeAttackAnimation() { + _isAttacking = false + _isAttackVisible = false + _attackProgress = 0f + _currentView = if (_currentView == 0) 1 else 0 + _isAttackButtonEnabled = true + Log.d(TAG, "Attack animation completed") + } + + //Apply damage to player or opponent + fun applyDamage(isPlayer: Boolean, damage: Float) { + if (isPlayer) { + _playerCurrentHP = (_playerCurrentHP - damage).coerceAtLeast(0f) + Log.d(TAG, "Player took $damage damage. HP: ${_playerCurrentHP}/${_playerMaxHP}") + } else { + _opponentCurrentHP = (_opponentCurrentHP - damage).coerceAtLeast(0f) + Log.d(TAG, "Opponent took $damage damage. HP: ${_opponentCurrentHP}/${_opponentMaxHP}") + } + } + + //Update critical bar progress + fun updateCritBarProgress(progress: Int) { + _critBarProgress = progress + } + + + //Check if battle is over + fun isBattleOver(): Boolean { + return _playerCurrentHP <= 0f || _opponentCurrentHP <= 0f + } + + //Get battle winner + fun getWinner(): String? { + return when { + _playerCurrentHP <= 0f -> "opponent" + _opponentCurrentHP <= 0f -> "player" + else -> null + } + } + + //Reset battle state + fun resetBattle() { + _playerCurrentHP = _playerMaxHP + _opponentCurrentHP = _opponentMaxHP + _isAttacking = false + _attackProgress = 0f + _currentView = 0 + _isAttackVisible = false + _critBarProgress = 0 + _isAttackButtonEnabled = true + Log.d(TAG, "Battle reset") + } + + //Clean up resources + fun cleanup() { + _isAttacking = false + _isAttackVisible = false + _attackProgress = 0f + Log.d(TAG, "Battle system cleaned up") + } +} + +@Composable +fun BattleScreen( + battleSystem: ArenaBattleSystem, + stage: String = "rookie", + playerName: String = "Player", + opponentName: String = "Opponent", + onBattleComplete: (String?) -> Unit = {}, + onExitBattle: () -> Unit = {} +) { + var animationProgress by remember { mutableStateOf(0f) } + + // Critical bar timer + LaunchedEffect(Unit) { + while (true) { + delay(30) + if (!battleSystem.isAttacking) { + battleSystem.updateCritBarProgress((battleSystem.critBarProgress + 5) % 101) + } + } + } + + // Attack animation + LaunchedEffect(battleSystem.isAttacking) { + if (battleSystem.isAttacking) { + animationProgress = 0f + animate( + initialValue = 0f, + targetValue = 1f, + animationSpec = tween(ArenaBattleSystem.ANIMATION_DURATION.toInt()) + ) { value, _ -> + animationProgress = value + battleSystem.updateAttackAnimation(value) + } + battleSystem.completeAttackAnimation() + + // Check if battle is over + if (battleSystem.isBattleOver()) { + onBattleComplete(battleSystem.getWinner()) + } + } + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black) + ) { + when (battleSystem.currentView) { + 0 -> PlayerBattleView( + battleSystem = battleSystem, + stage = stage, + playerName = playerName, + attackAnimationProgress = animationProgress, + onAttackClick = { + battleSystem.startPlayerAttack() + // Apply damage after animation + battleSystem.applyDamage(false, 20f) // Opponent takes damage + } + ) + 1 -> OpponentBattleView( + battleSystem = battleSystem, + stage = stage, + opponentName = opponentName, + attackAnimationProgress = animationProgress + ) + } + + // Exit button + Button( + onClick = onExitBattle, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(16.dp), + colors = ButtonDefaults.buttonColors(containerColor = Color.Red) + ) { + Text("Exit", color = Color.White) + } + } +} + +@Composable +fun PlayerBattleView( + battleSystem: ArenaBattleSystem, + stage: String, + playerName: String, + attackAnimationProgress: Float, + onAttackClick: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + // Health display + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column { + Text("Current HP", color = Color.White, fontSize = 12.sp) + Text( + text = battleSystem.playerCurrentHP.toInt().toString(), + color = Color.White, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + Column { + Text("Max HP", color = Color.White, fontSize = 12.sp) + Text( + text = battleSystem.playerMaxHP.toInt().toString(), + color = Color.White, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } + + // Health bar + LinearProgressIndicator( + progress = (battleSystem.playerCurrentHP / battleSystem.playerMaxHP).coerceIn(0f, 1f), + modifier = Modifier + .fillMaxWidth() + .height(20.dp), + color = Color.Green, + trackColor = Color.Gray + ) + + // Player character with attack animation + Box( + modifier = Modifier.size(120.dp), + contentAlignment = Alignment.Center + ) { + 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 + if (attackAnimationProgress > 0) { + Image( + painter = painterResource(R.drawable.atk_l_00), + contentDescription = "Attack Animation", + modifier = Modifier + .size(60.dp) + .offset( + x = (attackAnimationProgress * 200 - 100).dp, + y = 0.dp + ), + contentScale = ContentScale.Fit + ) + } + } + + // Critical bar + LinearProgressIndicator( + progress = battleSystem.critBarProgress / 100f, + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Yellow, + trackColor = Color.Gray + ) + + // Attack button + Button( + onClick = onAttackClick, + enabled = battleSystem.isAttackButtonEnabled, + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Red, + disabledContainerColor = Color.Gray + ), + shape = RoundedCornerShape(8.dp) + ) { + Text("Attack", color = Color.White, fontSize = 18.sp) + } + } +} + +@Composable +fun OpponentBattleView( + battleSystem: ArenaBattleSystem, + stage: String, + opponentName: String, + attackAnimationProgress: Float +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + // Health display + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column { + Text("Current HP", color = Color.White, fontSize = 12.sp) + Text( + text = battleSystem.opponentCurrentHP.toInt().toString(), + color = Color.White, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + Column { + Text("Max HP", color = Color.White, fontSize = 12.sp) + Text( + text = battleSystem.opponentMaxHP.toInt().toString(), + color = Color.White, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + } + + // Health bar + LinearProgressIndicator( + progress = (battleSystem.opponentCurrentHP / battleSystem.opponentMaxHP).coerceIn(0f, 1f), + modifier = Modifier + .fillMaxWidth() + .height(20.dp), + color = Color.Green, + trackColor = Color.Gray + ) + + // Opponent character with attack animation + Box( + modifier = Modifier.size(120.dp), + contentAlignment = Alignment.Center + ) { + 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 + if (attackAnimationProgress > 0) { + Image( + painter = painterResource(R.drawable.atk_l_00), + contentDescription = "Attack Animation", + modifier = Modifier + .size(60.dp) + .offset( + x = (-attackAnimationProgress * 200 + 100).dp, + y = 0.dp + ), + contentScale = ContentScale.Fit + ) + } + } + + // Spacer for layout balance + Spacer(modifier = Modifier.height(120.dp)) + } +} + @OptIn(ExperimentalMaterial3Api::class) @Composable fun BattlesScreen() { + val TAG = "BattleScreen" + var currentView by remember { mutableStateOf("main") } var opponentsList by remember { mutableStateOf(ArrayList()) } @@ -43,34 +493,36 @@ fun BattlesScreen() { val rookieButton = @Composable { Button( onClick = { - println("Rookie button clicked - starting API call") + //println("Rookie button clicked - starting API call") try { RetrofitHelper().getOpponents(context, "rookie") { opponents -> - println("API call completed successfully") + //println("API call completed successfully") try { - println("Received opponents data: $opponents") - println("Opponents list size: ${opponents.opponentsList.size}") + //println("Received opponents data: $opponents") + //println("Opponents list size: ${opponents.opponentsList.size}") // For loop to check opponents and print their names - for (opponent in opponents.opponentsList) { - println("Opponent: ${opponent.name}") - } + //for (opponent in opponents.opponentsList) { + // println("Opponent: ${opponent.name}") + //} // Store the opponents in your ArrayList opponentsList.clear() opponentsList.addAll(opponents.opponentsList) - println("Updated opponentsList size: ${opponentsList.size}") - println("About to change view to rookie") + //println("Updated opponentsList size: ${opponentsList.size}") + //println("About to change view to rookie") currentView = "rookie" - println("View changed to rookie") + //println("View changed to rookie") } catch (e: Exception) { - println("Error processing opponents data: ${e.message}") + //println("Error processing opponents data: ${e.message}") + Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() } } } catch (e: Exception) { - println("Error calling getOpponents: ${e.message}") + //println("Error calling getOpponents: ${e.message}") + Log.d(TAG,"Error calling getOpponents: ${e.message}") e.printStackTrace() } } @@ -82,34 +534,36 @@ fun BattlesScreen() { val championButton = @Composable { Button( onClick = { - println("Champion button clicked - starting API call") + //println("Champion button clicked - starting API call") try { RetrofitHelper().getOpponents(context, "champion") { opponents -> - println("API call completed successfully") + //println("API call completed successfully") try { - println("Received opponents data: $opponents") - println("Opponents list size: ${opponents.opponentsList.size}") + //println("Received opponents data: $opponents") + //println("Opponents list size: ${opponents.opponentsList.size}") // For loop to check opponents and print their names - for (opponent in opponents.opponentsList) { - println("Opponent: ${opponent.name}") - } + //for (opponent in opponents.opponentsList) { + // println("Opponent: ${opponent.name}") + //} // Store the opponents in your ArrayList opponentsList.clear() opponentsList.addAll(opponents.opponentsList) - println("Updated opponentsList size: ${opponentsList.size}") - println("About to change view to champion") + //println("Updated opponentsList size: ${opponentsList.size}") + //println("About to change view to champion") currentView = "champion" - println("View changed to champion") + //println("View changed to champion") } catch (e: Exception) { - println("Error processing opponents data: ${e.message}") + //println("Error processing opponents data: ${e.message}") + Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() } } } catch (e: Exception) { - println("Error calling getOpponents: ${e.message}") + //println("Error calling getOpponents: ${e.message}") + Log.d(TAG,"Error calling getOpponents: ${e.message}") e.printStackTrace() } } @@ -121,34 +575,36 @@ fun BattlesScreen() { val ultimateButton = @Composable { Button( onClick = { - println("Ultimate button clicked - starting API call") + //println("Ultimate button clicked - starting API call") try { RetrofitHelper().getOpponents(context, "ultimate") { opponents -> - println("API call completed successfully") + //println("API call completed successfully") try { - println("Received opponents data: $opponents") - println("Opponents list size: ${opponents.opponentsList.size}") + //println("Received opponents data: $opponents") + //println("Opponents list size: ${opponents.opponentsList.size}") // For loop to check opponents and print their names - for (opponent in opponents.opponentsList) { - println("Opponent: ${opponent.name}") - } + //for (opponent in opponents.opponentsList) { + // println("Opponent: ${opponent.name}") + //} // Store the opponents in your ArrayList opponentsList.clear() opponentsList.addAll(opponents.opponentsList) - println("Updated opponentsList size: ${opponentsList.size}") - println("About to change view to ultimate") + //println("Updated opponentsList size: ${opponentsList.size}") + //println("About to change view to ultimate") currentView = "ultimate" - println("View changed to ultimate") + //println("View changed to ultimate") } catch (e: Exception) { - println("Error processing opponents data: ${e.message}") + //println("Error processing opponents data: ${e.message}") + Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() } } } catch (e: Exception) { - println("Error calling getOpponents: ${e.message}") + //println("Error calling getOpponents: ${e.message}") + Log.d(TAG,"Error calling getOpponents: ${e.message}") e.printStackTrace() } } @@ -160,34 +616,36 @@ fun BattlesScreen() { val megaButton = @Composable { Button( onClick = { - println("Mega button clicked - starting API call") + //println("Mega button clicked - starting API call") try { RetrofitHelper().getOpponents(context, "mega") { opponents -> - println("API call completed successfully") + //println("API call completed successfully") try { - println("Received opponents data: $opponents") - println("Opponents list size: ${opponents.opponentsList.size}") + //println("Received opponents data: $opponents") + //println("Opponents list size: ${opponents.opponentsList.size}") // For loop to check opponents and print their names - for (opponent in opponents.opponentsList) { - println("Opponent: ${opponent.name}") - } + //for (opponent in opponents.opponentsList) { + // println("Opponent: ${opponent.name}") + //} // Store the opponents in your ArrayList opponentsList.clear() opponentsList.addAll(opponents.opponentsList) - println("Updated opponentsList size: ${opponentsList.size}") - println("About to change view to mega") + //println("Updated opponentsList size: ${opponentsList.size}") + //println("About to change view to mega") currentView = "mega" - println("View changed to mega") + //println("View changed to mega") } catch (e: Exception) { - println("Error processing opponents data: ${e.message}") + //println("Error processing opponents data: ${e.message}") + Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() } } } catch (e: Exception) { - println("Error calling getOpponents: ${e.message}") + //println("Error calling getOpponents: ${e.message}") + Log.d(TAG,"Error calling getOpponents: ${e.message}") e.printStackTrace() } } @@ -442,6 +900,14 @@ fun BattlesScreen() { backButton() } } + + "battle-main" -> { + + } + + "battle-results" -> { + + } } } } From 9bcbe85b7fca3004e593416cb26370e0f586e5c4 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sat, 2 Aug 2025 06:25:48 -0400 Subject: [PATCH 04/65] Create SpriteImage.kt --- .../nacabaro/vbhelper/battle/SpriteImage.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteImage.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteImage.kt new file mode 100644 index 0000000..7956511 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteImage.kt @@ -0,0 +1,41 @@ +package com.github.nacabaro.vbhelper.battle + +import androidx.compose.foundation.Image +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import com.github.nacabaro.vbhelper.battle.BattleSpriteManager + +@Composable +fun SpriteImage( + spriteName: String, + atlasName: String, + modifier: Modifier = Modifier, + contentScale: ContentScale = ContentScale.Fit +) { + val context = LocalContext.current + val spriteManager = remember { BattleSpriteManager(context) } + + var bitmap by remember { mutableStateOf(null) } + + LaunchedEffect(spriteName, atlasName) { + println("Loading sprite: $spriteName from atlas: $atlasName") + bitmap = spriteManager.loadSprite(spriteName, atlasName) + if (bitmap == null) { + println("Failed to load sprite: $spriteName from atlas: $atlasName") + } else { + println("Successfully loaded sprite: $spriteName from atlas: $atlasName") + } + } + + bitmap?.let { bmp -> + Image( + bitmap = bmp.asImageBitmap(), + contentDescription = "Sprite: $spriteName", + modifier = modifier, + contentScale = contentScale + ) + } +} \ No newline at end of file From c947e2519cb963d07458f33622c7aebb934bed10 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sat, 2 Aug 2025 06:25:54 -0400 Subject: [PATCH 05/65] Create AttackSpriteImage.kt --- .../vbhelper/components/AttackSpriteImage.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/components/AttackSpriteImage.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/components/AttackSpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/components/AttackSpriteImage.kt new file mode 100644 index 0000000..9bf6b12 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/components/AttackSpriteImage.kt @@ -0,0 +1,44 @@ +package com.github.nacabaro.vbhelper.battle + +import android.graphics.Bitmap +import androidx.compose.foundation.Image +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import com.github.nacabaro.vbhelper.battle.AttackSpriteManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +@Composable +fun AttackSpriteImage( + characterId: String, + isLarge: Boolean = false, + modifier: Modifier = Modifier, + contentScale: ContentScale = ContentScale.Fit +) { + var bitmap by remember { mutableStateOf(null) } + val coroutineScope = rememberCoroutineScope() + val context = LocalContext.current + + LaunchedEffect(characterId, isLarge) { + coroutineScope.launch { + val attackSpriteManager = AttackSpriteManager(context) + val loadedBitmap = withContext(Dispatchers.IO) { + attackSpriteManager.getAttackSprite(characterId, isLarge) + } + bitmap = loadedBitmap + } + } + + bitmap?.let { bmp -> + Image( + bitmap = bmp.asImageBitmap(), + contentDescription = "Attack Sprite", + modifier = modifier, + contentScale = contentScale + ) + } +} \ No newline at end of file From 8d9c5076454606c161f9e77ee4e9707245767dfe Mon Sep 17 00:00:00 2001 From: lightheel Date: Sat, 2 Aug 2025 06:25:57 -0400 Subject: [PATCH 06/65] Create SpriteFileManager.kt --- .../vbhelper/battle/SpriteFileManager.kt | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt new file mode 100644 index 0000000..e9769b8 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt @@ -0,0 +1,80 @@ +package com.github.nacabaro.vbhelper.battle + +import android.content.Context +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + +class SpriteFileManager(private val context: Context) { + + fun copySpriteFilesToInternalStorage() { + try { + // Create the base directory + val baseDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") + if (!baseDir.exists()) { + baseDir.mkdirs() + } + + // Copy files from assets to internal storage + copyAssetDirectory("Battle_Sprites_Reference/extracted_assets", baseDir) + + println("Sprite files copied successfully to: ${baseDir.absolutePath}") + + } catch (e: Exception) { + println("Error copying sprite files: ${e.message}") + e.printStackTrace() + } + } + + private fun copyAssetDirectory(assetPath: String, targetDir: File) { + try { + val assetManager = context.assets + val files = assetManager.list(assetPath) ?: return + + for (file in files) { + val assetFilePath = if (assetPath.isEmpty()) file else "$assetPath/$file" + val targetFile = File(targetDir, file) + + // Create subdirectories if needed + if (targetFile.parentFile != null && !targetFile.parentFile!!.exists()) { + targetFile.parentFile!!.mkdirs() + } + + // Check if it's a directory + val subFiles = assetManager.list(assetFilePath) + if (subFiles != null && subFiles.isNotEmpty()) { + // It's a directory, create it and copy contents + if (!targetFile.exists()) { + targetFile.mkdirs() + } + copyAssetDirectory(assetFilePath, targetFile) + } else { + // It's a file, copy it + copyAssetFile(assetFilePath, targetFile) + } + } + } catch (e: Exception) { + println("Error copying asset directory $assetPath: ${e.message}") + } + } + + private fun copyAssetFile(assetPath: String, targetFile: File) { + try { + val inputStream = context.assets.open(assetPath) + val outputStream = FileOutputStream(targetFile) + + inputStream.copyTo(outputStream) + inputStream.close() + outputStream.close() + + println("Copied: $assetPath -> ${targetFile.absolutePath}") + } catch (e: IOException) { + println("Error copying asset file $assetPath: ${e.message}") + } + } + + fun checkSpriteFilesExist(): Boolean { + val baseDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") + return baseDir.exists() && baseDir.listFiles()?.isNotEmpty() == true + } +} \ No newline at end of file From 09ee139addf4868427600675912487e2e5fb60ae Mon Sep 17 00:00:00 2001 From: lightheel Date: Sat, 2 Aug 2025 06:25:59 -0400 Subject: [PATCH 07/65] Create BattleSpriteManager.kt --- .../vbhelper/battle/BattleSpriteManager.kt | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt new file mode 100644 index 0000000..3de61f9 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt @@ -0,0 +1,164 @@ +package com.github.nacabaro.vbhelper.battle + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import com.google.gson.Gson +import java.io.File + +data class SpriteMapping( + val atlas_name: String, + val atlas_file: String, + val texture: TextureInfo, + val sprites: List +) + +data class TextureInfo( + val name: String, + val file: String, + val path_id: Long +) + +data class SpriteData( + val name: String, + val atlas_name: String, + val m_Name: String, + val texture_rect: TextureRect +) + +data class TextureRect( + val height: Float, + val width: Float, + val x: Float, + val y: Float +) + +class BattleSpriteManager(private val context: Context) { + private val gson = Gson() + private val spriteCache = mutableMapOf() + + // Base directory where your sprites are stored + private val spriteBaseDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") + + fun loadSprite(spriteName: String, atlasName: String): Bitmap? { + val cacheKey = "${spriteName}_${atlasName}" + + // Check cache first + if (spriteCache.containsKey(cacheKey)) { + return spriteCache[cacheKey] + } + + // Debug: Check if base directory exists + if (!spriteBaseDir.exists()) { + println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}") + return null + } + + println("Sprite base directory exists: ${spriteBaseDir.absolutePath}") + println("Available directories: ${spriteBaseDir.listFiles()?.map { it.name }}") + + try { + // Load the mapping file + val mappingFile = File(spriteBaseDir, "mappings/${atlasName}_mapping.json") + if (!mappingFile.exists()) { + println("Mapping file not found: ${mappingFile.absolutePath}") + return null + } + + val mappingJson = mappingFile.readText() + val mapping = gson.fromJson(mappingJson, SpriteMapping::class.java) + + // Load the PNG texture file + val textureFile = File(spriteBaseDir, "textures/${mapping.texture.file}") + if (!textureFile.exists()) { + println("Texture file not found: ${textureFile.absolutePath}") + return null + } + + val fullBitmap = BitmapFactory.decodeFile(textureFile.absolutePath) + if (fullBitmap == null) { + println("Failed to decode texture file: ${textureFile.absolutePath}") + return null + } + + // Load the specific sprite data file + val spriteDataFile = File(spriteBaseDir, "sprites/${atlasName}_sprite_${spriteName}.json") + if (!spriteDataFile.exists()) { + println("Sprite data file not found: ${spriteDataFile.absolutePath}") + return null + } + + val spriteDataJson = spriteDataFile.readText() + val spriteData = gson.fromJson(spriteDataJson, SpriteData::class.java) + + // Extract the sprite from the atlas using texture_rect coordinates + val spriteBitmap = Bitmap.createBitmap( + fullBitmap, + spriteData.texture_rect.x.toInt(), + spriteData.texture_rect.y.toInt(), + spriteData.texture_rect.width.toInt(), + spriteData.texture_rect.height.toInt() + ) + + // Cache the result + spriteCache[cacheKey] = spriteBitmap + + return spriteBitmap + + } catch (e: Exception) { + e.printStackTrace() + return null + } + } + + fun clearCache() { + spriteCache.clear() + } + + // Helper method to get available sprites for an atlas + fun getAvailableSprites(atlasName: String): List { + try { + val spritesDir = File(spriteBaseDir, "sprites") + if (!spritesDir.exists()) { + return emptyList() + } + + val spriteFiles = spritesDir.listFiles { file -> + file.name.startsWith("${atlasName}_sprite_") && file.name.endsWith(".json") + } ?: emptyArray() + + return spriteFiles.map { file -> + // Extract sprite number from filename (e.g., "dim000_mon01_sprite_00.json" -> "00") + val spriteNumber = file.name.substringAfter("_sprite_").substringBefore(".json") + spriteNumber + }.sorted() + + } catch (e: Exception) { + e.printStackTrace() + return emptyList() + } + } + + // Helper method to get available atlases + fun getAvailableAtlases(): List { + try { + val mappingsDir = File(spriteBaseDir, "mappings") + if (!mappingsDir.exists()) { + return emptyList() + } + + val mappingFiles = mappingsDir.listFiles { file -> + file.name.endsWith("_mapping.json") + } ?: emptyArray() + + return mappingFiles.map { file -> + // Extract atlas name from filename (e.g., "dim000_mon01_mapping.json" -> "dim000_mon01") + file.name.substringBefore("_mapping.json") + }.sorted() + + } catch (e: Exception) { + e.printStackTrace() + return emptyList() + } + } +} \ No newline at end of file From 1d2115519893be6efd1de580345f60a87fa38f2e Mon Sep 17 00:00:00 2001 From: lightheel Date: Sat, 2 Aug 2025 06:26:01 -0400 Subject: [PATCH 08/65] Create AttackSpriteManager.kt --- .../vbhelper/battle/AttackSpriteManager.kt | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt new file mode 100644 index 0000000..301c35f --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt @@ -0,0 +1,87 @@ +package com.github.nacabaro.vbhelper.battle + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import com.google.gson.Gson +import java.io.File + +data class CharacterData( + val name: String, + val charaId: String, + val smalefilename: String, + val laugeFileName: String +) + +class AttackSpriteManager(private val context: Context) { + private val gson = Gson() + private val characterDataCache = mutableMapOf() + + // Base path for attack textures + private val attackTexturesPath = "Battle_Sprites_Reference/extracted_assets/atk_textures" + + fun getAttackSprite(characterId: String, isLarge: Boolean = false): Bitmap? { + try { + // Get character data + val characterData = getCharacterData(characterId) ?: return null + + // Determine which attack file to use + val attackFileName = if (isLarge) { + characterData.laugeFileName + } else { + characterData.smalefilename + } + + // Skip if no attack file + if (attackFileName == "0") return null + + // Load the attack sprite + val attackFilePath = "$attackTexturesPath/$attackFileName.png" + val attackFile = File(context.filesDir, attackFilePath) + + return if (attackFile.exists()) { + BitmapFactory.decodeFile(attackFile.absolutePath) + } else { + null + } + } catch (e: Exception) { + e.printStackTrace() + return null + } + } + + private fun getCharacterData(characterId: String): CharacterData? { + // Check cache first + if (characterDataCache.containsKey(characterId)) { + return characterDataCache[characterId] + } + + try { + // Load character data from JSON file + val characterDataFile = File(context.filesDir, "Battle_Sprites_Reference/extracted_digimon_stats/character_data/CharacterData.json") + + if (!characterDataFile.exists()) { + return null + } + + val jsonContent = characterDataFile.readText() + // Parse the JSON and find the character with matching charaId + // This is a simplified version - you'll need to parse the actual JSON structure + + // For now, return a default character data + val characterData = CharacterData( + name = characterId, + charaId = characterId, + smalefilename = "atk_s_02", // Default small attack + laugeFileName = "atk_l_04" // Default large attack + ) + + characterDataCache[characterId] = characterData + return characterData + + } catch (e: Exception) { + e.printStackTrace() + return null + } + } +} \ No newline at end of file From a044d24f5f685d440345c42bb7c6520010cab7b2 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sat, 2 Aug 2025 06:26:27 -0400 Subject: [PATCH 09/65] Updated BattleScreen.kt. Now starts battle and allow clicking of first attack button. --- .../vbhelper/screens/BattlesScreen.kt | 149 ++++++++++++++++-- 1 file changed, 140 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index c270d2f..d249003 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -49,6 +49,9 @@ import com.github.nacabaro.vbhelper.battle.APIBattleCharacter import android.util.Log import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.battle.RetrofitHelper +import com.github.nacabaro.vbhelper.battle.SpriteImage +import com.github.nacabaro.vbhelper.battle.AttackSpriteImage +import com.github.nacabaro.vbhelper.battle.SpriteFileManager class ArenaBattleSystem { companion object { @@ -192,6 +195,7 @@ fun BattleScreen( stage: String = "rookie", playerName: String = "Player", opponentName: String = "Opponent", + activeCharacter: APIBattleCharacter? = null, onBattleComplete: (String?) -> Unit = {}, onExitBattle: () -> Unit = {} ) { @@ -243,13 +247,15 @@ fun BattleScreen( battleSystem.startPlayerAttack() // Apply damage after animation battleSystem.applyDamage(false, 20f) // Opponent takes damage - } + }, + activeCharacter = activeCharacter ) 1 -> OpponentBattleView( battleSystem = battleSystem, stage = stage, opponentName = opponentName, - attackAnimationProgress = animationProgress + attackAnimationProgress = animationProgress, + activeCharacter = activeCharacter ) } @@ -272,7 +278,8 @@ fun PlayerBattleView( stage: String, playerName: String, attackAnimationProgress: Float, - onAttackClick: () -> Unit + onAttackClick: () -> Unit, + activeCharacter: APIBattleCharacter? = null ) { Column( modifier = Modifier @@ -321,6 +328,21 @@ fun PlayerBattleView( modifier = Modifier.size(120.dp), contentAlignment = Alignment.Center ) { + SpriteImage( + spriteName = "00", // The sprite number (00, 01, 02, etc.) + atlasName = when (stage) { + "rookie" -> "dim000_mon01" + "champion" -> "dim012_mon04" + "ultimate" -> "dim137_mon09" + "mega" -> "dim012_mon14" + else -> "dim000_mon01" + }, + modifier = Modifier + .size(120.dp) + .scale(2f), + contentScale = ContentScale.Fit + ) + /* Image( painter = painterResource( when (stage) { @@ -337,9 +359,22 @@ fun PlayerBattleView( .scale(2f), contentScale = ContentScale.Fit ) + */ // Attack animation overlay if (attackAnimationProgress > 0) { + AttackSpriteImage( + characterId = activeCharacter?.charaId ?: "dim000_mon03", + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = (attackAnimationProgress * 200 - 100).dp, + y = 0.dp + ), + contentScale = ContentScale.Fit + ) + /* Image( painter = painterResource(R.drawable.atk_l_00), contentDescription = "Attack Animation", @@ -351,6 +386,7 @@ fun PlayerBattleView( ), contentScale = ContentScale.Fit ) + */ } } @@ -387,7 +423,8 @@ fun OpponentBattleView( battleSystem: ArenaBattleSystem, stage: String, opponentName: String, - attackAnimationProgress: Float + attackAnimationProgress: Float, + activeCharacter: APIBattleCharacter? = null ) { Column( modifier = Modifier @@ -436,6 +473,21 @@ fun OpponentBattleView( modifier = Modifier.size(120.dp), contentAlignment = Alignment.Center ) { + SpriteImage( + spriteName = "00", // The sprite number (00, 01, 02, etc.) + atlasName = when (stage) { + "rookie" -> "dim000_mon01" + "champion" -> "dim012_mon04" + "ultimate" -> "dim137_mon09" + "mega" -> "dim012_mon14" + else -> "dim000_mon01" + }, + modifier = Modifier + .size(120.dp) + .scale(2f), + contentScale = ContentScale.Fit + ) + /* Image( painter = painterResource( when (stage) { @@ -452,12 +504,13 @@ fun OpponentBattleView( .scale(-2f, 2f), // Flip horizontally contentScale = ContentScale.Fit ) + */ // Attack animation overlay if (attackAnimationProgress > 0) { - Image( - painter = painterResource(R.drawable.atk_l_00), - contentDescription = "Attack Animation", + AttackSpriteImage( + characterId = activeCharacter?.charaId ?: "dim000_mon03", + isLarge = true, modifier = Modifier .size(60.dp) .offset( @@ -489,6 +542,17 @@ fun BattlesScreen() { var selectedStage by remember { mutableStateOf("") } val context = LocalContext.current + + // Initialize sprite files on first load + LaunchedEffect(Unit) { + val spriteFileManager = SpriteFileManager(context) + if (!spriteFileManager.checkSpriteFilesExist()) { + println("Copying sprite files to internal storage...") + spriteFileManager.copySpriteFilesToInternalStorage() + } else { + println("Sprite files already exist in internal storage") + } + } val rookieButton = @Composable { Button( @@ -772,6 +836,7 @@ fun BattlesScreen() { onClick = { activeCharacter?.let { RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 0, 0, opponent.name, 0) { apiResult -> + currentView = "battle-main" } } }, @@ -808,6 +873,7 @@ fun BattlesScreen() { onClick = { activeCharacter?.let { RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 1, 0, opponent.name, 1) { apiResult -> + currentView = "battle-main" } } }, @@ -844,6 +910,7 @@ fun BattlesScreen() { onClick = { activeCharacter?.let { RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 2, 0, opponent.name, 2) { apiResult -> + currentView = "battle-main" } } }, @@ -880,6 +947,7 @@ fun BattlesScreen() { onClick = { activeCharacter?.let { RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 3, 0, opponent.name, 3) { apiResult -> + currentView = "battle-main" } } }, @@ -902,11 +970,74 @@ fun BattlesScreen() { } "battle-main" -> { - + var battleSystem by remember { mutableStateOf(ArenaBattleSystem()) } + var currentStage by remember { mutableStateOf("rookie") } + + // Initialize battle with character stats + LaunchedEffect(activeCharacter) { + activeCharacter?.let { character -> + battleSystem.initializeBattle( + playerHP = character.currentHp.toFloat(), + opponentHP = character.currentHp.toFloat(), + playerMaxHP = character.baseHp.toFloat(), + opponentMaxHP = character.baseHp.toFloat() + ) + } + } + + BattleScreen( + battleSystem = battleSystem, + stage = currentStage, + playerName = activeCharacter?.name ?: "Player", + opponentName = "Opponent", + activeCharacter = activeCharacter, + onBattleComplete = { winner -> + // Handle battle completion + currentView = "battle-results" + }, + onExitBattle = { + currentView = "main" + } + ) } "battle-results" -> { - + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = "Battle Complete!", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = Color.White + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Results will be displayed here", + fontSize = 16.sp, + color = Color.White + ) + + Spacer(modifier = Modifier.height(32.dp)) + + Button( + onClick = { + currentView = "main" + }, + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Blue + ), + shape = RoundedCornerShape(8.dp) + ) { + Text("Back to Main Menu", color = Color.White, fontSize = 16.sp) + } + } } } } From 6c9d057917ea8d5fe4f45650c7201310d6538ee8 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sat, 2 Aug 2025 07:49:43 -0400 Subject: [PATCH 10/65] Updated client for attack battle loop. --- .../vbhelper/battle/AttackSpriteManager.kt | 85 ++++++++++- .../vbhelper/battle/SpriteFileManager.kt | 33 ++-- .../vbhelper/components/AttackSpriteImage.kt | 2 + .../vbhelper/screens/BattlesScreen.kt | 143 ++++++++++++------ 4 files changed, 198 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt index 301c35f..6131c92 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt @@ -13,6 +13,20 @@ data class CharacterData( val laugeFileName: String ) +data class CharacterDataResponse( + val name: String, + val type: String, + val source_file: String, + val collection: String, + val unity_collection_id: String, + val relative_path: String, + val all_attributes: CharacterDataAttributes +) + +data class CharacterDataAttributes( + val DataList: List +) + class AttackSpriteManager(private val context: Context) { private val gson = Gson() private val characterDataCache = mutableMapOf() @@ -21,9 +35,11 @@ class AttackSpriteManager(private val context: Context) { private val attackTexturesPath = "Battle_Sprites_Reference/extracted_assets/atk_textures" fun getAttackSprite(characterId: String, isLarge: Boolean = false): Bitmap? { + println("AttackSpriteManager: Getting attack sprite for characterId=$characterId, isLarge=$isLarge") try { // Get character data val characterData = getCharacterData(characterId) ?: return null + println("AttackSpriteManager: Got character data: $characterData") // Determine which attack file to use val attackFileName = if (isLarge) { @@ -31,44 +47,99 @@ class AttackSpriteManager(private val context: Context) { } else { characterData.smalefilename } + println("AttackSpriteManager: Attack filename = $attackFileName") // Skip if no attack file - if (attackFileName == "0") return null + if (attackFileName == "0") { + println("AttackSpriteManager: Skipping attack file (filename is '0')") + return null + } // Load the attack sprite val attackFilePath = "$attackTexturesPath/$attackFileName.png" val attackFile = File(context.filesDir, attackFilePath) + println("AttackSpriteManager: Attack file path = ${attackFile.absolutePath}") + println("AttackSpriteManager: Attack file exists = ${attackFile.exists()}") return if (attackFile.exists()) { - BitmapFactory.decodeFile(attackFile.absolutePath) + val bitmap = BitmapFactory.decodeFile(attackFile.absolutePath) + println("AttackSpriteManager: Successfully loaded bitmap = ${bitmap != null}") + bitmap } else { + println("AttackSpriteManager: Attack file does not exist") null } } catch (e: Exception) { + println("AttackSpriteManager: Exception occurred: ${e.message}") e.printStackTrace() return null } } private fun getCharacterData(characterId: String): CharacterData? { + println("AttackSpriteManager: Getting character data for characterId=$characterId") // Check cache first if (characterDataCache.containsKey(characterId)) { + println("AttackSpriteManager: Found character data in cache") return characterDataCache[characterId] } try { // Load character data from JSON file val characterDataFile = File(context.filesDir, "Battle_Sprites_Reference/extracted_digimon_stats/character_data/CharacterData.json") + println("AttackSpriteManager: Character data file path = ${characterDataFile.absolutePath}") + println("AttackSpriteManager: Character data file exists = ${characterDataFile.exists()}") if (!characterDataFile.exists()) { - return null + println("AttackSpriteManager: Character data file does not exist, using default data") + // For now, return a default character data + val characterData = CharacterData( + name = characterId, + charaId = characterId, + smalefilename = "atk_s_02", // Default small attack + laugeFileName = "atk_l_04" // Default large attack + ) + + characterDataCache[characterId] = characterData + return characterData } val jsonContent = characterDataFile.readText() - // Parse the JSON and find the character with matching charaId - // This is a simplified version - you'll need to parse the actual JSON structure + println("AttackSpriteManager: JSON content length = ${jsonContent.length}") - // For now, return a default character data + // Parse the JSON response + val response = gson.fromJson(jsonContent, CharacterDataResponse::class.java) + + // Search through the DataList for the matching characterId + for (characterString in response.all_attributes.DataList) { + // Extract charaId from the string format: " id=0, charaId='dim000_mon03', ...>" + val charaIdMatch = Regex("charaId='([^']+)'").find(characterString) + if (charaIdMatch != null) { + val foundCharaId = charaIdMatch.groupValues[1] + if (foundCharaId == characterId) { + // Extract smalefilename and laugeFileName + val smallFileMatch = Regex("smalefilename='([^']+)'").find(characterString) + val largeFileMatch = Regex("laugeFileName='([^']+)'").find(characterString) + + val smallFileName = smallFileMatch?.groupValues?.get(1) ?: "0" + val largeFileName = largeFileMatch?.groupValues?.get(1) ?: "0" + + val characterData = CharacterData( + name = characterId, + charaId = characterId, + smalefilename = smallFileName, + laugeFileName = largeFileName + ) + + characterDataCache[characterId] = characterData + println("AttackSpriteManager: Found character data: $characterData") + return characterData + } + } + } + + // If character not found, return default data + println("AttackSpriteManager: Character not found in JSON, using default data") val characterData = CharacterData( name = characterId, charaId = characterId, @@ -77,9 +148,11 @@ class AttackSpriteManager(private val context: Context) { ) characterDataCache[characterId] = characterData + println("AttackSpriteManager: Created default character data: $characterData") return characterData } catch (e: Exception) { + println("AttackSpriteManager: Exception in getCharacterData: ${e.message}") e.printStackTrace() return null } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt index e9769b8..56f4667 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt @@ -9,16 +9,26 @@ class SpriteFileManager(private val context: Context) { fun copySpriteFilesToInternalStorage() { try { - // Create the base directory - val baseDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") - if (!baseDir.exists()) { - baseDir.mkdirs() + // Create the base directory for extracted_assets + val extractedAssetsDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") + if (!extractedAssetsDir.exists()) { + extractedAssetsDir.mkdirs() } - // Copy files from assets to internal storage - copyAssetDirectory("Battle_Sprites_Reference/extracted_assets", baseDir) + // Create the base directory for extracted_digimon_stats + val extractedStatsDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_digimon_stats") + if (!extractedStatsDir.exists()) { + extractedStatsDir.mkdirs() + } - println("Sprite files copied successfully to: ${baseDir.absolutePath}") + // Copy extracted_assets files from assets to internal storage + copyAssetDirectory("Battle_Sprites_Reference/extracted_assets", extractedAssetsDir) + + // Copy extracted_digimon_stats files from assets to internal storage + copyAssetDirectory("Battle_Sprites_Reference/extracted_digimon_stats", extractedStatsDir) + + println("Sprite files copied successfully to: ${extractedAssetsDir.absolutePath}") + println("Stats files copied successfully to: ${extractedStatsDir.absolutePath}") } catch (e: Exception) { println("Error copying sprite files: ${e.message}") @@ -74,7 +84,12 @@ class SpriteFileManager(private val context: Context) { } fun checkSpriteFilesExist(): Boolean { - val baseDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") - return baseDir.exists() && baseDir.listFiles()?.isNotEmpty() == true + val extractedAssetsDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") + val extractedStatsDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_digimon_stats") + + val assetsExist = extractedAssetsDir.exists() && extractedAssetsDir.listFiles()?.isNotEmpty() == true + val statsExist = extractedStatsDir.exists() && extractedStatsDir.listFiles()?.isNotEmpty() == true + + return assetsExist && statsExist } } \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/components/AttackSpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/components/AttackSpriteImage.kt index 9bf6b12..51f4ed3 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/components/AttackSpriteImage.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/components/AttackSpriteImage.kt @@ -24,11 +24,13 @@ fun AttackSpriteImage( val context = LocalContext.current LaunchedEffect(characterId, isLarge) { + println("AttackSpriteImage: Loading attack sprite for characterId=$characterId, isLarge=$isLarge") coroutineScope.launch { val attackSpriteManager = AttackSpriteManager(context) val loadedBitmap = withContext(Dispatchers.IO) { attackSpriteManager.getAttackSprite(characterId, isLarge) } + println("AttackSpriteImage: Loaded bitmap = ${loadedBitmap != null}") bitmap = loadedBitmap } } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index d249003..50ad664 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -196,6 +196,7 @@ fun BattleScreen( playerName: String = "Player", opponentName: String = "Opponent", activeCharacter: APIBattleCharacter? = null, + opponentCharacter: APIBattleCharacter? = null, onBattleComplete: (String?) -> Unit = {}, onExitBattle: () -> Unit = {} ) { @@ -232,31 +233,52 @@ fun BattleScreen( } } + // Opponent AI - automatically attack back after player attack + LaunchedEffect(battleSystem.currentView) { + if (battleSystem.currentView == 1 && !battleSystem.isAttacking) { + // Wait a bit before opponent attacks + delay(1000) + if (battleSystem.currentView == 1 && !battleSystem.isBattleOver()) { + println("Opponent attacking back!") + battleSystem.startOpponentAttack() + // Apply damage to player + battleSystem.applyDamage(true, 15f) // Player takes damage + } + } + } + Box( modifier = Modifier .fillMaxSize() .background(Color.Black) ) { + println("Current battle view: ${battleSystem.currentView}") when (battleSystem.currentView) { - 0 -> PlayerBattleView( - battleSystem = battleSystem, - stage = stage, - playerName = playerName, - attackAnimationProgress = animationProgress, - onAttackClick = { - battleSystem.startPlayerAttack() - // Apply damage after animation - battleSystem.applyDamage(false, 20f) // Opponent takes damage - }, - activeCharacter = activeCharacter - ) - 1 -> OpponentBattleView( - battleSystem = battleSystem, - stage = stage, - opponentName = opponentName, - attackAnimationProgress = animationProgress, - activeCharacter = activeCharacter - ) + 0 -> { + println("Showing PlayerBattleView") + PlayerBattleView( + battleSystem = battleSystem, + stage = stage, + playerName = playerName, + attackAnimationProgress = animationProgress, + onAttackClick = { + battleSystem.startPlayerAttack() + // Apply damage after animation + battleSystem.applyDamage(false, 20f) // Opponent takes damage + }, + activeCharacter = activeCharacter + ) + } + 1 -> { + println("Showing OpponentBattleView") + OpponentBattleView( + battleSystem = battleSystem, + stage = stage, + opponentName = opponentName, + attackAnimationProgress = animationProgress, + activeCharacter = opponentCharacter + ) + } } // Exit button @@ -330,7 +352,7 @@ fun PlayerBattleView( ) { SpriteImage( spriteName = "00", // The sprite number (00, 01, 02, etc.) - atlasName = when (stage) { + atlasName = activeCharacter?.charaId ?: when (stage) { "rookie" -> "dim000_mon01" "champion" -> "dim012_mon04" "ultimate" -> "dim137_mon09" @@ -401,8 +423,12 @@ fun PlayerBattleView( ) // Attack button + println("PlayerBattleView: Attack button enabled = ${battleSystem.isAttackButtonEnabled}") Button( - onClick = onAttackClick, + onClick = { + println("Attack button clicked!") + onAttackClick() + }, enabled = battleSystem.isAttackButtonEnabled, modifier = Modifier .fillMaxWidth() @@ -475,7 +501,7 @@ fun OpponentBattleView( ) { SpriteImage( spriteName = "00", // The sprite number (00, 01, 02, etc.) - atlasName = when (stage) { + atlasName = activeCharacter?.charaId ?: when (stage) { "rookie" -> "dim000_mon01" "champion" -> "dim012_mon04" "ultimate" -> "dim137_mon09" @@ -537,6 +563,7 @@ fun BattlesScreen() { var opponentsList by remember { mutableStateOf(ArrayList()) } var activeCharacter by remember { mutableStateOf(null) } + var selectedOpponent by remember { mutableStateOf(null) } var expanded by remember { mutableStateOf(false) } var selectedStage by remember { mutableStateOf("") } @@ -835,6 +862,7 @@ fun BattlesScreen() { Button( onClick = { activeCharacter?.let { + selectedOpponent = opponent RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 0, 0, opponent.name, 0) { apiResult -> currentView = "battle-main" } @@ -872,6 +900,7 @@ fun BattlesScreen() { Button( onClick = { activeCharacter?.let { + selectedOpponent = opponent RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 1, 0, opponent.name, 1) { apiResult -> currentView = "battle-main" } @@ -909,6 +938,7 @@ fun BattlesScreen() { Button( onClick = { activeCharacter?.let { + selectedOpponent = opponent RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 2, 0, opponent.name, 2) { apiResult -> currentView = "battle-main" } @@ -946,6 +976,7 @@ fun BattlesScreen() { Button( onClick = { activeCharacter?.let { + selectedOpponent = opponent RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 3, 0, opponent.name, 3) { apiResult -> currentView = "battle-main" } @@ -970,35 +1001,47 @@ fun BattlesScreen() { } "battle-main" -> { - var battleSystem by remember { mutableStateOf(ArenaBattleSystem()) } - var currentStage by remember { mutableStateOf("rookie") } - - // Initialize battle with character stats - LaunchedEffect(activeCharacter) { - activeCharacter?.let { character -> - battleSystem.initializeBattle( - playerHP = character.currentHp.toFloat(), - opponentHP = character.currentHp.toFloat(), - playerMaxHP = character.baseHp.toFloat(), - opponentMaxHP = character.baseHp.toFloat() - ) - } + val battleSystem = remember { ArenaBattleSystem() } + + // Determine the current stage based on the character's stage + val currentStage = when (activeCharacter?.stage) { + 0 -> "rookie" + 1 -> "champion" + 2 -> "ultimate" + 3 -> "mega" + else -> "rookie" } - - BattleScreen( - battleSystem = battleSystem, - stage = currentStage, - playerName = activeCharacter?.name ?: "Player", - opponentName = "Opponent", - activeCharacter = activeCharacter, - onBattleComplete = { winner -> - // Handle battle completion - currentView = "battle-results" - }, - onExitBattle = { - currentView = "main" - } - ) + + // Initialize battle with character stats + LaunchedEffect(activeCharacter, selectedOpponent) { + activeCharacter?.let { playerCharacter -> + selectedOpponent?.let { opponentCharacter -> + println("Initializing battle with player: ${playerCharacter.name}, opponent: ${opponentCharacter.name}") + battleSystem.initializeBattle( + playerHP = playerCharacter.currentHp.toFloat(), + opponentHP = opponentCharacter.currentHp.toFloat(), + playerMaxHP = playerCharacter.baseHp.toFloat(), + opponentMaxHP = opponentCharacter.baseHp.toFloat() + ) + } + } + } + + BattleScreen( + battleSystem = battleSystem, + stage = currentStage, + playerName = activeCharacter?.name ?: "Player", + opponentName = selectedOpponent?.name ?: "Opponent", + activeCharacter = activeCharacter, + opponentCharacter = selectedOpponent, + onBattleComplete = { winner -> + println("Battle complete! Winner: $winner") + currentView = "battle-results" + }, + onExitBattle = { + currentView = "main" + } + ) } "battle-results" -> { From d86ee001096c14e4190d58dba698e43287d5cb34 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sun, 3 Aug 2025 11:29:23 -0400 Subject: [PATCH 11/65] Update .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index d8d1c1d..7ff3a42 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ app/src/main/res/values/keys.xml app/src/test/resources/com/github/nacabaro/vbhelper/source/com.bandai.vitalbraceletarena.apk app/src/test/resources/com/github/nacabaro/vbhelper/source/classes.dex + +app/src/main/java/com/github/nacabaro/vbhelper/battle/Battle_Sprites_Reference/ + +app/src/main/assets/battle_sprites \ No newline at end of file From fa8546f2830148a81cc7a61fba44dd98805f8256 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sun, 3 Aug 2025 11:29:40 -0400 Subject: [PATCH 12/65] Updated file and sprite managers to use new file structure. --- .../vbhelper/battle/AttackSpriteManager.kt | 4 +- .../vbhelper/battle/BattleSpriteManager.kt | 29 ++++------ .../vbhelper/battle/SpriteFileManager.kt | 56 +++++++++++++++++-- 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt index 6131c92..d44bb43 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt @@ -32,7 +32,7 @@ class AttackSpriteManager(private val context: Context) { private val characterDataCache = mutableMapOf() // Base path for attack textures - private val attackTexturesPath = "Battle_Sprites_Reference/extracted_assets/atk_textures" + private val attackTexturesPath = "battle_sprites/extracted_assets/extracted_atksprites" fun getAttackSprite(characterId: String, isLarge: Boolean = false): Bitmap? { println("AttackSpriteManager: Getting attack sprite for characterId=$characterId, isLarge=$isLarge") @@ -86,7 +86,7 @@ class AttackSpriteManager(private val context: Context) { try { // Load character data from JSON file - val characterDataFile = File(context.filesDir, "Battle_Sprites_Reference/extracted_digimon_stats/character_data/CharacterData.json") + val characterDataFile = File(context.filesDir, "battle_sprites/extracted_digimon_stats/character_data/CharacterData.json") println("AttackSpriteManager: Character data file path = ${characterDataFile.absolutePath}") println("AttackSpriteManager: Character data file exists = ${characterDataFile.exists()}") diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt index 3de61f9..b4bd883 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt @@ -38,7 +38,7 @@ class BattleSpriteManager(private val context: Context) { private val spriteCache = mutableMapOf() // Base directory where your sprites are stored - private val spriteBaseDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") + private val spriteBaseDir = File(context.filesDir, "battle_sprites/extracted_assets") fun loadSprite(spriteName: String, atlasName: String): Bitmap? { val cacheKey = "${spriteName}_${atlasName}" @@ -58,18 +58,9 @@ class BattleSpriteManager(private val context: Context) { println("Available directories: ${spriteBaseDir.listFiles()?.map { it.name }}") try { - // Load the mapping file - val mappingFile = File(spriteBaseDir, "mappings/${atlasName}_mapping.json") - if (!mappingFile.exists()) { - println("Mapping file not found: ${mappingFile.absolutePath}") - return null - } + // Load the PNG texture file directly using the atlas name + val textureFile = File(spriteBaseDir, "extracted_textures/${atlasName}.png") - val mappingJson = mappingFile.readText() - val mapping = gson.fromJson(mappingJson, SpriteMapping::class.java) - - // Load the PNG texture file - val textureFile = File(spriteBaseDir, "textures/${mapping.texture.file}") if (!textureFile.exists()) { println("Texture file not found: ${textureFile.absolutePath}") return null @@ -142,18 +133,18 @@ class BattleSpriteManager(private val context: Context) { // Helper method to get available atlases fun getAvailableAtlases(): List { try { - val mappingsDir = File(spriteBaseDir, "mappings") - if (!mappingsDir.exists()) { + val texturesDir = File(spriteBaseDir, "extracted_textures") + if (!texturesDir.exists()) { return emptyList() } - val mappingFiles = mappingsDir.listFiles { file -> - file.name.endsWith("_mapping.json") + val textureFiles = texturesDir.listFiles { file -> + file.name.endsWith(".png") } ?: emptyArray() - return mappingFiles.map { file -> - // Extract atlas name from filename (e.g., "dim000_mon01_mapping.json" -> "dim000_mon01") - file.name.substringBefore("_mapping.json") + return textureFiles.map { file -> + // Extract atlas name from filename (e.g., "dim000_mon01.png" -> "dim000_mon01") + file.name.substringBefore(".png") }.sorted() } catch (e: Exception) { diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt index 56f4667..0298f3d 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt @@ -10,22 +10,22 @@ class SpriteFileManager(private val context: Context) { fun copySpriteFilesToInternalStorage() { try { // Create the base directory for extracted_assets - val extractedAssetsDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") + val extractedAssetsDir = File(context.filesDir, "battle_sprites/extracted_assets") if (!extractedAssetsDir.exists()) { extractedAssetsDir.mkdirs() } // Create the base directory for extracted_digimon_stats - val extractedStatsDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_digimon_stats") + val extractedStatsDir = File(context.filesDir, "battle_sprites/extracted_digimon_stats") if (!extractedStatsDir.exists()) { extractedStatsDir.mkdirs() } // Copy extracted_assets files from assets to internal storage - copyAssetDirectory("Battle_Sprites_Reference/extracted_assets", extractedAssetsDir) + copyAssetDirectory("battle_sprites/extracted_assets", extractedAssetsDir) // Copy extracted_digimon_stats files from assets to internal storage - copyAssetDirectory("Battle_Sprites_Reference/extracted_digimon_stats", extractedStatsDir) + copyAssetDirectory("battle_sprites/extracted_digimon_stats", extractedStatsDir) println("Sprite files copied successfully to: ${extractedAssetsDir.absolutePath}") println("Stats files copied successfully to: ${extractedStatsDir.absolutePath}") @@ -84,12 +84,56 @@ class SpriteFileManager(private val context: Context) { } fun checkSpriteFilesExist(): Boolean { - val extractedAssetsDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_assets") - val extractedStatsDir = File(context.filesDir, "Battle_Sprites_Reference/extracted_digimon_stats") + val extractedAssetsDir = File(context.filesDir, "battle_sprites/extracted_assets") + val extractedStatsDir = File(context.filesDir, "battle_sprites/extracted_digimon_stats") val assetsExist = extractedAssetsDir.exists() && extractedAssetsDir.listFiles()?.isNotEmpty() == true val statsExist = extractedStatsDir.exists() && extractedStatsDir.listFiles()?.isNotEmpty() == true return assetsExist && statsExist } + + fun clearSpriteFiles() { + try { + val extractedAssetsDir = File(context.filesDir, "battle_sprites/extracted_assets") + val extractedStatsDir = File(context.filesDir, "battle_sprites/extracted_digimon_stats") + + if (extractedAssetsDir.exists()) { + deleteDirectory(extractedAssetsDir) + println("Cleared extracted_assets directory") + } + + if (extractedStatsDir.exists()) { + deleteDirectory(extractedStatsDir) + println("Cleared extracted_digimon_stats directory") + } + + // Also clear the battle_sprites directory if it's empty + val battleSpritesDir = File(context.filesDir, "battle_sprites") + if (battleSpritesDir.exists() && battleSpritesDir.listFiles()?.isEmpty() == true) { + battleSpritesDir.delete() + println("Cleared battle_sprites directory") + } + + } catch (e: Exception) { + println("Error clearing sprite files: ${e.message}") + e.printStackTrace() + } + } + + private fun deleteDirectory(directory: File) { + if (directory.exists()) { + val files = directory.listFiles() + if (files != null) { + for (file in files) { + if (file.isDirectory) { + deleteDirectory(file) + } else { + file.delete() + } + } + } + directory.delete() + } + } } \ No newline at end of file From a4b159da4587293f2fcce04fee38521f2414f5c2 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sun, 3 Aug 2025 11:37:43 -0400 Subject: [PATCH 13/65] Sprites now load correctly for Digimon and attacks. --- .../vbhelper/battle/BattleSpriteManager.kt | 9 +++- .../vbhelper/screens/BattlesScreen.kt | 53 +++++++++++-------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt index b4bd883..0af8f2b 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt @@ -82,11 +82,18 @@ class BattleSpriteManager(private val context: Context) { val spriteDataJson = spriteDataFile.readText() val spriteData = gson.fromJson(spriteDataJson, SpriteData::class.java) + // Debug: Print sprite coordinates + println("Sprite coordinates: x=${spriteData.texture_rect.x}, y=${spriteData.texture_rect.y}, width=${spriteData.texture_rect.width}, height=${spriteData.texture_rect.height}") + println("Texture dimensions: width=${fullBitmap.width}, height=${fullBitmap.height}") + + // Calculate the correct Y coordinate (inverted coordinate system) + val correctedY = fullBitmap.height - spriteData.texture_rect.y.toInt() - spriteData.texture_rect.height.toInt() + // Extract the sprite from the atlas using texture_rect coordinates val spriteBitmap = Bitmap.createBitmap( fullBitmap, spriteData.texture_rect.x.toInt(), - spriteData.texture_rect.y.toInt(), + correctedY, spriteData.texture_rect.width.toInt(), spriteData.texture_rect.height.toInt() ) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 50ad664..8bcdf1a 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -252,10 +252,10 @@ fun BattleScreen( .fillMaxSize() .background(Color.Black) ) { - println("Current battle view: ${battleSystem.currentView}") + //println("Current battle view: ${battleSystem.currentView}") when (battleSystem.currentView) { 0 -> { - println("Showing PlayerBattleView") + //println("Showing PlayerBattleView") PlayerBattleView( battleSystem = battleSystem, stage = stage, @@ -270,8 +270,8 @@ fun BattleScreen( ) } 1 -> { - println("Showing OpponentBattleView") - OpponentBattleView( + //println("Showing OpponentBattleView") + OpponentBattleView( battleSystem = battleSystem, stage = stage, opponentName = opponentName, @@ -353,11 +353,11 @@ fun PlayerBattleView( SpriteImage( spriteName = "00", // The sprite number (00, 01, 02, etc.) atlasName = activeCharacter?.charaId ?: when (stage) { - "rookie" -> "dim000_mon01" + "rookie" -> "dim275_mon01" "champion" -> "dim012_mon04" "ultimate" -> "dim137_mon09" "mega" -> "dim012_mon14" - else -> "dim000_mon01" + else -> "dim275_mon01" }, modifier = Modifier .size(120.dp) @@ -385,17 +385,17 @@ fun PlayerBattleView( // Attack animation overlay if (attackAnimationProgress > 0) { - AttackSpriteImage( - characterId = activeCharacter?.charaId ?: "dim000_mon03", - isLarge = true, - modifier = Modifier - .size(60.dp) - .offset( - x = (attackAnimationProgress * 200 - 100).dp, - y = 0.dp - ), - contentScale = ContentScale.Fit - ) + AttackSpriteImage( + characterId = activeCharacter?.charaId ?: "dim275_mon01", + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = (attackAnimationProgress * 200 - 100).dp, + y = 0.dp + ), + contentScale = ContentScale.Fit + ) /* Image( painter = painterResource(R.drawable.atk_l_00), @@ -423,7 +423,7 @@ fun PlayerBattleView( ) // Attack button - println("PlayerBattleView: Attack button enabled = ${battleSystem.isAttackButtonEnabled}") + //println("PlayerBattleView: Attack button enabled = ${battleSystem.isAttackButtonEnabled}") Button( onClick = { println("Attack button clicked!") @@ -502,11 +502,11 @@ fun OpponentBattleView( SpriteImage( spriteName = "00", // The sprite number (00, 01, 02, etc.) atlasName = activeCharacter?.charaId ?: when (stage) { - "rookie" -> "dim000_mon01" + "rookie" -> "dim275_mon01" "champion" -> "dim012_mon04" "ultimate" -> "dim137_mon09" "mega" -> "dim012_mon14" - else -> "dim000_mon01" + else -> "dim275_mon01" }, modifier = Modifier .size(120.dp) @@ -535,7 +535,7 @@ fun OpponentBattleView( // Attack animation overlay if (attackAnimationProgress > 0) { AttackSpriteImage( - characterId = activeCharacter?.charaId ?: "dim000_mon03", + characterId = activeCharacter?.charaId ?: "dim275_mon01", isLarge = true, modifier = Modifier .size(60.dp) @@ -759,7 +759,7 @@ fun BattlesScreen() { // Create hardcoded character lists for each stage val rookieCharacters = listOf( APIBattleCharacter("AGUMON", "degimon_name_Dim012_003", "dim012_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), - APIBattleCharacter("PULSEMON", "degimon_name_Dim000_003", "dim000_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), + APIBattleCharacter("PULSEMON", "degimon_name_Dim275_001", "dim275_mon01", 0, 1, 1800, 1800, 2400.0f, 700.0f), APIBattleCharacter("DORUMON", "degimon_name_dim137_mon03", "dim137_mon03", 0, 1, 3000, 3000, 5100.0f, 1050.0f) ) @@ -844,6 +844,15 @@ fun BattlesScreen() { championButton() ultimateButton() megaButton() + Button( + onClick = { + val spriteFileManager = SpriteFileManager(context) + spriteFileManager.clearSpriteFiles() + println("Sprite files cleared!") + } + ) { + Text("Clear Sprite Files") + } } } From 266658342ab72686122d7087a2ff9edd17bea4ff Mon Sep 17 00:00:00 2001 From: lightheel Date: Sun, 3 Aug 2025 12:14:24 -0400 Subject: [PATCH 14/65] Fixed sprite sizing. --- .../vbhelper/battle/BattleSpriteManager.kt | 20 +++++++++++++--- .../vbhelper/screens/BattlesScreen.kt | 24 +++++++++---------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt index 0af8f2b..05d0e34 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt @@ -98,10 +98,24 @@ class BattleSpriteManager(private val context: Context) { spriteData.texture_rect.height.toInt() ) - // Cache the result - spriteCache[cacheKey] = spriteBitmap + // Ensure the bitmap is not scaled and has proper quality + val finalBitmap = if (spriteBitmap.width != spriteData.texture_rect.width.toInt() || + spriteBitmap.height != spriteData.texture_rect.height.toInt()) { + // If the bitmap was scaled during creation, create a new one with exact dimensions + Bitmap.createScaledBitmap(spriteBitmap, + spriteData.texture_rect.width.toInt(), + spriteData.texture_rect.height.toInt(), + false) // false = no filtering/interpolation + } else { + spriteBitmap + } - return spriteBitmap + println("Extracted sprite dimensions: ${finalBitmap.width}x${finalBitmap.height}") + + // Cache the result + spriteCache[cacheKey] = finalBitmap + + return finalBitmap } catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 8bcdf1a..df8dbae 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -347,21 +347,20 @@ fun PlayerBattleView( // Player character with attack animation Box( - modifier = Modifier.size(120.dp), + modifier = Modifier.size(80.dp), contentAlignment = Alignment.Center ) { SpriteImage( spriteName = "00", // The sprite number (00, 01, 02, etc.) atlasName = activeCharacter?.charaId ?: when (stage) { - "rookie" -> "dim275_mon01" + "rookie" -> "dim000_mon03" "champion" -> "dim012_mon04" "ultimate" -> "dim137_mon09" "mega" -> "dim012_mon14" - else -> "dim275_mon01" + else -> "dim011_mon01" }, modifier = Modifier - .size(120.dp) - .scale(2f), + .size(80.dp), contentScale = ContentScale.Fit ) /* @@ -386,7 +385,7 @@ fun PlayerBattleView( // Attack animation overlay if (attackAnimationProgress > 0) { AttackSpriteImage( - characterId = activeCharacter?.charaId ?: "dim275_mon01", + characterId = activeCharacter?.charaId ?: "dim011_mon01", isLarge = true, modifier = Modifier .size(60.dp) @@ -496,21 +495,20 @@ fun OpponentBattleView( // Opponent character with attack animation Box( - modifier = Modifier.size(120.dp), + modifier = Modifier.size(80.dp), contentAlignment = Alignment.Center ) { SpriteImage( spriteName = "00", // The sprite number (00, 01, 02, etc.) atlasName = activeCharacter?.charaId ?: when (stage) { - "rookie" -> "dim275_mon01" + "rookie" -> "dim000_mon03" "champion" -> "dim012_mon04" "ultimate" -> "dim137_mon09" "mega" -> "dim012_mon14" - else -> "dim275_mon01" + else -> "dim011_mon01" }, modifier = Modifier - .size(120.dp) - .scale(2f), + .size(80.dp), contentScale = ContentScale.Fit ) /* @@ -535,7 +533,7 @@ fun OpponentBattleView( // Attack animation overlay if (attackAnimationProgress > 0) { AttackSpriteImage( - characterId = activeCharacter?.charaId ?: "dim275_mon01", + characterId = activeCharacter?.charaId ?: "dim011_mon01", isLarge = true, modifier = Modifier .size(60.dp) @@ -759,7 +757,7 @@ fun BattlesScreen() { // Create hardcoded character lists for each stage val rookieCharacters = listOf( APIBattleCharacter("AGUMON", "degimon_name_Dim012_003", "dim012_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), - APIBattleCharacter("PULSEMON", "degimon_name_Dim275_001", "dim275_mon01", 0, 1, 1800, 1800, 2400.0f, 700.0f), + APIBattleCharacter("PULSEMON", "degimon_name_Dim000_003", "dim000_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), APIBattleCharacter("DORUMON", "degimon_name_dim137_mon03", "dim137_mon03", 0, 1, 3000, 3000, 5100.0f, 1050.0f) ) From 2901bcf0da44018a2471ae67c55664ac7200f200 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sun, 3 Aug 2025 12:39:03 -0400 Subject: [PATCH 15/65] Updated attack button to actually perform stage 1 API call. --- .../vbhelper/screens/BattlesScreen.kt | 182 +++++++++++------- 1 file changed, 116 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index df8dbae..ac2cfd5 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -198,7 +198,8 @@ fun BattleScreen( activeCharacter: APIBattleCharacter? = null, opponentCharacter: APIBattleCharacter? = null, onBattleComplete: (String?) -> Unit = {}, - onExitBattle: () -> Unit = {} + onExitBattle: () -> Unit = {}, + context: android.content.Context? = null ) { var animationProgress by remember { mutableStateOf(0f) } @@ -266,7 +267,9 @@ fun BattleScreen( // Apply damage after animation battleSystem.applyDamage(false, 20f) // Opponent takes damage }, - activeCharacter = activeCharacter + activeCharacter = activeCharacter, + context = context, + opponent = opponentCharacter ) } 1 -> { @@ -301,7 +304,9 @@ fun PlayerBattleView( playerName: String, attackAnimationProgress: Float, onAttackClick: () -> Unit, - activeCharacter: APIBattleCharacter? = null + activeCharacter: APIBattleCharacter? = null, + context: android.content.Context? = null, + opponent: APIBattleCharacter? = null ) { Column( modifier = Modifier @@ -347,8 +352,10 @@ fun PlayerBattleView( // Player character with attack animation Box( - modifier = Modifier.size(80.dp), - contentAlignment = Alignment.Center + modifier = Modifier + .fillMaxWidth() + .size(80.dp), + contentAlignment = Alignment.CenterStart ) { SpriteImage( spriteName = "00", // The sprite number (00, 01, 02, etc.) @@ -360,7 +367,8 @@ fun PlayerBattleView( else -> "dim011_mon01" }, modifier = Modifier - .size(80.dp), + .size(80.dp) + .scale(-1f, 1f), // Flip horizontally contentScale = ContentScale.Fit ) /* @@ -426,6 +434,45 @@ fun PlayerBattleView( Button( onClick = { println("Attack button clicked!") + + // Get crit bar progress as float (0.0f to 100.0f) + val critBarProgressFloat = battleSystem.critBarProgress.toFloat() + + // Determine player and opponent stages + val playerStage = when (activeCharacter?.stage) { + 0 -> 0 // rookie + 1 -> 1 // champion + 2 -> 2 // ultimate + 3 -> 3 // mega + else -> 0 + } + + val opponentStage = when (opponent?.stage) { + 0 -> 0 // rookie + 1 -> 1 // champion + 2 -> 2 // ultimate + 3 -> 3 // mega + else -> 0 + } + + // Send API call with all parameters + context?.let { ctx -> + RetrofitHelper().getPVPWinner( + ctx, + 1, + 2, + activeCharacter?.name ?: "Player", + playerStage, + opponentStage, + opponent?.name ?: "Opponent", + opponentStage + ) { apiResult -> + // Handle API response here + println("API Result: $apiResult") + // TODO: Parse response and apply damage/hit/miss logic + } + } + onAttackClick() }, enabled = battleSystem.isAttackButtonEnabled, @@ -495,8 +542,10 @@ fun OpponentBattleView( // Opponent character with attack animation Box( - modifier = Modifier.size(80.dp), - contentAlignment = Alignment.Center + modifier = Modifier + .fillMaxWidth() + .size(80.dp), + contentAlignment = Alignment.CenterEnd ) { SpriteImage( spriteName = "00", // The sprite number (00, 01, 02, etc.) @@ -856,41 +905,41 @@ fun BattlesScreen() { "rookie" -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text("Rookie Battle View") + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Rookie Battle View") - // Add character selection dropdown - characterDropdown("rookie") + // Add character selection dropdown + characterDropdown("rookie") - // Display buttons for each opponent - opponentsList.forEach { opponent -> - Button( - onClick = { - activeCharacter?.let { - selectedOpponent = opponent - RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 0, 0, opponent.name, 0) { apiResult -> - currentView = "battle-main" + // Display buttons for each opponent + opponentsList.forEach { opponent -> + Button( + onClick = { + activeCharacter?.let { + selectedOpponent = opponent + RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 0, 0, opponent.name, 0) { apiResult -> + currentView = "battle-main" + } } - } - }, - modifier = Modifier.padding(vertical = 4.dp) - ) { - Text("Battle ${opponent.name}") + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text("Battle ${opponent.name}") + } } - } - // Show selected character info - activeCharacter?.let { character -> - Text("Active Character: ${character.name}") - Text("HP: ${character.currentHp}/${character.baseHp}") - Text("BP: ${character.baseBp}") - Text("AP: ${character.baseAp}") - } + // Show selected character info + activeCharacter?.let { character -> + Text("Active Character: ${character.name}") + Text("HP: ${character.currentHp}/${character.baseHp}") + Text("BP: ${character.baseBp}") + Text("AP: ${character.baseAp}") + } - backButton() - } + backButton() + } } "champion" -> { @@ -1019,36 +1068,37 @@ fun BattlesScreen() { else -> "rookie" } - // Initialize battle with character stats - LaunchedEffect(activeCharacter, selectedOpponent) { - activeCharacter?.let { playerCharacter -> - selectedOpponent?.let { opponentCharacter -> - println("Initializing battle with player: ${playerCharacter.name}, opponent: ${opponentCharacter.name}") - battleSystem.initializeBattle( - playerHP = playerCharacter.currentHp.toFloat(), - opponentHP = opponentCharacter.currentHp.toFloat(), - playerMaxHP = playerCharacter.baseHp.toFloat(), - opponentMaxHP = opponentCharacter.baseHp.toFloat() - ) - } - } - } + // Initialize battle with character stats + LaunchedEffect(activeCharacter, selectedOpponent) { + activeCharacter?.let { playerCharacter -> + selectedOpponent?.let { opponentCharacter -> + println("Initializing battle with player: ${playerCharacter.name}, opponent: ${opponentCharacter.name}") + battleSystem.initializeBattle( + playerHP = playerCharacter.currentHp.toFloat(), + opponentHP = opponentCharacter.currentHp.toFloat(), + playerMaxHP = playerCharacter.baseHp.toFloat(), + opponentMaxHP = opponentCharacter.baseHp.toFloat() + ) + } + } + } - BattleScreen( - battleSystem = battleSystem, - stage = currentStage, - playerName = activeCharacter?.name ?: "Player", - opponentName = selectedOpponent?.name ?: "Opponent", - activeCharacter = activeCharacter, - opponentCharacter = selectedOpponent, - onBattleComplete = { winner -> - println("Battle complete! Winner: $winner") - currentView = "battle-results" - }, - onExitBattle = { - currentView = "main" - } - ) + BattleScreen( + battleSystem = battleSystem, + stage = currentStage, + playerName = activeCharacter?.name ?: "Player", + opponentName = selectedOpponent?.name ?: "Opponent", + activeCharacter = activeCharacter, + opponentCharacter = selectedOpponent, + onBattleComplete = { winner -> + println("Battle complete! Winner: $winner") + currentView = "battle-results" + }, + onExitBattle = { + currentView = "main" + }, + context = context + ) } "battle-results" -> { From 023af17b230df2c71387e32667d152d7214e1d72 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sun, 3 Aug 2025 13:23:46 -0400 Subject: [PATCH 16/65] Updated BattleScreen to perform full battle loop with API calls. --- .../vbhelper/screens/BattlesScreen.kt | 157 +++++++++++++++++- 1 file changed, 148 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index ac2cfd5..74aec30 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -152,6 +152,13 @@ class ArenaBattleSystem { _critBarProgress = progress } + // Update HP from API response + fun updateHPFromAPI(playerHP: Float, opponentHP: Float) { + _playerCurrentHP = playerHP.coerceAtLeast(0f) + _opponentCurrentHP = opponentHP.coerceAtLeast(0f) + Log.d(TAG, "HP updated from API: Player HP $playerHP, Opponent HP $opponentHP") + } + //Check if battle is over fun isBattleOver(): Boolean { @@ -187,6 +194,18 @@ class ArenaBattleSystem { _attackProgress = 0f Log.d(TAG, "Battle system cleaned up") } + + // Enable attack button + fun enableAttackButton() { + _isAttackButtonEnabled = true + Log.d(TAG, "Attack button enabled") + } + + // Disable attack button + fun disableAttackButton() { + _isAttackButtonEnabled = false + Log.d(TAG, "Attack button disabled") + } } @Composable @@ -288,7 +307,7 @@ fun BattleScreen( Button( onClick = onExitBattle, modifier = Modifier - .align(Alignment.TopEnd) + .align(Alignment.TopCenter) .padding(16.dp), colors = ButtonDefaults.buttonColors(containerColor = Color.Red) ) { @@ -457,6 +476,9 @@ fun PlayerBattleView( // Send API call with all parameters context?.let { ctx -> + // Start player attack animation + battleSystem.startPlayerAttack() + RetrofitHelper().getPVPWinner( ctx, 1, @@ -469,11 +491,66 @@ fun PlayerBattleView( ) { apiResult -> // Handle API response here println("API Result: $apiResult") - // TODO: Parse response and apply damage/hit/miss logic + + // Update HP based on API response + when (apiResult.state) { + 1 -> { + // Match is still ongoing - update HP and continue + println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") + + // Apply the actual damage from API + if (apiResult.playerAttackHit) { + val playerDamage = apiResult.playerAttackDamage.toFloat() + if (playerDamage > 0) { + battleSystem.applyDamage(false, playerDamage) // Opponent takes damage + println("Player attack hit! Damage: $playerDamage") + } + } else { + println("Player attack missed!") + } + + if (apiResult.opponentAttackDamage > 0) { + val opponentDamage = apiResult.opponentAttackDamage.toFloat() + battleSystem.applyDamage(true, opponentDamage) // Player takes damage + println("Opponent attack hit! Damage: $opponentDamage") + } + + // Update HP to match API response + battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) + + // Keep attack button enabled for next round + battleSystem.enableAttackButton() + } + 2 -> { + // Match is over - report winner and complete battle + println("Match over! Winner: ${apiResult.winner}") + println("Final HP - Player: ${apiResult.playerHP}, Opponent: ${apiResult.opponentHP}") + + // Update final HP + battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) + + // Disable attack button since match is over + battleSystem.disableAttackButton() + + // Complete the battle + onAttackClick() + } + -1 -> { + // Error state + println("API Error: ${apiResult.status}") + // Re-enable attack button on error + battleSystem.enableAttackButton() + } + else -> { + println("Unknown state: ${apiResult.state}") + // Re-enable attack button on unknown state + battleSystem.enableAttackButton() + } + } } } - onAttackClick() + // Don't call onAttackClick() here - only call it when match is over (state = 2) }, enabled = battleSystem.isAttackButtonEnabled, modifier = Modifier @@ -1102,6 +1179,59 @@ fun BattlesScreen() { } "battle-results" -> { + var winnerName by remember { mutableStateOf("") } + var isWinnerLoaded by remember { mutableStateOf(false) } + + // Send one more stage 1 call to get winner info, then cleanup + LaunchedEffect(Unit) { + // Determine player and opponent stages + val playerStage = when (activeCharacter?.stage) { + 0 -> 0 // rookie + 1 -> 1 // champion + 2 -> 2 // ultimate + 3 -> 3 // mega + else -> 0 + } + + val opponentStage = when (selectedOpponent?.stage) { + 0 -> 0 // rookie + 1 -> 1 // champion + 2 -> 2 // ultimate + 3 -> 3 // mega + else -> 0 + } + + // First, send one more stage 1 call to get winner info + RetrofitHelper().getPVPWinner( + context, + 1, + 2, + activeCharacter?.name ?: "Player", + playerStage, + opponentStage, + selectedOpponent?.name ?: "Opponent", + opponentStage + ) { winnerResult -> + println("Winner API Result: $winnerResult") + winnerName = winnerResult.winner + isWinnerLoaded = true + + // Now send cleanup call + RetrofitHelper().getPVPWinner( + context, + 2, + 2, + activeCharacter?.name ?: "Player", + playerStage, + opponentStage, + selectedOpponent?.name ?: "Opponent", + opponentStage + ) { cleanupResult -> + println("Cleanup API Result: $cleanupResult") + } + } + } + Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center @@ -1110,16 +1240,25 @@ fun BattlesScreen() { text = "Battle Complete!", fontSize = 24.sp, fontWeight = FontWeight.Bold, - color = Color.White + color = Color.Gray ) Spacer(modifier = Modifier.height(16.dp)) - Text( - text = "Results will be displayed here", - fontSize = 16.sp, - color = Color.White - ) + if (isWinnerLoaded) { + Text( + text = "Winner: $winnerName", + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = Color.Gray + ) + } else { + Text( + text = "Loading results...", + fontSize = 16.sp, + color = Color.Gray + ) + } Spacer(modifier = Modifier.height(32.dp)) From 3b762d6195c7fbff8efcc43fe9b25dbf04301d06 Mon Sep 17 00:00:00 2001 From: lightheel Date: Sun, 3 Aug 2025 15:25:37 -0400 Subject: [PATCH 17/65] Updated battle loop. Started to fix attack animations. --- .../vbhelper/screens/BattlesScreen.kt | 340 +++++++++++------- 1 file changed, 215 insertions(+), 125 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 74aec30..53814cc 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -10,14 +10,14 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.offset 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.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment 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.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -70,6 +70,10 @@ class ArenaBattleSystem { private var _isAttackVisible by mutableStateOf(false) private var _critBarProgress by mutableStateOf(0) 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 val playerCurrentHP: Float get() = _playerCurrentHP @@ -82,6 +86,10 @@ class ArenaBattleSystem { val isAttackVisible: Boolean get() = _isAttackVisible val critBarProgress: Int get() = _critBarProgress val isAttackButtonEnabled: Boolean get() = _isAttackButtonEnabled + + // Attack animation state + val attackIsHit: Boolean get() = _attackIsHit + val isPlayerAttacking: Boolean get() = _isPlayerAttacking //Initialize battle with character data fun initializeBattle( @@ -110,6 +118,8 @@ class ArenaBattleSystem { _currentView = 0 _isAttackVisible = true _isAttackButtonEnabled = false + _isPlayerAttacking = true + _attackProgress = 0f Log.d(TAG, "Player attack started") } @@ -118,6 +128,8 @@ class ArenaBattleSystem { _isAttacking = true _currentView = 1 _isAttackVisible = true + _isPlayerAttacking = false + _attackProgress = 0f Log.d(TAG, "Opponent attack started") } @@ -127,12 +139,23 @@ class ArenaBattleSystem { } //Complete attack animation - fun completeAttackAnimation() { + fun completeAttackAnimation(playerDamage: Float = 0f, opponentDamage: Float = 0f) { _isAttacking = false _isAttackVisible = false _attackProgress = 0f - _currentView = if (_currentView == 0) 1 else 0 - _isAttackButtonEnabled = true + _attackIsHit = false + + // 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") } @@ -206,6 +229,18 @@ class ArenaBattleSystem { _isAttackButtonEnabled = false 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 @@ -221,6 +256,9 @@ fun BattleScreen( context: android.content.Context? = null ) { 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 LaunchedEffect(Unit) { @@ -232,10 +270,19 @@ fun BattleScreen( } } - // Attack animation - LaunchedEffect(battleSystem.isAttacking) { - if (battleSystem.isAttacking) { + // Player attack animation + LaunchedEffect(battleSystem.isAttacking, battleSystem.isPlayerAttacking) { + if (battleSystem.isAttacking && battleSystem.isPlayerAttacking) { 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( initialValue = 0f, targetValue = 1f, @@ -243,26 +290,83 @@ fun BattleScreen( ) { value, _ -> animationProgress = 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 if (battleSystem.isBattleOver()) { onBattleComplete(battleSystem.getWinner()) } } } - - // Opponent AI - automatically attack back after player attack - LaunchedEffect(battleSystem.currentView) { - if (battleSystem.currentView == 1 && !battleSystem.isAttacking) { - // Wait a bit before opponent attacks - delay(1000) - if (battleSystem.currentView == 1 && !battleSystem.isBattleOver()) { - println("Opponent attacking back!") - battleSystem.startOpponentAttack() - // Apply damage to player - battleSystem.applyDamage(true, 15f) // Player takes damage + + // Enemy attack trigger (separate from animation) + LaunchedEffect(currentAttackType) { + if (currentAttackType == "player") { + // Player attack just completed - trigger enemy attack after delay + delay(500) // Wait before enemy attacks back + println("Starting enemy attack") + battleSystem.startOpponentAttack() + println("Enemy attack triggered - LaunchedEffect should re-trigger") + } + } + + // 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, onAttackClick = { battleSystem.startPlayerAttack() - // Apply damage after animation - battleSystem.applyDamage(false, 20f) // Opponent takes damage + // Damage will be applied at end of animation via pending damage system }, activeCharacter = activeCharacter, context = context, - opponent = opponentCharacter + opponent = opponentCharacter, + onSetPendingDamage = { playerDamage, opponentDamage -> + pendingPlayerDamage = playerDamage + pendingOpponentDamage = opponentDamage + } ) } 1 -> { @@ -325,7 +432,8 @@ fun PlayerBattleView( onAttackClick: () -> Unit, activeCharacter: APIBattleCharacter? = null, context: android.content.Context? = null, - opponent: APIBattleCharacter? = null + opponent: APIBattleCharacter? = null, + onSetPendingDamage: (Float, Float) -> Unit ) { Column( modifier = Modifier @@ -390,51 +498,37 @@ fun PlayerBattleView( .scale(-1f, 1f), // Flip horizontally 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 if (attackAnimationProgress > 0) { - AttackSpriteImage( - characterId = activeCharacter?.charaId ?: "dim011_mon01", - isLarge = true, - modifier = Modifier - .size(60.dp) - .offset( - x = (attackAnimationProgress * 200 - 100).dp, - y = 0.dp - ), - contentScale = ContentScale.Fit - ) - /* - Image( - painter = painterResource(R.drawable.atk_l_00), - contentDescription = "Attack Animation", - modifier = Modifier - .size(60.dp) - .offset( - x = (attackAnimationProgress * 200 - 100).dp, - y = 0.dp - ), - contentScale = ContentScale.Fit - ) - */ + // Show player attack on both player and enemy screens + // Show enemy attack on both enemy and player screens + val shouldShowAttack = true // Always show attack during animation + + if (shouldShowAttack) { + val xOffset = if (battleSystem.isPlayerAttacking) { + // Player attack: start from player position (left side) and fly to right edge + (attackAnimationProgress * 400 - 200).dp + } else { + // Enemy attack: start from right edge and fly to left edge + (-attackAnimationProgress * 400 + 200).dp + } + + println("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 + ) + } } } @@ -494,40 +588,37 @@ fun PlayerBattleView( // Update HP based on API response when (apiResult.state) { - 1 -> { - // Match is still ongoing - update HP and continue - println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") - - // Apply the actual damage from API - if (apiResult.playerAttackHit) { - val playerDamage = apiResult.playerAttackDamage.toFloat() - if (playerDamage > 0) { - battleSystem.applyDamage(false, playerDamage) // Opponent takes damage - println("Player attack hit! Damage: $playerDamage") - } - } else { - println("Player attack missed!") - } - - if (apiResult.opponentAttackDamage > 0) { - val opponentDamage = apiResult.opponentAttackDamage.toFloat() - battleSystem.applyDamage(true, opponentDamage) // Player takes damage - println("Opponent attack hit! Damage: $opponentDamage") - } - - // Update HP to match API response - battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) - - // Keep attack button enabled for next round - battleSystem.enableAttackButton() - } + 1 -> { + // Match is still ongoing - update HP and continue + println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") + + // Handle damage timing based on hit/miss + if (apiResult.playerAttackHit) { + // Player attack hit - enemy takes damage at end of player animation + // Player will dodge enemy attack + println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") + onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage + battleSystem.setAttackHitState(true) + } else { + // 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 + battleSystem.setAttackHitState(false) + } + + // Don't update HP immediately - wait for animation to complete + // battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) + + // Keep attack button enabled for next round + battleSystem.enableAttackButton() + } 2 -> { // Match is over - report winner and complete battle println("Match over! Winner: ${apiResult.winner}") println("Final HP - Player: ${apiResult.playerHP}, Opponent: ${apiResult.opponentHP}") - // Update final HP - battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) + // Don't update HP immediately - let animation complete first + // battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) // Disable attack button since match is over battleSystem.disableAttackButton() @@ -637,38 +728,37 @@ fun OpponentBattleView( .size(80.dp), 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 if (attackAnimationProgress > 0) { - AttackSpriteImage( - characterId = activeCharacter?.charaId ?: "dim011_mon01", - isLarge = true, - modifier = Modifier - .size(60.dp) - .offset( - x = (-attackAnimationProgress * 200 + 100).dp, - y = 0.dp - ), - contentScale = ContentScale.Fit - ) + // Show player attack on both player and enemy screens + // Show enemy attack on both enemy and player screens + val shouldShowAttack = true // Always show attack during animation + + if (shouldShowAttack) { + val xOffset = if (battleSystem.isPlayerAttacking) { + // Player attack: start from left edge and fly to right edge + (attackAnimationProgress * 400 - 200).dp + } else { + // 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 + ) + } } } From 942d8436011466ba2ff63d93ae8ef2c07450df0f Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 10:04:05 -0400 Subject: [PATCH 18/65] Create ArenaBattleSystem.kt --- .../vbhelper/battle/ArenaBattleSystem.kt | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt new file mode 100644 index 0000000..2c3fc37 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt @@ -0,0 +1,134 @@ +package com.github.nacabaro.vbhelper.battle + +import android.util.Log +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue + +class ArenaBattleSystem { + companion object { + private const val TAG = "ArenaBattleSystem" + } + + // Attack phases: 0=Idle, 1=Player attack on player screen, 2=Player attack on opponent screen, + // 3=Opponent attack on opponent screen, 4=Opponent attack on player screen + private var _attackPhase by mutableStateOf(0) + val attackPhase: Int get() = _attackPhase + + private var _attackProgress by mutableStateOf(0f) + val attackProgress: Float get() = _attackProgress + + private var _isPlayerAttacking by mutableStateOf(false) + val isPlayerAttacking: Boolean get() = _isPlayerAttacking + + private var _attackIsHit by mutableStateOf(false) + val attackIsHit: Boolean get() = _attackIsHit + + private var _isAttackButtonEnabled by mutableStateOf(true) + val isAttackButtonEnabled: Boolean get() = _isAttackButtonEnabled + + private var _currentView by mutableStateOf(0) + val currentView: Int get() = _currentView + + private var _playerHP by mutableStateOf(100f) + val playerHP: Float get() = _playerHP + + private var _opponentHP by mutableStateOf(100f) + val opponentHP: Float get() = _opponentHP + + private var _isBattleOver by mutableStateOf(false) + val isBattleOver: Boolean get() = _isBattleOver + + private var _critBarProgress by mutableStateOf(0) + val critBarProgress: Int get() = _critBarProgress + + fun startPlayerAttack() { + Log.d(TAG, "Starting player attack") + _attackPhase = 1 + _attackProgress = 0f + _isPlayerAttacking = true + _isAttackButtonEnabled = false + _currentView = 0 + } + + fun startOpponentAttack() { + Log.d(TAG, "Starting opponent attack") + _attackPhase = 3 + _attackProgress = 0f + _isPlayerAttacking = false + _currentView = 1 + } + + fun advanceAttackPhase() { + _attackPhase++ + _attackProgress = 0f + Log.d(TAG, "Advanced to attack phase: $_attackPhase") + } + + fun setAttackProgress(progress: Float) { + _attackProgress = progress + } + + fun setAttackHitState(isHit: Boolean) { + _attackIsHit = isHit + } + + fun switchToView(view: Int) { + _currentView = view + Log.d(TAG, "Switched to view: $view") + } + + fun enableAttackButton() { + _isAttackButtonEnabled = true + Log.d(TAG, "Attack button enabled") + } + + fun applyDamage(isPlayer: Boolean, damage: Float) { + if (isPlayer) { + _playerHP = (_playerHP - damage).coerceAtLeast(0f) + } else { + _opponentHP = (_opponentHP - damage).coerceAtLeast(0f) + } + Log.d(TAG, "Applied damage: ${if (isPlayer) "player" else "opponent"} -$damage") + } + + fun updateHPFromAPI(playerHP: Float, opponentHP: Float) { + _playerHP = playerHP + _opponentHP = opponentHP + Log.d(TAG, "Updated HP from API: Player=$playerHP, Opponent=$opponentHP") + } + + fun completeAttackAnimation(playerDamage: Float = 0f, opponentDamage: Float = 0f) { + if (playerDamage > 0f) { + applyDamage(true, playerDamage) + } + if (opponentDamage > 0f) { + applyDamage(false, opponentDamage) + } + Log.d(TAG, "Completed attack animation with damage: Player=$playerDamage, Opponent=$opponentDamage") + } + + fun resetAttackState() { + _attackPhase = 0 + _attackProgress = 0f + _isPlayerAttacking = false + _attackIsHit = false + _currentView = 0 + Log.d(TAG, "Reset attack state") + } + + fun checkBattleOver(): Boolean { + return _playerHP <= 0f || _opponentHP <= 0f + } + + fun endBattle() { + _isBattleOver = true + Log.d(TAG, "Battle ended") + } + + fun updateCritBarProgress(progress: Int) { + _critBarProgress = progress + Log.d(TAG, "Updated crit bar progress: $progress") + } +} \ No newline at end of file From d7b10b1ae8d261d01e43abb03cb63f210a7cfb8d Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 10:04:30 -0400 Subject: [PATCH 19/65] Update BattlesScreen.kt --- .../vbhelper/screens/BattlesScreen.kt | 1103 +++++------------ 1 file changed, 315 insertions(+), 788 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 53814cc..b354253 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -10,14 +10,12 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.Spacer -//import androidx.compose.foundation.Image import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -//import androidx.compose.ui.res.painterResource import androidx.compose.material3.Button import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -36,358 +34,133 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.foundation.layout.Box import androidx.compose.ui.platform.LocalContext import androidx.compose.runtime.LaunchedEffect -import androidx.compose.animation.core.animate +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween -//import androidx.compose.animation.core.animateFloatAsState import kotlinx.coroutines.delay import androidx.compose.foundation.background import androidx.compose.ui.graphics.Color import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import com.github.nacabaro.vbhelper.battle.APIBattleCharacter -//import com.github.nacabaro.vbhelper.battle.BattleSpriteManager import android.util.Log import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.battle.RetrofitHelper import com.github.nacabaro.vbhelper.battle.SpriteImage import com.github.nacabaro.vbhelper.battle.AttackSpriteImage import com.github.nacabaro.vbhelper.battle.SpriteFileManager - -class ArenaBattleSystem { - companion object { - private const val TAG = "VBArenaBattleSystem" - const val ANIMATION_DURATION = 1500L - } - - // Battle state - private var _playerCurrentHP by mutableStateOf(100f) - private var _playerMaxHP by mutableStateOf(100f) - private var _opponentCurrentHP by mutableStateOf(100f) - private var _opponentMaxHP by mutableStateOf(100f) - private var _isAttacking by mutableStateOf(false) - private var _attackProgress by mutableStateOf(0f) - private var _currentView by mutableStateOf(0) // 0 = player, 1 = opponent - private var _isAttackVisible by mutableStateOf(false) - private var _critBarProgress by mutableStateOf(0) - 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 - val playerCurrentHP: Float get() = _playerCurrentHP - val playerMaxHP: Float get() = _playerMaxHP - val opponentCurrentHP: Float get() = _opponentCurrentHP - val opponentMaxHP: Float get() = _opponentMaxHP - val isAttacking: Boolean get() = _isAttacking - val attackProgress: Float get() = _attackProgress - val currentView: Int get() = _currentView - val isAttackVisible: Boolean get() = _isAttackVisible - val critBarProgress: Int get() = _critBarProgress - val isAttackButtonEnabled: Boolean get() = _isAttackButtonEnabled - - // Attack animation state - val attackIsHit: Boolean get() = _attackIsHit - val isPlayerAttacking: Boolean get() = _isPlayerAttacking - - //Initialize battle with character data - fun initializeBattle( - playerHP: Float = 100f, - opponentHP: Float = 100f, - playerMaxHP: Float = 100f, - opponentMaxHP: Float = 100f - ) { - _playerCurrentHP = playerHP - _playerMaxHP = playerMaxHP - _opponentCurrentHP = opponentHP - _opponentMaxHP = opponentMaxHP - _currentView = 0 - _isAttacking = false - _attackProgress = 0f - _isAttackVisible = false - _critBarProgress = 0 - _isAttackButtonEnabled = true - - Log.d(TAG, "Battle initialized: Player HP $playerHP/$playerMaxHP, Opponent HP $opponentHP/$opponentMaxHP") - } - - //Start player attack - fun startPlayerAttack() { - _isAttacking = true - _currentView = 0 - _isAttackVisible = true - _isAttackButtonEnabled = false - _isPlayerAttacking = true - _attackProgress = 0f - Log.d(TAG, "Player attack started") - } - - //Start opponent attack - fun startOpponentAttack() { - _isAttacking = true - _currentView = 1 - _isAttackVisible = true - _isPlayerAttacking = false - _attackProgress = 0f - Log.d(TAG, "Opponent attack started") - } - - //Update attack animation progress - fun updateAttackAnimation(progress: Float) { - _attackProgress = progress - } - - //Complete attack animation - fun completeAttackAnimation(playerDamage: Float = 0f, opponentDamage: Float = 0f) { - _isAttacking = false - _isAttackVisible = false - _attackProgress = 0f - _attackIsHit = false - - // 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") - } - - //Apply damage to player or opponent - fun applyDamage(isPlayer: Boolean, damage: Float) { - if (isPlayer) { - _playerCurrentHP = (_playerCurrentHP - damage).coerceAtLeast(0f) - Log.d(TAG, "Player took $damage damage. HP: ${_playerCurrentHP}/${_playerMaxHP}") - } else { - _opponentCurrentHP = (_opponentCurrentHP - damage).coerceAtLeast(0f) - Log.d(TAG, "Opponent took $damage damage. HP: ${_opponentCurrentHP}/${_opponentMaxHP}") - } - } - - //Update critical bar progress - fun updateCritBarProgress(progress: Int) { - _critBarProgress = progress - } - - // Update HP from API response - fun updateHPFromAPI(playerHP: Float, opponentHP: Float) { - _playerCurrentHP = playerHP.coerceAtLeast(0f) - _opponentCurrentHP = opponentHP.coerceAtLeast(0f) - Log.d(TAG, "HP updated from API: Player HP $playerHP, Opponent HP $opponentHP") - } - - - //Check if battle is over - fun isBattleOver(): Boolean { - return _playerCurrentHP <= 0f || _opponentCurrentHP <= 0f - } - - //Get battle winner - fun getWinner(): String? { - return when { - _playerCurrentHP <= 0f -> "opponent" - _opponentCurrentHP <= 0f -> "player" - else -> null - } - } - - //Reset battle state - fun resetBattle() { - _playerCurrentHP = _playerMaxHP - _opponentCurrentHP = _opponentMaxHP - _isAttacking = false - _attackProgress = 0f - _currentView = 0 - _isAttackVisible = false - _critBarProgress = 0 - _isAttackButtonEnabled = true - Log.d(TAG, "Battle reset") - } - - //Clean up resources - fun cleanup() { - _isAttacking = false - _isAttackVisible = false - _attackProgress = 0f - Log.d(TAG, "Battle system cleaned up") - } - - // Enable attack button - fun enableAttackButton() { - _isAttackButtonEnabled = true - Log.d(TAG, "Attack button enabled") - } - - // Disable attack button - fun disableAttackButton() { - _isAttackButtonEnabled = false - 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") - } -} +import com.github.nacabaro.vbhelper.battle.ArenaBattleSystem @Composable fun BattleScreen( - battleSystem: ArenaBattleSystem, - stage: String = "rookie", - playerName: String = "Player", - opponentName: String = "Opponent", - activeCharacter: APIBattleCharacter? = null, - opponentCharacter: APIBattleCharacter? = null, - onBattleComplete: (String?) -> Unit = {}, - onExitBattle: () -> Unit = {}, + stage: String, + playerName: String, + opponentName: String, + activeCharacter: APIBattleCharacter?, + opponentCharacter: APIBattleCharacter?, + onAttackClick: () -> Unit, context: android.content.Context? = null ) { - var animationProgress by remember { mutableStateOf(0f) } + val battleSystem = remember { ArenaBattleSystem() } + + // Pending damage state for API integration 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 LaunchedEffect(Unit) { while (true) { delay(30) - if (!battleSystem.isAttacking) { + if (battleSystem.attackPhase == 0) { // Only update when not attacking battleSystem.updateCritBarProgress((battleSystem.critBarProgress + 5) % 101) } } } - - // Player attack animation - LaunchedEffect(battleSystem.isAttacking, battleSystem.isPlayerAttacking) { - if (battleSystem.isAttacking && battleSystem.isPlayerAttacking) { - 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( - initialValue = 0f, - targetValue = 1f, - animationSpec = tween(ArenaBattleSystem.ANIMATION_DURATION.toInt()) - ) { value, _ -> - animationProgress = value - battleSystem.updateAttackAnimation(value) - println("Animation progress: $value") - } - - 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 - if (battleSystem.isBattleOver()) { - onBattleComplete(battleSystem.getWinner()) - } - } - } - // Enemy attack trigger (separate from animation) - LaunchedEffect(currentAttackType) { - if (currentAttackType == "player") { - // Player attack just completed - trigger enemy attack after delay - delay(500) // Wait before enemy attacks back - println("Starting enemy attack") - battleSystem.startOpponentAttack() - println("Enemy attack triggered - LaunchedEffect should re-trigger") - } - } - - // 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") + // Animation for attack phases + LaunchedEffect(battleSystem.attackPhase) { + when (battleSystem.attackPhase) { + 1 -> { + // Phase 1: Player attack on player screen + println("Starting Phase 1: Player attack on player screen") + var progress = 0f + while (progress < 1f) { + progress += 0.016f // 60 FPS + battleSystem.setAttackProgress(progress) + delay(16) // 60 FPS + } + println("Phase 1 completed, advancing to Phase 2") + battleSystem.advanceAttackPhase() } - - 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()) + 2 -> { + // Phase 2: Player attack on opponent screen + println("Starting Phase 2: Player attack on opponent screen") + battleSystem.switchToView(1) + var progress = 0f + while (progress < 1f) { + progress += 0.016f // 60 FPS + battleSystem.setAttackProgress(progress) + delay(16) // 60 FPS + } + println("Phase 2 completed, applying damage and starting opponent attack") + // Apply player's damage and start opponent attack + battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) + pendingOpponentDamage = 0f + delay(500) + battleSystem.startOpponentAttack() + } + 3 -> { + // Phase 3: Opponent attack on opponent screen + println("Starting Phase 3: Opponent attack on opponent screen") + battleSystem.switchToView(1) + var progress = 0f + while (progress < 1f) { + progress += 0.016f // 60 FPS + battleSystem.setAttackProgress(progress) + delay(16) // 60 FPS + } + println("Phase 3 completed, advancing to Phase 4") + battleSystem.advanceAttackPhase() + } + 4 -> { + // Phase 4: Opponent attack on player screen + println("Starting Phase 4: Opponent attack on player screen") + battleSystem.switchToView(0) + var progress = 0f + while (progress < 1f) { + progress += 0.016f // 60 FPS + battleSystem.setAttackProgress(progress) + delay(16) // 60 FPS + } + println("Phase 4 completed, applying damage and resetting") + // Apply opponent's damage and reset + battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) + pendingPlayerDamage = 0f + battleSystem.resetAttackState() + battleSystem.enableAttackButton() + + // Check if battle is over + if (battleSystem.checkBattleOver()) { + battleSystem.endBattle() + onAttackClick() + } } } } Box( - modifier = Modifier - .fillMaxSize() - .background(Color.Black) + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center ) { - //println("Current battle view: ${battleSystem.currentView}") when (battleSystem.currentView) { 0 -> { - //println("Showing PlayerBattleView") + // Player view PlayerBattleView( battleSystem = battleSystem, stage = stage, playerName = playerName, - attackAnimationProgress = animationProgress, + attackAnimationProgress = battleSystem.attackProgress, onAttackClick = { battleSystem.startPlayerAttack() - // Damage will be applied at end of animation via pending damage system }, activeCharacter = activeCharacter, context = context, @@ -399,27 +172,16 @@ fun BattleScreen( ) } 1 -> { - //println("Showing OpponentBattleView") - OpponentBattleView( - battleSystem = battleSystem, - stage = stage, - opponentName = opponentName, - attackAnimationProgress = animationProgress, - activeCharacter = opponentCharacter - ) + // Opponent view + OpponentBattleView( + battleSystem = battleSystem, + stage = stage, + opponentName = opponentName, + attackAnimationProgress = battleSystem.attackProgress, + activeCharacter = opponentCharacter + ) } } - - // Exit button - Button( - onClick = onExitBattle, - modifier = Modifier - .align(Alignment.TopCenter) - .padding(16.dp), - colors = ButtonDefaults.buttonColors(containerColor = Color.Red) - ) { - Text("Exit", color = Color.White) - } } } @@ -430,54 +192,18 @@ fun PlayerBattleView( playerName: String, attackAnimationProgress: Float, onAttackClick: () -> Unit, - activeCharacter: APIBattleCharacter? = null, - context: android.content.Context? = null, - opponent: APIBattleCharacter? = null, + activeCharacter: APIBattleCharacter?, + context: android.content.Context?, + opponent: APIBattleCharacter?, onSetPendingDamage: (Float, Float) -> Unit ) { Column( modifier = Modifier .fillMaxSize() .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween + horizontalAlignment = Alignment.CenterHorizontally ) { - // Health display - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Column { - Text("Current HP", color = Color.White, fontSize = 12.sp) - Text( - text = battleSystem.playerCurrentHP.toInt().toString(), - color = Color.White, - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) - } - Column { - Text("Max HP", color = Color.White, fontSize = 12.sp) - Text( - text = battleSystem.playerMaxHP.toInt().toString(), - color = Color.White, - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) - } - } - - // Health bar - LinearProgressIndicator( - progress = (battleSystem.playerCurrentHP / battleSystem.playerMaxHP).coerceIn(0f, 1f), - modifier = Modifier - .fillMaxWidth() - .height(20.dp), - color = Color.Green, - trackColor = Color.Gray - ) - - // Player character with attack animation + // Player Digimon Box( modifier = Modifier .fillMaxWidth() @@ -485,50 +211,44 @@ fun PlayerBattleView( contentAlignment = Alignment.CenterStart ) { SpriteImage( - spriteName = "00", // The sprite number (00, 01, 02, etc.) - atlasName = activeCharacter?.charaId ?: when (stage) { - "rookie" -> "dim000_mon03" - "champion" -> "dim012_mon04" - "ultimate" -> "dim137_mon09" - "mega" -> "dim012_mon14" - else -> "dim011_mon01" - }, + spriteName = activeCharacter?.charaId ?: "dim011_mon01", + atlasName = "dim011_mon01", modifier = Modifier .size(80.dp) - .scale(-1f, 1f), // Flip horizontally + .scale(-1f, 1f), // Flip player Digimon horizontally contentScale = ContentScale.Fit ) - - // Attack animation overlay - if (attackAnimationProgress > 0) { - // Show player attack on both player and enemy screens - // Show enemy attack on both enemy and player screens - val shouldShowAttack = true // Always show attack during animation - - if (shouldShowAttack) { - val xOffset = if (battleSystem.isPlayerAttacking) { - // Player attack: start from player position (left side) and fly to right edge - (attackAnimationProgress * 400 - 200).dp - } else { - // Enemy attack: start from right edge and fly to left edge - (-attackAnimationProgress * 400 + 200).dp - } - - println("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 - ) + + // Attack sprite visibility and positioning based on attack phase + val shouldShowAttack = when (battleSystem.attackPhase) { + 1 -> true // Player attack on player screen + 2 -> true // Player attack on opponent screen + 3 -> false // Opponent attack on opponent screen + 4 -> false // Opponent attack on player screen + else -> false + } + + if (shouldShowAttack) { + val xOffset = when (battleSystem.attackPhase) { + 1 -> (attackAnimationProgress * 400 - 200).dp // Player attack on player screen + 2 -> (attackAnimationProgress * 400 - 200).dp // Player attack on opponent screen + else -> 0.dp } + + println("PlayerBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") + + AttackSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = xOffset, + y = 0.dp + ) + .scale(-1f, 1f), // Flip player attacks + contentScale = ContentScale.Fit + ) } } @@ -543,7 +263,6 @@ fun PlayerBattleView( ) // Attack button - //println("PlayerBattleView: Attack button enabled = ${battleSystem.isAttackButtonEnabled}") Button( onClick = { println("Attack button clicked!") @@ -595,7 +314,6 @@ fun PlayerBattleView( // Handle damage timing based on hit/miss if (apiResult.playerAttackHit) { // Player attack hit - enemy takes damage at end of player animation - // Player will dodge enemy attack println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage battleSystem.setAttackHitState(true) @@ -606,42 +324,24 @@ fun PlayerBattleView( battleSystem.setAttackHitState(false) } - // Don't update HP immediately - wait for animation to complete - // battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) - - // Keep attack button enabled for next round + // Update HP from API + battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) + } + 2 -> { + // Match is over - transition to results screen + println("Match is over! Winner: ${apiResult.winner}") + battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) + onAttackClick() // This will transition to battle-results screen + } + -1 -> { + // Error occurred + println("API Error: ${apiResult.status}") + battleSystem.resetAttackState() battleSystem.enableAttackButton() } - 2 -> { - // Match is over - report winner and complete battle - println("Match over! Winner: ${apiResult.winner}") - println("Final HP - Player: ${apiResult.playerHP}, Opponent: ${apiResult.opponentHP}") - - // Don't update HP immediately - let animation complete first - // battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) - - // Disable attack button since match is over - battleSystem.disableAttackButton() - - // Complete the battle - onAttackClick() - } - -1 -> { - // Error state - println("API Error: ${apiResult.status}") - // Re-enable attack button on error - battleSystem.enableAttackButton() - } - else -> { - println("Unknown state: ${apiResult.state}") - // Re-enable attack button on unknown state - battleSystem.enableAttackButton() - } - } - } + } + } } - - // Don't call onAttackClick() here - only call it when match is over (state = 2) }, enabled = battleSystem.isAttackButtonEnabled, modifier = Modifier @@ -671,44 +371,9 @@ fun OpponentBattleView( .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween + verticalArrangement = Arrangement.Center ) { - // Health display - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Column { - Text("Current HP", color = Color.White, fontSize = 12.sp) - Text( - text = battleSystem.opponentCurrentHP.toInt().toString(), - color = Color.White, - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) - } - Column { - Text("Max HP", color = Color.White, fontSize = 12.sp) - Text( - text = battleSystem.opponentMaxHP.toInt().toString(), - color = Color.White, - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) - } - } - - // Health bar - LinearProgressIndicator( - progress = (battleSystem.opponentCurrentHP / battleSystem.opponentMaxHP).coerceIn(0f, 1f), - modifier = Modifier - .fillMaxWidth() - .height(20.dp), - color = Color.Green, - trackColor = Color.Gray - ) - - // Opponent character with attack animation + // Opponent Digimon Box( modifier = Modifier .fillMaxWidth() @@ -716,49 +381,42 @@ fun OpponentBattleView( contentAlignment = Alignment.CenterEnd ) { SpriteImage( - spriteName = "00", // The sprite number (00, 01, 02, etc.) - atlasName = activeCharacter?.charaId ?: when (stage) { - "rookie" -> "dim000_mon03" - "champion" -> "dim012_mon04" - "ultimate" -> "dim137_mon09" - "mega" -> "dim012_mon14" - else -> "dim011_mon01" - }, - modifier = Modifier - .size(80.dp), + spriteName = activeCharacter?.charaId ?: "dim011_mon01", + atlasName = "dim011_mon01", + modifier = Modifier.size(80.dp), contentScale = ContentScale.Fit ) - - // Attack animation overlay - if (attackAnimationProgress > 0) { - // Show player attack on both player and enemy screens - // Show enemy attack on both enemy and player screens - val shouldShowAttack = true // Always show attack during animation - - if (shouldShowAttack) { - val xOffset = if (battleSystem.isPlayerAttacking) { - // Player attack: start from left edge and fly to right edge - (attackAnimationProgress * 400 - 200).dp - } else { - // 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 - ) + + // Attack sprite visibility and positioning based on attack phase + val shouldShowAttack = when (battleSystem.attackPhase) { + 1 -> false // Player attack on player screen + 2 -> true // Player attack on opponent screen + 3 -> true // Opponent attack on opponent screen + 4 -> false // Opponent attack on player screen + else -> false + } + + if (shouldShowAttack) { + val xOffset = when (battleSystem.attackPhase) { + 2 -> (attackAnimationProgress * 400 - 200).dp // Player attack on opponent screen + 3 -> (-attackAnimationProgress * 400 + 200).dp // Opponent attack on opponent screen + else -> 0.dp } + + println("OpponentBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") + + 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 + ) } } @@ -781,6 +439,7 @@ fun BattlesScreen() { var expanded by remember { mutableStateOf(false) } var selectedStage by remember { mutableStateOf("") } + var currentStage by remember { mutableStateOf("rookie") } val context = LocalContext.current @@ -798,35 +457,18 @@ fun BattlesScreen() { val rookieButton = @Composable { Button( onClick = { - //println("Rookie button clicked - starting API call") try { RetrofitHelper().getOpponents(context, "rookie") { opponents -> - //println("API call completed successfully") try { - //println("Received opponents data: $opponents") - //println("Opponents list size: ${opponents.opponentsList.size}") - - // For loop to check opponents and print their names - //for (opponent in opponents.opponentsList) { - // println("Opponent: ${opponent.name}") - //} - - // Store the opponents in your ArrayList opponentsList.clear() opponentsList.addAll(opponents.opponentsList) - - //println("Updated opponentsList size: ${opponentsList.size}") - //println("About to change view to rookie") currentView = "rookie" - //println("View changed to rookie") } catch (e: Exception) { - //println("Error processing opponents data: ${e.message}") Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() } } } catch (e: Exception) { - //println("Error calling getOpponents: ${e.message}") Log.d(TAG,"Error calling getOpponents: ${e.message}") e.printStackTrace() } @@ -839,35 +481,18 @@ fun BattlesScreen() { val championButton = @Composable { Button( onClick = { - //println("Champion button clicked - starting API call") try { RetrofitHelper().getOpponents(context, "champion") { opponents -> - //println("API call completed successfully") try { - //println("Received opponents data: $opponents") - //println("Opponents list size: ${opponents.opponentsList.size}") - - // For loop to check opponents and print their names - //for (opponent in opponents.opponentsList) { - // println("Opponent: ${opponent.name}") - //} - - // Store the opponents in your ArrayList opponentsList.clear() opponentsList.addAll(opponents.opponentsList) - - //println("Updated opponentsList size: ${opponentsList.size}") - //println("About to change view to champion") currentView = "champion" - //println("View changed to champion") } catch (e: Exception) { - //println("Error processing opponents data: ${e.message}") Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() } } } catch (e: Exception) { - //println("Error calling getOpponents: ${e.message}") Log.d(TAG,"Error calling getOpponents: ${e.message}") e.printStackTrace() } @@ -880,35 +505,18 @@ fun BattlesScreen() { val ultimateButton = @Composable { Button( onClick = { - //println("Ultimate button clicked - starting API call") try { RetrofitHelper().getOpponents(context, "ultimate") { opponents -> - //println("API call completed successfully") try { - //println("Received opponents data: $opponents") - //println("Opponents list size: ${opponents.opponentsList.size}") - - // For loop to check opponents and print their names - //for (opponent in opponents.opponentsList) { - // println("Opponent: ${opponent.name}") - //} - - // Store the opponents in your ArrayList opponentsList.clear() opponentsList.addAll(opponents.opponentsList) - - //println("Updated opponentsList size: ${opponentsList.size}") - //println("About to change view to ultimate") currentView = "ultimate" - //println("View changed to ultimate") } catch (e: Exception) { - //println("Error processing opponents data: ${e.message}") Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() } } } catch (e: Exception) { - //println("Error calling getOpponents: ${e.message}") Log.d(TAG,"Error calling getOpponents: ${e.message}") e.printStackTrace() } @@ -921,35 +529,18 @@ fun BattlesScreen() { val megaButton = @Composable { Button( onClick = { - //println("Mega button clicked - starting API call") try { RetrofitHelper().getOpponents(context, "mega") { opponents -> - //println("API call completed successfully") try { - //println("Received opponents data: $opponents") - //println("Opponents list size: ${opponents.opponentsList.size}") - - // For loop to check opponents and print their names - //for (opponent in opponents.opponentsList) { - // println("Opponent: ${opponent.name}") - //} - - // Store the opponents in your ArrayList opponentsList.clear() opponentsList.addAll(opponents.opponentsList) - - //println("Updated opponentsList size: ${opponentsList.size}") - //println("About to change view to mega") currentView = "mega" - //println("View changed to mega") } catch (e: Exception) { - //println("Error processing opponents data: ${e.message}") Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() } } } catch (e: Exception) { - //println("Error calling getOpponents: ${e.message}") Log.d(TAG,"Error calling getOpponents: ${e.message}") e.printStackTrace() } @@ -961,49 +552,13 @@ fun BattlesScreen() { val backButton = @Composable { Button( - onClick = { - currentView = "main" - } + onClick = { currentView = "main" } ) { Text("Back") } } val characterDropdown = @Composable { currentStage: String -> - // Create hardcoded character lists for each stage - val rookieCharacters = listOf( - APIBattleCharacter("AGUMON", "degimon_name_Dim012_003", "dim012_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), - APIBattleCharacter("PULSEMON", "degimon_name_Dim000_003", "dim000_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), - APIBattleCharacter("DORUMON", "degimon_name_dim137_mon03", "dim137_mon03", 0, 1, 3000, 3000, 5100.0f, 1050.0f) - ) - - val championCharacters = listOf( - APIBattleCharacter("GREYMON","degimon_name_Dim012_004","dim012_mon04",1,1,2000, 2000, 3000.0f,900.0f), - APIBattleCharacter("TYRANNOMON","degimon_name_Dim008_006","dim008_mon06",1,3,2000, 2000, 2400.0f,600.0f), - APIBattleCharacter("DORUGAMON","degimon_name_dim137_mon05","dim137_mon05",1,3,3500, 3500, 5200.0f,1200.0f) - ) - - val ultimateCharacters = listOf( - APIBattleCharacter("METALGREYMON (VIRUS)","degimon_name_Dim014_005","dim014_mon05",2,2,2640, 2640, 2450.0f,800.0f), - APIBattleCharacter("MAMEMON", "degimon_name_Dim000_005", "dim000_mon05", 2, 1, 3000, 3000, 4000.0f, 1000.0f), - APIBattleCharacter("DORUGREYMON","degimon_name_dim137_mon09","dim137_mon09",2,3,5000, 5000, 6400.0f,1400.0f) - ) - - val megaCharacters = listOf( - APIBattleCharacter("WARGREYMON","degimon_name_Dim012_014","dim012_mon14",3,1,3080, 3080, 3825.0f,800.0f), - APIBattleCharacter("SLAYERDRAMON","degimon_name_dim129_mon15","dim129_mon15",3,1,4800, 4800, 6300.0f,1950.0f), - APIBattleCharacter("BREAKDRAMON","degimon_name_dim129_mon17","dim129_mon17",3,2,6000, 6000, 4000.0f,1980.0f) - ) - - // Get the appropriate character list based on current stage - val characterList = when (currentStage.lowercase()) { - "rookie" -> rookieCharacters - "champion" -> championCharacters - "ultimate" -> ultimateCharacters - "mega" -> megaCharacters - else -> rookieCharacters - } - ExposedDropdownMenuBox( expanded = expanded, onExpandedChange = { expanded = !expanded } @@ -1070,7 +625,6 @@ fun BattlesScreen() { } } - "rookie" -> { Column( horizontalAlignment = Alignment.CenterHorizontally @@ -1110,159 +664,129 @@ fun BattlesScreen() { } "champion" -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text("Champion Battle View") + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Champion Battle View") - // Add character selection dropdown - characterDropdown("champion") + // Add character selection dropdown + characterDropdown("champion") - // Display buttons for each opponent - opponentsList.forEach { opponent -> - Button( - onClick = { - activeCharacter?.let { - selectedOpponent = opponent - RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 1, 0, opponent.name, 1) { apiResult -> - currentView = "battle-main" + // Display buttons for each opponent + opponentsList.forEach { opponent -> + Button( + onClick = { + activeCharacter?.let { + selectedOpponent = opponent + RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 1, 0, opponent.name, 1) { apiResult -> + currentView = "battle-main" + } } - } - }, - modifier = Modifier.padding(vertical = 4.dp) - ) { - Text("Battle ${opponent.name}") + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text("Battle ${opponent.name}") + } } - } - // Show selected character info - activeCharacter?.let { character -> - Text("Active Character: ${character.name}") - Text("HP: ${character.currentHp}/${character.baseHp}") - Text("BP: ${character.baseBp}") - Text("AP: ${character.baseAp}") - } + // Show selected character info + activeCharacter?.let { character -> + Text("Active Character: ${character.name}") + Text("HP: ${character.currentHp}/${character.baseHp}") + Text("BP: ${character.baseBp}") + Text("AP: ${character.baseAp}") + } - backButton() - } + backButton() + } } "ultimate" -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text("Ultimate Battle View") + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Ultimate Battle View") - // Add character selection dropdown - characterDropdown("ultimate") + // Add character selection dropdown + characterDropdown("ultimate") - // Display buttons for each opponent - opponentsList.forEach { opponent -> - Button( - onClick = { - activeCharacter?.let { - selectedOpponent = opponent - RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 2, 0, opponent.name, 2) { apiResult -> - currentView = "battle-main" + // Display buttons for each opponent + opponentsList.forEach { opponent -> + Button( + onClick = { + activeCharacter?.let { + selectedOpponent = opponent + RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 2, 0, opponent.name, 2) { apiResult -> + currentView = "battle-main" + } } - } - }, - modifier = Modifier.padding(vertical = 4.dp) - ) { - Text("Battle ${opponent.name}") + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text("Battle ${opponent.name}") + } } - } - // Show selected character info - activeCharacter?.let { character -> - Text("Active Character: ${character.name}") - Text("HP: ${character.currentHp}/${character.baseHp}") - Text("BP: ${character.baseBp}") - Text("AP: ${character.baseAp}") - } + // Show selected character info + activeCharacter?.let { character -> + Text("Active Character: ${character.name}") + Text("HP: ${character.currentHp}/${character.baseHp}") + Text("BP: ${character.baseBp}") + Text("AP: ${character.baseAp}") + } - backButton() - } + backButton() + } } "mega" -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text("Mega Battle View") + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Mega Battle View") - // Add character selection dropdown - characterDropdown("mega") + // Add character selection dropdown + characterDropdown("mega") - // Display buttons for each opponent - opponentsList.forEach { opponent -> - Button( - onClick = { - activeCharacter?.let { - selectedOpponent = opponent - RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 3, 0, opponent.name, 3) { apiResult -> - currentView = "battle-main" + // Display buttons for each opponent + opponentsList.forEach { opponent -> + Button( + onClick = { + activeCharacter?.let { + selectedOpponent = opponent + RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 3, 0, opponent.name, 3) { apiResult -> + currentView = "battle-main" + } } - } - }, - modifier = Modifier.padding(vertical = 4.dp) - ) { - Text("Battle ${opponent.name}") + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text("Battle ${opponent.name}") + } } - } - // Show selected character info - activeCharacter?.let { character -> - Text("Active Character: ${character.name}") - Text("HP: ${character.currentHp}/${character.baseHp}") - Text("BP: ${character.baseBp}") - Text("AP: ${character.baseAp}") - } + // Show selected character info + activeCharacter?.let { character -> + Text("Active Character: ${character.name}") + Text("HP: ${character.currentHp}/${character.baseHp}") + Text("BP: ${character.baseBp}") + Text("AP: ${character.baseAp}") + } - backButton() - } + backButton() + } } "battle-main" -> { - val battleSystem = remember { ArenaBattleSystem() } - - // Determine the current stage based on the character's stage - val currentStage = when (activeCharacter?.stage) { - 0 -> "rookie" - 1 -> "champion" - 2 -> "ultimate" - 3 -> "mega" - else -> "rookie" - } - - // Initialize battle with character stats - LaunchedEffect(activeCharacter, selectedOpponent) { - activeCharacter?.let { playerCharacter -> - selectedOpponent?.let { opponentCharacter -> - println("Initializing battle with player: ${playerCharacter.name}, opponent: ${opponentCharacter.name}") - battleSystem.initializeBattle( - playerHP = playerCharacter.currentHp.toFloat(), - opponentHP = opponentCharacter.currentHp.toFloat(), - playerMaxHP = playerCharacter.baseHp.toFloat(), - opponentMaxHP = opponentCharacter.baseHp.toFloat() - ) - } - } - } - BattleScreen( - battleSystem = battleSystem, stage = currentStage, playerName = activeCharacter?.name ?: "Player", opponentName = selectedOpponent?.name ?: "Opponent", activeCharacter = activeCharacter, opponentCharacter = selectedOpponent, - onBattleComplete = { winner -> - println("Battle complete! Winner: $winner") - currentView = "battle-results" - }, - onExitBattle = { - currentView = "main" + onAttackClick = { + // This will be called when the battle is over + currentView = "battle-results" }, context = context ) @@ -1272,7 +796,6 @@ fun BattlesScreen() { var winnerName by remember { mutableStateOf("") } var isWinnerLoaded by remember { mutableStateOf(false) } - // Send one more stage 1 call to get winner info, then cleanup LaunchedEffect(Unit) { // Determine player and opponent stages val playerStage = when (activeCharacter?.stage) { @@ -1291,7 +814,7 @@ fun BattlesScreen() { else -> 0 } - // First, send one more stage 1 call to get winner info + // First get the winner info RetrofitHelper().getPVPWinner( context, 1, @@ -1301,12 +824,11 @@ fun BattlesScreen() { opponentStage, selectedOpponent?.name ?: "Opponent", opponentStage - ) { winnerResult -> - println("Winner API Result: $winnerResult") - winnerName = winnerResult.winner + ) { apiResult -> + winnerName = apiResult.winner ?: "Unknown" isWinnerLoaded = true - // Now send cleanup call + // Then send the cleanup call RetrofitHelper().getPVPWinner( context, 2, @@ -1317,54 +839,50 @@ fun BattlesScreen() { selectedOpponent?.name ?: "Opponent", opponentStage ) { cleanupResult -> - println("Cleanup API Result: $cleanupResult") + println("Cleanup call completed") } } } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center ) { - Text( - text = "Battle Complete!", - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = Color.Gray - ) - - Spacer(modifier = Modifier.height(16.dp)) - - if (isWinnerLoaded) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { Text( - text = "Winner: $winnerName", - fontSize = 18.sp, + text = "Battle Complete!", + fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color.Gray ) - } else { - Text( - text = "Loading results...", - fontSize = 16.sp, - color = Color.Gray - ) + + Spacer(modifier = Modifier.height(16.dp)) + + if (isWinnerLoaded) { + Text( + text = "Winner: $winnerName", + fontSize = 20.sp, + color = Color.Gray + ) + } else { + Text( + text = "Loading results...", + fontSize = 20.sp, + color = Color.Gray + ) + } } - Spacer(modifier = Modifier.height(32.dp)) - + // Exit button Button( - onClick = { - currentView = "main" - }, - modifier = Modifier - .fillMaxWidth() - .height(50.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.Blue - ), - shape = RoundedCornerShape(8.dp) + onClick = { currentView = "main" }, + modifier = Modifier.align(Alignment.TopCenter), + colors = ButtonDefaults.buttonColors(containerColor = Color.Red) ) { - Text("Back to Main Menu", color = Color.White, fontSize = 16.sp) + Text("Exit", color = Color.White) } } } @@ -1372,3 +890,12 @@ fun BattlesScreen() { } } } + +// Character list for dropdown +val characterList = listOf( + APIBattleCharacter("Agumon", "agumon", "dim011_mon01", 0, 0, 100, 100, 50f, 50f), + APIBattleCharacter("Gabumon", "gabumon", "dim012_mon02", 0, 0, 90, 90, 45f, 55f), + APIBattleCharacter("Biyomon", "biyomon", "dim013_mon03", 0, 0, 85, 85, 40f, 60f), + APIBattleCharacter("Tentomon", "tentomon", "dim014_mon04", 0, 0, 95, 95, 55f, 45f), + APIBattleCharacter("Palmon", "palmon", "dim015_mon05", 0, 0, 88, 88, 42f, 58f) +) \ No newline at end of file From b3a4ced28d039ef8998297fa015d7d8de9260e08 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 10:48:06 -0400 Subject: [PATCH 20/65] Commented out crit bar progress to stop log noise. --- .../com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt index 2c3fc37..fa8d84e 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt @@ -129,6 +129,6 @@ class ArenaBattleSystem { fun updateCritBarProgress(progress: Int) { _critBarProgress = progress - Log.d(TAG, "Updated crit bar progress: $progress") + //Log.d(TAG, "Updated crit bar progress: $progress") } } \ No newline at end of file From 4e12962c055a833e03d2e0cdf693ef70d1673f02 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 10:48:21 -0400 Subject: [PATCH 21/65] Fixed sprite naming bug. --- .../vbhelper/battle/BattleSpriteManager.kt | 2 +- .../vbhelper/screens/BattlesScreen.kt | 49 ++++++++++++++----- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt index 05d0e34..5347c5e 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt @@ -73,7 +73,7 @@ class BattleSpriteManager(private val context: Context) { } // Load the specific sprite data file - val spriteDataFile = File(spriteBaseDir, "sprites/${atlasName}_sprite_${spriteName}.json") + val spriteDataFile = File(spriteBaseDir, "sprites/${spriteName}_sprite_00.json") if (!spriteDataFile.exists()) { println("Sprite data file not found: ${spriteDataFile.absolutePath}") return null diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index b354253..8fc2d47 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -212,7 +212,7 @@ fun PlayerBattleView( ) { SpriteImage( spriteName = activeCharacter?.charaId ?: "dim011_mon01", - atlasName = "dim011_mon01", + atlasName = activeCharacter?.charaId ?: "dim011_mon01", modifier = Modifier .size(80.dp) .scale(-1f, 1f), // Flip player Digimon horizontally @@ -382,7 +382,7 @@ fun OpponentBattleView( ) { SpriteImage( spriteName = activeCharacter?.charaId ?: "dim011_mon01", - atlasName = "dim011_mon01", + atlasName = activeCharacter?.charaId ?: "dim011_mon01", modifier = Modifier.size(80.dp), contentScale = ContentScale.Fit ) @@ -441,6 +441,40 @@ fun BattlesScreen() { var selectedStage by remember { mutableStateOf("") } var currentStage by remember { mutableStateOf("rookie") } + // Create hardcoded character lists for each stage + val rookieCharacters = listOf( + APIBattleCharacter("AGUMON", "degimon_name_Dim012_003", "dim012_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), + APIBattleCharacter("PULSEMON", "degimon_name_Dim000_003", "dim000_mon03", 0, 1, 1800, 1800, 2400.0f, 700.0f), + APIBattleCharacter("DORUMON", "degimon_name_dim137_mon03", "dim137_mon03", 0, 1, 3000, 3000, 5100.0f, 1050.0f) + ) + + val championCharacters = listOf( + APIBattleCharacter("GREYMON","degimon_name_Dim012_004","dim012_mon04",1,1,2000, 2000, 3000.0f,900.0f), + APIBattleCharacter("TYRANNOMON","degimon_name_Dim008_006","dim008_mon06",1,3,2000, 2000, 2400.0f,600.0f), + APIBattleCharacter("DORUGAMON","degimon_name_dim137_mon05","dim137_mon05",1,3,3500, 3500, 5200.0f,1200.0f) + ) + + val ultimateCharacters = listOf( + APIBattleCharacter("METALGREYMON (VIRUS)","degimon_name_Dim014_005","dim014_mon05",2,2,2640, 2640, 2450.0f,800.0f), + APIBattleCharacter("MAMEMON", "degimon_name_Dim000_005", "dim000_mon05", 2, 1, 3000, 3000, 4000.0f, 1000.0f), + APIBattleCharacter("DORUGREYMON","degimon_name_dim137_mon09","dim137_mon09",2,3,5000, 5000, 6400.0f,1400.0f) + ) + + val megaCharacters = listOf( + APIBattleCharacter("WARGREYMON","degimon_name_Dim012_014","dim012_mon14",3,1,3080, 3080, 3825.0f,800.0f), + APIBattleCharacter("SLAYERDRAMON","degimon_name_dim129_mon15","dim129_mon15",3,1,4800, 4800, 6300.0f,1950.0f), + APIBattleCharacter("BREAKDRAMON","degimon_name_dim129_mon17","dim129_mon17",3,2,6000, 6000, 4000.0f,1980.0f) + ) + + // Get the appropriate character list based on current stage + val characterList = when (currentStage.lowercase()) { + "rookie" -> rookieCharacters + "champion" -> championCharacters + "ultimate" -> ultimateCharacters + "mega" -> megaCharacters + else -> rookieCharacters + } + val context = LocalContext.current // Initialize sprite files on first load @@ -889,13 +923,4 @@ fun BattlesScreen() { } } } -} - -// Character list for dropdown -val characterList = listOf( - APIBattleCharacter("Agumon", "agumon", "dim011_mon01", 0, 0, 100, 100, 50f, 50f), - APIBattleCharacter("Gabumon", "gabumon", "dim012_mon02", 0, 0, 90, 90, 45f, 55f), - APIBattleCharacter("Biyomon", "biyomon", "dim013_mon03", 0, 0, 85, 85, 40f, 60f), - APIBattleCharacter("Tentomon", "tentomon", "dim014_mon04", 0, 0, 95, 95, 55f, 45f), - APIBattleCharacter("Palmon", "palmon", "dim015_mon05", 0, 0, 88, 88, 42f, 58f) -) \ No newline at end of file +} \ No newline at end of file From 7af8e00e6fc891c5a0f5bc2f73c03d7f8f1328a5 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 11:02:51 -0400 Subject: [PATCH 22/65] Re-added missing exit button and health bars. --- .../vbhelper/battle/ArenaBattleSystem.kt | 6 ++ .../vbhelper/screens/BattlesScreen.kt | 95 +++++++++++++++++-- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt index fa8d84e..09999f7 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt @@ -99,6 +99,12 @@ class ArenaBattleSystem { Log.d(TAG, "Updated HP from API: Player=$playerHP, Opponent=$opponentHP") } + fun initializeHP(playerMaxHP: Float, opponentMaxHP: Float) { + _playerHP = playerMaxHP + _opponentHP = opponentMaxHP + Log.d(TAG, "Initialized HP: Player=$playerMaxHP, Opponent=$opponentMaxHP") + } + fun completeAttackAnimation(playerDamage: Float = 0f, opponentDamage: Float = 0f) { if (playerDamage > 0f) { applyDamage(true, playerDamage) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 8fc2d47..af5ef13 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -62,6 +62,13 @@ fun BattleScreen( ) { val battleSystem = remember { ArenaBattleSystem() } + // Initialize HP when battle starts + LaunchedEffect(activeCharacter, opponentCharacter) { + val playerMaxHP = activeCharacter?.baseHp?.toFloat() ?: 100f + val opponentMaxHP = opponentCharacter?.baseHp?.toFloat() ?: 100f + battleSystem.initializeHP(playerMaxHP, opponentMaxHP) + } + // Pending damage state for API integration var pendingPlayerDamage by remember { mutableStateOf(0f) } var pendingOpponentDamage by remember { mutableStateOf(0f) } @@ -197,13 +204,29 @@ fun PlayerBattleView( opponent: APIBattleCharacter?, onSetPendingDamage: (Float, Float) -> Unit ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally + Box( + modifier = Modifier.fillMaxSize() ) { - // Player Digimon + // Exit button at the top-right + Button( + onClick = { /* TODO: Add exit functionality */ }, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(16.dp), + colors = ButtonDefaults.buttonColors(containerColor = Color.Red) + ) { + Text("Exit", color = Color.White, fontSize = 14.sp) + } + + // Main content + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + // Player Digimon Box( modifier = Modifier .fillMaxWidth() @@ -262,6 +285,46 @@ fun PlayerBattleView( trackColor = Color.Gray ) + Spacer(modifier = Modifier.height(16.dp)) + + // Health bar + LinearProgressIndicator( + progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Green, + trackColor = Color.Gray + ) + + // Health display numbers + Text( + text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = 14.sp, + color = Color.Black + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Enemy HP bar + LinearProgressIndicator( + progress = battleSystem.opponentHP / (opponent?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Red, + trackColor = Color.Gray + ) + + // Enemy HP display numbers + Text( + text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponent?.baseHp ?: 100}", + fontSize = 14.sp, + color = Color.Black + ) + + Spacer(modifier = Modifier.height(16.dp)) + // Attack button Button( onClick = { @@ -355,6 +418,9 @@ fun PlayerBattleView( ) { Text("Attack", color = Color.White, fontSize = 18.sp) } + + Spacer(modifier = Modifier.height(16.dp)) + } } } @@ -420,6 +486,23 @@ fun OpponentBattleView( } } + // Enemy HP bar + LinearProgressIndicator( + progress = battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Red, + trackColor = Color.Gray + ) + + // Enemy HP display numbers + Text( + text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = 14.sp, + color = Color.Black + ) + // Spacer for layout balance Spacer(modifier = Modifier.height(120.dp)) } From 31fab9dba45a38a29d96d6d7d0d571d41e1f3148 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 11:13:01 -0400 Subject: [PATCH 23/65] Fixed damage application timing. --- .../vbhelper/screens/BattlesScreen.kt | 70 +++++++------------ 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index af5ef13..3aa7348 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -108,12 +108,12 @@ fun BattleScreen( battleSystem.setAttackProgress(progress) delay(16) // 60 FPS } - println("Phase 2 completed, applying damage and starting opponent attack") - // Apply player's damage and start opponent attack - battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) - pendingOpponentDamage = 0f - delay(500) - battleSystem.startOpponentAttack() + println("Phase 2 completed, applying damage and starting opponent attack") + // Apply player's damage and start opponent attack + battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) + pendingOpponentDamage = 0f + delay(500) + battleSystem.startOpponentAttack() } 3 -> { // Phase 3: Opponent attack on opponent screen @@ -138,12 +138,12 @@ fun BattleScreen( battleSystem.setAttackProgress(progress) delay(16) // 60 FPS } - println("Phase 4 completed, applying damage and resetting") - // Apply opponent's damage and reset - battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) - pendingPlayerDamage = 0f - battleSystem.resetAttackState() - battleSystem.enableAttackButton() + println("Phase 4 completed, applying damage and resetting") + // Apply opponent's damage and reset + battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) + pendingPlayerDamage = 0f + battleSystem.resetAttackState() + battleSystem.enableAttackButton() // Check if battle is over if (battleSystem.checkBattleOver()) { @@ -306,25 +306,6 @@ fun PlayerBattleView( Spacer(modifier = Modifier.height(16.dp)) - // Enemy HP bar - LinearProgressIndicator( - progress = battleSystem.opponentHP / (opponent?.baseHp?.toFloat() ?: 100f), - modifier = Modifier - .fillMaxWidth() - .height(10.dp), - color = Color.Red, - trackColor = Color.Gray - ) - - // Enemy HP display numbers - Text( - text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponent?.baseHp ?: 100}", - fontSize = 14.sp, - color = Color.Black - ) - - Spacer(modifier = Modifier.height(16.dp)) - // Attack button Button( onClick = { @@ -374,21 +355,18 @@ fun PlayerBattleView( // Match is still ongoing - update HP and continue println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") - // Handle damage timing based on hit/miss - if (apiResult.playerAttackHit) { - // Player attack hit - enemy takes damage at end of player animation - println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") - onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage - battleSystem.setAttackHitState(true) - } else { - // 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 - battleSystem.setAttackHitState(false) - } - - // Update HP from API - battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) + // Set pending damage based on API result + if (apiResult.playerAttackHit) { + // Player attack hit - enemy takes damage at end of player animation + println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") + onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage + battleSystem.setAttackHitState(true) + } else { + // Player attack missed - enemy counter-attacks and player takes damage + println("Player attack missed! Enemy counter-attacks and player takes ${apiResult.opponentAttackDamage} damage") + onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), 0f) // Player takes damage + battleSystem.setAttackHitState(false) + } } 2 -> { // Match is over - transition to results screen From 9d0e68fb8a5a1e98814d2c8eb7b932eb3f251fd2 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 11:22:22 -0400 Subject: [PATCH 24/65] Re-added missing UI pieces (HP bar, HP numbers, & exit button). --- .../vbhelper/screens/BattlesScreen.kt | 349 +++++++++--------- 1 file changed, 182 insertions(+), 167 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 3aa7348..19578f0 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -207,197 +207,212 @@ fun PlayerBattleView( Box( modifier = Modifier.fillMaxSize() ) { - // Exit button at the top-right - Button( - onClick = { /* TODO: Add exit functionality */ }, + // Top section: Exit button, HP bar, and HP numbers + Column( modifier = Modifier - .align(Alignment.TopEnd) - .padding(16.dp), - colors = ButtonDefaults.buttonColors(containerColor = Color.Red) + .fillMaxWidth() + .padding(16.dp) ) { - Text("Exit", color = Color.White, fontSize = 14.sp) + // Exit button at the top-right + Box( + modifier = Modifier.fillMaxWidth() + ) { + Button( + onClick = { /* TODO: Add exit functionality */ }, + modifier = Modifier.align(Alignment.TopEnd), + colors = ButtonDefaults.buttonColors(containerColor = Color.Red) + ) { + Text("Exit", color = Color.White, fontSize = 14.sp) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // Health bar + LinearProgressIndicator( + progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Green, + trackColor = Color.Gray + ) + + // Health display numbers + Text( + text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = 14.sp, + color = Color.Black + ) } - // Main content - Column( + // Middle section: Player Digimon only + Box( modifier = Modifier .fillMaxSize() .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween + contentAlignment = Alignment.Center ) { - // Player Digimon - Box( - modifier = Modifier - .fillMaxWidth() - .size(80.dp), - contentAlignment = Alignment.CenterStart - ) { - SpriteImage( - spriteName = activeCharacter?.charaId ?: "dim011_mon01", - atlasName = activeCharacter?.charaId ?: "dim011_mon01", + // Player Digimon (center) + Box( modifier = Modifier - .size(80.dp) - .scale(-1f, 1f), // Flip player Digimon horizontally - contentScale = ContentScale.Fit - ) - - // Attack sprite visibility and positioning based on attack phase - val shouldShowAttack = when (battleSystem.attackPhase) { - 1 -> true // Player attack on player screen - 2 -> true // Player attack on opponent screen - 3 -> false // Opponent attack on opponent screen - 4 -> false // Opponent attack on player screen - else -> false - } - - if (shouldShowAttack) { - val xOffset = when (battleSystem.attackPhase) { - 1 -> (attackAnimationProgress * 400 - 200).dp // Player attack on player screen - 2 -> (attackAnimationProgress * 400 - 200).dp // Player attack on opponent screen - else -> 0.dp - } - - println("PlayerBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") - - AttackSpriteImage( - characterId = activeCharacter?.charaId ?: "dim011_mon01", - isLarge = true, + .fillMaxWidth() + .size(80.dp), + contentAlignment = Alignment.Center + ) { + SpriteImage( + spriteName = activeCharacter?.charaId ?: "dim011_mon01", + atlasName = activeCharacter?.charaId ?: "dim011_mon01", modifier = Modifier - .size(60.dp) - .offset( - x = xOffset, - y = 0.dp - ) - .scale(-1f, 1f), // Flip player attacks + .size(80.dp) + .scale(-1f, 1f), // Flip player Digimon horizontally contentScale = ContentScale.Fit ) + + // Attack sprite visibility and positioning based on attack phase + val shouldShowAttack = when (battleSystem.attackPhase) { + 1 -> true // Player attack on player screen + 2 -> true // Player attack on opponent screen + 3 -> false // Opponent attack on opponent screen + 4 -> false // Opponent attack on player screen + else -> false + } + + if (shouldShowAttack) { + val xOffset = when (battleSystem.attackPhase) { + 1 -> (attackAnimationProgress * 400 - 200).dp // Player attack on player screen + 2 -> (attackAnimationProgress * 400 - 200).dp // Player attack on opponent screen + else -> 0.dp + } + + println("PlayerBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") + + AttackSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = xOffset, + y = 0.dp + ) + .scale(-1f, 1f), // Flip player attacks + contentScale = ContentScale.Fit + ) + } } } - // Critical bar - LinearProgressIndicator( - progress = battleSystem.critBarProgress / 100f, + // Bottom section: Critical bar and Attack button + Column( modifier = Modifier .fillMaxWidth() - .height(10.dp), - color = Color.Yellow, - trackColor = Color.Gray - ) + .padding(16.dp) + .align(Alignment.BottomCenter), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Critical bar + LinearProgressIndicator( + progress = battleSystem.critBarProgress / 100f, + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Yellow, + trackColor = Color.Gray + ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) - // Health bar - LinearProgressIndicator( - progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), - modifier = Modifier - .fillMaxWidth() - .height(10.dp), - color = Color.Green, - trackColor = Color.Gray - ) - - // Health display numbers - Text( - text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 14.sp, - color = Color.Black - ) - - Spacer(modifier = Modifier.height(16.dp)) - - // Attack button - Button( - onClick = { - println("Attack button clicked!") - - // Get crit bar progress as float (0.0f to 100.0f) - val critBarProgressFloat = battleSystem.critBarProgress.toFloat() - - // Determine player and opponent stages - val playerStage = when (activeCharacter?.stage) { - 0 -> 0 // rookie - 1 -> 1 // champion - 2 -> 2 // ultimate - 3 -> 3 // mega - else -> 0 - } - - val opponentStage = when (opponent?.stage) { - 0 -> 0 // rookie - 1 -> 1 // champion - 2 -> 2 // ultimate - 3 -> 3 // mega - else -> 0 - } - - // Send API call with all parameters - context?.let { ctx -> - // Start player attack animation - battleSystem.startPlayerAttack() + // Attack button + Button( + onClick = { + println("Attack button clicked!") - RetrofitHelper().getPVPWinner( - ctx, - 1, - 2, - activeCharacter?.name ?: "Player", - playerStage, - opponentStage, - opponent?.name ?: "Opponent", - opponentStage - ) { apiResult -> - // Handle API response here - println("API Result: $apiResult") + // Get crit bar progress as float (0.0f to 100.0f) + val critBarProgressFloat = battleSystem.critBarProgress.toFloat() + + // Determine player and opponent stages + val playerStage = when (activeCharacter?.stage) { + 0 -> 0 // rookie + 1 -> 1 // champion + 2 -> 2 // ultimate + 3 -> 3 // mega + else -> 0 + } + + val opponentStage = when (opponent?.stage) { + 0 -> 0 // rookie + 1 -> 1 // champion + 2 -> 2 // ultimate + 3 -> 3 // mega + else -> 0 + } + + // Send API call with all parameters + context?.let { ctx -> + // Start player attack animation + battleSystem.startPlayerAttack() - // Update HP based on API response - when (apiResult.state) { - 1 -> { - // Match is still ongoing - update HP and continue - println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") - - // Set pending damage based on API result - if (apiResult.playerAttackHit) { - // Player attack hit - enemy takes damage at end of player animation - println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") - onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage - battleSystem.setAttackHitState(true) - } else { - // Player attack missed - enemy counter-attacks and player takes damage - println("Player attack missed! Enemy counter-attacks and player takes ${apiResult.opponentAttackDamage} damage") - onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), 0f) // Player takes damage - battleSystem.setAttackHitState(false) - } - } - 2 -> { - // Match is over - transition to results screen - println("Match is over! Winner: ${apiResult.winner}") - battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) - onAttackClick() // This will transition to battle-results screen - } - -1 -> { - // Error occurred - println("API Error: ${apiResult.status}") - battleSystem.resetAttackState() - battleSystem.enableAttackButton() + RetrofitHelper().getPVPWinner( + ctx, + 1, + 2, + activeCharacter?.name ?: "Player", + playerStage, + opponentStage, + opponent?.name ?: "Opponent", + opponentStage + ) { apiResult -> + // Handle API response here + println("API Result: $apiResult") + + // Update HP based on API response + when (apiResult.state) { + 1 -> { + // Match is still ongoing - update HP and continue + println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") + + // Set pending damage based on API result + if (apiResult.playerAttackHit) { + // Player attack hit - enemy takes damage at end of player animation + println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") + onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage + battleSystem.setAttackHitState(true) + } else { + // Player attack missed - enemy counter-attacks and player takes damage + println("Player attack missed! Enemy counter-attacks and player takes ${apiResult.opponentAttackDamage} damage") + onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), 0f) // Player takes damage + battleSystem.setAttackHitState(false) + } + } + 2 -> { + // Match is over - transition to results screen + println("Match is over! Winner: ${apiResult.winner}") + battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) + onAttackClick() // This will transition to battle-results screen + } + -1 -> { + // Error occurred + println("API Error: ${apiResult.status}") + battleSystem.resetAttackState() + battleSystem.enableAttackButton() + } } } } - } - }, - enabled = battleSystem.isAttackButtonEnabled, - modifier = Modifier - .fillMaxWidth() - .height(50.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.Red, - disabledContainerColor = Color.Gray - ), - shape = RoundedCornerShape(8.dp) - ) { - Text("Attack", color = Color.White, fontSize = 18.sp) - } - - Spacer(modifier = Modifier.height(16.dp)) + }, + enabled = battleSystem.isAttackButtonEnabled, + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Red, + disabledContainerColor = Color.Gray + ), + shape = RoundedCornerShape(8.dp) + ) { + Text("Attack", color = Color.White, fontSize = 18.sp) + } } } } From 8f790eea41d4afcd33c5907a9bf5d63fb911038a Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 11:25:01 -0400 Subject: [PATCH 25/65] Fixed player Digimon positioning. --- .../com/github/nacabaro/vbhelper/screens/BattlesScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 19578f0..71b671b 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -253,12 +253,12 @@ fun PlayerBattleView( .padding(16.dp), contentAlignment = Alignment.Center ) { - // Player Digimon (center) + // Player Digimon (left side) Box( modifier = Modifier .fillMaxWidth() .size(80.dp), - contentAlignment = Alignment.Center + contentAlignment = Alignment.CenterStart ) { SpriteImage( spriteName = activeCharacter?.charaId ?: "dim011_mon01", From eff2fadb5503c7fd8b7f385e0f5ba43a5e14c239 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 11:32:41 -0400 Subject: [PATCH 26/65] Enemy attack sprite now shows on player screen. --- .../nacabaro/vbhelper/screens/BattlesScreen.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 71b671b..905379e 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -274,7 +274,7 @@ fun PlayerBattleView( 1 -> true // Player attack on player screen 2 -> true // Player attack on opponent screen 3 -> false // Opponent attack on opponent screen - 4 -> false // Opponent attack on player screen + 4 -> true // Opponent attack on player screen else -> false } @@ -282,13 +282,20 @@ fun PlayerBattleView( val xOffset = when (battleSystem.attackPhase) { 1 -> (attackAnimationProgress * 400 - 200).dp // Player attack on player screen 2 -> (attackAnimationProgress * 400 - 200).dp // Player attack on opponent screen + 4 -> (-attackAnimationProgress * 400 + 200).dp // Opponent attack on player screen else -> 0.dp } + // Use opponent character ID for Phase 4 (opponent attack) + val characterId = when (battleSystem.attackPhase) { + 4 -> opponent?.charaId ?: "dim011_mon01" // Use opponent's character ID + else -> activeCharacter?.charaId ?: "dim011_mon01" // Use player's character ID + } + println("PlayerBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") AttackSpriteImage( - characterId = activeCharacter?.charaId ?: "dim011_mon01", + characterId = characterId, isLarge = true, modifier = Modifier .size(60.dp) @@ -296,7 +303,7 @@ fun PlayerBattleView( x = xOffset, y = 0.dp ) - .scale(-1f, 1f), // Flip player attacks + .scale(if (battleSystem.attackPhase == 4) 1f else -1f, 1f), // Don't flip opponent attacks contentScale = ContentScale.Fit ) } From c8690152bcb1b65d415cb9693e671261043e7b36 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 12:02:41 -0400 Subject: [PATCH 27/65] Fixed player attack sprite on enemy screen. Was showing enemy attack sprite. --- .../nacabaro/vbhelper/screens/BattlesScreen.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 905379e..d8f83cb 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -185,7 +185,8 @@ fun BattleScreen( stage = stage, opponentName = opponentName, attackAnimationProgress = battleSystem.attackProgress, - activeCharacter = opponentCharacter + activeCharacter = opponentCharacter, + playerCharacter = activeCharacter ) } } @@ -430,7 +431,8 @@ fun OpponentBattleView( stage: String, opponentName: String, attackAnimationProgress: Float, - activeCharacter: APIBattleCharacter? = null + activeCharacter: APIBattleCharacter? = null, + playerCharacter: APIBattleCharacter? = null ) { Column( modifier = Modifier @@ -469,10 +471,17 @@ fun OpponentBattleView( else -> 0.dp } + // Use correct character ID based on attack phase + val characterId = when (battleSystem.attackPhase) { + 2 -> playerCharacter?.charaId ?: "dim011_mon01" // Use player's character ID for player attack + 3 -> activeCharacter?.charaId ?: "dim011_mon01" // Use opponent's character ID for opponent attack + else -> "dim011_mon01" + } + println("OpponentBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") AttackSpriteImage( - characterId = activeCharacter?.charaId ?: "dim011_mon01", + characterId = characterId, isLarge = true, modifier = Modifier .size(60.dp) @@ -480,7 +489,7 @@ fun OpponentBattleView( x = xOffset, y = 0.dp ) - .scale(if (battleSystem.isPlayerAttacking) -1f else 1f, 1f), // Flip player attacks + .scale(if (battleSystem.attackPhase == 2) -1f else 1f, 1f), // Flip player attacks only contentScale = ContentScale.Fit ) } From acd990d32b2f8c20782082860553bb15a3899a24 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 12:06:46 -0400 Subject: [PATCH 28/65] Adjusted player attack sprite positioning. --- .../com/github/nacabaro/vbhelper/screens/BattlesScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index d8f83cb..84a09a9 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -281,7 +281,7 @@ fun PlayerBattleView( if (shouldShowAttack) { val xOffset = when (battleSystem.attackPhase) { - 1 -> (attackAnimationProgress * 400 - 200).dp // Player attack on player screen + 1 -> (attackAnimationProgress * 400 + 50).dp // Player attack on player screen - start and end more to the right 2 -> (attackAnimationProgress * 400 - 200).dp // Player attack on opponent screen 4 -> (-attackAnimationProgress * 400 + 200).dp // Opponent attack on player screen else -> 0.dp @@ -466,7 +466,7 @@ fun OpponentBattleView( if (shouldShowAttack) { val xOffset = when (battleSystem.attackPhase) { - 2 -> (attackAnimationProgress * 400 - 200).dp // Player attack on opponent screen + 2 -> (attackAnimationProgress * 400 - 350).dp // Player attack on opponent screen - start more to the left 3 -> (-attackAnimationProgress * 400 + 200).dp // Opponent attack on opponent screen else -> 0.dp } From cfa52bce9b04297c4c33be094b46da0ea41fd441 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 12:14:29 -0400 Subject: [PATCH 29/65] Fixed enemy attack sprite positioning. --- .../com/github/nacabaro/vbhelper/screens/BattlesScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 84a09a9..eb18dd5 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -283,7 +283,7 @@ fun PlayerBattleView( val xOffset = when (battleSystem.attackPhase) { 1 -> (attackAnimationProgress * 400 + 50).dp // Player attack on player screen - start and end more to the right 2 -> (attackAnimationProgress * 400 - 200).dp // Player attack on opponent screen - 4 -> (-attackAnimationProgress * 400 + 200).dp // Opponent attack on player screen + 4 -> (-attackAnimationProgress * 400 + 350).dp // Opponent attack on player screen - start more to the right else -> 0.dp } @@ -467,7 +467,7 @@ fun OpponentBattleView( if (shouldShowAttack) { val xOffset = when (battleSystem.attackPhase) { 2 -> (attackAnimationProgress * 400 - 350).dp // Player attack on opponent screen - start more to the left - 3 -> (-attackAnimationProgress * 400 + 200).dp // Opponent attack on opponent screen + 3 -> (-attackAnimationProgress * 400 + -50).dp // Opponent attack on opponent screen - start more to the left else -> 0.dp } From c5cebd8213d001b3e1b2bd167cf0615a9ee0b8d2 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 12:21:41 -0400 Subject: [PATCH 30/65] Fixed bug where enemy attack was showing as player attack sprite for a moment before animation. --- .../vbhelper/screens/BattlesScreen.kt | 88 ++++++++++++++----- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index eb18dd5..3b2fdcf 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -205,6 +205,11 @@ fun PlayerBattleView( opponent: APIBattleCharacter?, onSetPendingDamage: (Float, Float) -> Unit ) { + // Track previous character ID to detect transitions + var previousCharacterId by remember { mutableStateOf(null) } + var previousAttackPhase by remember { mutableStateOf(null) } + var isTransitioning by remember { mutableStateOf(false) } + Box( modifier = Modifier.fillMaxSize() ) { @@ -293,20 +298,35 @@ fun PlayerBattleView( else -> activeCharacter?.charaId ?: "dim011_mon01" // Use player's character ID } + // Handle sprite transition + LaunchedEffect(characterId, battleSystem.attackPhase) { + if ((previousCharacterId != null && previousCharacterId != characterId) || + (previousAttackPhase != null && previousAttackPhase != battleSystem.attackPhase)) { + // Character ID or attack phase changed, start transition + isTransitioning = true + delay(100) // Brief invisibility period + isTransitioning = false + } + previousCharacterId = characterId + previousAttackPhase = battleSystem.attackPhase + } + println("PlayerBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") - AttackSpriteImage( - characterId = characterId, - isLarge = true, - modifier = Modifier - .size(60.dp) - .offset( - x = xOffset, - y = 0.dp - ) - .scale(if (battleSystem.attackPhase == 4) 1f else -1f, 1f), // Don't flip opponent attacks - contentScale = ContentScale.Fit - ) + if (!isTransitioning) { + AttackSpriteImage( + characterId = characterId, + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = xOffset, + y = 0.dp + ) + .scale(if (battleSystem.attackPhase == 4) 1f else -1f, 1f), // Don't flip opponent attacks + contentScale = ContentScale.Fit + ) + } } } } @@ -434,6 +454,11 @@ fun OpponentBattleView( activeCharacter: APIBattleCharacter? = null, playerCharacter: APIBattleCharacter? = null ) { + // Track previous character ID to detect transitions + var previousCharacterId by remember { mutableStateOf(null) } + var previousAttackPhase by remember { mutableStateOf(null) } + var isTransitioning by remember { mutableStateOf(false) } + Column( modifier = Modifier .fillMaxSize() @@ -478,20 +503,35 @@ fun OpponentBattleView( else -> "dim011_mon01" } + // Handle sprite transition + LaunchedEffect(characterId, battleSystem.attackPhase) { + if ((previousCharacterId != null && previousCharacterId != characterId) || + (previousAttackPhase != null && previousAttackPhase != battleSystem.attackPhase)) { + // Character ID or attack phase changed, start transition + isTransitioning = true + delay(100) // Brief invisibility period + isTransitioning = false + } + previousCharacterId = characterId + previousAttackPhase = battleSystem.attackPhase + } + println("OpponentBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") - AttackSpriteImage( - characterId = characterId, - isLarge = true, - modifier = Modifier - .size(60.dp) - .offset( - x = xOffset, - y = 0.dp - ) - .scale(if (battleSystem.attackPhase == 2) -1f else 1f, 1f), // Flip player attacks only - contentScale = ContentScale.Fit - ) + if (!isTransitioning) { + AttackSpriteImage( + characterId = characterId, + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = xOffset, + y = 0.dp + ) + .scale(if (battleSystem.attackPhase == 2) -1f else 1f, 1f), // Flip player attacks only + contentScale = ContentScale.Fit + ) + } } } From 37e5efa87433f17f9975df1434558c8afee2e43e Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 12:57:35 -0400 Subject: [PATCH 31/65] Started setting up sprite animations. --- .../vbhelper/battle/AnimatedSpriteImage.kt | 62 ++++++++++ .../vbhelper/battle/BattleSpriteManager.kt | 2 +- .../vbhelper/battle/DigimonAnimationState.kt | 113 ++++++++++++++++++ .../vbhelper/screens/BattlesScreen.kt | 32 ++++- 4 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt new file mode 100644 index 0000000..0394d85 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt @@ -0,0 +1,62 @@ +package com.github.nacabaro.vbhelper.battle + +import androidx.compose.foundation.Image +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import kotlinx.coroutines.launch + +@Composable +fun AnimatedSpriteImage( + characterId: String, + animationType: DigimonAnimationType = DigimonAnimationType.IDLE, + modifier: Modifier = Modifier, + contentScale: ContentScale = ContentScale.Fit +) { + val context = LocalContext.current + val spriteManager = remember { BattleSpriteManager(context) } + val animationStateMachine = remember { DigimonAnimationStateMachine(characterId) } + val coroutineScope = rememberCoroutineScope() + + var bitmap by remember { mutableStateOf(null) } + + // Start the animation when the component is first created + LaunchedEffect(characterId) { + coroutineScope.launch { + animationStateMachine.playAnimation(DigimonAnimationType.IDLE) + } + } + + // Change animation when animationType changes + LaunchedEffect(animationType) { + coroutineScope.launch { + animationStateMachine.playAnimation(animationType) + } + } + + // Update sprite when animation state changes + LaunchedEffect(animationStateMachine.currentSpriteIndex) { + val spriteName = animationStateMachine.getCurrentSpriteName() + val atlasName = animationStateMachine.getCurrentAtlasName() + + println("Loading animated sprite: $spriteName from atlas: $atlasName") + bitmap = spriteManager.loadSprite(spriteName, atlasName) + + if (bitmap == null) { + println("Failed to load animated sprite: $spriteName from atlas: $atlasName") + } else { + println("Successfully loaded animated sprite: $spriteName from atlas: $atlasName") + } + } + + bitmap?.let { bmp -> + Image( + bitmap = bmp.asImageBitmap(), + contentDescription = "Animated Sprite: $characterId - ${animationStateMachine.currentAnimation}", + modifier = modifier, + contentScale = contentScale + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt index 5347c5e..316d486 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt @@ -73,7 +73,7 @@ class BattleSpriteManager(private val context: Context) { } // Load the specific sprite data file - val spriteDataFile = File(spriteBaseDir, "sprites/${spriteName}_sprite_00.json") + val spriteDataFile = File(spriteBaseDir, "sprites/${spriteName}.json") if (!spriteDataFile.exists()) { println("Sprite data file not found: ${spriteDataFile.absolutePath}") return null diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt new file mode 100644 index 0000000..bd3a9c0 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt @@ -0,0 +1,113 @@ +package com.github.nacabaro.vbhelper.battle + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import kotlinx.coroutines.delay + +enum class DigimonAnimationType { + IDLE, + IDLE2, + WALK, + WALK2, + RUN, + RUN2, + WORKOUT, + WORKOUT2, + HAPPY, + SLEEP, + ATTACK, + FLEE +} + +data class AnimationState( + val type: DigimonAnimationType, + val spriteIndex: Int, // 00, 01, 02, etc. + val duration: Long = 100L, // Duration in milliseconds + val loop: Boolean = true +) + +class DigimonAnimationStateMachine( + private val characterId: String +) { + var currentAnimation by mutableStateOf(DigimonAnimationType.IDLE) + private set + + var currentSpriteIndex by mutableStateOf(0) + private set + + var isPlaying by mutableStateOf(false) + private set + + // Animation mapping - maps animation types to sprite indices + // For now, we'll assume the sprite indices 0-11 correspond to the 12 animation types + private val animationMapping = mapOf( + DigimonAnimationType.IDLE to 0, + DigimonAnimationType.IDLE2 to 1, + DigimonAnimationType.WALK to 2, + DigimonAnimationType.WALK2 to 3, + DigimonAnimationType.RUN to 4, + DigimonAnimationType.RUN2 to 5, + DigimonAnimationType.WORKOUT to 6, + DigimonAnimationType.WORKOUT2 to 7, + DigimonAnimationType.HAPPY to 8, + DigimonAnimationType.SLEEP to 9, + DigimonAnimationType.ATTACK to 10, + DigimonAnimationType.FLEE to 11 + ) + + // Animation durations for each type + private val animationDurations = mapOf( + DigimonAnimationType.IDLE to 500L, + DigimonAnimationType.IDLE2 to 500L, + DigimonAnimationType.WALK to 200L, + DigimonAnimationType.WALK2 to 200L, + DigimonAnimationType.RUN to 150L, + DigimonAnimationType.RUN2 to 150L, + DigimonAnimationType.WORKOUT to 300L, + DigimonAnimationType.WORKOUT2 to 300L, + DigimonAnimationType.HAPPY to 400L, + DigimonAnimationType.SLEEP to 1000L, + DigimonAnimationType.ATTACK to 300L, // Longer for attack animation + DigimonAnimationType.FLEE to 150L + ) + + suspend fun playAnimation(animationType: DigimonAnimationType) { + if (currentAnimation == animationType && isPlaying) { + return // Already playing this animation + } + + currentAnimation = animationType + isPlaying = true + + val spriteIndex = animationMapping[animationType] ?: 0 + currentSpriteIndex = spriteIndex + + val duration = animationDurations[animationType] ?: 100L + + // For non-looping animations like ATTACK, play once and return to IDLE + if (animationType == DigimonAnimationType.ATTACK) { + delay(duration) + playAnimation(DigimonAnimationType.IDLE) + } else { + // For looping animations, keep playing + while (isPlaying && currentAnimation == animationType) { + delay(duration) + // For now, we'll just keep the same sprite + // In the future, we could cycle through multiple sprites for each animation + } + } + } + + fun stopAnimation() { + isPlaying = false + } + + fun getCurrentSpriteName(): String { + return "${characterId}_sprite_${String.format("%02d", currentSpriteIndex)}" + } + + fun getCurrentAtlasName(): String { + return characterId + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 3b2fdcf..8ed70b7 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -49,6 +49,8 @@ import com.github.nacabaro.vbhelper.battle.SpriteImage import com.github.nacabaro.vbhelper.battle.AttackSpriteImage import com.github.nacabaro.vbhelper.battle.SpriteFileManager import com.github.nacabaro.vbhelper.battle.ArenaBattleSystem +import com.github.nacabaro.vbhelper.battle.DigimonAnimationType +import com.github.nacabaro.vbhelper.battle.AnimatedSpriteImage @Composable fun BattleScreen( @@ -266,9 +268,18 @@ fun PlayerBattleView( .size(80.dp), contentAlignment = Alignment.CenterStart ) { - SpriteImage( - spriteName = activeCharacter?.charaId ?: "dim011_mon01", - atlasName = activeCharacter?.charaId ?: "dim011_mon01", + // Determine animation type based on battle state + val animationType = when (battleSystem.attackPhase) { + 1 -> DigimonAnimationType.ATTACK // Player attack on player screen + 2 -> DigimonAnimationType.ATTACK // Player attack on opponent screen + 3 -> DigimonAnimationType.IDLE // Opponent attack on opponent screen + 4 -> DigimonAnimationType.IDLE // Opponent attack on player screen + else -> DigimonAnimationType.IDLE + } + + AnimatedSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + animationType = animationType, modifier = Modifier .size(80.dp) .scale(-1f, 1f), // Flip player Digimon horizontally @@ -473,9 +484,18 @@ fun OpponentBattleView( .size(80.dp), contentAlignment = Alignment.CenterEnd ) { - SpriteImage( - spriteName = activeCharacter?.charaId ?: "dim011_mon01", - atlasName = activeCharacter?.charaId ?: "dim011_mon01", + // Determine animation type based on battle state + val animationType = when (battleSystem.attackPhase) { + 1 -> DigimonAnimationType.IDLE // Player attack on player screen + 2 -> DigimonAnimationType.IDLE // Player attack on opponent screen + 3 -> DigimonAnimationType.ATTACK // Opponent attack on opponent screen + 4 -> DigimonAnimationType.ATTACK // Opponent attack on player screen + else -> DigimonAnimationType.IDLE + } + + AnimatedSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + animationType = animationType, modifier = Modifier.size(80.dp), contentScale = ContentScale.Fit ) From 9f7e4528502ca89ec4ab1b1dc729484bca984fb4 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 13:00:16 -0400 Subject: [PATCH 32/65] Added Digimon idle animation. --- .../vbhelper/battle/DigimonAnimationState.kt | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt index bd3a9c0..7875295 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt @@ -56,6 +56,22 @@ class DigimonAnimationStateMachine( DigimonAnimationType.FLEE to 11 ) + // Animation frame sequences - defines which frames to cycle through for each animation + private val animationFrameSequences = mapOf( + DigimonAnimationType.IDLE to listOf(0, 1), // Cycle between idle frames + DigimonAnimationType.IDLE2 to listOf(1, 0), // Alternative idle cycle + DigimonAnimationType.WALK to listOf(2, 3), // Walk animation frames + DigimonAnimationType.WALK2 to listOf(3, 2), // Alternative walk cycle + DigimonAnimationType.RUN to listOf(4, 5), // Run animation frames + DigimonAnimationType.RUN2 to listOf(5, 4), // Alternative run cycle + DigimonAnimationType.WORKOUT to listOf(6, 7), // Workout animation frames + DigimonAnimationType.WORKOUT2 to listOf(7, 6), // Alternative workout cycle + DigimonAnimationType.HAPPY to listOf(8), // Single happy frame + DigimonAnimationType.SLEEP to listOf(9), // Single sleep frame + DigimonAnimationType.ATTACK to listOf(10), // Single attack frame + DigimonAnimationType.FLEE to listOf(11) // Single flee frame + ) + // Animation durations for each type private val animationDurations = mapOf( DigimonAnimationType.IDLE to 500L, @@ -80,21 +96,21 @@ class DigimonAnimationStateMachine( currentAnimation = animationType isPlaying = true - val spriteIndex = animationMapping[animationType] ?: 0 - currentSpriteIndex = spriteIndex - + val frameSequence = animationFrameSequences[animationType] ?: listOf(0) val duration = animationDurations[animationType] ?: 100L // For non-looping animations like ATTACK, play once and return to IDLE if (animationType == DigimonAnimationType.ATTACK) { + currentSpriteIndex = frameSequence[0] delay(duration) playAnimation(DigimonAnimationType.IDLE) } else { - // For looping animations, keep playing + // For looping animations, cycle through frames + var frameIndex = 0 while (isPlaying && currentAnimation == animationType) { + currentSpriteIndex = frameSequence[frameIndex % frameSequence.size] delay(duration) - // For now, we'll just keep the same sprite - // In the future, we could cycle through multiple sprites for each animation + frameIndex++ } } } From c973030d9da4fe1be2c17f2cfbda2f4823e6c7a0 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 13:23:04 -0400 Subject: [PATCH 33/65] Fixed animation mapping. Was previously using _00, _01, _02, etc. number suffix at end of .json file. Actual sprite index is stored as the m_Name field inside this file. --- .../vbhelper/battle/AnimatedSpriteImage.kt | 2 +- .../vbhelper/battle/DigimonAnimationState.kt | 123 +++++++++++++----- 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt index 0394d85..64ad74c 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt @@ -17,7 +17,7 @@ fun AnimatedSpriteImage( ) { val context = LocalContext.current val spriteManager = remember { BattleSpriteManager(context) } - val animationStateMachine = remember { DigimonAnimationStateMachine(characterId) } + val animationStateMachine = remember { DigimonAnimationStateMachine(characterId, context) } val coroutineScope = rememberCoroutineScope() var bitmap by remember { mutableStateOf(null) } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt index 7875295..41141c3 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt @@ -4,6 +4,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import kotlinx.coroutines.delay +import android.content.Context +import java.io.File +import com.google.gson.Gson enum class DigimonAnimationType { IDLE, @@ -28,7 +31,8 @@ data class AnimationState( ) class DigimonAnimationStateMachine( - private val characterId: String + private val characterId: String, + private val context: Context ) { var currentAnimation by mutableStateOf(DigimonAnimationType.IDLE) private set @@ -39,38 +43,24 @@ class DigimonAnimationStateMachine( var isPlaying by mutableStateOf(false) private set - // Animation mapping - maps animation types to sprite indices - // For now, we'll assume the sprite indices 0-11 correspond to the 12 animation types - private val animationMapping = mapOf( - DigimonAnimationType.IDLE to 0, - DigimonAnimationType.IDLE2 to 1, - DigimonAnimationType.WALK to 2, - DigimonAnimationType.WALK2 to 3, - DigimonAnimationType.RUN to 4, - DigimonAnimationType.RUN2 to 5, - DigimonAnimationType.WORKOUT to 6, - DigimonAnimationType.WORKOUT2 to 7, - DigimonAnimationType.HAPPY to 8, - DigimonAnimationType.SLEEP to 9, - DigimonAnimationType.ATTACK to 10, - DigimonAnimationType.FLEE to 11 + // Animation mapping based on m_Name values + private val mNameToAnimationType = mapOf( + "01" to DigimonAnimationType.IDLE, + "02" to DigimonAnimationType.IDLE2, + "03" to DigimonAnimationType.WALK, + "04" to DigimonAnimationType.WALK2, + "05" to DigimonAnimationType.RUN, + "06" to DigimonAnimationType.RUN2, + "07" to DigimonAnimationType.WORKOUT, + "08" to DigimonAnimationType.WORKOUT2, + "09" to DigimonAnimationType.HAPPY, + "10" to DigimonAnimationType.SLEEP, + "11" to DigimonAnimationType.ATTACK, + "12" to DigimonAnimationType.FLEE ) - // Animation frame sequences - defines which frames to cycle through for each animation - private val animationFrameSequences = mapOf( - DigimonAnimationType.IDLE to listOf(0, 1), // Cycle between idle frames - DigimonAnimationType.IDLE2 to listOf(1, 0), // Alternative idle cycle - DigimonAnimationType.WALK to listOf(2, 3), // Walk animation frames - DigimonAnimationType.WALK2 to listOf(3, 2), // Alternative walk cycle - DigimonAnimationType.RUN to listOf(4, 5), // Run animation frames - DigimonAnimationType.RUN2 to listOf(5, 4), // Alternative run cycle - DigimonAnimationType.WORKOUT to listOf(6, 7), // Workout animation frames - DigimonAnimationType.WORKOUT2 to listOf(7, 6), // Alternative workout cycle - DigimonAnimationType.HAPPY to listOf(8), // Single happy frame - DigimonAnimationType.SLEEP to listOf(9), // Single sleep frame - DigimonAnimationType.ATTACK to listOf(10), // Single attack frame - DigimonAnimationType.FLEE to listOf(11) // Single flee frame - ) + // Cache for sprite file mappings + private var spriteFileMappings: Map> = emptyMap() // Animation durations for each type private val animationDurations = mapOf( @@ -84,10 +74,65 @@ class DigimonAnimationStateMachine( DigimonAnimationType.WORKOUT2 to 300L, DigimonAnimationType.HAPPY to 400L, DigimonAnimationType.SLEEP to 1000L, - DigimonAnimationType.ATTACK to 300L, // Longer for attack animation + DigimonAnimationType.ATTACK to 300L, DigimonAnimationType.FLEE to 150L ) + init { + loadSpriteFileMappings() + } + + private fun loadSpriteFileMappings() { + try { + val spriteBaseDir = File(context.filesDir, "battle_sprites/extracted_assets/sprites") + val gson = Gson() + + val mappings = mutableMapOf>() + + // Initialize all animation types + DigimonAnimationType.values().forEach { animationType -> + mappings[animationType] = mutableListOf() + } + + println("Loading sprite mappings for character: $characterId") + + // Scan all sprite files for this character + val spriteFiles = spriteBaseDir.listFiles { file -> + file.name.startsWith("${characterId}_sprite_") && file.name.endsWith(".json") + } + + println("Found ${spriteFiles?.size ?: 0} sprite files for $characterId") + + spriteFiles?.forEach { spriteFile -> + println("Processing sprite file: ${spriteFile.name}") + val spriteDataJson = spriteFile.readText() + val spriteData = gson.fromJson(spriteDataJson, SpriteData::class.java) + + println(" m_Name: ${spriteData.m_Name}") + + // Get the animation type from m_Name + val animationType = mNameToAnimationType[spriteData.m_Name] + if (animationType != null) { + // Extract the sprite index from filename (e.g., "dim000_mon01_sprite_00.json" -> "00") + val spriteIndex = spriteFile.name.substringAfter("_sprite_").substringBefore(".json") + mappings[animationType]?.add(spriteIndex) + println(" Mapped to animation type: $animationType with sprite index: $spriteIndex") + } else { + println(" Unknown m_Name: ${spriteData.m_Name}") + } + } + + // Convert to immutable map + spriteFileMappings = mappings.mapValues { it.value.sorted() } + + println("Final sprite mappings for $characterId: $spriteFileMappings") + + } catch (e: Exception) { + println("Error loading sprite file mappings: ${e.message}") + e.printStackTrace() + } + } + suspend fun playAnimation(animationType: DigimonAnimationType) { if (currentAnimation == animationType && isPlaying) { return // Already playing this animation @@ -96,19 +141,27 @@ class DigimonAnimationStateMachine( currentAnimation = animationType isPlaying = true - val frameSequence = animationFrameSequences[animationType] ?: listOf(0) + val frameSequence = spriteFileMappings[animationType] ?: listOf("00") val duration = animationDurations[animationType] ?: 100L + // Ensure we have at least one frame + if (frameSequence.isEmpty()) { + println("Warning: No sprite files found for animation type $animationType") + currentSpriteIndex = 0 + return + } + // For non-looping animations like ATTACK, play once and return to IDLE if (animationType == DigimonAnimationType.ATTACK) { - currentSpriteIndex = frameSequence[0] + currentSpriteIndex = frameSequence.firstOrNull()?.toIntOrNull() ?: 0 delay(duration) playAnimation(DigimonAnimationType.IDLE) } else { // For looping animations, cycle through frames var frameIndex = 0 while (isPlaying && currentAnimation == animationType) { - currentSpriteIndex = frameSequence[frameIndex % frameSequence.size] + val spriteIndex = frameSequence[frameIndex % frameSequence.size] + currentSpriteIndex = spriteIndex.toIntOrNull() ?: 0 delay(duration) frameIndex++ } From 3687ff2c21d846d154606e662ca512d8ee1ac2e3 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 13:26:39 -0400 Subject: [PATCH 34/65] Updated idle animation to use 2 sprites instead of just 1. --- .../vbhelper/battle/AnimatedSpriteImage.kt | 8 +++-- .../vbhelper/battle/DigimonAnimationState.kt | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt index 64ad74c..65cb652 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt @@ -25,14 +25,18 @@ fun AnimatedSpriteImage( // Start the animation when the component is first created LaunchedEffect(characterId) { coroutineScope.launch { - animationStateMachine.playAnimation(DigimonAnimationType.IDLE) + animationStateMachine.playIdleAnimation() } } // Change animation when animationType changes LaunchedEffect(animationType) { coroutineScope.launch { - animationStateMachine.playAnimation(animationType) + if (animationType == DigimonAnimationType.IDLE) { + animationStateMachine.playIdleAnimation() + } else { + animationStateMachine.playAnimation(animationType) + } } } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt index 41141c3..0c5d9a5 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt @@ -168,6 +168,40 @@ class DigimonAnimationStateMachine( } } + // Special method for idle animation that cycles between IDLE and IDLE2 + suspend fun playIdleAnimation() { + if (currentAnimation == DigimonAnimationType.IDLE && isPlaying) { + return // Already playing idle animation + } + + currentAnimation = DigimonAnimationType.IDLE + isPlaying = true + + // Get both IDLE and IDLE2 frames + val idleFrames = spriteFileMappings[DigimonAnimationType.IDLE] ?: listOf("00") + val idle2Frames = spriteFileMappings[DigimonAnimationType.IDLE2] ?: listOf("01") + + // Combine frames for cycling idle animation + val combinedFrames = (idleFrames + idle2Frames).distinct() + + if (combinedFrames.isEmpty()) { + println("Warning: No idle sprite files found") + currentSpriteIndex = 0 + return + } + + val duration = animationDurations[DigimonAnimationType.IDLE] ?: 500L + + // Cycle through idle frames + var frameIndex = 0 + while (isPlaying && currentAnimation == DigimonAnimationType.IDLE) { + val spriteIndex = combinedFrames[frameIndex % combinedFrames.size] + currentSpriteIndex = spriteIndex.toIntOrNull() ?: 0 + delay(duration) + frameIndex++ + } + } + fun stopAnimation() { isPlaying = false } From 615fb852041241b14b848f8ffae8bb176d8e7631 Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 14:13:26 -0400 Subject: [PATCH 35/65] Added sprite test button to battle. --- .../vbhelper/battle/DigimonAnimationState.kt | 6 +- .../vbhelper/screens/BattlesScreen.kt | 157 ++++++++++++++++-- 2 files changed, 146 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt index 0c5d9a5..2042dbd 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt @@ -176,10 +176,10 @@ class DigimonAnimationStateMachine( currentAnimation = DigimonAnimationType.IDLE isPlaying = true - - // Get both IDLE and IDLE2 frames + val idleFrames = spriteFileMappings[DigimonAnimationType.IDLE] ?: listOf("00") - val idle2Frames = spriteFileMappings[DigimonAnimationType.IDLE2] ?: listOf("01") + + val idle2Frames = spriteFileMappings[DigimonAnimationType.HAPPY] ?: listOf("08") // Combine frames for cycling idle animation val combinedFrames = (idleFrames + idle2Frames).distinct() diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 8ed70b7..5f14e58 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -52,6 +52,7 @@ import com.github.nacabaro.vbhelper.battle.ArenaBattleSystem import com.github.nacabaro.vbhelper.battle.DigimonAnimationType import com.github.nacabaro.vbhelper.battle.AnimatedSpriteImage + @Composable fun BattleScreen( stage: String, @@ -592,6 +593,13 @@ fun BattlesScreen() { var expanded by remember { mutableStateOf(false) } var selectedStage by remember { mutableStateOf("") } var currentStage by remember { mutableStateOf("rookie") } + + // Sprite animation tester state + var showSpriteTester by remember { mutableStateOf(false) } + var dimId by remember { mutableStateOf("") } + var monId by remember { mutableStateOf("") } + var currentTestAnimation by remember { mutableStateOf(DigimonAnimationType.IDLE) } + var testCharacterId by remember { mutableStateOf("") } // Create hardcoded character lists for each stage val rookieCharacters = listOf( @@ -775,6 +783,118 @@ fun BattlesScreen() { } } } + + val spriteTester = @Composable { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(16.dp) + ) { + Text("Sprite Animation Tester", fontSize = 20.sp, fontWeight = FontWeight.Bold) + + Spacer(modifier = Modifier.height(16.dp)) + + // DIM ID input + OutlinedTextField( + value = dimId, + onValueChange = { dimId = it }, + label = { Text("DIM ID (e.g., 012)") }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // Mon ID input + OutlinedTextField( + value = monId, + onValueChange = { monId = it }, + label = { Text("Mon ID (e.g., 03)") }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Load sprite button + Button( + onClick = { + if (dimId.isNotEmpty() && monId.isNotEmpty()) { + testCharacterId = "dim${dimId}_mon${monId}" + println("Testing sprite for: $testCharacterId") + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Load Sprite") + } + + Spacer(modifier = Modifier.height(16.dp)) + + // Animation test buttons + if (testCharacterId.isNotEmpty()) { + Text("Animation States:", fontSize = 16.sp, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(8.dp)) + + // Create a grid of animation buttons + val animationTypes = listOf( + DigimonAnimationType.IDLE to "IDLE", + DigimonAnimationType.IDLE2 to "IDLE2", + DigimonAnimationType.WALK to "WALK", + DigimonAnimationType.WALK2 to "WALK2", + DigimonAnimationType.RUN to "RUN", + DigimonAnimationType.RUN2 to "RUN2", + DigimonAnimationType.WORKOUT to "WORKOUT", + DigimonAnimationType.WORKOUT2 to "WORKOUT2", + DigimonAnimationType.HAPPY to "HAPPY", + DigimonAnimationType.SLEEP to "SLEEP", + DigimonAnimationType.ATTACK to "ATTACK", + DigimonAnimationType.FLEE to "FLEE" + ) + + // Display sprite + AnimatedSpriteImage( + characterId = testCharacterId, + animationType = currentTestAnimation, + modifier = Modifier.size(120.dp), + contentScale = ContentScale.Fit + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Animation buttons in a grid + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + // Create rows of 3 buttons each + animationTypes.chunked(3).forEach { row -> + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + row.forEach { (animationType, label) -> + Button( + onClick = { currentTestAnimation = animationType }, + colors = ButtonDefaults.buttonColors( + containerColor = if (currentTestAnimation == animationType) Color.Blue else Color.Gray + ), + modifier = Modifier.weight(1f) + ) { + Text(label, fontSize = 12.sp) + } + } + } + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { showSpriteTester = false }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Back to Main") + } + } + } Scaffold ( topBar = { @@ -792,21 +912,30 @@ fun BattlesScreen() { ) { when (currentView) { "main" -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - rookieButton() - championButton() - ultimateButton() - megaButton() - Button( - onClick = { - val spriteFileManager = SpriteFileManager(context) - spriteFileManager.clearSpriteFiles() - println("Sprite files cleared!") - } + if (showSpriteTester) { + spriteTester() + } else { + Column( + horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Clear Sprite Files") + rookieButton() + championButton() + ultimateButton() + megaButton() + Button( + onClick = { showSpriteTester = true } + ) { + Text("Sprite Animation Tester") + } + Button( + onClick = { + val spriteFileManager = SpriteFileManager(context) + spriteFileManager.clearSpriteFiles() + println("Sprite files cleared!") + } + ) { + Text("Clear Sprite Files") + } } } } From fb0935082525c9c120ea9d39dccbf934f32a18ed Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 14:26:24 -0400 Subject: [PATCH 36/65] Created sprite and animation tester menu. --- .../vbhelper/screens/BattlesScreen.kt | 167 ++++++++++++------ .../nacabhelper/screens/BattlesScreen.kt | 1 + 2 files changed, 111 insertions(+), 57 deletions(-) create mode 100644 app/src/main/java/com/github/nacabhelper/screens/BattlesScreen.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 5f14e58..10afc2f 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -51,6 +51,8 @@ import com.github.nacabaro.vbhelper.battle.SpriteFileManager import com.github.nacabaro.vbhelper.battle.ArenaBattleSystem import com.github.nacabaro.vbhelper.battle.DigimonAnimationType import com.github.nacabaro.vbhelper.battle.AnimatedSpriteImage +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items @Composable @@ -596,6 +598,7 @@ fun BattlesScreen() { // Sprite animation tester state var showSpriteTester by remember { mutableStateOf(false) } + var spriteTesterView by remember { mutableStateOf("entry") } // "entry" or "testing" var dimId by remember { mutableStateOf("") } var monId by remember { mutableStateOf("") } var currentTestAnimation by remember { mutableStateOf(DigimonAnimationType.IDLE) } @@ -784,7 +787,7 @@ fun BattlesScreen() { } } - val spriteTester = @Composable { + val spriteTesterEntry = @Composable { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) @@ -819,6 +822,7 @@ fun BattlesScreen() { if (dimId.isNotEmpty() && monId.isNotEmpty()) { testCharacterId = "dim${dimId}_mon${monId}" println("Testing sprite for: $testCharacterId") + spriteTesterView = "testing" } }, modifier = Modifier.fillMaxWidth() @@ -828,57 +832,76 @@ fun BattlesScreen() { Spacer(modifier = Modifier.height(16.dp)) - // Animation test buttons - if (testCharacterId.isNotEmpty()) { - Text("Animation States:", fontSize = 16.sp, fontWeight = FontWeight.Bold) - Spacer(modifier = Modifier.height(8.dp)) - - // Create a grid of animation buttons - val animationTypes = listOf( - DigimonAnimationType.IDLE to "IDLE", - DigimonAnimationType.IDLE2 to "IDLE2", - DigimonAnimationType.WALK to "WALK", - DigimonAnimationType.WALK2 to "WALK2", - DigimonAnimationType.RUN to "RUN", - DigimonAnimationType.RUN2 to "RUN2", - DigimonAnimationType.WORKOUT to "WORKOUT", - DigimonAnimationType.WORKOUT2 to "WORKOUT2", - DigimonAnimationType.HAPPY to "HAPPY", - DigimonAnimationType.SLEEP to "SLEEP", - DigimonAnimationType.ATTACK to "ATTACK", - DigimonAnimationType.FLEE to "FLEE" - ) - - // Display sprite - AnimatedSpriteImage( - characterId = testCharacterId, - animationType = currentTestAnimation, - modifier = Modifier.size(120.dp), - contentScale = ContentScale.Fit - ) - - Spacer(modifier = Modifier.height(16.dp)) - - // Animation buttons in a grid - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - // Create rows of 3 buttons each - animationTypes.chunked(3).forEach { row -> - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - row.forEach { (animationType, label) -> - Button( - onClick = { currentTestAnimation = animationType }, - colors = ButtonDefaults.buttonColors( - containerColor = if (currentTestAnimation == animationType) Color.Blue else Color.Gray - ), - modifier = Modifier.weight(1f) - ) { - Text(label, fontSize = 12.sp) - } + Button( + onClick = { showSpriteTester = false }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Back to Main") + } + } + } + + val spriteTesterTesting = @Composable { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(16.dp) + ) { + Text("Sprite Animation Testing", fontSize = 20.sp, fontWeight = FontWeight.Bold) + Text("Character: $testCharacterId", fontSize = 14.sp, color = Color.Gray) + + Spacer(modifier = Modifier.height(16.dp)) + + // Display sprite + AnimatedSpriteImage( + characterId = testCharacterId, + animationType = currentTestAnimation, + modifier = Modifier.size(120.dp), + contentScale = ContentScale.Fit + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Animation buttons in a grid + Text("Animation Buttons:", fontSize = 16.sp, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(8.dp)) + + val animationTypes = listOf( + DigimonAnimationType.IDLE to "IDLE", + DigimonAnimationType.IDLE2 to "IDLE2", + DigimonAnimationType.WALK to "WALK", + DigimonAnimationType.WALK2 to "WALK2", + DigimonAnimationType.RUN to "RUN", + DigimonAnimationType.RUN2 to "RUN2", + DigimonAnimationType.WORKOUT to "WORKOUT", + DigimonAnimationType.WORKOUT2 to "WORKOUT2", + DigimonAnimationType.HAPPY to "HAPPY", + DigimonAnimationType.SLEEP to "SLEEP", + DigimonAnimationType.ATTACK to "ATTACK", + DigimonAnimationType.FLEE to "FLEE" + ) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + // Create rows of 3 buttons each + animationTypes.chunked(3).forEach { row -> + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + row.forEach { (animationType, label) -> + Button( + onClick = { + currentTestAnimation = animationType + println("Switched to animation: $label") + }, + colors = ButtonDefaults.buttonColors( + containerColor = if (currentTestAnimation == animationType) Color.Blue else Color.Gray + ), + modifier = Modifier.weight(1f), + contentPadding = androidx.compose.foundation.layout.PaddingValues(vertical = 4.dp, horizontal = 2.dp) + ) { + Text(label, fontSize = 10.sp) } } } @@ -887,11 +910,30 @@ fun BattlesScreen() { Spacer(modifier = Modifier.height(16.dp)) - Button( - onClick = { showSpriteTester = false }, + // Navigation buttons + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth() ) { - Text("Back to Main") + Button( + onClick = { spriteTesterView = "entry" }, + modifier = Modifier.weight(1f) + ) { + Text("Back to Entry") + } + + Button( + onClick = { + showSpriteTester = false + spriteTesterView = "entry" + testCharacterId = "" + dimId = "" + monId = "" + }, + modifier = Modifier.weight(1f) + ) { + Text("Back to Main") + } } } } @@ -913,7 +955,11 @@ fun BattlesScreen() { when (currentView) { "main" -> { if (showSpriteTester) { - spriteTester() + when (spriteTesterView) { + "entry" -> spriteTesterEntry() + "testing" -> spriteTesterTesting() + else -> spriteTesterEntry() + } } else { Column( horizontalAlignment = Alignment.CenterHorizontally @@ -923,7 +969,14 @@ fun BattlesScreen() { ultimateButton() megaButton() Button( - onClick = { showSpriteTester = true } + onClick = { + showSpriteTester = true + spriteTesterView = "entry" + testCharacterId = "" + dimId = "" + monId = "" + currentTestAnimation = DigimonAnimationType.IDLE + } ) { Text("Sprite Animation Tester") } diff --git a/app/src/main/java/com/github/nacabhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabhelper/screens/BattlesScreen.kt new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/app/src/main/java/com/github/nacabhelper/screens/BattlesScreen.kt @@ -0,0 +1 @@ + \ No newline at end of file From 71ba5e0207a99f0d384f4762be6416f8d69f8700 Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 06:54:55 -0400 Subject: [PATCH 37/65] Fixed atk sprite import bug. Readjusted sprites to load from individual files instead of full spritesheets. --- .../vbhelper/battle/AnimatedSpriteImage.kt | 25 ++- .../vbhelper/battle/AttackSpriteManager.kt | 4 +- .../vbhelper/battle/DigimonAnimationState.kt | 134 ++++--------- .../battle/IndividualSpriteManager.kt | 142 ++++++++++++++ .../vbhelper/battle/SpriteFileManager.kt | 180 ++++++++++++++---- .../nacabaro/vbhelper/battle/SpriteImage.kt | 19 +- .../vbhelper/screens/BattlesScreen.kt | 15 +- 7 files changed, 356 insertions(+), 163 deletions(-) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/IndividualSpriteManager.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt index 65cb652..e8c2049 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt @@ -13,15 +13,23 @@ fun AnimatedSpriteImage( characterId: String, animationType: DigimonAnimationType = DigimonAnimationType.IDLE, modifier: Modifier = Modifier, - contentScale: ContentScale = ContentScale.Fit + contentScale: ContentScale = ContentScale.Fit, + reloadMappings: Boolean = false ) { val context = LocalContext.current - val spriteManager = remember { BattleSpriteManager(context) } + val spriteManager = remember { IndividualSpriteManager(context) } val animationStateMachine = remember { DigimonAnimationStateMachine(characterId, context) } val coroutineScope = rememberCoroutineScope() var bitmap by remember { mutableStateOf(null) } + // Reload mappings when reloadMappings parameter changes + LaunchedEffect(reloadMappings) { + if (reloadMappings) { + animationStateMachine.reloadMappings() + } + } + // Start the animation when the component is first created LaunchedEffect(characterId) { coroutineScope.launch { @@ -41,17 +49,16 @@ fun AnimatedSpriteImage( } // Update sprite when animation state changes - LaunchedEffect(animationStateMachine.currentSpriteIndex) { - val spriteName = animationStateMachine.getCurrentSpriteName() - val atlasName = animationStateMachine.getCurrentAtlasName() + LaunchedEffect(animationStateMachine.currentFrameNumber) { + val frameNumber = animationStateMachine.getCurrentFrame() - println("Loading animated sprite: $spriteName from atlas: $atlasName") - bitmap = spriteManager.loadSprite(spriteName, atlasName) + println("Loading animated sprite frame: $frameNumber for character: $characterId") + bitmap = spriteManager.loadSpriteFrame(characterId, frameNumber) if (bitmap == null) { - println("Failed to load animated sprite: $spriteName from atlas: $atlasName") + println("Failed to load animated sprite frame: $frameNumber for character: $characterId") } else { - println("Successfully loaded animated sprite: $spriteName from atlas: $atlasName") + println("Successfully loaded animated sprite frame: $frameNumber for character: $characterId") } } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt index d44bb43..064350b 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AttackSpriteManager.kt @@ -31,8 +31,8 @@ class AttackSpriteManager(private val context: Context) { private val gson = Gson() private val characterDataCache = mutableMapOf() - // Base path for attack textures - private val attackTexturesPath = "battle_sprites/extracted_assets/extracted_atksprites" + // Base path for attack textures (updated for new folder structure) + private val attackTexturesPath = "battle_sprites/extracted_atksprites" fun getAttackSprite(characterId: String, isLarge: Boolean = false): Bitmap? { println("AttackSpriteManager: Getting attack sprite for characterId=$characterId, isLarge=$isLarge") diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt index 2042dbd..77028d3 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.setValue import kotlinx.coroutines.delay import android.content.Context import java.io.File -import com.google.gson.Gson enum class DigimonAnimationType { IDLE, @@ -25,7 +24,7 @@ enum class DigimonAnimationType { data class AnimationState( val type: DigimonAnimationType, - val spriteIndex: Int, // 00, 01, 02, etc. + val frameNumber: Int, // 1-12 for individual PNG files val duration: Long = 100L, // Duration in milliseconds val loop: Boolean = true ) @@ -37,30 +36,31 @@ class DigimonAnimationStateMachine( var currentAnimation by mutableStateOf(DigimonAnimationType.IDLE) private set - var currentSpriteIndex by mutableStateOf(0) + var currentFrameNumber by mutableStateOf(1) private set var isPlaying by mutableStateOf(false) private set - // Animation mapping based on m_Name values - private val mNameToAnimationType = mapOf( - "01" to DigimonAnimationType.IDLE, - "02" to DigimonAnimationType.IDLE2, - "03" to DigimonAnimationType.WALK, - "04" to DigimonAnimationType.WALK2, - "05" to DigimonAnimationType.RUN, - "06" to DigimonAnimationType.RUN2, - "07" to DigimonAnimationType.WORKOUT, - "08" to DigimonAnimationType.WORKOUT2, - "09" to DigimonAnimationType.HAPPY, - "10" to DigimonAnimationType.SLEEP, - "11" to DigimonAnimationType.ATTACK, - "12" to DigimonAnimationType.FLEE + // Direct mapping of frame numbers (1-12) to animation types + // This is based on the standard Digimon sprite frame order + private val frameToAnimationType = mapOf( + 1 to DigimonAnimationType.IDLE, + 2 to DigimonAnimationType.IDLE2, + 3 to DigimonAnimationType.WALK, + 4 to DigimonAnimationType.WALK2, + 5 to DigimonAnimationType.RUN, + 6 to DigimonAnimationType.RUN2, + 7 to DigimonAnimationType.WORKOUT, + 8 to DigimonAnimationType.WORKOUT2, + 9 to DigimonAnimationType.HAPPY, + 10 to DigimonAnimationType.SLEEP, + 11 to DigimonAnimationType.ATTACK, + 12 to DigimonAnimationType.FLEE ) - // Cache for sprite file mappings - private var spriteFileMappings: Map> = emptyMap() + // Reverse mapping for getting frame numbers for each animation type + private val animationTypeToFrames = frameToAnimationType.entries.groupBy({ it.value }, { it.key }) // Animation durations for each type private val animationDurations = mapOf( @@ -79,58 +79,8 @@ class DigimonAnimationStateMachine( ) init { - loadSpriteFileMappings() - } - - private fun loadSpriteFileMappings() { - try { - val spriteBaseDir = File(context.filesDir, "battle_sprites/extracted_assets/sprites") - val gson = Gson() - - val mappings = mutableMapOf>() - - // Initialize all animation types - DigimonAnimationType.values().forEach { animationType -> - mappings[animationType] = mutableListOf() - } - - println("Loading sprite mappings for character: $characterId") - - // Scan all sprite files for this character - val spriteFiles = spriteBaseDir.listFiles { file -> - file.name.startsWith("${characterId}_sprite_") && file.name.endsWith(".json") - } - - println("Found ${spriteFiles?.size ?: 0} sprite files for $characterId") - - spriteFiles?.forEach { spriteFile -> - println("Processing sprite file: ${spriteFile.name}") - val spriteDataJson = spriteFile.readText() - val spriteData = gson.fromJson(spriteDataJson, SpriteData::class.java) - - println(" m_Name: ${spriteData.m_Name}") - - // Get the animation type from m_Name - val animationType = mNameToAnimationType[spriteData.m_Name] - if (animationType != null) { - // Extract the sprite index from filename (e.g., "dim000_mon01_sprite_00.json" -> "00") - val spriteIndex = spriteFile.name.substringAfter("_sprite_").substringBefore(".json") - mappings[animationType]?.add(spriteIndex) - println(" Mapped to animation type: $animationType with sprite index: $spriteIndex") - } else { - println(" Unknown m_Name: ${spriteData.m_Name}") - } - } - - // Convert to immutable map - spriteFileMappings = mappings.mapValues { it.value.sorted() } - - println("Final sprite mappings for $characterId: $spriteFileMappings") - - } catch (e: Exception) { - println("Error loading sprite file mappings: ${e.message}") - e.printStackTrace() - } + println("Initialized DigimonAnimationStateMachine for character: $characterId") + println("Available animation types: ${animationTypeToFrames.keys}") } suspend fun playAnimation(animationType: DigimonAnimationType) { @@ -141,27 +91,22 @@ class DigimonAnimationStateMachine( currentAnimation = animationType isPlaying = true - val frameSequence = spriteFileMappings[animationType] ?: listOf("00") + val frameNumbers = animationTypeToFrames[animationType] ?: listOf(1) val duration = animationDurations[animationType] ?: 100L - // Ensure we have at least one frame - if (frameSequence.isEmpty()) { - println("Warning: No sprite files found for animation type $animationType") - currentSpriteIndex = 0 - return - } + println("Playing animation: $animationType with frames: $frameNumbers") // For non-looping animations like ATTACK, play once and return to IDLE if (animationType == DigimonAnimationType.ATTACK) { - currentSpriteIndex = frameSequence.firstOrNull()?.toIntOrNull() ?: 0 + currentFrameNumber = frameNumbers.firstOrNull() ?: 1 delay(duration) playAnimation(DigimonAnimationType.IDLE) } else { // For looping animations, cycle through frames var frameIndex = 0 while (isPlaying && currentAnimation == animationType) { - val spriteIndex = frameSequence[frameIndex % frameSequence.size] - currentSpriteIndex = spriteIndex.toIntOrNull() ?: 0 + val frameNumber = frameNumbers[frameIndex % frameNumbers.size] + currentFrameNumber = frameNumber delay(duration) frameIndex++ } @@ -177,26 +122,21 @@ class DigimonAnimationStateMachine( currentAnimation = DigimonAnimationType.IDLE isPlaying = true - val idleFrames = spriteFileMappings[DigimonAnimationType.IDLE] ?: listOf("00") - - val idle2Frames = spriteFileMappings[DigimonAnimationType.HAPPY] ?: listOf("08") + val idleFrames = animationTypeToFrames[DigimonAnimationType.IDLE] ?: listOf(1) + val idle2Frames = animationTypeToFrames[DigimonAnimationType.IDLE2] ?: listOf(2) // Combine frames for cycling idle animation val combinedFrames = (idleFrames + idle2Frames).distinct() - if (combinedFrames.isEmpty()) { - println("Warning: No idle sprite files found") - currentSpriteIndex = 0 - return - } + println("Playing idle animation with frames: $combinedFrames") val duration = animationDurations[DigimonAnimationType.IDLE] ?: 500L // Cycle through idle frames var frameIndex = 0 while (isPlaying && currentAnimation == DigimonAnimationType.IDLE) { - val spriteIndex = combinedFrames[frameIndex % combinedFrames.size] - currentSpriteIndex = spriteIndex.toIntOrNull() ?: 0 + val frameNumber = combinedFrames[frameIndex % combinedFrames.size] + currentFrameNumber = frameNumber delay(duration) frameIndex++ } @@ -206,11 +146,17 @@ class DigimonAnimationStateMachine( isPlaying = false } - fun getCurrentSpriteName(): String { - return "${characterId}_sprite_${String.format("%02d", currentSpriteIndex)}" + fun getCurrentFrame(): Int { + return currentFrameNumber } - fun getCurrentAtlasName(): String { + fun getCurrentCharacterId(): String { return characterId } + + // Method to reload mappings (useful for testing) + fun reloadMappings() { + println("Reloading mappings for character: $characterId") + // No need to reload since we use direct frame mapping + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/IndividualSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/IndividualSpriteManager.kt new file mode 100644 index 0000000..f4b0d25 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/IndividualSpriteManager.kt @@ -0,0 +1,142 @@ +package com.github.nacabaro.vbhelper.battle + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import java.io.File + +class IndividualSpriteManager(private val context: Context) { + private val spriteCache = mutableMapOf() + + // Base directory where individual sprite PNGs are stored + private val spriteBaseDir = File(context.filesDir, "battle_sprites/extracted_assets/sprites") + + /** + * Load a specific sprite frame for a character + * @param characterId The character ID (e.g., "dim012_mon03") + * @param frameNumber The frame number (1-12) + * @return Bitmap of the sprite frame, or null if not found + */ + fun loadSpriteFrame(characterId: String, frameNumber: Int): Bitmap? { + val cacheKey = "${characterId}_frame_${frameNumber}" + + // Check cache first + if (spriteCache.containsKey(cacheKey)) { + return spriteCache[cacheKey] + } + + // Debug: Check if base directory exists + if (!spriteBaseDir.exists()) { + println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}") + return null + } + + try { + // Construct the sprite file path + val spriteFileName = "${characterId}_${String.format("%02d", frameNumber)}.png" + val spriteFile = File(spriteBaseDir, "$characterId/$spriteFileName") + + if (!spriteFile.exists()) { + println("Sprite file not found: ${spriteFile.absolutePath}") + return null + } + + // Load the PNG file directly + val bitmap = BitmapFactory.decodeFile(spriteFile.absolutePath) + if (bitmap == null) { + println("Failed to decode sprite file: ${spriteFile.absolutePath}") + return null + } + + println("Successfully loaded sprite frame: $spriteFileName (${bitmap.width}x${bitmap.height})") + + // Cache the result + spriteCache[cacheKey] = bitmap + + return bitmap + + } catch (e: Exception) { + println("Error loading sprite frame: ${e.message}") + e.printStackTrace() + return null + } + } + + /** + * Get all available sprite frames for a character + * @param characterId The character ID + * @return List of frame numbers (1-12) that exist for this character + */ + fun getAvailableFrames(characterId: String): List { + try { + val characterDir = File(spriteBaseDir, characterId) + if (!characterDir.exists()) { + println("Character directory not found: ${characterDir.absolutePath}") + return emptyList() + } + + val spriteFiles = characterDir.listFiles { file -> + file.name.startsWith("${characterId}_") && file.name.endsWith(".png") + } ?: emptyArray() + + return spriteFiles.mapNotNull { file -> + // Extract frame number from filename (e.g., "dim012_mon03_01.png" -> 1) + val frameNumberStr = file.name.substringAfter("_").substringBefore(".png") + frameNumberStr.toIntOrNull() + }.sorted() + + } catch (e: Exception) { + println("Error getting available frames: ${e.message}") + e.printStackTrace() + return emptyList() + } + } + + /** + * Get all available characters + * @return List of character IDs that have sprite directories + */ + fun getAvailableCharacters(): List { + try { + if (!spriteBaseDir.exists()) { + return emptyList() + } + + val characterDirs = spriteBaseDir.listFiles { file -> + file.isDirectory && file.name.matches(Regex("dim\\d+_mon\\d+.*")) + } ?: emptyArray() + + return characterDirs.map { it.name }.sorted() + + } catch (e: Exception) { + println("Error getting available characters: ${e.message}") + e.printStackTrace() + return emptyList() + } + } + + /** + * Clear the sprite cache + */ + fun clearCache() { + spriteCache.clear() + } + + /** + * Check if a character has sprite files + * @param characterId The character ID to check + * @return true if the character has sprite files, false otherwise + */ + fun hasCharacterSprites(characterId: String): Boolean { + val characterDir = File(spriteBaseDir, characterId) + if (!characterDir.exists()) { + return false + } + + val spriteFiles = characterDir.listFiles { file -> + file.name.startsWith("${characterId}_") && file.name.endsWith(".png") + } ?: emptyArray() + + return spriteFiles.isNotEmpty() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt index 0298f3d..5dd3699 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt @@ -9,26 +9,90 @@ class SpriteFileManager(private val context: Context) { fun copySpriteFilesToInternalStorage() { try { - // Create the base directory for extracted_assets - val extractedAssetsDir = File(context.filesDir, "battle_sprites/extracted_assets") - if (!extractedAssetsDir.exists()) { - extractedAssetsDir.mkdirs() + println("Starting sprite file copy process...") + + // Debug: List what's in the assets directory + val assetManager = context.assets + val battleSpritesFiles = assetManager.list("battle_sprites") + println("battle_sprites directory in assets contains: ${battleSpritesFiles?.joinToString(", ")}") + + val extractedAssetsFiles = assetManager.list("battle_sprites/extracted_assets") + println("battle_sprites/extracted_assets directory in assets contains: ${extractedAssetsFiles?.joinToString(", ")}") + + // Check specifically for extracted_atksprites in assets (now directly under battle_sprites) + val atkspritesInAssets = assetManager.list("battle_sprites/extracted_atksprites") + println("extracted_atksprites in assets contains: ${atkspritesInAssets?.size ?: 0} files") + if (atkspritesInAssets != null && atkspritesInAssets.isNotEmpty()) { + println("First few attack files in assets: ${atkspritesInAssets.take(5).joinToString(", ")}") } - // Create the base directory for extracted_digimon_stats - val extractedStatsDir = File(context.filesDir, "battle_sprites/extracted_digimon_stats") - if (!extractedStatsDir.exists()) { - extractedStatsDir.mkdirs() + // Check for extracted_battlebgs in assets (now directly under battle_sprites) + val battlebgsInAssets = assetManager.list("battle_sprites/extracted_battlebgs") + println("extracted_battlebgs in assets contains: ${battlebgsInAssets?.size ?: 0} files") + if (battlebgsInAssets != null && battlebgsInAssets.isNotEmpty()) { + println("First few battle background files in assets: ${battlebgsInAssets.take(5).joinToString(", ")}") } - // Copy extracted_assets files from assets to internal storage - copyAssetDirectory("battle_sprites/extracted_assets", extractedAssetsDir) + // Try to list all possible subdirectories in battle_sprites + println("Checking all possible subdirectories in battle_sprites...") + battleSpritesFiles?.forEach { subdir -> + try { + val subdirFiles = assetManager.list("battle_sprites/$subdir") + println(" $subdir contains: ${subdirFiles?.size ?: 0} files") + if (subdirFiles != null && subdirFiles.isNotEmpty()) { + println(" First few files: ${subdirFiles.take(3).joinToString(", ")}") + } + } catch (e: Exception) { + println(" Error listing $subdir: ${e.message}") + } + } - // Copy extracted_digimon_stats files from assets to internal storage - copyAssetDirectory("battle_sprites/extracted_digimon_stats", extractedStatsDir) + // Create the base directory for battle_sprites + val battleSpritesDir = File(context.filesDir, "battle_sprites") + if (!battleSpritesDir.exists()) { + battleSpritesDir.mkdirs() + println("Created battle_sprites directory: ${battleSpritesDir.absolutePath}") + } else { + println("battle_sprites directory already exists: ${battleSpritesDir.absolutePath}") + } - println("Sprite files copied successfully to: ${extractedAssetsDir.absolutePath}") - println("Stats files copied successfully to: ${extractedStatsDir.absolutePath}") + // Copy all subdirectories from battle_sprites assets to internal storage + println("Copying all battle_sprites subdirectories...") + battleSpritesFiles?.forEach { subdir -> + val sourcePath = "battle_sprites/$subdir" + val targetDir = File(battleSpritesDir, subdir) + println("Copying $sourcePath to ${targetDir.absolutePath}") + copyAssetDirectory(sourcePath, targetDir) + } + + println("Sprite files copied successfully to: ${battleSpritesDir.absolutePath}") + + // Verify that attack sprites were copied + val atkspritesDir = File(battleSpritesDir, "extracted_atksprites") + if (atkspritesDir.exists()) { + val attackFiles = atkspritesDir.listFiles() + println("Attack sprites directory exists with ${attackFiles?.size ?: 0} files") + if (attackFiles != null && attackFiles.isNotEmpty()) { + println("First few attack files: ${attackFiles.take(5).map { it.name }}") + } + } else { + println("WARNING: extracted_atksprites directory does not exist!") + // List what's actually in the battle_sprites directory + val battleSpritesContents = battleSpritesDir.listFiles() + println("battle_sprites directory contains: ${battleSpritesContents?.map { it.name }?.joinToString(", ")}") + } + + // Verify that battle backgrounds were copied + val battlebgsDir = File(battleSpritesDir, "extracted_battlebgs") + if (battlebgsDir.exists()) { + val bgFiles = battlebgsDir.listFiles() + println("Battle backgrounds directory exists with ${bgFiles?.size ?: 0} files") + if (bgFiles != null && bgFiles.isNotEmpty()) { + println("First few battle background files: ${bgFiles.take(5).map { it.name }}") + } + } else { + println("WARNING: extracted_battlebgs directory does not exist!") + } } catch (e: Exception) { println("Error copying sprite files: ${e.message}") @@ -41,6 +105,9 @@ class SpriteFileManager(private val context: Context) { val assetManager = context.assets val files = assetManager.list(assetPath) ?: return + println("Copying asset directory: $assetPath (${files.size} items)") + println("Files found: ${files.joinToString(", ")}") + for (file in files) { val assetFilePath = if (assetPath.isEmpty()) file else "$assetPath/$file" val targetFile = File(targetDir, file) @@ -50,21 +117,50 @@ class SpriteFileManager(private val context: Context) { targetFile.parentFile!!.mkdirs() } - // Check if it's a directory - val subFiles = assetManager.list(assetFilePath) - if (subFiles != null && subFiles.isNotEmpty()) { - // It's a directory, create it and copy contents - if (!targetFile.exists()) { - targetFile.mkdirs() + // Check if it's a directory by trying to list its contents + try { + val subFiles = assetManager.list(assetFilePath) + if (subFiles != null && subFiles.isNotEmpty()) { + // It's a directory, create it and copy contents + println("Copying subdirectory: $assetFilePath (${subFiles.size} files)") + if (!targetFile.exists()) { + targetFile.mkdirs() + } + copyAssetDirectory(assetFilePath, targetFile) + } else { + // It's a file, copy it + copyAssetFile(assetFilePath, targetFile) } - copyAssetDirectory(assetFilePath, targetFile) - } else { - // It's a file, copy it + } catch (e: Exception) { + // If we can't list contents, it's probably a file + println("Treating $assetFilePath as file (could not list contents)") copyAssetFile(assetFilePath, targetFile) } } + + // Special handling for extracted_atksprites - try to copy it directly if it wasn't found + if (assetPath == "battle_sprites/extracted_assets") { + println("Special handling: Checking for extracted_atksprites directory...") + try { + val atkspritesFiles = assetManager.list("battle_sprites/extracted_assets/extracted_atksprites") + if (atkspritesFiles != null && atkspritesFiles.isNotEmpty()) { + println("Found extracted_atksprites with ${atkspritesFiles.size} files") + val atkspritesDir = File(targetDir, "extracted_atksprites") + if (!atkspritesDir.exists()) { + atkspritesDir.mkdirs() + } + copyAssetDirectory("battle_sprites/extracted_assets/extracted_atksprites", atkspritesDir) + } else { + println("extracted_atksprites directory not found in assets") + } + } catch (e: Exception) { + println("Error checking extracted_atksprites: ${e.message}") + } + } + } catch (e: Exception) { println("Error copying asset directory $assetPath: ${e.message}") + e.printStackTrace() } } @@ -84,34 +180,34 @@ class SpriteFileManager(private val context: Context) { } fun checkSpriteFilesExist(): Boolean { - val extractedAssetsDir = File(context.filesDir, "battle_sprites/extracted_assets") - val extractedStatsDir = File(context.filesDir, "battle_sprites/extracted_digimon_stats") + val battleSpritesDir = File(context.filesDir, "battle_sprites") + val extractedAssetsDir = File(battleSpritesDir, "extracted_assets") + val extractedStatsDir = File(battleSpritesDir, "extracted_digimon_stats") + val atkspritesDir = File(battleSpritesDir, "extracted_atksprites") + val battlebgsDir = File(battleSpritesDir, "extracted_battlebgs") + val battleSpritesExist = battleSpritesDir.exists() && battleSpritesDir.listFiles()?.isNotEmpty() == true val assetsExist = extractedAssetsDir.exists() && extractedAssetsDir.listFiles()?.isNotEmpty() == true val statsExist = extractedStatsDir.exists() && extractedStatsDir.listFiles()?.isNotEmpty() == true + val atkspritesExist = atkspritesDir.exists() && atkspritesDir.listFiles()?.isNotEmpty() == true + val battlebgsExist = battlebgsDir.exists() && battlebgsDir.listFiles()?.isNotEmpty() == true - return assetsExist && statsExist + println("Checking sprite files exist:") + println(" battle_sprites exists: $battleSpritesExist") + println(" extracted_assets exists: $assetsExist") + println(" extracted_digimon_stats exists: $statsExist") + println(" extracted_atksprites exists: $atkspritesExist") + println(" extracted_battlebgs exists: $battlebgsExist") + + return battleSpritesExist && assetsExist && statsExist && atkspritesExist && battlebgsExist } fun clearSpriteFiles() { try { - val extractedAssetsDir = File(context.filesDir, "battle_sprites/extracted_assets") - val extractedStatsDir = File(context.filesDir, "battle_sprites/extracted_digimon_stats") - - if (extractedAssetsDir.exists()) { - deleteDirectory(extractedAssetsDir) - println("Cleared extracted_assets directory") - } - - if (extractedStatsDir.exists()) { - deleteDirectory(extractedStatsDir) - println("Cleared extracted_digimon_stats directory") - } - - // Also clear the battle_sprites directory if it's empty val battleSpritesDir = File(context.filesDir, "battle_sprites") - if (battleSpritesDir.exists() && battleSpritesDir.listFiles()?.isEmpty() == true) { - battleSpritesDir.delete() + + if (battleSpritesDir.exists()) { + deleteDirectory(battleSpritesDir) println("Cleared battle_sprites directory") } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteImage.kt index 7956511..f7d4430 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteImage.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteImage.kt @@ -6,34 +6,33 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import com.github.nacabaro.vbhelper.battle.BattleSpriteManager @Composable fun SpriteImage( - spriteName: String, - atlasName: String, + characterId: String, + frameNumber: Int, modifier: Modifier = Modifier, contentScale: ContentScale = ContentScale.Fit ) { val context = LocalContext.current - val spriteManager = remember { BattleSpriteManager(context) } + val spriteManager = remember { IndividualSpriteManager(context) } var bitmap by remember { mutableStateOf(null) } - LaunchedEffect(spriteName, atlasName) { - println("Loading sprite: $spriteName from atlas: $atlasName") - bitmap = spriteManager.loadSprite(spriteName, atlasName) + LaunchedEffect(characterId, frameNumber) { + println("Loading sprite frame: $frameNumber for character: $characterId") + bitmap = spriteManager.loadSpriteFrame(characterId, frameNumber) if (bitmap == null) { - println("Failed to load sprite: $spriteName from atlas: $atlasName") + println("Failed to load sprite frame: $frameNumber for character: $characterId") } else { - println("Successfully loaded sprite: $spriteName from atlas: $atlasName") + println("Successfully loaded sprite frame: $frameNumber for character: $characterId") } } bitmap?.let { bmp -> Image( bitmap = bmp.asImageBitmap(), - contentDescription = "Sprite: $spriteName", + contentDescription = "Sprite: $characterId frame $frameNumber", modifier = modifier, contentScale = contentScale ) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 10afc2f..4582e3e 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -45,7 +45,6 @@ import com.github.nacabaro.vbhelper.battle.APIBattleCharacter import android.util.Log import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.battle.RetrofitHelper -import com.github.nacabaro.vbhelper.battle.SpriteImage import com.github.nacabaro.vbhelper.battle.AttackSpriteImage import com.github.nacabaro.vbhelper.battle.SpriteFileManager import com.github.nacabaro.vbhelper.battle.ArenaBattleSystem @@ -286,7 +285,8 @@ fun PlayerBattleView( modifier = Modifier .size(80.dp) .scale(-1f, 1f), // Flip player Digimon horizontally - contentScale = ContentScale.Fit + contentScale = ContentScale.Fit, + reloadMappings = false ) // Attack sprite visibility and positioning based on attack phase @@ -500,7 +500,8 @@ fun OpponentBattleView( characterId = activeCharacter?.charaId ?: "dim011_mon01", animationType = animationType, modifier = Modifier.size(80.dp), - contentScale = ContentScale.Fit + contentScale = ContentScale.Fit, + reloadMappings = false ) // Attack sprite visibility and positioning based on attack phase @@ -642,12 +643,13 @@ fun BattlesScreen() { // Initialize sprite files on first load LaunchedEffect(Unit) { + println("BATTLESCREEN: LaunchedEffect triggered - checking sprite files...") val spriteFileManager = SpriteFileManager(context) if (!spriteFileManager.checkSpriteFilesExist()) { - println("Copying sprite files to internal storage...") + println("BATTLESCREEN: Copying sprite files to internal storage...") spriteFileManager.copySpriteFilesToInternalStorage() } else { - println("Sprite files already exist in internal storage") + println("BATTLESCREEN: Sprite files already exist in internal storage") } } @@ -856,7 +858,8 @@ fun BattlesScreen() { characterId = testCharacterId, animationType = currentTestAnimation, modifier = Modifier.size(120.dp), - contentScale = ContentScale.Fit + contentScale = ContentScale.Fit, + reloadMappings = false ) Spacer(modifier = Modifier.height(16.dp)) From c404f4f436eb35d4d9b63355dc42c8a1b355c3aa Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 07:01:39 -0400 Subject: [PATCH 38/65] Raised opponent HP bar. --- .../vbhelper/screens/BattlesScreen.kt | 192 +++++++++--------- 1 file changed, 100 insertions(+), 92 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 4582e3e..c41ec2b 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -473,111 +473,119 @@ fun OpponentBattleView( var previousAttackPhase by remember { mutableStateOf(null) } var isTransitioning by remember { mutableStateOf(false) } - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + Box( + modifier = Modifier.fillMaxSize() ) { - // Opponent Digimon - Box( + // Top section: Enemy HP bar and HP numbers + Column( modifier = Modifier .fillMaxWidth() - .size(80.dp), - contentAlignment = Alignment.CenterEnd + .padding(16.dp) ) { - // Determine animation type based on battle state - val animationType = when (battleSystem.attackPhase) { - 1 -> DigimonAnimationType.IDLE // Player attack on player screen - 2 -> DigimonAnimationType.IDLE // Player attack on opponent screen - 3 -> DigimonAnimationType.ATTACK // Opponent attack on opponent screen - 4 -> DigimonAnimationType.ATTACK // Opponent attack on player screen - else -> DigimonAnimationType.IDLE - } - - AnimatedSpriteImage( - characterId = activeCharacter?.charaId ?: "dim011_mon01", - animationType = animationType, - modifier = Modifier.size(80.dp), - contentScale = ContentScale.Fit, - reloadMappings = false + // Enemy HP bar + LinearProgressIndicator( + progress = battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Red, + trackColor = Color.Gray ) - - // Attack sprite visibility and positioning based on attack phase - val shouldShowAttack = when (battleSystem.attackPhase) { - 1 -> false // Player attack on player screen - 2 -> true // Player attack on opponent screen - 3 -> true // Opponent attack on opponent screen - 4 -> false // Opponent attack on player screen - else -> false - } - - if (shouldShowAttack) { - val xOffset = when (battleSystem.attackPhase) { - 2 -> (attackAnimationProgress * 400 - 350).dp // Player attack on opponent screen - start more to the left - 3 -> (-attackAnimationProgress * 400 + -50).dp // Opponent attack on opponent screen - start more to the left - else -> 0.dp + + // Enemy HP display numbers + Text( + text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = 14.sp, + color = Color.Black + ) + } + + // Middle section: Opponent Digimon + Box( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + // Opponent Digimon + Box( + modifier = Modifier + .fillMaxWidth() + .size(80.dp), + contentAlignment = Alignment.CenterEnd + ) { + // Determine animation type based on battle state + val animationType = when (battleSystem.attackPhase) { + 1 -> DigimonAnimationType.IDLE // Player attack on player screen + 2 -> DigimonAnimationType.IDLE // Player attack on opponent screen + 3 -> DigimonAnimationType.ATTACK // Opponent attack on opponent screen + 4 -> DigimonAnimationType.ATTACK // Opponent attack on player screen + else -> DigimonAnimationType.IDLE } - // Use correct character ID based on attack phase - val characterId = when (battleSystem.attackPhase) { - 2 -> playerCharacter?.charaId ?: "dim011_mon01" // Use player's character ID for player attack - 3 -> activeCharacter?.charaId ?: "dim011_mon01" // Use opponent's character ID for opponent attack - else -> "dim011_mon01" + AnimatedSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + animationType = animationType, + modifier = Modifier.size(80.dp), + contentScale = ContentScale.Fit, + reloadMappings = false + ) + + // Attack sprite visibility and positioning based on attack phase + val shouldShowAttack = when (battleSystem.attackPhase) { + 1 -> false // Player attack on player screen + 2 -> true // Player attack on opponent screen + 3 -> true // Opponent attack on opponent screen + 4 -> false // Opponent attack on player screen + else -> false } - // Handle sprite transition - LaunchedEffect(characterId, battleSystem.attackPhase) { - if ((previousCharacterId != null && previousCharacterId != characterId) || - (previousAttackPhase != null && previousAttackPhase != battleSystem.attackPhase)) { - // Character ID or attack phase changed, start transition - isTransitioning = true - delay(100) // Brief invisibility period - isTransitioning = false + if (shouldShowAttack) { + val xOffset = when (battleSystem.attackPhase) { + 2 -> (attackAnimationProgress * 400 - 350).dp // Player attack on opponent screen - start more to the left + 3 -> (-attackAnimationProgress * 400 + -50).dp // Opponent attack on opponent screen - start more to the left + else -> 0.dp + } + + // Use correct character ID based on attack phase + val characterId = when (battleSystem.attackPhase) { + 2 -> playerCharacter?.charaId ?: "dim011_mon01" // Use player's character ID for player attack + 3 -> activeCharacter?.charaId ?: "dim011_mon01" // Use opponent's character ID for opponent attack + else -> "dim011_mon01" + } + + // Handle sprite transition + LaunchedEffect(characterId, battleSystem.attackPhase) { + if ((previousCharacterId != null && previousCharacterId != characterId) || + (previousAttackPhase != null && previousAttackPhase != battleSystem.attackPhase)) { + // Character ID or attack phase changed, start transition + isTransitioning = true + delay(100) // Brief invisibility period + isTransitioning = false + } + previousCharacterId = characterId + previousAttackPhase = battleSystem.attackPhase + } + + println("OpponentBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") + + if (!isTransitioning) { + AttackSpriteImage( + characterId = characterId, + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = xOffset, + y = 0.dp + ) + .scale(if (battleSystem.attackPhase == 2) -1f else 1f, 1f), // Flip player attacks only + contentScale = ContentScale.Fit + ) } - previousCharacterId = characterId - previousAttackPhase = battleSystem.attackPhase - } - - println("OpponentBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") - - if (!isTransitioning) { - AttackSpriteImage( - characterId = characterId, - isLarge = true, - modifier = Modifier - .size(60.dp) - .offset( - x = xOffset, - y = 0.dp - ) - .scale(if (battleSystem.attackPhase == 2) -1f else 1f, 1f), // Flip player attacks only - contentScale = ContentScale.Fit - ) } } } - - // Enemy HP bar - LinearProgressIndicator( - progress = battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), - modifier = Modifier - .fillMaxWidth() - .height(10.dp), - color = Color.Red, - trackColor = Color.Gray - ) - - // Enemy HP display numbers - Text( - text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 14.sp, - color = Color.Black - ) - - // Spacer for layout balance - Spacer(modifier = Modifier.height(120.dp)) } } From f0f1d9830e6381803b2f9276f0fd072a5923b9cf Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 16:14:17 -0400 Subject: [PATCH 39/65] Added dodge and attack hit animations. --- .../vbhelper/battle/ArenaBattleSystem.kt | 187 ++++++++++ .../vbhelper/screens/BattlesScreen.kt | 326 ++++++++++++++++-- 2 files changed, 475 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt index 09999f7..3c4d048 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt @@ -43,6 +43,46 @@ class ArenaBattleSystem { private var _critBarProgress by mutableStateOf(0) val critBarProgress: Int get() = _critBarProgress + // Dodge animation states + private var _isDodging by mutableStateOf(false) + val isDodging: Boolean get() = _isDodging + + private var _dodgeProgress by mutableStateOf(0f) + val dodgeProgress: Float get() = _dodgeProgress + + private var _dodgeDirection by mutableStateOf(1f) // 1f = up, -1f = down + val dodgeDirection: Float get() = _dodgeDirection + + private var _isHit by mutableStateOf(false) + val isHit: Boolean get() = _isHit + + private var _hitProgress by mutableStateOf(0f) + val hitProgress: Float get() = _hitProgress + + // Separate states for player and opponent + private var _isPlayerDodging by mutableStateOf(false) + val isPlayerDodging: Boolean get() = _isPlayerDodging + + private var _isOpponentDodging by mutableStateOf(false) + val isOpponentDodging: Boolean get() = _isOpponentDodging + + private var _isPlayerHit by mutableStateOf(false) + val isPlayerHit: Boolean get() = _isPlayerHit + + private var _isOpponentHit by mutableStateOf(false) + val isOpponentHit: Boolean get() = _isOpponentHit + + // Counter-attack tracking + private var _shouldCounterAttack by mutableStateOf(false) + val shouldCounterAttack: Boolean get() = _shouldCounterAttack + + private var _counterAttackIsHit by mutableStateOf(false) + val counterAttackIsHit: Boolean get() = _counterAttackIsHit + + // Separate tracking for opponent attack result + private var _opponentAttackIsHit by mutableStateOf(false) + val opponentAttackIsHit: Boolean get() = _opponentAttackIsHit + fun startPlayerAttack() { Log.d(TAG, "Starting player attack") _attackPhase = 1 @@ -121,6 +161,18 @@ class ArenaBattleSystem { _isPlayerAttacking = false _attackIsHit = false _currentView = 0 + _isDodging = false + _dodgeProgress = 0f + _dodgeDirection = 1f + _isHit = false + _hitProgress = 0f + _isPlayerDodging = false + _isOpponentDodging = false + _isPlayerHit = false + _isOpponentHit = false + _shouldCounterAttack = false + _counterAttackIsHit = false + _opponentAttackIsHit = false Log.d(TAG, "Reset attack state") } @@ -137,4 +189,139 @@ class ArenaBattleSystem { _critBarProgress = progress //Log.d(TAG, "Updated crit bar progress: $progress") } + + // Dodge animation methods + fun startDodge() { + _isDodging = true + _dodgeProgress = 0f + _dodgeDirection = 1f // Start moving up + Log.d(TAG, "Started dodge animation") + } + + fun setDodgeProgress(progress: Float) { + _dodgeProgress = progress + } + + fun setDodgeDirection(direction: Float) { + _dodgeDirection = direction + } + + fun endDodge() { + _isDodging = false + _dodgeProgress = 0f + Log.d(TAG, "Ended dodge animation") + } + + // Hit animation methods + fun startHit() { + _isHit = true + _hitProgress = 0f + Log.d(TAG, "Started hit animation") + } + + fun setHitProgress(progress: Float) { + _hitProgress = progress + } + + fun endHit() { + _isHit = false + _hitProgress = 0f + Log.d(TAG, "Ended hit animation") + } + + // Player-specific dodge methods + fun startPlayerDodge() { + _isPlayerDodging = true + _dodgeProgress = 0f + _dodgeDirection = 1f + Log.d(TAG, "Started player dodge animation") + } + + fun endPlayerDodge() { + _isPlayerDodging = false + _dodgeProgress = 0f + Log.d(TAG, "Ended player dodge animation") + } + + // Opponent-specific dodge methods + fun startOpponentDodge() { + _isOpponentDodging = true + _dodgeProgress = 0f + _dodgeDirection = 1f + Log.d(TAG, "Started opponent dodge animation") + } + + fun endOpponentDodge() { + _isOpponentDodging = false + _dodgeProgress = 0f + Log.d(TAG, "Ended opponent dodge animation") + } + + // Player-specific hit methods + fun startPlayerHit() { + _isPlayerHit = true + _hitProgress = 0f + Log.d(TAG, "Started player hit animation") + } + + fun endPlayerHit() { + _isPlayerHit = false + _hitProgress = 0f + Log.d(TAG, "Ended player hit animation") + } + + // Opponent-specific hit methods + fun startOpponentHit() { + _isOpponentHit = true + _hitProgress = 0f + Log.d(TAG, "Started opponent hit animation") + } + + fun endOpponentHit() { + _isOpponentHit = false + _hitProgress = 0f + Log.d(TAG, "Ended opponent hit animation") + } + + // Combined method to handle attack result + fun handleAttackResult(isHit: Boolean) { + _attackIsHit = isHit + if (isHit) { + // Player attack hit - opponent gets hit + startOpponentHit() + } else { + // Player attack missed - opponent dodges + startOpponentDodge() + } + Log.d(TAG, "Handled player attack result: ${if (isHit) "HIT" else "DODGE"}") + } + + // Method to handle opponent attack result + fun handleOpponentAttackResult(isHit: Boolean) { + _opponentAttackIsHit = isHit + if (isHit) { + // Opponent attack hit - player gets hit + startPlayerHit() + } else { + // Opponent attack missed - player dodges + startPlayerDodge() + } + Log.d(TAG, "Handled opponent attack result: ${if (isHit) "HIT" else "DODGE"}") + } + + // Counter-attack methods + fun setupCounterAttack(isHit: Boolean) { + _shouldCounterAttack = true + _counterAttackIsHit = isHit + Log.d(TAG, "Setup counter-attack: ${if (isHit) "HIT" else "DODGE"}, isHit=$isHit") + } + + fun startCounterAttack() { + _attackPhase = 3 + _attackProgress = 0f + _isPlayerAttacking = false + _currentView = 1 + _opponentAttackIsHit = _counterAttackIsHit + Log.d(TAG, "Started counter-attack with opponentAttackIsHit=$_opponentAttackIsHit, counterAttackIsHit=$_counterAttackIsHit") + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index c41ec2b..03a9b61 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -37,6 +37,8 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.foundation.background import androidx.compose.ui.graphics.Color import androidx.compose.material3.ButtonDefaults @@ -52,6 +54,8 @@ import com.github.nacabaro.vbhelper.battle.DigimonAnimationType import com.github.nacabaro.vbhelper.battle.AnimatedSpriteImage import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import kotlin.math.sin +import kotlin.math.PI @Composable @@ -65,6 +69,7 @@ fun BattleScreen( context: android.content.Context? = null ) { val battleSystem = remember { ArenaBattleSystem() } + val coroutineScope = rememberCoroutineScope() // Initialize HP when battle starts LaunchedEffect(activeCharacter, opponentCharacter) { @@ -110,14 +115,36 @@ fun BattleScreen( while (progress < 1f) { progress += 0.016f // 60 FPS battleSystem.setAttackProgress(progress) + + // Trigger animation when attack reaches the opponent (around 55% progress for opponent dodge) + if (progress >= 0.55f && !battleSystem.isOpponentHit && !battleSystem.isOpponentDodging) { + if (battleSystem.attackIsHit) { + // Player attack hits opponent + println("Player attack hits opponent at progress $progress") + battleSystem.startOpponentHit() + } else { + // Player attack misses, opponent dodges + println("Player attack misses, opponent dodges at progress $progress") + battleSystem.startOpponentDodge() + } + } + delay(16) // 60 FPS } - println("Phase 2 completed, applying damage and starting opponent attack") - // Apply player's damage and start opponent attack - battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) - pendingOpponentDamage = 0f - delay(500) - battleSystem.startOpponentAttack() + println("Phase 2 completed, applying damage and starting opponent attack") + // Apply player's damage + battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) + pendingOpponentDamage = 0f + delay(500) + + // Check if there's a counter-attack set up + if (battleSystem.shouldCounterAttack) { + println("Starting counter-attack") + battleSystem.startCounterAttack() + } else { + // Normal opponent attack + battleSystem.startOpponentAttack() + } } 3 -> { // Phase 3: Opponent attack on opponent screen @@ -140,14 +167,31 @@ fun BattleScreen( while (progress < 1f) { progress += 0.016f // 60 FPS battleSystem.setAttackProgress(progress) + + // Trigger animation when attack reaches the player (around 75% progress - earlier for better timing) + if (progress >= 0.75f && !battleSystem.isPlayerHit && !battleSystem.isPlayerDodging) { + println("Phase 4: Checking player animation at progress $progress, opponentAttackIsHit=${battleSystem.opponentAttackIsHit}, shouldCounterAttack=${battleSystem.shouldCounterAttack}, counterAttackIsHit=${battleSystem.counterAttackIsHit}") + println("Phase 4: Player animation decision - opponentAttackIsHit=${battleSystem.opponentAttackIsHit}, will ${if (battleSystem.opponentAttackIsHit) "HIT" else "DODGE"}") + println("Phase 4: Full debug - attackPhase=${battleSystem.attackPhase}, isPlayerAttacking=${battleSystem.isPlayerAttacking}") + if (battleSystem.opponentAttackIsHit) { + // Opponent attack hits player + println("Opponent attack hits player at progress $progress") + battleSystem.startPlayerHit() + } else { + // Opponent attack misses, player dodges + println("Opponent attack misses, player dodges at progress $progress") + battleSystem.startPlayerDodge() + } + } + delay(16) // 60 FPS } - println("Phase 4 completed, applying damage and resetting") - // Apply opponent's damage and reset - battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) - pendingPlayerDamage = 0f - battleSystem.resetAttackState() - battleSystem.enableAttackButton() + println("Phase 4 completed, applying damage and resetting") + // Apply opponent's damage and reset + battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) + pendingPlayerDamage = 0f + battleSystem.resetAttackState() + battleSystem.enableAttackButton() // Check if battle is over if (battleSystem.checkBattleOver()) { @@ -158,6 +202,112 @@ fun BattleScreen( } } + // Player dodge animation + LaunchedEffect(battleSystem.isPlayerDodging) { + if (battleSystem.isPlayerDodging) { + println("Starting player dodge animation") + var dodgeProgress = 0f + var dodgeDirection = 1f // Start moving up + + // Move up + while (dodgeProgress < 1f) { + dodgeProgress += 0.05f // Faster dodge movement + battleSystem.setDodgeProgress(dodgeProgress) + battleSystem.setDodgeDirection(dodgeDirection) + delay(16) // 60 FPS + } + + // Wait at the top + delay(200) + + // Move back down + dodgeDirection = -1f + dodgeProgress = 0f + while (dodgeProgress < 1f) { + dodgeProgress += 0.05f + battleSystem.setDodgeProgress(dodgeProgress) + battleSystem.setDodgeDirection(dodgeDirection) + delay(16) + } + + battleSystem.endPlayerDodge() + println("Player dodge animation completed") + } + } + + // Opponent dodge animation + LaunchedEffect(battleSystem.isOpponentDodging) { + if (battleSystem.isOpponentDodging) { + println("Starting opponent dodge animation") + var dodgeProgress = 0f + var dodgeDirection = 1f // Start moving up + + // Move up + while (dodgeProgress < 1f) { + dodgeProgress += 0.05f // Faster dodge movement + battleSystem.setDodgeProgress(dodgeProgress) + battleSystem.setDodgeDirection(dodgeDirection) + delay(16) // 60 FPS + } + + // Wait at the top + delay(200) + + // Move back down + dodgeDirection = -1f + dodgeProgress = 0f + while (dodgeProgress < 1f) { + dodgeProgress += 0.05f + battleSystem.setDodgeProgress(dodgeProgress) + battleSystem.setDodgeDirection(dodgeDirection) + delay(16) + } + + battleSystem.endOpponentDodge() + println("Opponent dodge animation completed") + } + } + + // Player hit animation + LaunchedEffect(battleSystem.isPlayerHit) { + if (battleSystem.isPlayerHit) { + println("Starting player hit animation") + var hitProgress = 0f + + // Quick hit effect + while (hitProgress < 1f) { + hitProgress += 0.1f // Fast hit effect + battleSystem.setHitProgress(hitProgress) + delay(16) + } + + delay(100) // Brief pause + + battleSystem.endPlayerHit() + println("Player hit animation completed") + } + } + + // Opponent hit animation + LaunchedEffect(battleSystem.isOpponentHit) { + if (battleSystem.isOpponentHit) { + println("Starting opponent hit animation") + var hitProgress = 0f + + // Quick hit effect + while (hitProgress < 1f) { + hitProgress += 0.1f // Fast hit effect + battleSystem.setHitProgress(hitProgress) + delay(16) + } + + delay(100) // Brief pause + + battleSystem.endOpponentHit() + println("Opponent hit animation completed") + } + } + Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -179,7 +329,8 @@ fun BattleScreen( onSetPendingDamage = { playerDamage, opponentDamage -> pendingPlayerDamage = playerDamage pendingOpponentDamage = opponentDamage - } + }, + coroutineScope = coroutineScope ) } 1 -> { @@ -207,12 +358,14 @@ fun PlayerBattleView( activeCharacter: APIBattleCharacter?, context: android.content.Context?, opponent: APIBattleCharacter?, - onSetPendingDamage: (Float, Float) -> Unit + onSetPendingDamage: (Float, Float) -> Unit, + coroutineScope: kotlinx.coroutines.CoroutineScope ) { // Track previous character ID to detect transitions var previousCharacterId by remember { mutableStateOf(null) } var previousAttackPhase by remember { mutableStateOf(null) } var isTransitioning by remember { mutableStateOf(false) } + var lastApiResult by remember { mutableStateOf(null) } Box( modifier = Modifier.fillMaxSize() @@ -238,6 +391,16 @@ fun PlayerBattleView( Spacer(modifier = Modifier.height(16.dp)) + // Debug display + if (lastApiResult != null) { + Text( + text = "Debug: state=${lastApiResult!!.state}, playerAttackHit=${lastApiResult!!.playerAttackHit}, opponentDamage=${lastApiResult!!.opponentAttackDamage}", + color = Color.Red, + fontSize = 10.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + } + // Health bar LinearProgressIndicator( progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), @@ -271,20 +434,54 @@ fun PlayerBattleView( contentAlignment = Alignment.CenterStart ) { // Determine animation type based on battle state - val animationType = when (battleSystem.attackPhase) { - 1 -> DigimonAnimationType.ATTACK // Player attack on player screen - 2 -> DigimonAnimationType.ATTACK // Player attack on opponent screen - 3 -> DigimonAnimationType.IDLE // Opponent attack on opponent screen - 4 -> DigimonAnimationType.IDLE // Opponent attack on player screen + val animationType = when { + battleSystem.isPlayerDodging -> DigimonAnimationType.WALK // Use walk animation for dodge + battleSystem.isPlayerHit -> DigimonAnimationType.SLEEP // Use sleep animation for hit effect (injured sprite) + battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Player attack on player screen + battleSystem.attackPhase == 2 -> DigimonAnimationType.ATTACK // Player attack on opponent screen + battleSystem.attackPhase == 3 -> DigimonAnimationType.IDLE // Opponent attack on opponent screen + battleSystem.attackPhase == 4 -> DigimonAnimationType.IDLE // Opponent attack on player screen else -> DigimonAnimationType.IDLE } + // Calculate vertical offset for dodge animation + val verticalOffset = if (battleSystem.isPlayerDodging) { + val dodgeHeight = 30.dp + val progress = battleSystem.dodgeProgress + val direction = battleSystem.dodgeDirection + + if (direction > 0) { + // Moving up + (progress * dodgeHeight.value).dp + } else { + // Moving back down + ((1f - progress) * dodgeHeight.value).dp + } + } else { + 0.dp + } + + // Calculate hit effect (slight shake) + val hitOffset = if (battleSystem.isPlayerHit) { + val shakeAmount = 5.dp + val progress = battleSystem.hitProgress + // Simple shake effect without complex math + val shake = if (progress < 0.5f) progress * 2f else (1f - progress) * 2f + (shake * shakeAmount.value).dp + } else { + 0.dp + } + AnimatedSpriteImage( characterId = activeCharacter?.charaId ?: "dim011_mon01", animationType = animationType, modifier = Modifier .size(80.dp) - .scale(-1f, 1f), // Flip player Digimon horizontally + .scale(-1f, 1f) // Flip player Digimon horizontally + .offset( + x = hitOffset, + y = verticalOffset + ), contentScale = ContentScale.Fit, reloadMappings = false ) @@ -407,6 +604,7 @@ fun PlayerBattleView( ) { apiResult -> // Handle API response here println("API Result: $apiResult") + lastApiResult = apiResult // Store for debug display // Update HP based on API response when (apiResult.state) { @@ -414,18 +612,35 @@ fun PlayerBattleView( // Match is still ongoing - update HP and continue println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") - // Set pending damage based on API result - if (apiResult.playerAttackHit) { - // Player attack hit - enemy takes damage at end of player animation - println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") - onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage - battleSystem.setAttackHitState(true) - } else { - // Player attack missed - enemy counter-attacks and player takes damage - println("Player attack missed! Enemy counter-attacks and player takes ${apiResult.opponentAttackDamage} damage") - onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), 0f) // Player takes damage - battleSystem.setAttackHitState(false) - } + // Set pending damage based on API result + if (apiResult.playerAttackDamage > 0) { + // Player attack hit - enemy takes damage at end of player animation + println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") + onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage + battleSystem.setAttackHitState(true) + } else { + // Player attack missed - enemy counter-attacks + println("Player attack missed! Enemy counter-attacks") + battleSystem.setAttackHitState(false) + // Set up counter-attack - determine if it hits based on API result + val counterAttackHits = apiResult.opponentAttackDamage > 0 + println("Setting up counter-attack: counterAttackHits=$counterAttackHits, opponentAttackDamage=${apiResult.opponentAttackDamage}") + println("Full API response: status=${apiResult.status}, state=${apiResult.state}, playerAttackHit=${apiResult.playerAttackHit}, playerAttackDamage=${apiResult.playerAttackDamage}, opponentAttackDamage=${apiResult.opponentAttackDamage}, playerHP=${apiResult.playerHP}, opponentHP=${apiResult.opponentHP}") + println("DEBUG: Using playerAttackDamage > 0 instead of playerAttackHit for hit detection") + + // Use opponentAttackDamage to determine counter-attack hit + val finalCounterAttackHits = counterAttackHits + println("Using opponentAttackDamage > 0 for counter-attack: $finalCounterAttackHits") + + if (finalCounterAttackHits) { + println("Counter-attack hits! Player takes ${apiResult.opponentAttackDamage} damage") + onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), 0f) // Player takes damage + } else { + println("Counter-attack misses! Player dodges") + onSetPendingDamage(0f, 0f) // No damage + } + battleSystem.setupCounterAttack(finalCounterAttackHits) + } } 2 -> { // Match is over - transition to results screen @@ -515,18 +730,53 @@ fun OpponentBattleView( contentAlignment = Alignment.CenterEnd ) { // Determine animation type based on battle state - val animationType = when (battleSystem.attackPhase) { - 1 -> DigimonAnimationType.IDLE // Player attack on player screen - 2 -> DigimonAnimationType.IDLE // Player attack on opponent screen - 3 -> DigimonAnimationType.ATTACK // Opponent attack on opponent screen - 4 -> DigimonAnimationType.ATTACK // Opponent attack on player screen + val animationType = when { + battleSystem.isOpponentDodging -> DigimonAnimationType.WALK // Use walk animation for dodge + battleSystem.isOpponentHit -> DigimonAnimationType.SLEEP // Use sleep animation for hit effect (injured sprite) + battleSystem.attackPhase == 1 -> DigimonAnimationType.IDLE // Player attack on player screen + battleSystem.attackPhase == 2 -> DigimonAnimationType.IDLE // Player attack on opponent screen + battleSystem.attackPhase == 3 -> DigimonAnimationType.ATTACK // Opponent attack on opponent screen + battleSystem.attackPhase == 4 -> DigimonAnimationType.ATTACK // Opponent attack on player screen else -> DigimonAnimationType.IDLE } + // Calculate vertical offset for dodge animation + val verticalOffset = if (battleSystem.isOpponentDodging) { + val dodgeHeight = 30.dp + val progress = battleSystem.dodgeProgress + val direction = battleSystem.dodgeDirection + + if (direction > 0) { + // Moving up + (progress * dodgeHeight.value).dp + } else { + // Moving back down + ((1f - progress) * dodgeHeight.value).dp + } + } else { + 0.dp + } + + // Calculate hit effect (slight shake) + val hitOffset = if (battleSystem.isOpponentHit) { + val shakeAmount = 5.dp + val progress = battleSystem.hitProgress + // Simple shake effect without complex math + val shake = if (progress < 0.5f) progress * 2f else (1f - progress) * 2f + (shake * shakeAmount.value).dp + } else { + 0.dp + } + AnimatedSpriteImage( characterId = activeCharacter?.charaId ?: "dim011_mon01", animationType = animationType, - modifier = Modifier.size(80.dp), + modifier = Modifier + .size(80.dp) + .offset( + x = hitOffset, + y = verticalOffset + ), contentScale = ContentScale.Fit, reloadMappings = false ) From 7843be700441d06b0fa33c283667955bfe739faf Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 17:35:44 -0400 Subject: [PATCH 40/65] Fixed dodge animation. Sprites now move up then back to original location. --- .../vbhelper/battle/ArenaBattleSystem.kt | 45 ++++++++++++++++--- .../vbhelper/screens/BattlesScreen.kt | 40 ++++++++--------- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt index 3c4d048..6105bd3 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt @@ -66,6 +66,19 @@ class ArenaBattleSystem { private var _isOpponentDodging by mutableStateOf(false) val isOpponentDodging: Boolean get() = _isOpponentDodging + // Separate dodge progress and direction for player and opponent + private var _playerDodgeProgress by mutableStateOf(0f) + val playerDodgeProgress: Float get() = _playerDodgeProgress + + private var _playerDodgeDirection by mutableStateOf(1f) + val playerDodgeDirection: Float get() = _playerDodgeDirection + + private var _opponentDodgeProgress by mutableStateOf(0f) + val opponentDodgeProgress: Float get() = _opponentDodgeProgress + + private var _opponentDodgeDirection by mutableStateOf(1f) + val opponentDodgeDirection: Float get() = _opponentDodgeDirection + private var _isPlayerHit by mutableStateOf(false) val isPlayerHit: Boolean get() = _isPlayerHit @@ -168,6 +181,10 @@ class ArenaBattleSystem { _hitProgress = 0f _isPlayerDodging = false _isOpponentDodging = false + _playerDodgeProgress = 0f + _playerDodgeDirection = 1f + _opponentDodgeProgress = 0f + _opponentDodgeDirection = 1f _isPlayerHit = false _isOpponentHit = false _shouldCounterAttack = false @@ -232,31 +249,47 @@ class ArenaBattleSystem { // Player-specific dodge methods fun startPlayerDodge() { _isPlayerDodging = true - _dodgeProgress = 0f - _dodgeDirection = 1f + _playerDodgeProgress = 0f + _playerDodgeDirection = 1f Log.d(TAG, "Started player dodge animation") } fun endPlayerDodge() { _isPlayerDodging = false - _dodgeProgress = 0f + _playerDodgeProgress = 0f Log.d(TAG, "Ended player dodge animation") } + fun setPlayerDodgeProgress(progress: Float) { + _playerDodgeProgress = progress + } + + fun setPlayerDodgeDirection(direction: Float) { + _playerDodgeDirection = direction + } + // Opponent-specific dodge methods fun startOpponentDodge() { _isOpponentDodging = true - _dodgeProgress = 0f - _dodgeDirection = 1f + _opponentDodgeProgress = 0f + _opponentDodgeDirection = 1f Log.d(TAG, "Started opponent dodge animation") } fun endOpponentDodge() { _isOpponentDodging = false - _dodgeProgress = 0f + _opponentDodgeProgress = 0f Log.d(TAG, "Ended opponent dodge animation") } + fun setOpponentDodgeProgress(progress: Float) { + _opponentDodgeProgress = progress + } + + fun setOpponentDodgeDirection(direction: Float) { + _opponentDodgeDirection = direction + } + // Player-specific hit methods fun startPlayerHit() { _isPlayerHit = true diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 03a9b61..0311542 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -212,8 +212,8 @@ fun BattleScreen( // Move up while (dodgeProgress < 1f) { dodgeProgress += 0.05f // Faster dodge movement - battleSystem.setDodgeProgress(dodgeProgress) - battleSystem.setDodgeDirection(dodgeDirection) + battleSystem.setPlayerDodgeProgress(dodgeProgress) + battleSystem.setPlayerDodgeDirection(dodgeDirection) delay(16) // 60 FPS } @@ -225,8 +225,8 @@ fun BattleScreen( dodgeProgress = 0f while (dodgeProgress < 1f) { dodgeProgress += 0.05f - battleSystem.setDodgeProgress(dodgeProgress) - battleSystem.setDodgeDirection(dodgeDirection) + battleSystem.setPlayerDodgeProgress(dodgeProgress) + battleSystem.setPlayerDodgeDirection(dodgeDirection) delay(16) } @@ -245,8 +245,8 @@ fun BattleScreen( // Move up while (dodgeProgress < 1f) { dodgeProgress += 0.05f // Faster dodge movement - battleSystem.setDodgeProgress(dodgeProgress) - battleSystem.setDodgeDirection(dodgeDirection) + battleSystem.setOpponentDodgeProgress(dodgeProgress) + battleSystem.setOpponentDodgeDirection(dodgeDirection) delay(16) // 60 FPS } @@ -258,8 +258,8 @@ fun BattleScreen( dodgeProgress = 0f while (dodgeProgress < 1f) { dodgeProgress += 0.05f - battleSystem.setDodgeProgress(dodgeProgress) - battleSystem.setDodgeDirection(dodgeDirection) + battleSystem.setOpponentDodgeProgress(dodgeProgress) + battleSystem.setOpponentDodgeDirection(dodgeDirection) delay(16) } @@ -447,15 +447,15 @@ fun PlayerBattleView( // Calculate vertical offset for dodge animation val verticalOffset = if (battleSystem.isPlayerDodging) { val dodgeHeight = 30.dp - val progress = battleSystem.dodgeProgress - val direction = battleSystem.dodgeDirection + val progress = battleSystem.playerDodgeProgress + val direction = battleSystem.playerDodgeDirection if (direction > 0) { - // Moving up - (progress * dodgeHeight.value).dp + // Moving up (negative offset to move UP visually) + -(progress * dodgeHeight.value).dp } else { - // Moving back down - ((1f - progress) * dodgeHeight.value).dp + // Moving back down (from negative peak to 0) + -((1f - progress) * dodgeHeight.value).dp } } else { 0.dp @@ -743,15 +743,15 @@ fun OpponentBattleView( // Calculate vertical offset for dodge animation val verticalOffset = if (battleSystem.isOpponentDodging) { val dodgeHeight = 30.dp - val progress = battleSystem.dodgeProgress - val direction = battleSystem.dodgeDirection + val progress = battleSystem.opponentDodgeProgress + val direction = battleSystem.opponentDodgeDirection if (direction > 0) { - // Moving up - (progress * dodgeHeight.value).dp + // Moving up (negative offset to move UP visually) + -(progress * dodgeHeight.value).dp } else { - // Moving back down - ((1f - progress) * dodgeHeight.value).dp + // Moving back down (from negative peak to 0) + -((1f - progress) * dodgeHeight.value).dp } } else { 0.dp From b3cf823c3f4edb75e1b8b4f1fcf51a712b53ab5b Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 18:35:50 -0400 Subject: [PATCH 41/65] Swapped to 3 panel battle. --- .../vbhelper/screens/BattlesScreen.kt | 545 +++++++++++++++--- 1 file changed, 463 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 0311542..f16f13d 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -96,8 +96,8 @@ fun BattleScreen( LaunchedEffect(battleSystem.attackPhase) { when (battleSystem.attackPhase) { 1 -> { - // Phase 1: Player attack on player screen - println("Starting Phase 1: Player attack on player screen") + // Phase 1: Both attacks from middle screen + println("Starting Phase 1: Both attacks from middle screen") var progress = 0f while (progress < 1f) { progress += 0.016f // 60 FPS @@ -108,86 +108,72 @@ fun BattleScreen( battleSystem.advanceAttackPhase() } 2 -> { - // Phase 2: Player attack on opponent screen - println("Starting Phase 2: Player attack on opponent screen") - battleSystem.switchToView(1) + // Phase 2: Player attack on enemy screen + println("Starting Phase 2: Player attack on enemy screen") + battleSystem.switchToView(2) // Enemy screen var progress = 0f while (progress < 1f) { progress += 0.016f // 60 FPS battleSystem.setAttackProgress(progress) - // Trigger animation when attack reaches the opponent (around 55% progress for opponent dodge) + // Trigger animation when attack reaches the enemy (around 55% progress for enemy dodge) if (progress >= 0.55f && !battleSystem.isOpponentHit && !battleSystem.isOpponentDodging) { if (battleSystem.attackIsHit) { - // Player attack hits opponent - println("Player attack hits opponent at progress $progress") + // Player attack hits enemy + println("Player attack hits enemy at progress $progress") battleSystem.startOpponentHit() } else { - // Player attack misses, opponent dodges - println("Player attack misses, opponent dodges at progress $progress") + // Player attack misses, enemy dodges + println("Player attack misses, enemy dodges at progress $progress") battleSystem.startOpponentDodge() } } delay(16) // 60 FPS } - println("Phase 2 completed, applying damage and starting opponent attack") + println("Phase 2 completed, applying damage and starting Phase 3") // Apply player's damage battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) pendingOpponentDamage = 0f delay(500) - // Check if there's a counter-attack set up + // Check if there should be a counter-attack if (battleSystem.shouldCounterAttack) { - println("Starting counter-attack") + println("Starting counter-attack from Phase 2") battleSystem.startCounterAttack() } else { - // Normal opponent attack - battleSystem.startOpponentAttack() + println("No counter-attack, advancing to Phase 3") + battleSystem.advanceAttackPhase() } } 3 -> { - // Phase 3: Opponent attack on opponent screen - println("Starting Phase 3: Opponent attack on opponent screen") - battleSystem.switchToView(1) - var progress = 0f - while (progress < 1f) { - progress += 0.016f // 60 FPS - battleSystem.setAttackProgress(progress) - delay(16) // 60 FPS - } - println("Phase 3 completed, advancing to Phase 4") - battleSystem.advanceAttackPhase() - } - 4 -> { - // Phase 4: Opponent attack on player screen - println("Starting Phase 4: Opponent attack on player screen") - battleSystem.switchToView(0) + // Phase 3: Enemy attack on player screen + println("Starting Phase 3: Enemy attack on player screen") + battleSystem.switchToView(1) // Player screen var progress = 0f while (progress < 1f) { progress += 0.016f // 60 FPS battleSystem.setAttackProgress(progress) - // Trigger animation when attack reaches the player (around 75% progress - earlier for better timing) + // Trigger animation when attack reaches the player (around 75% progress for player dodge) if (progress >= 0.75f && !battleSystem.isPlayerHit && !battleSystem.isPlayerDodging) { - println("Phase 4: Checking player animation at progress $progress, opponentAttackIsHit=${battleSystem.opponentAttackIsHit}, shouldCounterAttack=${battleSystem.shouldCounterAttack}, counterAttackIsHit=${battleSystem.counterAttackIsHit}") - println("Phase 4: Player animation decision - opponentAttackIsHit=${battleSystem.opponentAttackIsHit}, will ${if (battleSystem.opponentAttackIsHit) "HIT" else "DODGE"}") - println("Phase 4: Full debug - attackPhase=${battleSystem.attackPhase}, isPlayerAttacking=${battleSystem.isPlayerAttacking}") + println("Phase 3: Checking player animation at progress $progress, opponentAttackIsHit=${battleSystem.opponentAttackIsHit}") + println("Phase 3: Player animation decision - opponentAttackIsHit=${battleSystem.opponentAttackIsHit}, will ${if (battleSystem.opponentAttackIsHit) "HIT" else "DODGE"}") if (battleSystem.opponentAttackIsHit) { - // Opponent attack hits player - println("Opponent attack hits player at progress $progress") + // Enemy attack hits player + println("Enemy attack hits player at progress $progress") battleSystem.startPlayerHit() } else { - // Opponent attack misses, player dodges - println("Opponent attack misses, player dodges at progress $progress") + // Enemy attack misses, player dodges + println("Enemy attack misses, player dodges at progress $progress") battleSystem.startPlayerDodge() } } delay(16) // 60 FPS } - println("Phase 4 completed, applying damage and resetting") - // Apply opponent's damage and reset + println("Phase 3 completed, applying damage and resetting") + // Apply enemy's damage and reset battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) pendingPlayerDamage = 0f battleSystem.resetAttackState() @@ -314,7 +300,28 @@ fun BattleScreen( ) { when (battleSystem.currentView) { 0 -> { - // Player view + // Middle screen - both Digimon + MiddleBattleView( + battleSystem = battleSystem, + stage = stage, + playerName = playerName, + opponentName = opponentName, + attackAnimationProgress = battleSystem.attackProgress, + onAttackClick = { + battleSystem.startPlayerAttack() + }, + activeCharacter = activeCharacter, + opponentCharacter = opponentCharacter, + context = context, + onSetPendingDamage = { playerDamage, opponentDamage -> + pendingPlayerDamage = playerDamage + pendingOpponentDamage = opponentDamage + }, + coroutineScope = coroutineScope + ) + } + 1 -> { + // Player screen - enemy attack PlayerBattleView( battleSystem = battleSystem, stage = stage, @@ -333,9 +340,9 @@ fun BattleScreen( coroutineScope = coroutineScope ) } - 1 -> { - // Opponent view - OpponentBattleView( + 2 -> { + // Enemy screen - player attack + EnemyBattleView( battleSystem = battleSystem, stage = stage, opponentName = opponentName, @@ -348,6 +355,394 @@ fun BattleScreen( } } +@Composable +fun MiddleBattleView( + battleSystem: ArenaBattleSystem, + stage: String, + playerName: String, + opponentName: String, + attackAnimationProgress: Float, + onAttackClick: () -> Unit, + activeCharacter: APIBattleCharacter?, + opponentCharacter: APIBattleCharacter?, + context: android.content.Context?, + onSetPendingDamage: (Float, Float) -> Unit, + coroutineScope: kotlinx.coroutines.CoroutineScope +) { + // Track previous character ID to detect transitions + var previousCharacterId by remember { mutableStateOf(null) } + var previousAttackPhase by remember { mutableStateOf(null) } + var isTransitioning by remember { mutableStateOf(false) } + var lastApiResult by remember { mutableStateOf(null) } + + Box( + modifier = Modifier.fillMaxSize() + ) { + // Top section: Exit button, HP bars, and HP numbers + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + // Exit button at the top-right + Box( + modifier = Modifier.fillMaxWidth() + ) { + Button( + onClick = { /* TODO: Add exit functionality */ }, + modifier = Modifier.align(Alignment.TopEnd), + colors = ButtonDefaults.buttonColors(containerColor = Color.Red) + ) { + Text("Exit", color = Color.White, fontSize = 14.sp) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // Debug display + if (lastApiResult != null) { + Text( + text = "Debug: state=${lastApiResult!!.state}, playerAttackHit=${lastApiResult!!.playerAttackHit}, opponentDamage=${lastApiResult!!.opponentAttackDamage}", + color = Color.Red, + fontSize = 10.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + } + + // Enemy HP bar (top) + LinearProgressIndicator( + progress = battleSystem.opponentHP / (opponentCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Red, + trackColor = Color.Gray + ) + + // Enemy HP display numbers + Text( + text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponentCharacter?.baseHp ?: 100}", + fontSize = 14.sp, + color = Color.Black + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Player HP bar (bottom) + LinearProgressIndicator( + progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Green, + trackColor = Color.Gray + ) + + // Player HP display numbers + Text( + text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = 14.sp, + color = Color.Black + ) + } + + // Middle section: Both Digimon with horizontal line separator + Box( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.SpaceEvenly + ) { + // Enemy Digimon (top half) + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + contentAlignment = Alignment.CenterEnd + ) { + // Determine animation type for enemy + val enemyAnimationType = when { + battleSystem.isOpponentDodging -> DigimonAnimationType.WALK + battleSystem.isOpponentHit -> DigimonAnimationType.SLEEP + battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Both attacking + else -> DigimonAnimationType.IDLE + } + + // Calculate vertical offset for enemy dodge animation + val enemyVerticalOffset = if (battleSystem.isOpponentDodging) { + val dodgeHeight = 30.dp + val progress = battleSystem.opponentDodgeProgress + val direction = battleSystem.opponentDodgeDirection + + if (direction > 0) { + -(progress * dodgeHeight.value).dp + } else { + -((1f - progress) * dodgeHeight.value).dp + } + } else { + 0.dp + } + + // Calculate hit effect for enemy + val enemyHitOffset = if (battleSystem.isOpponentHit) { + val shakeAmount = 5.dp + val progress = battleSystem.hitProgress + val shake = if (progress < 0.5f) progress * 2f else (1f - progress) * 2f + (shake * shakeAmount.value).dp + } else { + 0.dp + } + + AnimatedSpriteImage( + characterId = opponentCharacter?.charaId ?: "dim011_mon01", + animationType = enemyAnimationType, + modifier = Modifier + .size(80.dp) + .offset( + x = enemyHitOffset, + y = enemyVerticalOffset + ), + contentScale = ContentScale.Fit, + reloadMappings = false + ) + + // Enemy attack sprite (Phase 1 only) + if (battleSystem.attackPhase == 1) { + val xOffset = (attackAnimationProgress * 400 + 50).dp // Move right off screen + + AttackSpriteImage( + characterId = opponentCharacter?.charaId ?: "dim011_mon01", + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = xOffset, + y = 0.dp + ), + contentScale = ContentScale.Fit + ) + } + } + + // Horizontal line separator + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .background(Color.Black) + ) + + // Player Digimon (bottom half) + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + contentAlignment = Alignment.CenterStart + ) { + // Determine animation type for player + val playerAnimationType = when { + battleSystem.isPlayerDodging -> DigimonAnimationType.WALK + battleSystem.isPlayerHit -> DigimonAnimationType.SLEEP + battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Both attacking + else -> DigimonAnimationType.IDLE + } + + // Calculate vertical offset for player dodge animation + val playerVerticalOffset = if (battleSystem.isPlayerDodging) { + val dodgeHeight = 30.dp + val progress = battleSystem.playerDodgeProgress + val direction = battleSystem.playerDodgeDirection + + if (direction > 0) { + -(progress * dodgeHeight.value).dp + } else { + -((1f - progress) * dodgeHeight.value).dp + } + } else { + 0.dp + } + + // Calculate hit effect for player + val playerHitOffset = if (battleSystem.isPlayerHit) { + val shakeAmount = 5.dp + val progress = battleSystem.hitProgress + val shake = if (progress < 0.5f) progress * 2f else (1f - progress) * 2f + (shake * shakeAmount.value).dp + } else { + 0.dp + } + + AnimatedSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + animationType = playerAnimationType, + modifier = Modifier + .size(80.dp) + .scale(-1f, 1f) // Flip player Digimon horizontally + .offset( + x = playerHitOffset, + y = playerVerticalOffset + ), + contentScale = ContentScale.Fit, + reloadMappings = false + ) + + // Player attack sprite (Phase 1 only) + if (battleSystem.attackPhase == 1) { + val xOffset = (-attackAnimationProgress * 400 - 50).dp // Move left off screen + + AttackSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = xOffset, + y = 0.dp + ) + .scale(-1f, 1f), // Flip attack sprite + contentScale = ContentScale.Fit + ) + } + } + } + } + + // Bottom section: Critical bar and Attack button + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .align(Alignment.BottomCenter), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Critical bar + LinearProgressIndicator( + progress = battleSystem.critBarProgress / 100f, + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Yellow, + trackColor = Color.Gray + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Attack button + Button( + onClick = { + println("Attack button clicked!") + + // Get crit bar progress as float (0.0f to 100.0f) + val critBarProgressFloat = battleSystem.critBarProgress.toFloat() + + // Determine player and opponent stages + val playerStage = when (activeCharacter?.stage) { + 0 -> 0 // rookie + 1 -> 1 // champion + 2 -> 2 // ultimate + 3 -> 3 // mega + else -> 0 + } + + val opponentStage = when (opponentCharacter?.stage) { + 0 -> 0 // rookie + 1 -> 1 // champion + 2 -> 2 // ultimate + 3 -> 3 // mega + else -> 0 + } + + // Send API call with all parameters + context?.let { ctx -> + // Start both attacks simultaneously + battleSystem.startPlayerAttack() + + RetrofitHelper().getPVPWinner( + ctx, + 1, + 2, + activeCharacter?.name ?: "Player", + playerStage, + opponentStage, + opponentCharacter?.name ?: "Opponent", + opponentStage + ) { apiResult -> + // Handle API response here + println("API Result: $apiResult") + lastApiResult = apiResult // Store for debug display + + // Update HP based on API response + when (apiResult.state) { + 1 -> { + // Match is still ongoing - update HP and continue + println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") + + // Set pending damage based on API result + if (apiResult.playerAttackDamage > 0) { + // Player attack hit - enemy takes damage at end of player animation + println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") + onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage + battleSystem.setAttackHitState(true) + } else { + // Player attack missed - enemy counter-attacks + println("Player attack missed! Enemy counter-attacks") + battleSystem.setAttackHitState(false) + // Set up counter-attack - determine if it hits based on API result + val counterAttackHits = apiResult.opponentAttackDamage > 0 + println("Setting up counter-attack: counterAttackHits=$counterAttackHits, opponentAttackDamage=${apiResult.opponentAttackDamage}") + println("Full API response: status=${apiResult.status}, state=${apiResult.state}, playerAttackHit=${apiResult.playerAttackHit}, playerAttackDamage=${apiResult.playerAttackDamage}, opponentAttackDamage=${apiResult.opponentAttackDamage}, playerHP=${apiResult.playerHP}, opponentHP=${apiResult.opponentHP}") + println("DEBUG: Using playerAttackDamage > 0 instead of playerAttackHit for hit detection") + + // Use opponentAttackDamage to determine counter-attack hit + val finalCounterAttackHits = counterAttackHits + println("Using opponentAttackDamage > 0 for counter-attack: $finalCounterAttackHits") + + if (finalCounterAttackHits) { + println("Counter-attack hits! Player takes ${apiResult.opponentAttackDamage} damage") + onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), 0f) // Player takes damage + } else { + println("Counter-attack misses! Player dodges") + onSetPendingDamage(0f, 0f) // No damage + } + battleSystem.setupCounterAttack(finalCounterAttackHits) + } + } + 2 -> { + // Match is over - transition to results screen + println("Match is over! Winner: ${apiResult.winner}") + battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) + onAttackClick() // This will transition to battle-results screen + } + -1 -> { + // Error occurred + println("API Error: ${apiResult.status}") + battleSystem.resetAttackState() + battleSystem.enableAttackButton() + } + } + } + } + }, + enabled = battleSystem.isAttackButtonEnabled, + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Red, + disabledContainerColor = Color.Gray + ), + shape = RoundedCornerShape(8.dp) + ) { + Text("Attack", color = Color.White, fontSize = 18.sp) + } + } + } +} + @Composable fun PlayerBattleView( battleSystem: ArenaBattleSystem, @@ -488,24 +883,21 @@ fun PlayerBattleView( // Attack sprite visibility and positioning based on attack phase val shouldShowAttack = when (battleSystem.attackPhase) { - 1 -> true // Player attack on player screen - 2 -> true // Player attack on opponent screen - 3 -> false // Opponent attack on opponent screen - 4 -> true // Opponent attack on player screen + 1 -> false // Both attacks from middle screen + 2 -> false // Player attack on enemy screen + 3 -> true // Enemy attack on player screen else -> false } if (shouldShowAttack) { val xOffset = when (battleSystem.attackPhase) { - 1 -> (attackAnimationProgress * 400 + 50).dp // Player attack on player screen - start and end more to the right - 2 -> (attackAnimationProgress * 400 - 200).dp // Player attack on opponent screen - 4 -> (-attackAnimationProgress * 400 + 350).dp // Opponent attack on player screen - start more to the right + 3 -> (-attackAnimationProgress * 400 + 350).dp // Enemy attack on player screen - start more to the right else -> 0.dp } - // Use opponent character ID for Phase 4 (opponent attack) + // Use opponent character ID for Phase 3 (enemy attack) val characterId = when (battleSystem.attackPhase) { - 4 -> opponent?.charaId ?: "dim011_mon01" // Use opponent's character ID + 3 -> opponent?.charaId ?: "dim011_mon01" // Use opponent's character ID else -> activeCharacter?.charaId ?: "dim011_mon01" // Use player's character ID } @@ -534,7 +926,7 @@ fun PlayerBattleView( x = xOffset, y = 0.dp ) - .scale(if (battleSystem.attackPhase == 4) 1f else -1f, 1f), // Don't flip opponent attacks + .scale(if (battleSystem.attackPhase == 3) 1f else -1f, 1f), // Don't flip enemy attacks contentScale = ContentScale.Fit ) } @@ -675,7 +1067,7 @@ fun PlayerBattleView( } @Composable -fun OpponentBattleView( +fun EnemyBattleView( battleSystem: ArenaBattleSystem, stage: String, opponentName: String, @@ -715,14 +1107,14 @@ fun OpponentBattleView( ) } - // Middle section: Opponent Digimon + // Middle section: Enemy Digimon Box( modifier = Modifier .fillMaxSize() .padding(16.dp), contentAlignment = Alignment.Center ) { - // Opponent Digimon + // Enemy Digimon Box( modifier = Modifier .fillMaxWidth() @@ -733,10 +1125,7 @@ fun OpponentBattleView( val animationType = when { battleSystem.isOpponentDodging -> DigimonAnimationType.WALK // Use walk animation for dodge battleSystem.isOpponentHit -> DigimonAnimationType.SLEEP // Use sleep animation for hit effect (injured sprite) - battleSystem.attackPhase == 1 -> DigimonAnimationType.IDLE // Player attack on player screen - battleSystem.attackPhase == 2 -> DigimonAnimationType.IDLE // Player attack on opponent screen - battleSystem.attackPhase == 3 -> DigimonAnimationType.ATTACK // Opponent attack on opponent screen - battleSystem.attackPhase == 4 -> DigimonAnimationType.ATTACK // Opponent attack on player screen + battleSystem.attackPhase == 2 -> DigimonAnimationType.IDLE // Player attack on enemy screen else -> DigimonAnimationType.IDLE } @@ -783,26 +1172,15 @@ fun OpponentBattleView( // Attack sprite visibility and positioning based on attack phase val shouldShowAttack = when (battleSystem.attackPhase) { - 1 -> false // Player attack on player screen - 2 -> true // Player attack on opponent screen - 3 -> true // Opponent attack on opponent screen - 4 -> false // Opponent attack on player screen + 2 -> true // Player attack on enemy screen else -> false } if (shouldShowAttack) { - val xOffset = when (battleSystem.attackPhase) { - 2 -> (attackAnimationProgress * 400 - 350).dp // Player attack on opponent screen - start more to the left - 3 -> (-attackAnimationProgress * 400 + -50).dp // Opponent attack on opponent screen - start more to the left - else -> 0.dp - } + val xOffset = (attackAnimationProgress * 400 - 350).dp // Player attack on enemy screen - start more to the left - // Use correct character ID based on attack phase - val characterId = when (battleSystem.attackPhase) { - 2 -> playerCharacter?.charaId ?: "dim011_mon01" // Use player's character ID for player attack - 3 -> activeCharacter?.charaId ?: "dim011_mon01" // Use opponent's character ID for opponent attack - else -> "dim011_mon01" - } + // Use player's character ID for player attack + val characterId = playerCharacter?.charaId ?: "dim011_mon01" // Handle sprite transition LaunchedEffect(characterId, battleSystem.attackPhase) { @@ -817,7 +1195,7 @@ fun OpponentBattleView( previousAttackPhase = battleSystem.attackPhase } - println("OpponentBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") + println("EnemyBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") if (!isTransitioning) { AttackSpriteImage( @@ -829,7 +1207,7 @@ fun OpponentBattleView( x = xOffset, y = 0.dp ) - .scale(if (battleSystem.attackPhase == 2) -1f else 1f, 1f), // Flip player attacks only + .scale(-1f, 1f), // Flip player attacks contentScale = ContentScale.Fit ) } @@ -1201,9 +1579,12 @@ fun BattlesScreen() { Scaffold ( topBar = { - TopBanner( - text = "Online battles" - ) + // Only show TopBanner when not in battle mode + if (currentView != "battle-main" && currentView != "battle-results") { + TopBanner( + text = "Online battles" + ) + } } ) { contentPadding -> Column( From a3bebcf290ed3127e7548c1f8a38f21a8ad94b77 Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 19:42:31 -0400 Subject: [PATCH 42/65] Adjusted animation timings. --- .../nacabaro/vbhelper/battle/DigimonAnimationState.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt index 77028d3..2ff49cd 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt @@ -64,8 +64,8 @@ class DigimonAnimationStateMachine( // Animation durations for each type private val animationDurations = mapOf( - DigimonAnimationType.IDLE to 500L, - DigimonAnimationType.IDLE2 to 500L, + DigimonAnimationType.IDLE to 750L, + DigimonAnimationType.IDLE2 to 750L, DigimonAnimationType.WALK to 200L, DigimonAnimationType.WALK2 to 200L, DigimonAnimationType.RUN to 150L, @@ -73,8 +73,8 @@ class DigimonAnimationStateMachine( DigimonAnimationType.WORKOUT to 300L, DigimonAnimationType.WORKOUT2 to 300L, DigimonAnimationType.HAPPY to 400L, - DigimonAnimationType.SLEEP to 1000L, - DigimonAnimationType.ATTACK to 300L, + DigimonAnimationType.SLEEP to 1500L, + DigimonAnimationType.ATTACK to 650L, DigimonAnimationType.FLEE to 150L ) From 481c0b6d9a1375a31dfb09b6417519d48bf6c659 Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 19:42:41 -0400 Subject: [PATCH 43/65] Adjusted BattlesScreen UI. --- .../vbhelper/screens/BattlesScreen.kt | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index f16f13d..5e5616e 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -400,6 +400,7 @@ fun MiddleBattleView( Spacer(modifier = Modifier.height(16.dp)) // Debug display + /* if (lastApiResult != null) { Text( text = "Debug: state=${lastApiResult!!.state}, playerAttackHit=${lastApiResult!!.playerAttackHit}, opponentDamage=${lastApiResult!!.opponentAttackDamage}", @@ -408,6 +409,7 @@ fun MiddleBattleView( ) Spacer(modifier = Modifier.height(8.dp)) } + */ // Enemy HP bar (top) LinearProgressIndicator( @@ -428,22 +430,14 @@ fun MiddleBattleView( Spacer(modifier = Modifier.height(16.dp)) - // Player HP bar (bottom) - LinearProgressIndicator( - progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), - modifier = Modifier - .fillMaxWidth() - .height(10.dp), - color = Color.Green, - trackColor = Color.Gray - ) - // Player HP display numbers + /* Text( text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", fontSize = 14.sp, color = Color.Black ) + */ } // Middle section: Both Digimon with horizontal line separator @@ -611,7 +605,7 @@ fun MiddleBattleView( } } - // Bottom section: Critical bar and Attack button + // Bottom section: Player HP bar, Critical bar and Attack button Column( modifier = Modifier .fillMaxWidth() @@ -629,7 +623,26 @@ fun MiddleBattleView( trackColor = Color.Gray ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(6.dp)) + + // Player HP bar + LinearProgressIndicator( + progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Green, + trackColor = Color.Gray + ) + + // Player HP display numbers + Text( + text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = 14.sp, + color = Color.Black + ) + + Spacer(modifier = Modifier.height(6.dp)) // Attack button Button( @@ -780,13 +793,14 @@ fun PlayerBattleView( modifier = Modifier.align(Alignment.TopEnd), colors = ButtonDefaults.buttonColors(containerColor = Color.Red) ) { - Text("Exit", color = Color.White, fontSize = 14.sp) + Text("Exit", color = Color.White, fontSize = 12.sp) } } - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(8.dp)) // Debug display + /* if (lastApiResult != null) { Text( text = "Debug: state=${lastApiResult!!.state}, playerAttackHit=${lastApiResult!!.playerAttackHit}, opponentDamage=${lastApiResult!!.opponentAttackDamage}", @@ -795,6 +809,7 @@ fun PlayerBattleView( ) Spacer(modifier = Modifier.height(8.dp)) } + */ // Health bar LinearProgressIndicator( @@ -1298,6 +1313,7 @@ fun BattlesScreen() { opponentsList.clear() opponentsList.addAll(opponents.opponentsList) currentView = "rookie" + currentStage = "rookie" } catch (e: Exception) { Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() @@ -1322,6 +1338,7 @@ fun BattlesScreen() { opponentsList.clear() opponentsList.addAll(opponents.opponentsList) currentView = "champion" + currentStage = "champion" } catch (e: Exception) { Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() @@ -1346,6 +1363,7 @@ fun BattlesScreen() { opponentsList.clear() opponentsList.addAll(opponents.opponentsList) currentView = "ultimate" + currentStage = "ultimate" } catch (e: Exception) { Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() @@ -1370,6 +1388,7 @@ fun BattlesScreen() { opponentsList.clear() opponentsList.addAll(opponents.opponentsList) currentView = "mega" + currentStage = "mega" } catch (e: Exception) { Log.d(TAG, "Error processing opponents data: ${e.message}") e.printStackTrace() @@ -1394,6 +1413,15 @@ fun BattlesScreen() { } val characterDropdown = @Composable { currentStage: String -> + // Get the appropriate character list based on the passed currentStage parameter + val characterListForStage = when (currentStage.lowercase()) { + "rookie" -> rookieCharacters + "champion" -> championCharacters + "ultimate" -> ultimateCharacters + "mega" -> megaCharacters + else -> rookieCharacters + } + ExposedDropdownMenuBox( expanded = expanded, onExpandedChange = { expanded = !expanded } @@ -1410,7 +1438,7 @@ fun BattlesScreen() { expanded = expanded, onDismissRequest = { expanded = false } ) { - characterList.forEach { character -> + characterListForStage.forEach { character -> DropdownMenuItem( text = { Text(character.name) }, onClick = { From b74b04cda96562e0a9deab37620c96a856be51a2 Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 19:50:37 -0400 Subject: [PATCH 44/65] Fixed sprite positioning on main screen. --- .../com/github/nacabaro/vbhelper/screens/BattlesScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 5e5616e..763716e 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -498,7 +498,7 @@ fun MiddleBattleView( .size(80.dp) .offset( x = enemyHitOffset, - y = enemyVerticalOffset + y = enemyVerticalOffset + 40.dp ), contentScale = ContentScale.Fit, reloadMappings = false @@ -578,7 +578,7 @@ fun MiddleBattleView( .scale(-1f, 1f) // Flip player Digimon horizontally .offset( x = playerHitOffset, - y = playerVerticalOffset + y = playerVerticalOffset - 40.dp ), contentScale = ContentScale.Fit, reloadMappings = false From 26842d1b1b62f7435e0883a1cd56394ce4ee89b3 Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 19:55:36 -0400 Subject: [PATCH 45/65] Reduced attack button size. --- .../com/github/nacabaro/vbhelper/screens/BattlesScreen.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 763716e..891bd20 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -742,15 +742,15 @@ fun MiddleBattleView( }, enabled = battleSystem.isAttackButtonEnabled, modifier = Modifier - .fillMaxWidth() - .height(50.dp), + .fillMaxWidth(0.5f) + .height(35.dp), colors = ButtonDefaults.buttonColors( - containerColor = Color.Red, + containerColor = Color.Blue, disabledContainerColor = Color.Gray ), shape = RoundedCornerShape(8.dp) ) { - Text("Attack", color = Color.White, fontSize = 18.sp) + Text("Attack", color = Color.White, fontSize = 12.sp) } } } From ec3058b511f13bae18f03c2e340eb98709fba083 Mon Sep 17 00:00:00 2001 From: lightheel Date: Tue, 5 Aug 2025 20:03:50 -0400 Subject: [PATCH 46/65] Adjusted dodge trigger. --- .../com/github/nacabaro/vbhelper/screens/BattlesScreen.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 891bd20..9aba1e8 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -116,8 +116,8 @@ fun BattleScreen( progress += 0.016f // 60 FPS battleSystem.setAttackProgress(progress) - // Trigger animation when attack reaches the enemy (around 55% progress for enemy dodge) - if (progress >= 0.55f && !battleSystem.isOpponentHit && !battleSystem.isOpponentDodging) { + // Trigger animation when attack reaches the enemy (around 40% progress for enemy dodge) + if (progress >= 0.50f && !battleSystem.isOpponentHit && !battleSystem.isOpponentDodging) { if (battleSystem.attackIsHit) { // Player attack hits enemy println("Player attack hits enemy at progress $progress") @@ -155,8 +155,8 @@ fun BattleScreen( progress += 0.016f // 60 FPS battleSystem.setAttackProgress(progress) - // Trigger animation when attack reaches the player (around 75% progress for player dodge) - if (progress >= 0.75f && !battleSystem.isPlayerHit && !battleSystem.isPlayerDodging) { + // Trigger animation when attack reaches the player (around 60% progress for player dodge) + if (progress >= 0.50f && !battleSystem.isPlayerHit && !battleSystem.isPlayerDodging) { println("Phase 3: Checking player animation at progress $progress, opponentAttackIsHit=${battleSystem.opponentAttackIsHit}") println("Phase 3: Player animation decision - opponentAttackIsHit=${battleSystem.opponentAttackIsHit}, will ${if (battleSystem.opponentAttackIsHit) "HIT" else "DODGE"}") if (battleSystem.opponentAttackIsHit) { From 6ea9946412b1b167993c25d6eda481afed4bc9c1 Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 09:00:51 -0400 Subject: [PATCH 47/65] Attack sprites now shows on main battle screen. --- .../nacabaro/vbhelper/screens/BattlesScreen.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 9aba1e8..3decf0b 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -460,9 +460,9 @@ fun MiddleBattleView( ) { // Determine animation type for enemy val enemyAnimationType = when { + battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Both attacking in Phase 1 battleSystem.isOpponentDodging -> DigimonAnimationType.WALK battleSystem.isOpponentHit -> DigimonAnimationType.SLEEP - battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Both attacking else -> DigimonAnimationType.IDLE } @@ -506,7 +506,8 @@ fun MiddleBattleView( // Enemy attack sprite (Phase 1 only) if (battleSystem.attackPhase == 1) { - val xOffset = (attackAnimationProgress * 400 + 50).dp // Move right off screen + val xOffset = (-attackAnimationProgress * 400).dp // Start at center, move left off screen + val yOffset = 30.dp // Lower enemy attack sprite by 30 pixels AttackSpriteImage( characterId = opponentCharacter?.charaId ?: "dim011_mon01", @@ -515,7 +516,7 @@ fun MiddleBattleView( .size(60.dp) .offset( x = xOffset, - y = 0.dp + y = yOffset ), contentScale = ContentScale.Fit ) @@ -539,9 +540,9 @@ fun MiddleBattleView( ) { // Determine animation type for player val playerAnimationType = when { + battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Both attacking in Phase 1 battleSystem.isPlayerDodging -> DigimonAnimationType.WALK battleSystem.isPlayerHit -> DigimonAnimationType.SLEEP - battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Both attacking else -> DigimonAnimationType.IDLE } @@ -586,7 +587,8 @@ fun MiddleBattleView( // Player attack sprite (Phase 1 only) if (battleSystem.attackPhase == 1) { - val xOffset = (-attackAnimationProgress * 400 - 50).dp // Move left off screen + val xOffset = (attackAnimationProgress * 400).dp // Start at center, move right off screen + val yOffset = (-30).dp // Raise player attack sprite by 30 pixels AttackSpriteImage( characterId = activeCharacter?.charaId ?: "dim011_mon01", @@ -595,7 +597,7 @@ fun MiddleBattleView( .size(60.dp) .offset( x = xOffset, - y = 0.dp + y = yOffset ) .scale(-1f, 1f), // Flip attack sprite contentScale = ContentScale.Fit @@ -722,6 +724,8 @@ fun MiddleBattleView( onSetPendingDamage(0f, 0f) // No damage } battleSystem.setupCounterAttack(finalCounterAttackHits) + // Set the opponent attack hit state for Phase 3 + battleSystem.handleOpponentAttackResult(finalCounterAttackHits) } } 2 -> { @@ -1047,6 +1051,8 @@ fun PlayerBattleView( onSetPendingDamage(0f, 0f) // No damage } battleSystem.setupCounterAttack(finalCounterAttackHits) + // Set the opponent attack hit state for Phase 3 + battleSystem.handleOpponentAttackResult(finalCounterAttackHits) } } 2 -> { From cd33d06ecfc8983b299237973cbf45277d3b1b45 Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 10:25:26 -0400 Subject: [PATCH 48/65] Added pop up damage number. --- .../vbhelper/screens/BattlesScreen.kt | 173 +++++++++++++++++- 1 file changed, 169 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 3decf0b..44cc3cb 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -56,8 +56,77 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import kotlin.math.sin import kotlin.math.PI +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateIntAsState +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Shadow +import androidx.compose.ui.text.TextStyle +@Composable +fun AnimatedDamageNumber( + damage: Int, + isVisible: Boolean, + modifier: Modifier = Modifier +) { + if (!isVisible) return + + println("DEBUG: AnimatedDamageNumber called with damage=$damage, isVisible=$isVisible") + + var animationProgress by remember { mutableStateOf(0f) } + var scale by remember { mutableStateOf(1f) } + var alpha by remember { mutableStateOf(1f) } + var yOffset by remember { mutableStateOf(0.dp) } + + LaunchedEffect(isVisible) { + if (isVisible) { + println("DEBUG: Starting damage number animation for damage=$damage") + // Start animation + animationProgress = 0f + scale = 0.5f + alpha = 1f + yOffset = 0.dp + + // Animate scale up + while (scale < 1.5f) { + scale += 0.1f + delay(16) + } + + // Hold at max scale briefly + delay(200) + + // Animate fade out and move up + while (alpha > 0f) { + alpha -= 0.05f + yOffset -= 1.dp + delay(16) + } + println("DEBUG: Damage number animation completed for damage=$damage") + } + } + + Text( + text = "-$damage", + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + color = Color.Red, + textAlign = TextAlign.Center, + style = TextStyle( + shadow = Shadow( + color = Color.Black, + offset = androidx.compose.ui.geometry.Offset(2f, 2f), + blurRadius = 4f + ) + ), + modifier = modifier + .scale(scale) + .alpha(alpha) + .offset(y = yOffset) + ) +} + @Composable fun BattleScreen( stage: String, @@ -82,6 +151,12 @@ fun BattleScreen( var pendingPlayerDamage by remember { mutableStateOf(0f) } var pendingOpponentDamage by remember { mutableStateOf(0f) } + // Damage number animation state + var showPlayerDamageNumber by remember { mutableStateOf(false) } + var showOpponentDamageNumber by remember { mutableStateOf(false) } + var playerDamageValue by remember { mutableStateOf(0) } + var opponentDamageValue by remember { mutableStateOf(0) } + // Critical bar timer LaunchedEffect(Unit) { while (true) { @@ -132,9 +207,7 @@ fun BattleScreen( delay(16) // 60 FPS } println("Phase 2 completed, applying damage and starting Phase 3") - // Apply player's damage battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) - pendingOpponentDamage = 0f delay(500) // Check if there should be a counter-attack @@ -173,9 +246,8 @@ fun BattleScreen( delay(16) // 60 FPS } println("Phase 3 completed, applying damage and resetting") - // Apply enemy's damage and reset + println("DEBUG: pendingPlayerDamage = $pendingPlayerDamage") battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) - pendingPlayerDamage = 0f battleSystem.resetAttackState() battleSystem.enableAttackButton() @@ -294,6 +366,45 @@ fun BattleScreen( } } + // Damage number handling + LaunchedEffect(pendingPlayerDamage) { + if (pendingPlayerDamage > 0) { + println("DEBUG: LaunchedEffect triggered for pendingPlayerDamage = $pendingPlayerDamage") + playerDamageValue = pendingPlayerDamage.toInt() + showPlayerDamageNumber = true + println("DEBUG: Setting player damage number: $playerDamageValue, showPlayerDamageNumber = $showPlayerDamageNumber") + + // Hide damage number after animation + delay(3000) // Increased delay to ensure UI recomposition + showPlayerDamageNumber = false + println("DEBUG: Hiding player damage number after delay") + + // Reset pending damage after animation completes + delay(500) // Additional delay before reset + pendingPlayerDamage = 0f + println("DEBUG: Resetting pendingPlayerDamage to 0") + } + } + + LaunchedEffect(pendingOpponentDamage) { + if (pendingOpponentDamage > 0) { + println("DEBUG: LaunchedEffect triggered for pendingOpponentDamage = $pendingOpponentDamage") + opponentDamageValue = pendingOpponentDamage.toInt() + showOpponentDamageNumber = true + println("DEBUG: Setting opponent damage number: $opponentDamageValue, showOpponentDamageNumber = $showOpponentDamageNumber") + + // Hide damage number after animation + delay(3000) // Increased delay to ensure UI recomposition + showOpponentDamageNumber = false + println("DEBUG: Hiding opponent damage number after delay") + + // Reset pending damage after animation completes + delay(500) // Additional delay before reset + pendingOpponentDamage = 0f + println("DEBUG: Resetting pendingOpponentDamage to 0") + } + } + Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -352,6 +463,48 @@ fun BattleScreen( ) } } + + // Damage number overlays - moved inside the Box for proper positioning + when (battleSystem.currentView) { + 0 -> { + // Middle screen - NO damage numbers should show here + // This screen is for the initial attack phase only + } + 1 -> { + // Player screen - show player damage (when opponent attacks player) + println("DEBUG: Player screen damage overlay - playerDamageValue=$playerDamageValue, showPlayerDamageNumber=$showPlayerDamageNumber") + AnimatedDamageNumber( + damage = playerDamageValue, + isVisible = showPlayerDamageNumber, + modifier = Modifier + .align(Alignment.Center) + .offset(y = 0.dp) + .background(Color.Yellow.copy(alpha = 0.3f)) // Debug background + ) + + // Debug text overlay + Text( + text = "View: ${battleSystem.currentView}, Player Damage: $playerDamageValue, Show: $showPlayerDamageNumber", + color = Color.Red, + fontSize = 12.sp, + modifier = Modifier + .align(Alignment.TopCenter) + .offset(y = 200.dp) + .background(Color.White.copy(alpha = 0.8f)) + ) + } + 2 -> { + // Enemy screen - show opponent damage (when player attacks opponent) + println("DEBUG: Enemy screen damage overlay - opponentDamageValue=$opponentDamageValue, showOpponentDamageNumber=$showOpponentDamageNumber") + AnimatedDamageNumber( + damage = opponentDamageValue, + isVisible = showOpponentDamageNumber, + modifier = Modifier + .align(Alignment.Center) + .offset(y = (-100).dp) + ) + } + } } } @@ -702,6 +855,12 @@ fun MiddleBattleView( println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage battleSystem.setAttackHitState(true) + + // Also check if enemy counter-attacks and hits + if (apiResult.opponentAttackDamage > 0) { + println("Enemy counter-attack hits! Player takes ${apiResult.opponentAttackDamage} damage") + onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), apiResult.playerAttackDamage.toFloat()) // Both take damage + } } else { // Player attack missed - enemy counter-attacks println("Player attack missed! Enemy counter-attacks") @@ -1029,6 +1188,12 @@ fun PlayerBattleView( println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage battleSystem.setAttackHitState(true) + + // Also check if enemy counter-attacks and hits + if (apiResult.opponentAttackDamage > 0) { + println("Enemy counter-attack hits! Player takes ${apiResult.opponentAttackDamage} damage") + onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), apiResult.playerAttackDamage.toFloat()) // Both take damage + } } else { // Player attack missed - enemy counter-attacks println("Player attack missed! Enemy counter-attacks") From de3d312a325636dd7215e5caa7f722bc6c28e2eb Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 10:49:30 -0400 Subject: [PATCH 49/65] Fixed pop up damage timing and positioning. --- .../vbhelper/screens/BattlesScreen.kt | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 44cc3cb..b477c8f 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -191,12 +191,17 @@ fun BattleScreen( progress += 0.016f // 60 FPS battleSystem.setAttackProgress(progress) - // Trigger animation when attack reaches the enemy (around 40% progress for enemy dodge) + // Trigger animation when attack reaches the enemy (around 50% progress for enemy dodge) if (progress >= 0.50f && !battleSystem.isOpponentHit && !battleSystem.isOpponentDodging) { if (battleSystem.attackIsHit) { // Player attack hits enemy println("Player attack hits enemy at progress $progress") battleSystem.startOpponentHit() + // Show damage number when attack reaches enemy + if (pendingOpponentDamage > 0) { + showOpponentDamageNumber = true + println("DEBUG: Showing opponent damage number at progress $progress") + } } else { // Player attack misses, enemy dodges println("Player attack misses, enemy dodges at progress $progress") @@ -208,7 +213,16 @@ fun BattleScreen( } println("Phase 2 completed, applying damage and starting Phase 3") battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) - delay(500) + + // Hide damage number and reset pending damage after animation + if (showOpponentDamageNumber) { + delay(800) // Wait for damage number animation (scale up + hold + fade out) + showOpponentDamageNumber = false + pendingOpponentDamage = 0f + println("DEBUG: Hiding opponent damage number and resetting pending damage") + } + + delay(200) // Check if there should be a counter-attack if (battleSystem.shouldCounterAttack) { @@ -228,7 +242,7 @@ fun BattleScreen( progress += 0.016f // 60 FPS battleSystem.setAttackProgress(progress) - // Trigger animation when attack reaches the player (around 60% progress for player dodge) + // Trigger animation when attack reaches the player (around 50% progress for player dodge) if (progress >= 0.50f && !battleSystem.isPlayerHit && !battleSystem.isPlayerDodging) { println("Phase 3: Checking player animation at progress $progress, opponentAttackIsHit=${battleSystem.opponentAttackIsHit}") println("Phase 3: Player animation decision - opponentAttackIsHit=${battleSystem.opponentAttackIsHit}, will ${if (battleSystem.opponentAttackIsHit) "HIT" else "DODGE"}") @@ -236,6 +250,11 @@ fun BattleScreen( // Enemy attack hits player println("Enemy attack hits player at progress $progress") battleSystem.startPlayerHit() + // Show damage number when attack reaches player + if (pendingPlayerDamage > 0) { + showPlayerDamageNumber = true + println("DEBUG: Showing player damage number at progress $progress") + } } else { // Enemy attack misses, player dodges println("Enemy attack misses, player dodges at progress $progress") @@ -248,6 +267,15 @@ fun BattleScreen( println("Phase 3 completed, applying damage and resetting") println("DEBUG: pendingPlayerDamage = $pendingPlayerDamage") battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) + + // Hide damage number and reset pending damage after animation + if (showPlayerDamageNumber) { + delay(800) // Wait for damage number animation (scale up + hold + fade out) + showPlayerDamageNumber = false + pendingPlayerDamage = 0f + println("DEBUG: Hiding player damage number and resetting pending damage") + } + battleSystem.resetAttackState() battleSystem.enableAttackButton() @@ -366,23 +394,12 @@ fun BattleScreen( } } - // Damage number handling + // Damage number handling - store pending damage but don't show immediately LaunchedEffect(pendingPlayerDamage) { if (pendingPlayerDamage > 0) { println("DEBUG: LaunchedEffect triggered for pendingPlayerDamage = $pendingPlayerDamage") playerDamageValue = pendingPlayerDamage.toInt() - showPlayerDamageNumber = true - println("DEBUG: Setting player damage number: $playerDamageValue, showPlayerDamageNumber = $showPlayerDamageNumber") - - // Hide damage number after animation - delay(3000) // Increased delay to ensure UI recomposition - showPlayerDamageNumber = false - println("DEBUG: Hiding player damage number after delay") - - // Reset pending damage after animation completes - delay(500) // Additional delay before reset - pendingPlayerDamage = 0f - println("DEBUG: Resetting pendingPlayerDamage to 0") + // Don't show immediately - wait for attack animation to reach the Digimon } } @@ -390,18 +407,7 @@ fun BattleScreen( if (pendingOpponentDamage > 0) { println("DEBUG: LaunchedEffect triggered for pendingOpponentDamage = $pendingOpponentDamage") opponentDamageValue = pendingOpponentDamage.toInt() - showOpponentDamageNumber = true - println("DEBUG: Setting opponent damage number: $opponentDamageValue, showOpponentDamageNumber = $showOpponentDamageNumber") - - // Hide damage number after animation - delay(3000) // Increased delay to ensure UI recomposition - showOpponentDamageNumber = false - println("DEBUG: Hiding opponent damage number after delay") - - // Reset pending damage after animation completes - delay(500) // Additional delay before reset - pendingOpponentDamage = 0f - println("DEBUG: Resetting pendingOpponentDamage to 0") + // Don't show immediately - wait for attack animation to reach the Digimon } } @@ -478,11 +484,12 @@ fun BattleScreen( isVisible = showPlayerDamageNumber, modifier = Modifier .align(Alignment.Center) - .offset(y = 0.dp) - .background(Color.Yellow.copy(alpha = 0.3f)) // Debug background + .offset(y = (-50).dp) + //.background(Color.Yellow.copy(alpha = 0.3f)) // Debug background ) // Debug text overlay + /* Text( text = "View: ${battleSystem.currentView}, Player Damage: $playerDamageValue, Show: $showPlayerDamageNumber", color = Color.Red, @@ -492,6 +499,7 @@ fun BattleScreen( .offset(y = 200.dp) .background(Color.White.copy(alpha = 0.8f)) ) + */ } 2 -> { // Enemy screen - show opponent damage (when player attacks opponent) @@ -501,7 +509,7 @@ fun BattleScreen( isVisible = showOpponentDamageNumber, modifier = Modifier .align(Alignment.Center) - .offset(y = (-100).dp) + .offset(y = (-50).dp) ) } } From 5989f48355e80997de46773b31e5b5c2159309e6 Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 11:51:37 -0400 Subject: [PATCH 50/65] Added battle background. --- .../vbhelper/screens/BattlesScreen.kt | 90 ++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index b477c8f..cb1d3d6 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -62,7 +62,12 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.text.TextStyle - +import androidx.compose.foundation.Image +import androidx.compose.ui.graphics.asImageBitmap +import android.graphics.BitmapFactory +import java.io.File +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalConfiguration @Composable fun AnimatedDamageNumber( @@ -222,7 +227,7 @@ fun BattleScreen( println("DEBUG: Hiding opponent damage number and resetting pending damage") } - delay(200) + delay(100) // Check if there should be a counter-attack if (battleSystem.shouldCounterAttack) { @@ -539,6 +544,11 @@ fun MiddleBattleView( Box( modifier = Modifier.fillMaxSize() ) { + // Animated background - positioned underneath all other sprites + AnimatedBattleBackground( + modifier = Modifier.fillMaxSize() + ) + // Top section: Exit button, HP bars, and HP numbers Column( modifier = Modifier @@ -2106,4 +2116,80 @@ fun BattlesScreen() { } } } +} + +@Composable +fun AnimatedBattleBackground( + modifier: Modifier = Modifier +) { + val context = LocalContext.current + var backgroundBitmap by remember { mutableStateOf(null) } + var xOffset by remember { mutableStateOf(0f) } + var screenWidth by remember { mutableStateOf(0.dp) } + var screenHeight by remember { mutableStateOf(0.dp) } + + // Get screen dimensions + val density = LocalDensity.current + val configuration = LocalConfiguration.current + LaunchedEffect(Unit) { + screenWidth = with(density) { configuration.screenWidthDp.dp } + screenHeight = with(density) { configuration.screenHeightDp.dp } + println("DEBUG: Screen dimensions = ${screenWidth.value}x${screenHeight.value}dp") + } + + // Load background image from internal storage + LaunchedEffect(Unit) { + try { + val backgroundFile = File(context.filesDir, "battle_sprites/extracted_battlebgs/BattleBg_0015_BattleBg_0012.png") + if (backgroundFile.exists()) { + backgroundBitmap = BitmapFactory.decodeFile(backgroundFile.absolutePath) + println("Successfully loaded battle background: ${backgroundFile.absolutePath}") + println("DEBUG: Image dimensions = ${backgroundBitmap?.width}x${backgroundBitmap?.height} pixels") + } else { + println("Battle background file not found: ${backgroundFile.absolutePath}") + } + } catch (e: Exception) { + println("Error loading battle background: ${e.message}") + } + } + + // Animate horizontal movement to the left with perfect loop + LaunchedEffect(screenWidth) { + if (screenWidth > 0.dp) { + while (true) { + delay(50) // Update every 50ms for smooth animation + xOffset -= 1f // Move 1 pixel to the left + + // Create perfect loop by resetting when one full screen width has moved + if (xOffset <= -screenWidth.value) { + xOffset = 0f + println("DEBUG: Background loop reset at xOffset = ${xOffset}") + } + } + } + } + + backgroundBitmap?.let { bitmap -> + Box(modifier = modifier.fillMaxSize()) { + // First image (main) + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Animated Battle Background 1", + modifier = Modifier + .fillMaxSize() + .offset(x = xOffset.dp), + contentScale = ContentScale.FillBounds + ) + + // Second image (for seamless loop) + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Animated Battle Background 2", + modifier = Modifier + .fillMaxSize() + .offset(x = (xOffset + screenWidth.value).dp), + contentScale = ContentScale.FillBounds + ) + } + } } \ No newline at end of file From 0a643053af42ae04c91614adbd6ccda9307270bb Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 12:21:30 -0400 Subject: [PATCH 51/65] Adjusted HP text color and size. --- .../vbhelper/screens/BattlesScreen.kt | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index cb1d3d6..212047e 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -595,8 +595,15 @@ fun MiddleBattleView( // Enemy HP display numbers Text( text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponentCharacter?.baseHp ?: 100}", - fontSize = 14.sp, - color = Color.Black + fontSize = 16.sp, + color = Color.LightGray, + style = TextStyle( + shadow = Shadow( + color = Color.Black, + offset = androidx.compose.ui.geometry.Offset(2f, 2f), + //blurRadius = 2f + ) + ) ) Spacer(modifier = Modifier.height(16.dp)) @@ -811,8 +818,15 @@ fun MiddleBattleView( // Player HP display numbers Text( text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 14.sp, - color = Color.Black + fontSize = 16.sp, + color = Color.LightGray, + style = TextStyle( + shadow = Shadow( + color = Color.Black, + offset = androidx.compose.ui.geometry.Offset(2f, 2f), + //blurRadius = 2f + ) + ) ) Spacer(modifier = Modifier.height(6.dp)) @@ -959,6 +973,9 @@ fun PlayerBattleView( Box( modifier = Modifier.fillMaxSize() ) { + // Animated Battle Background + AnimatedBattleBackground(modifier = Modifier.fillMaxSize()) + // Top section: Exit button, HP bar, and HP numbers Column( modifier = Modifier @@ -1005,8 +1022,15 @@ fun PlayerBattleView( // Health display numbers Text( text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 14.sp, - color = Color.Black + fontSize = 16.sp, + color = Color.LightGray, + style = TextStyle( + shadow = Shadow( + color = Color.Black, + offset = androidx.compose.ui.geometry.Offset(2f, 2f), + //blurRadius = 2f + ) + ) ) } @@ -1287,6 +1311,9 @@ fun EnemyBattleView( Box( modifier = Modifier.fillMaxSize() ) { + // Animated Battle Background + AnimatedBattleBackground(modifier = Modifier.fillMaxSize()) + // Top section: Enemy HP bar and HP numbers Column( modifier = Modifier @@ -1306,8 +1333,15 @@ fun EnemyBattleView( // Enemy HP display numbers Text( text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 14.sp, - color = Color.Black + fontSize = 16.sp, + color = Color.LightGray, + style = TextStyle( + shadow = Shadow( + color = Color.Black, + offset = androidx.compose.ui.geometry.Offset(2f, 2f), + //blurRadius = 2f + ) + ) ) } From 28cb824bf319cb762b8a71c054b243eb3749d4aa Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 12:36:21 -0400 Subject: [PATCH 52/65] Changed battle background to use all 3 layers. --- .../vbhelper/screens/BattlesScreen.kt | 175 ++++++++++++++++-- 1 file changed, 162 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 212047e..c78b0db 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -545,7 +545,7 @@ fun MiddleBattleView( modifier = Modifier.fillMaxSize() ) { // Animated background - positioned underneath all other sprites - AnimatedBattleBackground( + MultiLayerAnimatedBattleBackground( modifier = Modifier.fillMaxSize() ) @@ -596,11 +596,11 @@ fun MiddleBattleView( Text( text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponentCharacter?.baseHp ?: 100}", fontSize = 16.sp, - color = Color.LightGray, + color = Color.White, style = TextStyle( shadow = Shadow( color = Color.Black, - offset = androidx.compose.ui.geometry.Offset(2f, 2f), + offset = androidx.compose.ui.geometry.Offset(4f, 4f), //blurRadius = 2f ) ) @@ -819,11 +819,11 @@ fun MiddleBattleView( Text( text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", fontSize = 16.sp, - color = Color.LightGray, + color = Color.White, style = TextStyle( shadow = Shadow( color = Color.Black, - offset = androidx.compose.ui.geometry.Offset(2f, 2f), + offset = androidx.compose.ui.geometry.Offset(4f, 4f), //blurRadius = 2f ) ) @@ -973,8 +973,8 @@ fun PlayerBattleView( Box( modifier = Modifier.fillMaxSize() ) { - // Animated Battle Background - AnimatedBattleBackground(modifier = Modifier.fillMaxSize()) + // Multi-layer animated battle background + MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize()) // Top section: Exit button, HP bar, and HP numbers Column( @@ -1023,11 +1023,11 @@ fun PlayerBattleView( Text( text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", fontSize = 16.sp, - color = Color.LightGray, + color = Color.White, style = TextStyle( shadow = Shadow( color = Color.Black, - offset = androidx.compose.ui.geometry.Offset(2f, 2f), + offset = androidx.compose.ui.geometry.Offset(4f, 4f), //blurRadius = 2f ) ) @@ -1311,8 +1311,8 @@ fun EnemyBattleView( Box( modifier = Modifier.fillMaxSize() ) { - // Animated Battle Background - AnimatedBattleBackground(modifier = Modifier.fillMaxSize()) + // Multi-layer animated battle background + MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize()) // Top section: Enemy HP bar and HP numbers Column( @@ -1334,11 +1334,11 @@ fun EnemyBattleView( Text( text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}", fontSize = 16.sp, - color = Color.LightGray, + color = Color.White, style = TextStyle( shadow = Shadow( color = Color.Black, - offset = androidx.compose.ui.geometry.Offset(2f, 2f), + offset = androidx.compose.ui.geometry.Offset(4f, 4f), //blurRadius = 2f ) ) @@ -2226,4 +2226,153 @@ fun AnimatedBattleBackground( ) } } +} + +@Composable +fun MultiLayerAnimatedBattleBackground( + modifier: Modifier = Modifier +) { + val context = LocalContext.current + var backLayerBitmap by remember { mutableStateOf(null) } + var middleLayerBitmap by remember { mutableStateOf(null) } + var frontLayerBitmap by remember { mutableStateOf(null) } + + var backLayerXOffset by remember { mutableStateOf(0f) } + var middleLayerXOffset by remember { mutableStateOf(0f) } + var frontLayerXOffset by remember { mutableStateOf(0f) } + + var screenWidth by remember { mutableStateOf(0.dp) } + var screenHeight by remember { mutableStateOf(0.dp) } + + // Get screen dimensions + val density = LocalDensity.current + val configuration = LocalConfiguration.current + LaunchedEffect(Unit) { + screenWidth = with(density) { configuration.screenWidthDp.dp } + screenHeight = with(density) { configuration.screenHeightDp.dp } + println("DEBUG: Multi-layer screen dimensions = ${screenWidth.value}x${screenHeight.value}dp") + } + + // Load all three background layers from internal storage + LaunchedEffect(Unit) { + try { + // Back layer (BattleBg_0018_BattleBg_0013.png) + val backLayerFile = File(context.filesDir, "battle_sprites/extracted_battlebgs/BattleBg_0018_BattleBg_0013.png") + if (backLayerFile.exists()) { + backLayerBitmap = BitmapFactory.decodeFile(backLayerFile.absolutePath) + println("Successfully loaded back layer background: ${backLayerFile.absolutePath}") + } else { + println("Back layer background file not found: ${backLayerFile.absolutePath}") + } + + // Middle layer (BattleBg_0015_BattleBg_0012.png) + val middleLayerFile = File(context.filesDir, "battle_sprites/extracted_battlebgs/BattleBg_0015_BattleBg_0012.png") + if (middleLayerFile.exists()) { + middleLayerBitmap = BitmapFactory.decodeFile(middleLayerFile.absolutePath) + println("Successfully loaded middle layer background: ${middleLayerFile.absolutePath}") + } else { + println("Middle layer background file not found: ${middleLayerFile.absolutePath}") + } + + // Front layer (BattleBg_0005_BattleBg_0011.png) + val frontLayerFile = File(context.filesDir, "battle_sprites/extracted_battlebgs/BattleBg_0005_BattleBg_0011.png") + if (frontLayerFile.exists()) { + frontLayerBitmap = BitmapFactory.decodeFile(frontLayerFile.absolutePath) + println("Successfully loaded front layer background: ${frontLayerFile.absolutePath}") + } else { + println("Front layer background file not found: ${frontLayerFile.absolutePath}") + } + } catch (e: Exception) { + println("Error loading multi-layer battle backgrounds: ${e.message}") + } + } + + // Animate all three layers with different speeds + LaunchedEffect(screenWidth) { + if (screenWidth > 0.dp) { + while (true) { + delay(50) // Update every 50ms for smooth animation + + // Back layer moves slowest (parallax effect) + backLayerXOffset -= 0.5f + if (backLayerXOffset <= -screenWidth.value) { + backLayerXOffset = 0f + } + + // Middle layer moves at medium speed + middleLayerXOffset -= 1f + if (middleLayerXOffset <= -screenWidth.value) { + middleLayerXOffset = 0f + } + + // Front layer moves fastest + frontLayerXOffset -= 1.5f + if (frontLayerXOffset <= -screenWidth.value) { + frontLayerXOffset = 0f + } + } + } + } + + Box(modifier = modifier.fillMaxSize()) { + // Back layer (underneath everything) + backLayerBitmap?.let { bitmap -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Back Layer Battle Background 1", + modifier = Modifier + .fillMaxSize() + .offset(x = backLayerXOffset.dp), + contentScale = ContentScale.FillBounds + ) + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Back Layer Battle Background 2", + modifier = Modifier + .fillMaxSize() + .offset(x = (backLayerXOffset + screenWidth.value).dp), + contentScale = ContentScale.FillBounds + ) + } + + // Middle layer + middleLayerBitmap?.let { bitmap -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Middle Layer Battle Background 1", + modifier = Modifier + .fillMaxSize() + .offset(x = middleLayerXOffset.dp), + contentScale = ContentScale.FillBounds + ) + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Middle Layer Battle Background 2", + modifier = Modifier + .fillMaxSize() + .offset(x = (middleLayerXOffset + screenWidth.value).dp), + contentScale = ContentScale.FillBounds + ) + } + + // Front layer (on top of other backgrounds) + frontLayerBitmap?.let { bitmap -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Front Layer Battle Background 1", + modifier = Modifier + .fillMaxSize() + .offset(x = frontLayerXOffset.dp), + contentScale = ContentScale.FillBounds + ) + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Front Layer Battle Background 2", + modifier = Modifier + .fillMaxSize() + .offset(x = (frontLayerXOffset + screenWidth.value).dp), + contentScale = ContentScale.FillBounds + ) + } + } } \ No newline at end of file From d833a89c170c7540a2db4b98a6b970bc0d2a6e0a Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 18:03:03 -0400 Subject: [PATCH 53/65] Added hit sprites. --- .../vbhelper/battle/HitEffectComposables.kt | 103 ++++++++++ .../vbhelper/battle/HitEffectSpriteManager.kt | 176 ++++++++++++++++++ .../vbhelper/screens/BattlesScreen.kt | 51 +++++ 3 files changed, 330 insertions(+) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectSpriteManager.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt new file mode 100644 index 0000000..7ccf1c6 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt @@ -0,0 +1,103 @@ +package com.github.nacabaro.vbhelper.battle + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +@Composable +fun HitEffectOverlay( + isVisible: Boolean, + modifier: Modifier = Modifier, + isPlayerScreen: Boolean = false, + onAnimationComplete: () -> Unit = {} +) { + if (!isVisible) return + + val context = LocalContext.current + val hitEffectManager = remember { HitEffectSpriteManager(context) } + val coroutineScope = rememberCoroutineScope() + + var currentFrame by remember { mutableStateOf(0) } + var currentSprite by remember { mutableStateOf(null) } + var animationProgress by remember { mutableStateOf(0f) } + var scale by remember { mutableStateOf(0.5f) } + var alpha by remember { mutableStateOf(1f) } + + LaunchedEffect(isVisible) { + if (isVisible) { + println("DEBUG: Starting hit effect animation") + + // Randomly choose between hit_01, hit_02, and hit_02_white + val hitSpriteName = when (kotlin.random.Random.nextInt(3)) { + 0 -> "hit_01" + 1 -> "hit_02" + else -> "hit_02_white" + } + currentSprite = hitEffectManager.loadHitSprite(hitSpriteName) + + if (currentSprite != null) { + // Animate the hit effect + animationProgress = 0f + scale = 0.5f + alpha = 1f + + // Scale up animation - slowed down + while (scale < 1.2f) { + scale += 0.05f // Reduced from 0.1f to 0.05f + delay(32) // Increased from 16ms to 32ms + } + + // Hold for a moment - increased duration + delay(300) // Increased from 100ms to 300ms + + // Fade out - slowed down + while (alpha > 0f) { + alpha -= 0.03f // Reduced from 0.05f to 0.03f + delay(32) // Increased from 16ms to 32ms + } + + println("DEBUG: Hit effect animation completed") + onAnimationComplete() + } else { + println("DEBUG: Failed to load hit sprite") + onAnimationComplete() + } + } + } + + currentSprite?.let { sprite -> + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Image( + bitmap = sprite.asImageBitmap(), + contentDescription = "Hit Effect", + modifier = Modifier + .size((sprite.width * scale).dp, (sprite.height * scale).dp) + .offset( + x = if (isPlayerScreen) { + // On player screen, position further to the left + (-sprite.width * scale / 2 - 100).dp + } else { + // On enemy screen, position further to the right + (-sprite.width * scale / 2 + 150).dp + }, + y = (-sprite.height * scale / 2 + 40).dp // Position lower on screen (was -60, now +40) + ), + contentScale = ContentScale.Fit + ) + } + } +} diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectSpriteManager.kt new file mode 100644 index 0000000..33d2189 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectSpriteManager.kt @@ -0,0 +1,176 @@ +package com.github.nacabaro.vbhelper.battle + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Rect +import java.io.File + +class HitEffectSpriteManager(private val context: Context) { + private val spriteCache = mutableMapOf() + + // Base directory where hit effect sprites are stored + private val hitSpritesDir = File(context.filesDir, "battle_sprites/extracted_hit_sprites") + + /** + * Load a hit sprite (hit_01.png, hit_02.png, hit_02_white.png) + * @param spriteName The sprite name (e.g., "hit_01", "hit_02", "hit_02_white") + * @return Bitmap of the hit sprite, or null if not found + */ + fun loadHitSprite(spriteName: String): Bitmap? { + val cacheKey = "hit_$spriteName" + + // Check cache first + if (spriteCache.containsKey(cacheKey)) { + return spriteCache[cacheKey] + } + + try { + val spriteFile = File(hitSpritesDir, "$spriteName.png") + + if (!spriteFile.exists()) { + println("Hit sprite file not found: ${spriteFile.absolutePath}") + return null + } + + val bitmap = BitmapFactory.decodeFile(spriteFile.absolutePath) + if (bitmap == null) { + println("Failed to decode hit sprite file: ${spriteFile.absolutePath}") + return null + } + + println("Successfully loaded hit sprite: $spriteName.png (${bitmap.width}x${bitmap.height})") + + // Cache the result + spriteCache[cacheKey] = bitmap + + return bitmap + + } catch (e: Exception) { + println("Error loading hit sprite: ${e.message}") + e.printStackTrace() + return null + } + } + + /** + * Load a damage effect sprite from spritesheet + * @param spritesheetName The spritesheet name (e.g., "dmg_ef1", "dmg_ef2") + * @param frameIndex The frame index (0-3 for dmg_ef1 and dmg_ef2, 0 for dmg_ef3) + * @return Bitmap of the damage effect frame, or null if not found + */ + fun loadDamageEffectSprite(spritesheetName: String, frameIndex: Int = 0): Bitmap? { + val cacheKey = "dmg_${spritesheetName}_frame_${frameIndex}" + + // Check cache first + if (spriteCache.containsKey(cacheKey)) { + return spriteCache[cacheKey] + } + + try { + val spritesheetFile = File(hitSpritesDir, "$spritesheetName.png") + + if (!spritesheetFile.exists()) { + println("Damage effect spritesheet not found: ${spritesheetFile.absolutePath}") + return null + } + + val spritesheet = BitmapFactory.decodeFile(spritesheetFile.absolutePath) + if (spritesheet == null) { + println("Failed to decode damage effect spritesheet: ${spritesheetFile.absolutePath}") + return null + } + + // Extract frame from spritesheet + val frameBitmap = when (spritesheetName) { + "dmg_ef1", "dmg_ef2" -> { + // These are 2x2 spritesheets (4 frames) + val frameWidth = spritesheet.width / 2 + val frameHeight = spritesheet.height / 2 + val row = frameIndex / 2 + val col = frameIndex % 2 + val x = col * frameWidth + val y = row * frameHeight + Bitmap.createBitmap(spritesheet, x, y, frameWidth, frameHeight) + } + "dmg_ef3" -> { + // This is a single sprite + spritesheet + } + else -> { + println("Unknown spritesheet name: $spritesheetName") + return null + } + } + + println("Successfully loaded damage effect frame: $spritesheetName frame $frameIndex (${frameBitmap.width}x${frameBitmap.height})") + + // Cache the result + spriteCache[cacheKey] = frameBitmap + + return frameBitmap + + } catch (e: Exception) { + println("Error loading damage effect sprite: ${e.message}") + e.printStackTrace() + return null + } + } + + /** + * Get available hit sprite names + * @return List of available hit sprite names + */ + fun getAvailableHitSprites(): List { + try { + if (!hitSpritesDir.exists()) { + return emptyList() + } + + val hitFiles = hitSpritesDir.listFiles { file -> + file.name.startsWith("hit_") && file.name.endsWith(".png") + } ?: emptyArray() + + return hitFiles.map { file -> + file.name.substringBefore(".png") + }.sorted() + + } catch (e: Exception) { + println("Error getting available hit sprites: ${e.message}") + e.printStackTrace() + return emptyList() + } + } + + /** + * Get available damage effect spritesheet names + * @return List of available damage effect spritesheet names + */ + fun getAvailableDamageEffectSpritesheets(): List { + try { + if (!hitSpritesDir.exists()) { + return emptyList() + } + + val dmgFiles = hitSpritesDir.listFiles { file -> + file.name.startsWith("dmg_ef") && file.name.endsWith(".png") + } ?: emptyArray() + + return dmgFiles.map { file -> + file.name.substringBefore(".png") + }.sorted() + + } catch (e: Exception) { + println("Error getting available damage effect spritesheets: ${e.message}") + e.printStackTrace() + return emptyList() + } + } + + /** + * Clear the sprite cache + */ + fun clearCache() { + spriteCache.clear() + } +} diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index c78b0db..065308a 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -52,6 +52,7 @@ import com.github.nacabaro.vbhelper.battle.SpriteFileManager import com.github.nacabaro.vbhelper.battle.ArenaBattleSystem import com.github.nacabaro.vbhelper.battle.DigimonAnimationType import com.github.nacabaro.vbhelper.battle.AnimatedSpriteImage +import com.github.nacabaro.vbhelper.battle.HitEffectOverlay import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import kotlin.math.sin @@ -162,6 +163,20 @@ fun BattleScreen( var playerDamageValue by remember { mutableStateOf(0) } var opponentDamageValue by remember { mutableStateOf(0) } + // Hit effect animation state + var showPlayerHitEffect by remember { mutableStateOf(false) } + var showOpponentHitEffect by remember { mutableStateOf(false) } + + // Reset hit effect states when attack phase returns to idle + LaunchedEffect(battleSystem.attackPhase) { + if (battleSystem.attackPhase == 0) { + // Reset hit effect states when returning to idle + showPlayerHitEffect = false + showOpponentHitEffect = false + println("DEBUG: Reset hit effect states - attack phase returned to idle") + } + } + // Critical bar timer LaunchedEffect(Unit) { while (true) { @@ -190,6 +205,7 @@ fun BattleScreen( 2 -> { // Phase 2: Player attack on enemy screen println("Starting Phase 2: Player attack on enemy screen") + println("DEBUG: Phase 2 - showPlayerHitEffect=$showPlayerHitEffect, showOpponentHitEffect=$showOpponentHitEffect") battleSystem.switchToView(2) // Enemy screen var progress = 0f while (progress < 1f) { @@ -202,6 +218,9 @@ fun BattleScreen( // Player attack hits enemy println("Player attack hits enemy at progress $progress") battleSystem.startOpponentHit() + // Show hit effect and damage effect + println("DEBUG: Setting showOpponentHitEffect = true (player attack hits enemy)") + showOpponentHitEffect = true // Show damage number when attack reaches enemy if (pendingOpponentDamage > 0) { showOpponentDamageNumber = true @@ -241,6 +260,7 @@ fun BattleScreen( 3 -> { // Phase 3: Enemy attack on player screen println("Starting Phase 3: Enemy attack on player screen") + println("DEBUG: Phase 3 - showPlayerHitEffect=$showPlayerHitEffect, showOpponentHitEffect=$showOpponentHitEffect") battleSystem.switchToView(1) // Player screen var progress = 0f while (progress < 1f) { @@ -255,6 +275,9 @@ fun BattleScreen( // Enemy attack hits player println("Enemy attack hits player at progress $progress") battleSystem.startPlayerHit() + // Show hit effect and damage effect + println("DEBUG: Setting showPlayerHitEffect = true (enemy attack hits player)") + showPlayerHitEffect = true // Show damage number when attack reaches player if (pendingPlayerDamage > 0) { showPlayerDamageNumber = true @@ -493,6 +516,20 @@ fun BattleScreen( //.background(Color.Yellow.copy(alpha = 0.3f)) // Debug background ) + // Player hit effects + HitEffectOverlay( + isVisible = showPlayerHitEffect, + modifier = Modifier.fillMaxSize(), + isPlayerScreen = true, + onAnimationComplete = { + println("DEBUG: Player hit effect animation completed, setting showPlayerHitEffect = false") + showPlayerHitEffect = false + println("DEBUG: Player hit effect animation completed") + } + ) + + + // Debug text overlay /* Text( @@ -516,6 +553,20 @@ fun BattleScreen( .align(Alignment.Center) .offset(y = (-50).dp) ) + + // Enemy hit effects + HitEffectOverlay( + isVisible = showOpponentHitEffect, + modifier = Modifier.fillMaxSize(), + isPlayerScreen = false, + onAnimationComplete = { + println("DEBUG: Enemy hit effect animation completed, setting showOpponentHitEffect = false") + showOpponentHitEffect = false + println("DEBUG: Enemy hit effect animation completed") + } + ) + + } } } From 371a850d451cf286e2bc8ac5b7bb5bb698188741 Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 18:38:11 -0400 Subject: [PATCH 54/65] Offset idle animation so player/enemy Digimon aren't in sync. --- .../vbhelper/battle/AnimatedSpriteImage.kt | 16 ++++++++++++++-- .../vbhelper/battle/DigimonAnimationState.kt | 17 ++++++++++++----- .../nacabaro/vbhelper/screens/BattlesScreen.kt | 15 ++++++++++----- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt index e8c2049..ae872bf 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import kotlinx.coroutines.launch +import kotlinx.coroutines.delay @Composable fun AnimatedSpriteImage( @@ -14,11 +15,22 @@ fun AnimatedSpriteImage( animationType: DigimonAnimationType = DigimonAnimationType.IDLE, modifier: Modifier = Modifier, contentScale: ContentScale = ContentScale.Fit, - reloadMappings: Boolean = false + reloadMappings: Boolean = false, + animationOffset: Long = 0L // New parameter for offsetting animation timing ) { val context = LocalContext.current val spriteManager = remember { IndividualSpriteManager(context) } - val animationStateMachine = remember { DigimonAnimationStateMachine(characterId, context) } + + // Calculate frame offset based on animation offset + // 750ms is the idle animation duration, so we calculate how many frames to offset + val frameOffset = if (animationOffset > 0L) { + // Convert time offset to frame offset (2 frames per cycle, 750ms per frame) + ((animationOffset / 750L) * 2).toInt() + } else { + 0 + } + + val animationStateMachine = remember { DigimonAnimationStateMachine(characterId, context, frameOffset, animationOffset) } val coroutineScope = rememberCoroutineScope() var bitmap by remember { mutableStateOf(null) } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt index 2ff49cd..d23ea11 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt @@ -31,7 +31,9 @@ data class AnimationState( class DigimonAnimationStateMachine( private val characterId: String, - private val context: Context + private val context: Context, + private val initialFrameOffset: Int = 0, // New parameter for offsetting the starting frame + private val timingOffset: Long = 0L // New parameter for offsetting the timing ) { var currentAnimation by mutableStateOf(DigimonAnimationType.IDLE) private set @@ -79,7 +81,7 @@ class DigimonAnimationStateMachine( ) init { - println("Initialized DigimonAnimationStateMachine for character: $characterId") + println("Initialized DigimonAnimationStateMachine for character: $characterId with frame offset: $initialFrameOffset, timing offset: $timingOffset") println("Available animation types: ${animationTypeToFrames.keys}") } @@ -128,12 +130,17 @@ class DigimonAnimationStateMachine( // Combine frames for cycling idle animation val combinedFrames = (idleFrames + idle2Frames).distinct() - println("Playing idle animation with frames: $combinedFrames") + println("Playing idle animation with frames: $combinedFrames, starting at offset: $initialFrameOffset, timing offset: $timingOffset") val duration = animationDurations[DigimonAnimationType.IDLE] ?: 500L - // Cycle through idle frames - var frameIndex = 0 + // Apply initial timing offset + if (timingOffset > 0L) { + delay(timingOffset) + } + + // Cycle through idle frames, starting from the offset + var frameIndex = initialFrameOffset while (isPlaying && currentAnimation == DigimonAnimationType.IDLE) { val frameNumber = combinedFrames[frameIndex % combinedFrames.size] currentFrameNumber = frameNumber diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 065308a..4711ca5 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -730,7 +730,8 @@ fun MiddleBattleView( y = enemyVerticalOffset + 40.dp ), contentScale = ContentScale.Fit, - reloadMappings = false + reloadMappings = false, + animationOffset = 375L // Offset enemy animation by half the idle duration ) // Enemy attack sprite (Phase 1 only) @@ -811,7 +812,8 @@ fun MiddleBattleView( y = playerVerticalOffset - 40.dp ), contentScale = ContentScale.Fit, - reloadMappings = false + reloadMappings = false, + animationOffset = 0L // Player animation starts immediately ) // Player attack sprite (Phase 1 only) @@ -1149,7 +1151,8 @@ fun PlayerBattleView( y = verticalOffset ), contentScale = ContentScale.Fit, - reloadMappings = false + reloadMappings = false, + animationOffset = 0L // Player animation starts immediately ) // Attack sprite visibility and positioning based on attack phase @@ -1456,7 +1459,8 @@ fun EnemyBattleView( y = verticalOffset ), contentScale = ContentScale.Fit, - reloadMappings = false + reloadMappings = false, + animationOffset = 375L // Offset enemy animation by half the idle duration ) // Attack sprite visibility and positioning based on attack phase @@ -1797,7 +1801,8 @@ fun BattlesScreen() { animationType = currentTestAnimation, modifier = Modifier.size(120.dp), contentScale = ContentScale.Fit, - reloadMappings = false + reloadMappings = false, + animationOffset = 0L // No offset for sprite tester ) Spacer(modifier = Modifier.height(16.dp)) From 26fc0ced5635906105d561c8e8d33471c9d6a6c6 Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 18:53:23 -0400 Subject: [PATCH 55/65] Adjusted hit effect timing. --- .../vbhelper/battle/HitEffectComposables.kt | 3 ++ .../vbhelper/screens/BattlesScreen.kt | 48 ++++++++++++++----- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt index 7ccf1c6..5110d1c 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt @@ -38,6 +38,9 @@ fun HitEffectOverlay( if (isVisible) { println("DEBUG: Starting hit effect animation") + // Add delay before starting hit effect animation + delay(400) // Increased from 200ms to 400ms delay before hit effect appears + // Randomly choose between hit_01, hit_02, and hit_02_white val hitSpriteName = when (kotlin.random.Random.nextInt(3)) { 0 -> "hit_01" diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 4711ca5..50ca188 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -167,12 +167,18 @@ fun BattleScreen( var showPlayerHitEffect by remember { mutableStateOf(false) } var showOpponentHitEffect by remember { mutableStateOf(false) } + // Attack sprite visibility state + var hidePlayerAttackSprite by remember { mutableStateOf(false) } + var hideEnemyAttackSprite by remember { mutableStateOf(false) } + // Reset hit effect states when attack phase returns to idle LaunchedEffect(battleSystem.attackPhase) { if (battleSystem.attackPhase == 0) { // Reset hit effect states when returning to idle showPlayerHitEffect = false showOpponentHitEffect = false + hidePlayerAttackSprite = false + hideEnemyAttackSprite = false println("DEBUG: Reset hit effect states - attack phase returned to idle") } } @@ -221,6 +227,11 @@ fun BattleScreen( // Show hit effect and damage effect println("DEBUG: Setting showOpponentHitEffect = true (player attack hits enemy)") showOpponentHitEffect = true + // Delay hiding the attack sprite to match hit effect timing + coroutineScope.launch { + delay(400) // Match the hit effect delay + hideEnemyAttackSprite = true + } // Show damage number when attack reaches enemy if (pendingOpponentDamage > 0) { showOpponentDamageNumber = true @@ -278,6 +289,11 @@ fun BattleScreen( // Show hit effect and damage effect println("DEBUG: Setting showPlayerHitEffect = true (enemy attack hits player)") showPlayerHitEffect = true + // Delay hiding the attack sprite to match hit effect timing + coroutineScope.launch { + delay(400) // Match the hit effect delay + hidePlayerAttackSprite = true + } // Show damage number when attack reaches player if (pendingPlayerDamage > 0) { showPlayerDamageNumber = true @@ -462,7 +478,9 @@ fun BattleScreen( pendingPlayerDamage = playerDamage pendingOpponentDamage = opponentDamage }, - coroutineScope = coroutineScope + coroutineScope = coroutineScope, + hidePlayerAttackSprite = hidePlayerAttackSprite, + hideEnemyAttackSprite = hideEnemyAttackSprite ) } 1 -> { @@ -482,7 +500,8 @@ fun BattleScreen( pendingPlayerDamage = playerDamage pendingOpponentDamage = opponentDamage }, - coroutineScope = coroutineScope + coroutineScope = coroutineScope, + hidePlayerAttackSprite = hidePlayerAttackSprite ) } 2 -> { @@ -493,7 +512,8 @@ fun BattleScreen( opponentName = opponentName, attackAnimationProgress = battleSystem.attackProgress, activeCharacter = opponentCharacter, - playerCharacter = activeCharacter + playerCharacter = activeCharacter, + hideEnemyAttackSprite = hideEnemyAttackSprite ) } } @@ -524,6 +544,7 @@ fun BattleScreen( onAnimationComplete = { println("DEBUG: Player hit effect animation completed, setting showPlayerHitEffect = false") showPlayerHitEffect = false + hidePlayerAttackSprite = false // Show attack sprite again println("DEBUG: Player hit effect animation completed") } ) @@ -562,6 +583,7 @@ fun BattleScreen( onAnimationComplete = { println("DEBUG: Enemy hit effect animation completed, setting showOpponentHitEffect = false") showOpponentHitEffect = false + hideEnemyAttackSprite = false // Show attack sprite again println("DEBUG: Enemy hit effect animation completed") } ) @@ -584,7 +606,9 @@ fun MiddleBattleView( opponentCharacter: APIBattleCharacter?, context: android.content.Context?, onSetPendingDamage: (Float, Float) -> Unit, - coroutineScope: kotlinx.coroutines.CoroutineScope + coroutineScope: kotlinx.coroutines.CoroutineScope, + hidePlayerAttackSprite: Boolean, + hideEnemyAttackSprite: Boolean ) { // Track previous character ID to detect transitions var previousCharacterId by remember { mutableStateOf(null) } @@ -735,7 +759,7 @@ fun MiddleBattleView( ) // Enemy attack sprite (Phase 1 only) - if (battleSystem.attackPhase == 1) { + if (battleSystem.attackPhase == 1 && !hideEnemyAttackSprite) { val xOffset = (-attackAnimationProgress * 400).dp // Start at center, move left off screen val yOffset = 30.dp // Lower enemy attack sprite by 30 pixels @@ -817,7 +841,7 @@ fun MiddleBattleView( ) // Player attack sprite (Phase 1 only) - if (battleSystem.attackPhase == 1) { + if (battleSystem.attackPhase == 1 && !hidePlayerAttackSprite) { val xOffset = (attackAnimationProgress * 400).dp // Start at center, move right off screen val yOffset = (-30).dp // Raise player attack sprite by 30 pixels @@ -1015,7 +1039,8 @@ fun PlayerBattleView( context: android.content.Context?, opponent: APIBattleCharacter?, onSetPendingDamage: (Float, Float) -> Unit, - coroutineScope: kotlinx.coroutines.CoroutineScope + coroutineScope: kotlinx.coroutines.CoroutineScope, + hidePlayerAttackSprite: Boolean ) { // Track previous character ID to detect transitions var previousCharacterId by remember { mutableStateOf(null) } @@ -1190,7 +1215,7 @@ fun PlayerBattleView( println("PlayerBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") - if (!isTransitioning) { + if (!isTransitioning && !hidePlayerAttackSprite) { AttackSpriteImage( characterId = characterId, isLarge = true, @@ -1200,7 +1225,7 @@ fun PlayerBattleView( x = xOffset, y = 0.dp ) - .scale(if (battleSystem.attackPhase == 3) 1f else -1f, 1f), // Don't flip enemy attacks + .scale(1f, 1f), // Don't flip enemy attacks on player screen contentScale = ContentScale.Fit ) } @@ -1355,7 +1380,8 @@ fun EnemyBattleView( opponentName: String, attackAnimationProgress: Float, activeCharacter: APIBattleCharacter? = null, - playerCharacter: APIBattleCharacter? = null + playerCharacter: APIBattleCharacter? = null, + hideEnemyAttackSprite: Boolean ) { // Track previous character ID to detect transitions var previousCharacterId by remember { mutableStateOf(null) } @@ -1490,7 +1516,7 @@ fun EnemyBattleView( println("EnemyBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") - if (!isTransitioning) { + if (!isTransitioning && !hideEnemyAttackSprite) { AttackSpriteImage( characterId = characterId, isLarge = true, From 5bb9fe52099818b708d9e7c21b7bb6891c3081f2 Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 18:59:08 -0400 Subject: [PATCH 56/65] Delayed damage text to show up when attack sprite hits. --- .../nacabaro/vbhelper/screens/BattlesScreen.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 50ca188..412fd25 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -232,10 +232,13 @@ fun BattleScreen( delay(400) // Match the hit effect delay hideEnemyAttackSprite = true } - // Show damage number when attack reaches enemy + // Delay showing damage number to match hit effect timing if (pendingOpponentDamage > 0) { - showOpponentDamageNumber = true - println("DEBUG: Showing opponent damage number at progress $progress") + coroutineScope.launch { + delay(400) // Match the hit effect delay + showOpponentDamageNumber = true + println("DEBUG: Showing opponent damage number after delay") + } } } else { // Player attack misses, enemy dodges @@ -294,10 +297,13 @@ fun BattleScreen( delay(400) // Match the hit effect delay hidePlayerAttackSprite = true } - // Show damage number when attack reaches player + // Delay showing damage number to match hit effect timing if (pendingPlayerDamage > 0) { - showPlayerDamageNumber = true - println("DEBUG: Showing player damage number at progress $progress") + coroutineScope.launch { + delay(400) // Match the hit effect delay + showPlayerDamageNumber = true + println("DEBUG: Showing player damage number after delay") + } } } else { // Enemy attack misses, player dodges From 61a2439f84c2a32cac6d1c9b7e2407f8a9903376 Mon Sep 17 00:00:00 2001 From: lightheel Date: Wed, 6 Aug 2025 19:19:58 -0400 Subject: [PATCH 57/65] Adjusted horizontal sprite shake timing to sync with attack hit. --- .../vbhelper/battle/ArenaBattleSystem.kt | 60 +++++++++++++ .../vbhelper/screens/BattlesScreen.kt | 86 +++++++++++++++++-- 2 files changed, 137 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt index 6105bd3..61073fe 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/ArenaBattleSystem.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue +import androidx.compose.runtime.State class ArenaBattleSystem { companion object { @@ -85,6 +86,20 @@ class ArenaBattleSystem { private var _isOpponentHit by mutableStateOf(false) val isOpponentHit: Boolean get() = _isOpponentHit + // Delayed hit states for SLEEP animation timing + private var _isPlayerHitDelayed by mutableStateOf(false) + val isPlayerHitDelayed: Boolean get() = _isPlayerHitDelayed + + private var _isOpponentHitDelayed by mutableStateOf(false) + val isOpponentHitDelayed: Boolean get() = _isOpponentHitDelayed + + // Delayed shake states for shake animation timing + private var _isPlayerShakeDelayed by mutableStateOf(false) + val isPlayerShakeDelayed: Boolean get() = _isPlayerShakeDelayed + + private var _isOpponentShakeDelayed by mutableStateOf(false) + val isOpponentShakeDelayed: Boolean get() = _isOpponentShakeDelayed + // Counter-attack tracking private var _shouldCounterAttack by mutableStateOf(false) val shouldCounterAttack: Boolean get() = _shouldCounterAttack @@ -187,6 +202,10 @@ class ArenaBattleSystem { _opponentDodgeDirection = 1f _isPlayerHit = false _isOpponentHit = false + _isPlayerHitDelayed = false + _isOpponentHitDelayed = false + _isPlayerShakeDelayed = false + _isOpponentShakeDelayed = false _shouldCounterAttack = false _counterAttackIsHit = false _opponentAttackIsHit = false @@ -297,12 +316,22 @@ class ArenaBattleSystem { Log.d(TAG, "Started player hit animation") } + fun startPlayerHitDelayed() { + _isPlayerHitDelayed = true + Log.d(TAG, "Started delayed player hit animation") + } + fun endPlayerHit() { _isPlayerHit = false _hitProgress = 0f Log.d(TAG, "Ended player hit animation") } + fun endPlayerHitDelayed() { + _isPlayerHitDelayed = false + Log.d(TAG, "Ended delayed player hit animation") + } + // Opponent-specific hit methods fun startOpponentHit() { _isOpponentHit = true @@ -310,12 +339,43 @@ class ArenaBattleSystem { Log.d(TAG, "Started opponent hit animation") } + fun startOpponentHitDelayed() { + _isOpponentHitDelayed = true + Log.d(TAG, "Started delayed opponent hit animation") + } + fun endOpponentHit() { _isOpponentHit = false _hitProgress = 0f Log.d(TAG, "Ended opponent hit animation") } + fun endOpponentHitDelayed() { + _isOpponentHitDelayed = false + Log.d(TAG, "Ended delayed opponent hit animation") + } + + // Delayed shake methods + fun startPlayerShakeDelayed() { + _isPlayerShakeDelayed = true + Log.d(TAG, "Started delayed player shake animation") + } + + fun endPlayerShakeDelayed() { + _isPlayerShakeDelayed = false + Log.d(TAG, "Ended delayed player shake animation") + } + + fun startOpponentShakeDelayed() { + _isOpponentShakeDelayed = true + Log.d(TAG, "Started delayed opponent shake animation") + } + + fun endOpponentShakeDelayed() { + _isOpponentShakeDelayed = false + Log.d(TAG, "Ended delayed opponent shake animation") + } + // Combined method to handle attack result fun handleAttackResult(isHit: Boolean) { _attackIsHit = isHit diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 412fd25..e0b808d 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -179,7 +179,11 @@ fun BattleScreen( showOpponentHitEffect = false hidePlayerAttackSprite = false hideEnemyAttackSprite = false - println("DEBUG: Reset hit effect states - attack phase returned to idle") + battleSystem.endPlayerHitDelayed() + battleSystem.endOpponentHitDelayed() + battleSystem.endPlayerShakeDelayed() + battleSystem.endOpponentShakeDelayed() + println("DEBUG: Reset hit effect states, attack sprite visibility, and delayed hit states") } } @@ -240,6 +244,18 @@ fun BattleScreen( println("DEBUG: Showing opponent damage number after delay") } } + // Delay SLEEP animation to match hit effect timing + coroutineScope.launch { + delay(400) // Match the hit effect delay + battleSystem.startOpponentHitDelayed() + println("DEBUG: Starting delayed opponent hit animation") + } + // Delay shake animation to match hit effect timing + coroutineScope.launch { + delay(400) // Match the hit effect delay + battleSystem.startOpponentShakeDelayed() + println("DEBUG: Starting delayed opponent shake animation") + } } else { // Player attack misses, enemy dodges println("Player attack misses, enemy dodges at progress $progress") @@ -305,6 +321,18 @@ fun BattleScreen( println("DEBUG: Showing player damage number after delay") } } + // Delay SLEEP animation to match hit effect timing + coroutineScope.launch { + delay(400) // Match the hit effect delay + battleSystem.startPlayerHitDelayed() + println("DEBUG: Starting delayed player hit animation") + } + // Delay shake animation to match hit effect timing + coroutineScope.launch { + delay(400) // Match the hit effect delay + battleSystem.startPlayerShakeDelayed() + println("DEBUG: Starting delayed player shake animation") + } } else { // Enemy attack misses, player dodges println("Enemy attack misses, player dodges at progress $progress") @@ -424,6 +452,26 @@ fun BattleScreen( } } + // Player delayed shake animation + LaunchedEffect(battleSystem.isPlayerShakeDelayed) { + if (battleSystem.isPlayerShakeDelayed) { + println("Starting delayed player shake animation") + var hitProgress = 0f + + // Quick hit effect + while (hitProgress < 1f) { + hitProgress += 0.1f // Fast hit effect + battleSystem.setHitProgress(hitProgress) + delay(16) + } + + delay(100) // Brief pause + + battleSystem.endPlayerShakeDelayed() + println("Delayed player shake animation completed") + } + } + // Opponent hit animation LaunchedEffect(battleSystem.isOpponentHit) { if (battleSystem.isOpponentHit) { @@ -444,6 +492,26 @@ fun BattleScreen( } } + // Opponent delayed shake animation + LaunchedEffect(battleSystem.isOpponentShakeDelayed) { + if (battleSystem.isOpponentShakeDelayed) { + println("Starting delayed opponent shake animation") + var hitProgress = 0f + + // Quick hit effect + while (hitProgress < 1f) { + hitProgress += 0.1f // Fast hit effect + battleSystem.setHitProgress(hitProgress) + delay(16) + } + + delay(100) // Brief pause + + battleSystem.endOpponentShakeDelayed() + println("Delayed opponent shake animation completed") + } + } + // Damage number handling - store pending damage but don't show immediately LaunchedEffect(pendingPlayerDamage) { if (pendingPlayerDamage > 0) { @@ -721,7 +789,7 @@ fun MiddleBattleView( val enemyAnimationType = when { battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Both attacking in Phase 1 battleSystem.isOpponentDodging -> DigimonAnimationType.WALK - battleSystem.isOpponentHit -> DigimonAnimationType.SLEEP + battleSystem.isOpponentHitDelayed -> DigimonAnimationType.SLEEP else -> DigimonAnimationType.IDLE } @@ -741,7 +809,7 @@ fun MiddleBattleView( } // Calculate hit effect for enemy - val enemyHitOffset = if (battleSystem.isOpponentHit) { + val enemyHitOffset = if (battleSystem.isOpponentShakeDelayed) { val shakeAmount = 5.dp val progress = battleSystem.hitProgress val shake = if (progress < 0.5f) progress * 2f else (1f - progress) * 2f @@ -802,7 +870,7 @@ fun MiddleBattleView( val playerAnimationType = when { battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Both attacking in Phase 1 battleSystem.isPlayerDodging -> DigimonAnimationType.WALK - battleSystem.isPlayerHit -> DigimonAnimationType.SLEEP + battleSystem.isPlayerHitDelayed -> DigimonAnimationType.SLEEP else -> DigimonAnimationType.IDLE } @@ -822,7 +890,7 @@ fun MiddleBattleView( } // Calculate hit effect for player - val playerHitOffset = if (battleSystem.isPlayerHit) { + val playerHitOffset = if (battleSystem.isPlayerShakeDelayed) { val shakeAmount = 5.dp val progress = battleSystem.hitProgress val shake = if (progress < 0.5f) progress * 2f else (1f - progress) * 2f @@ -1135,7 +1203,7 @@ fun PlayerBattleView( // Determine animation type based on battle state val animationType = when { battleSystem.isPlayerDodging -> DigimonAnimationType.WALK // Use walk animation for dodge - battleSystem.isPlayerHit -> DigimonAnimationType.SLEEP // Use sleep animation for hit effect (injured sprite) + battleSystem.isPlayerHitDelayed -> DigimonAnimationType.SLEEP // Use sleep animation for hit effect (injured sprite) battleSystem.attackPhase == 1 -> DigimonAnimationType.ATTACK // Player attack on player screen battleSystem.attackPhase == 2 -> DigimonAnimationType.ATTACK // Player attack on opponent screen battleSystem.attackPhase == 3 -> DigimonAnimationType.IDLE // Opponent attack on opponent screen @@ -1161,7 +1229,7 @@ fun PlayerBattleView( } // Calculate hit effect (slight shake) - val hitOffset = if (battleSystem.isPlayerHit) { + val hitOffset = if (battleSystem.isPlayerShakeDelayed) { val shakeAmount = 5.dp val progress = battleSystem.hitProgress // Simple shake effect without complex math @@ -1448,7 +1516,7 @@ fun EnemyBattleView( // Determine animation type based on battle state val animationType = when { battleSystem.isOpponentDodging -> DigimonAnimationType.WALK // Use walk animation for dodge - battleSystem.isOpponentHit -> DigimonAnimationType.SLEEP // Use sleep animation for hit effect (injured sprite) + battleSystem.isOpponentHitDelayed -> DigimonAnimationType.SLEEP // Use sleep animation for hit effect (injured sprite) battleSystem.attackPhase == 2 -> DigimonAnimationType.IDLE // Player attack on enemy screen else -> DigimonAnimationType.IDLE } @@ -1471,7 +1539,7 @@ fun EnemyBattleView( } // Calculate hit effect (slight shake) - val hitOffset = if (battleSystem.isOpponentHit) { + val hitOffset = if (battleSystem.isOpponentShakeDelayed) { val shakeAmount = 5.dp val progress = battleSystem.hitProgress // Simple shake effect without complex math From f0760f9ed05ca036b219ea85c386f8a07dad00bd Mon Sep 17 00:00:00 2001 From: lightheel Date: Thu, 7 Aug 2025 11:23:32 -0400 Subject: [PATCH 58/65] Added gray boxes around HP --- .../vbhelper/screens/BattlesScreen.kt | 220 +++++++++++------- 1 file changed, 140 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index e0b808d..ab82952 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -731,29 +731,44 @@ fun MiddleBattleView( } */ - // Enemy HP bar (top) - LinearProgressIndicator( - progress = battleSystem.opponentHP / (opponentCharacter?.baseHp?.toFloat() ?: 100f), + // Enemy HP bar and text with background box + Box( modifier = Modifier .fillMaxWidth() - .height(10.dp), - color = Color.Red, - trackColor = Color.Gray - ) - - // Enemy HP display numbers - Text( - text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponentCharacter?.baseHp ?: 100}", - fontSize = 16.sp, - color = Color.White, - style = TextStyle( - shadow = Shadow( - color = Color.Black, - offset = androidx.compose.ui.geometry.Offset(4f, 4f), - //blurRadius = 2f + .background( + color = Color.Gray.copy(alpha = 0.6f), + shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) ) - ) - ) + .padding(8.dp) + ) { + Column { + // Enemy HP bar (top) + LinearProgressIndicator( + progress = battleSystem.opponentHP / (opponentCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Red, + trackColor = Color.Gray + ) + + Spacer(modifier = Modifier.height(4.dp)) + + // Enemy HP display numbers + Text( + text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponentCharacter?.baseHp ?: 100}", + fontSize = 16.sp, + color = Color.White, + style = TextStyle( + shadow = Shadow( + color = Color.Black, + offset = androidx.compose.ui.geometry.Offset(4f, 4f), + //blurRadius = 2f + ) + ) + ) + } + } Spacer(modifier = Modifier.height(16.dp)) @@ -956,29 +971,44 @@ fun MiddleBattleView( Spacer(modifier = Modifier.height(6.dp)) - // Player HP bar - LinearProgressIndicator( - progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + // Player HP bar and text with background box + Box( modifier = Modifier .fillMaxWidth() - .height(10.dp), - color = Color.Green, - trackColor = Color.Gray - ) - - // Player HP display numbers - Text( - text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 16.sp, - color = Color.White, - style = TextStyle( - shadow = Shadow( - color = Color.Black, - offset = androidx.compose.ui.geometry.Offset(4f, 4f), - //blurRadius = 2f + .background( + color = Color.Gray.copy(alpha = 0.6f), + shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) ) - ) - ) + .padding(8.dp) + ) { + Column { + // Player HP bar + LinearProgressIndicator( + progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Green, + trackColor = Color.Gray + ) + + Spacer(modifier = Modifier.height(4.dp)) + + // Player HP display numbers + Text( + text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = 16.sp, + color = Color.White, + style = TextStyle( + shadow = Shadow( + color = Color.Black, + offset = androidx.compose.ui.geometry.Offset(4f, 4f), + //blurRadius = 2f + ) + ) + ) + } + } Spacer(modifier = Modifier.height(6.dp)) @@ -1161,29 +1191,44 @@ fun PlayerBattleView( } */ - // Health bar - LinearProgressIndicator( - progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + // Health bar and text with background box + Box( modifier = Modifier .fillMaxWidth() - .height(10.dp), - color = Color.Green, - trackColor = Color.Gray - ) - - // Health display numbers - Text( - text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 16.sp, - color = Color.White, - style = TextStyle( - shadow = Shadow( - color = Color.Black, - offset = androidx.compose.ui.geometry.Offset(4f, 4f), - //blurRadius = 2f + .background( + color = Color.Gray.copy(alpha = 0.6f), + shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) ) - ) - ) + .padding(8.dp) + ) { + Column { + // Health bar + LinearProgressIndicator( + progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Green, + trackColor = Color.Gray + ) + + Spacer(modifier = Modifier.height(4.dp)) + + // Health display numbers + Text( + text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = 16.sp, + color = Color.White, + style = TextStyle( + shadow = Shadow( + color = Color.Black, + offset = androidx.compose.ui.geometry.Offset(4f, 4f), + //blurRadius = 2f + ) + ) + ) + } + } } // Middle section: Player Digimon only @@ -1474,29 +1519,44 @@ fun EnemyBattleView( .fillMaxWidth() .padding(16.dp) ) { - // Enemy HP bar - LinearProgressIndicator( - progress = battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + // Enemy HP bar and text with background box + Box( modifier = Modifier .fillMaxWidth() - .height(10.dp), - color = Color.Red, - trackColor = Color.Gray - ) - - // Enemy HP display numbers - Text( - text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 16.sp, - color = Color.White, - style = TextStyle( - shadow = Shadow( - color = Color.Black, - offset = androidx.compose.ui.geometry.Offset(4f, 4f), - //blurRadius = 2f + .background( + color = Color.Gray.copy(alpha = 0.6f), + shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) ) - ) - ) + .padding(8.dp) + ) { + Column { + // Enemy HP bar + LinearProgressIndicator( + progress = battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = Color.Red, + trackColor = Color.Gray + ) + + Spacer(modifier = Modifier.height(4.dp)) + + // Enemy HP display numbers + Text( + text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = 16.sp, + color = Color.White, + style = TextStyle( + shadow = Shadow( + color = Color.Black, + offset = androidx.compose.ui.geometry.Offset(4f, 4f), + //blurRadius = 2f + ) + ) + ) + } + } } // Middle section: Enemy Digimon From bb1c29bbb42ffd6b9dbe4fa41c223f54ac5ba6b8 Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 8 Aug 2025 07:44:36 -0400 Subject: [PATCH 59/65] Moved sprites to load from external Android storage. Setup landscape view for battle. --- app/src/main/AndroidManifest.xml | 2 + .../vbhelper/battle/AttackSpriteManager.kt | 19 +- .../vbhelper/battle/BattleSpriteManager.kt | 13 +- .../vbhelper/battle/HitEffectSpriteManager.kt | 44 ++- .../battle/IndividualSpriteManager.kt | 68 ++-- .../vbhelper/battle/SpriteFileManager.kt | 31 +- .../vbhelper/screens/BattlesScreen.kt | 329 ++++++++++-------- 7 files changed, 280 insertions(+), 226 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c28d47b..d8da12c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,8 @@ + + () - // Base path for attack textures (updated for new folder structure) - private val attackTexturesPath = "battle_sprites/extracted_atksprites" + // Get the external storage directory for attack sprites + private fun getAttackTexturesPath(): String { + return "VBHelper/battle_sprites/extracted_atksprites" + } fun getAttackSprite(characterId: String, isLarge: Boolean = false): Bitmap? { println("AttackSpriteManager: Getting attack sprite for characterId=$characterId, isLarge=$isLarge") @@ -55,9 +58,10 @@ class AttackSpriteManager(private val context: Context) { return null } - // Load the attack sprite - val attackFilePath = "$attackTexturesPath/$attackFileName.png" - val attackFile = File(context.filesDir, attackFilePath) + // Load the attack sprite from external storage + val externalDir = Environment.getExternalStorageDirectory() + val attackFilePath = "${getAttackTexturesPath()}/$attackFileName.png" + val attackFile = File(externalDir, attackFilePath) println("AttackSpriteManager: Attack file path = ${attackFile.absolutePath}") println("AttackSpriteManager: Attack file exists = ${attackFile.exists()}") @@ -85,8 +89,9 @@ class AttackSpriteManager(private val context: Context) { } try { - // Load character data from JSON file - val characterDataFile = File(context.filesDir, "battle_sprites/extracted_digimon_stats/character_data/CharacterData.json") + // Load character data from JSON file in external storage + val externalDir = Environment.getExternalStorageDirectory() + val characterDataFile = File(externalDir, "VBHelper/battle_sprites/extracted_digimon_stats/character_data/CharacterData.json") println("AttackSpriteManager: Character data file path = ${characterDataFile.absolutePath}") println("AttackSpriteManager: Character data file exists = ${characterDataFile.exists()}") diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt index 316d486..f7fd5c1 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt @@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.battle import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.os.Environment import com.google.gson.Gson import java.io.File @@ -37,8 +38,11 @@ class BattleSpriteManager(private val context: Context) { private val gson = Gson() private val spriteCache = mutableMapOf() - // Base directory where your sprites are stored - private val spriteBaseDir = File(context.filesDir, "battle_sprites/extracted_assets") + // Get the external storage directory for sprite files + private fun getSpriteBaseDir(): File { + val externalDir = Environment.getExternalStorageDirectory() + return File(externalDir, "VBHelper/battle_sprites/extracted_assets") + } fun loadSprite(spriteName: String, atlasName: String): Bitmap? { val cacheKey = "${spriteName}_${atlasName}" @@ -49,6 +53,7 @@ class BattleSpriteManager(private val context: Context) { } // Debug: Check if base directory exists + val spriteBaseDir = getSpriteBaseDir() if (!spriteBaseDir.exists()) { println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}") return null @@ -130,7 +135,7 @@ class BattleSpriteManager(private val context: Context) { // Helper method to get available sprites for an atlas fun getAvailableSprites(atlasName: String): List { try { - val spritesDir = File(spriteBaseDir, "sprites") + val spritesDir = File(getSpriteBaseDir(), "sprites") if (!spritesDir.exists()) { return emptyList() } @@ -154,7 +159,7 @@ class BattleSpriteManager(private val context: Context) { // Helper method to get available atlases fun getAvailableAtlases(): List { try { - val texturesDir = File(spriteBaseDir, "extracted_textures") + val texturesDir = File(getSpriteBaseDir(), "extracted_textures") if (!texturesDir.exists()) { return emptyList() } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectSpriteManager.kt index 33d2189..6536040 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectSpriteManager.kt @@ -4,13 +4,17 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Rect +import android.os.Environment import java.io.File class HitEffectSpriteManager(private val context: Context) { private val spriteCache = mutableMapOf() - // Base directory where hit effect sprites are stored - private val hitSpritesDir = File(context.filesDir, "battle_sprites/extracted_hit_sprites") + // Get the external storage directory for hit effect sprites + private fun getHitSpritesDir(): File { + val externalDir = Environment.getExternalStorageDirectory() + return File(externalDir, "VBHelper/battle_sprites/extracted_hit_sprites") + } /** * Load a hit sprite (hit_01.png, hit_02.png, hit_02_white.png) @@ -26,6 +30,7 @@ class HitEffectSpriteManager(private val context: Context) { } try { + val hitSpritesDir = getHitSpritesDir() val spriteFile = File(hitSpritesDir, "$spriteName.png") if (!spriteFile.exists()) { @@ -68,7 +73,7 @@ class HitEffectSpriteManager(private val context: Context) { } try { - val spritesheetFile = File(hitSpritesDir, "$spritesheetName.png") + val spritesheetFile = File(getHitSpritesDir(), "$spritesheetName.png") if (!spritesheetFile.exists()) { println("Damage effect spritesheet not found: ${spritesheetFile.absolutePath}") @@ -118,28 +123,21 @@ class HitEffectSpriteManager(private val context: Context) { } /** - * Get available hit sprite names - * @return List of available hit sprite names + * Get all available hit sprites + * @return List of hit sprite names (without .png extension) */ fun getAvailableHitSprites(): List { - try { - if (!hitSpritesDir.exists()) { - return emptyList() - } - - val hitFiles = hitSpritesDir.listFiles { file -> - file.name.startsWith("hit_") && file.name.endsWith(".png") - } ?: emptyArray() - - return hitFiles.map { file -> - file.name.substringBefore(".png") - }.sorted() - - } catch (e: Exception) { - println("Error getting available hit sprites: ${e.message}") - e.printStackTrace() + val hitSpritesDir = getHitSpritesDir() + + if (!hitSpritesDir.exists()) { return emptyList() } + + return hitSpritesDir.listFiles { file -> + file.name.startsWith("hit_") && file.name.endsWith(".png") + }?.map { file -> + file.name.substringBefore(".png") + }?.sorted() ?: emptyList() } /** @@ -148,11 +146,11 @@ class HitEffectSpriteManager(private val context: Context) { */ fun getAvailableDamageEffectSpritesheets(): List { try { - if (!hitSpritesDir.exists()) { + if (!getHitSpritesDir().exists()) { return emptyList() } - val dmgFiles = hitSpritesDir.listFiles { file -> + val dmgFiles = getHitSpritesDir().listFiles { file -> file.name.startsWith("dmg_ef") && file.name.endsWith(".png") } ?: emptyArray() diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/IndividualSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/IndividualSpriteManager.kt index f4b0d25..f6b9179 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/IndividualSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/IndividualSpriteManager.kt @@ -3,13 +3,17 @@ package com.github.nacabaro.vbhelper.battle import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.os.Environment import java.io.File class IndividualSpriteManager(private val context: Context) { private val spriteCache = mutableMapOf() - // Base directory where individual sprite PNGs are stored - private val spriteBaseDir = File(context.filesDir, "battle_sprites/extracted_assets/sprites") + // Get the external storage directory for sprite files + private fun getSpriteBaseDir(): File { + val externalDir = Environment.getExternalStorageDirectory() + return File(externalDir, "VBHelper/battle_sprites/extracted_assets/sprites") + } /** * Load a specific sprite frame for a character @@ -26,6 +30,7 @@ class IndividualSpriteManager(private val context: Context) { } // Debug: Check if base directory exists + val spriteBaseDir = getSpriteBaseDir() if (!spriteBaseDir.exists()) { println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}") return null @@ -68,51 +73,38 @@ class IndividualSpriteManager(private val context: Context) { * @return List of frame numbers (1-12) that exist for this character */ fun getAvailableFrames(characterId: String): List { - try { - val characterDir = File(spriteBaseDir, characterId) - if (!characterDir.exists()) { - println("Character directory not found: ${characterDir.absolutePath}") - return emptyList() - } - - val spriteFiles = characterDir.listFiles { file -> - file.name.startsWith("${characterId}_") && file.name.endsWith(".png") - } ?: emptyArray() - - return spriteFiles.mapNotNull { file -> - // Extract frame number from filename (e.g., "dim012_mon03_01.png" -> 1) - val frameNumberStr = file.name.substringAfter("_").substringBefore(".png") - frameNumberStr.toIntOrNull() - }.sorted() - - } catch (e: Exception) { - println("Error getting available frames: ${e.message}") - e.printStackTrace() + val spriteBaseDir = getSpriteBaseDir() + val characterDir = File(spriteBaseDir, characterId) + + if (!characterDir.exists()) { return emptyList() } + + val spriteFiles = characterDir.listFiles { file -> + file.name.startsWith("${characterId}_") && file.name.endsWith(".png") + } ?: emptyArray() + + return spriteFiles.mapNotNull { file -> + val fileName = file.name + val frameMatch = Regex("${characterId}_(\\d{2})\\.png").find(fileName) + frameMatch?.groupValues?.get(1)?.toIntOrNull() + }.sorted() } /** - * Get all available characters + * Get all available character IDs * @return List of character IDs that have sprite directories */ fun getAvailableCharacters(): List { - try { - if (!spriteBaseDir.exists()) { - return emptyList() - } - - val characterDirs = spriteBaseDir.listFiles { file -> - file.isDirectory && file.name.matches(Regex("dim\\d+_mon\\d+.*")) - } ?: emptyArray() - - return characterDirs.map { it.name }.sorted() - - } catch (e: Exception) { - println("Error getting available characters: ${e.message}") - e.printStackTrace() + val spriteBaseDir = getSpriteBaseDir() + + if (!spriteBaseDir.exists()) { return emptyList() } + + return spriteBaseDir.listFiles { file -> + file.isDirectory && file.listFiles()?.any { it.name.endsWith(".png") } == true + }?.map { it.name }?.sorted() ?: emptyList() } /** @@ -128,7 +120,7 @@ class IndividualSpriteManager(private val context: Context) { * @return true if the character has sprite files, false otherwise */ fun hasCharacterSprites(characterId: String): Boolean { - val characterDir = File(spriteBaseDir, characterId) + val characterDir = File(getSpriteBaseDir(), characterId) if (!characterDir.exists()) { return false } diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt index 5dd3699..2f663cb 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/SpriteFileManager.kt @@ -1,15 +1,22 @@ package com.github.nacabaro.vbhelper.battle import android.content.Context +import android.os.Environment import java.io.File import java.io.FileOutputStream import java.io.IOException class SpriteFileManager(private val context: Context) { - fun copySpriteFilesToInternalStorage() { + // Get the external storage directory for sprite files + private fun getSpriteBaseDir(): File { + val externalDir = Environment.getExternalStorageDirectory() + return File(externalDir, "VBHelper/battle_sprites") + } + + fun copySpriteFilesToExternalStorage() { try { - println("Starting sprite file copy process...") + println("Starting sprite file copy process to external storage...") // Debug: List what's in the assets directory val assetManager = context.assets @@ -47,17 +54,17 @@ class SpriteFileManager(private val context: Context) { } } - // Create the base directory for battle_sprites - val battleSpritesDir = File(context.filesDir, "battle_sprites") + // Create the base directory for battle_sprites in external storage + val battleSpritesDir = getSpriteBaseDir() if (!battleSpritesDir.exists()) { battleSpritesDir.mkdirs() - println("Created battle_sprites directory: ${battleSpritesDir.absolutePath}") + println("Created battle_sprites directory in external storage: ${battleSpritesDir.absolutePath}") } else { - println("battle_sprites directory already exists: ${battleSpritesDir.absolutePath}") + println("battle_sprites directory already exists in external storage: ${battleSpritesDir.absolutePath}") } - // Copy all subdirectories from battle_sprites assets to internal storage - println("Copying all battle_sprites subdirectories...") + // Copy all subdirectories from battle_sprites assets to external storage + println("Copying all battle_sprites subdirectories to external storage...") battleSpritesFiles?.forEach { subdir -> val sourcePath = "battle_sprites/$subdir" val targetDir = File(battleSpritesDir, subdir) @@ -65,7 +72,7 @@ class SpriteFileManager(private val context: Context) { copyAssetDirectory(sourcePath, targetDir) } - println("Sprite files copied successfully to: ${battleSpritesDir.absolutePath}") + println("Sprite files copied successfully to external storage: ${battleSpritesDir.absolutePath}") // Verify that attack sprites were copied val atkspritesDir = File(battleSpritesDir, "extracted_atksprites") @@ -95,7 +102,7 @@ class SpriteFileManager(private val context: Context) { } } catch (e: Exception) { - println("Error copying sprite files: ${e.message}") + println("Error copying sprite files to external storage: ${e.message}") e.printStackTrace() } } @@ -180,7 +187,7 @@ class SpriteFileManager(private val context: Context) { } fun checkSpriteFilesExist(): Boolean { - val battleSpritesDir = File(context.filesDir, "battle_sprites") + val battleSpritesDir = getSpriteBaseDir() val extractedAssetsDir = File(battleSpritesDir, "extracted_assets") val extractedStatsDir = File(battleSpritesDir, "extracted_digimon_stats") val atkspritesDir = File(battleSpritesDir, "extracted_atksprites") @@ -204,7 +211,7 @@ class SpriteFileManager(private val context: Context) { fun clearSpriteFiles() { try { - val battleSpritesDir = File(context.filesDir, "battle_sprites") + val battleSpritesDir = getSpriteBaseDir() if (battleSpritesDir.exists()) { deleteDirectory(battleSpritesDir) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index ab82952..c9e2f79 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -66,9 +66,72 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.foundation.Image import androidx.compose.ui.graphics.asImageBitmap import android.graphics.BitmapFactory +import android.os.Environment import java.io.File import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.foundation.layout.width + +@Composable +fun isLandscapeMode(): Boolean { + val configuration = LocalConfiguration.current + return configuration.screenWidthDp > configuration.screenHeightDp +} + +@Composable +fun getLandscapeModifier(): Modifier { + val configuration = LocalConfiguration.current + val isLandscape = configuration.screenWidthDp > configuration.screenHeightDp + return if (isLandscape) { + Modifier.width(200.dp).height(8.dp) + } else { + Modifier.fillMaxWidth().height(10.dp) + } +} + +@Composable +fun getLandscapeAlignment(): Alignment { + val configuration = LocalConfiguration.current + val isLandscape = configuration.screenWidthDp > configuration.screenHeightDp + return if (isLandscape) Alignment.Center else Alignment.TopStart +} + +@Composable +fun getLandscapeHorizontalAlignment(): Alignment.Horizontal { + val configuration = LocalConfiguration.current + val isLandscape = configuration.screenWidthDp > configuration.screenHeightDp + return if (isLandscape) Alignment.CenterHorizontally else Alignment.Start +} + +@Composable +fun getLandscapeFontSize(): androidx.compose.ui.unit.TextUnit { + val configuration = LocalConfiguration.current + val isLandscape = configuration.screenWidthDp > configuration.screenHeightDp + return if (isLandscape) 14.sp else 16.sp +} + +@Composable +fun getLandscapeBoxModifier(): Modifier { + val configuration = LocalConfiguration.current + val isLandscape = configuration.screenWidthDp > configuration.screenHeightDp + return if (isLandscape) { + Modifier + .width(220.dp) // Slightly wider than the progress bar to accommodate padding + .background( + color = Color.Gray.copy(alpha = 0.6f), + shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) + ) + .padding(8.dp) + } else { + Modifier + .fillMaxWidth() + .background( + color = Color.Gray.copy(alpha = 0.6f), + shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) + ) + .padding(8.dp) + } +} @Composable fun AnimatedDamageNumber( @@ -266,13 +329,13 @@ fun BattleScreen( delay(16) // 60 FPS } println("Phase 2 completed, applying damage and starting Phase 3") - battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) + battleSystem.completeAttackAnimation(opponentDamage = pendingOpponentDamage) // Hide damage number and reset pending damage after animation if (showOpponentDamageNumber) { delay(800) // Wait for damage number animation (scale up + hold + fade out) showOpponentDamageNumber = false - pendingOpponentDamage = 0f + pendingOpponentDamage = 0f println("DEBUG: Hiding opponent damage number and resetting pending damage") } @@ -344,18 +407,18 @@ fun BattleScreen( } println("Phase 3 completed, applying damage and resetting") println("DEBUG: pendingPlayerDamage = $pendingPlayerDamage") - battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) + battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) // Hide damage number and reset pending damage after animation if (showPlayerDamageNumber) { delay(800) // Wait for damage number animation (scale up + hold + fade out) showPlayerDamageNumber = false - pendingPlayerDamage = 0f + pendingPlayerDamage = 0f println("DEBUG: Hiding player damage number and resetting pending damage") } - battleSystem.resetAttackState() - battleSystem.enableAttackButton() + battleSystem.resetAttackState() + battleSystem.enableAttackButton() // Check if battle is over if (battleSystem.checkBattleOver()) { @@ -733,21 +796,16 @@ fun MiddleBattleView( // Enemy HP bar and text with background box Box( - modifier = Modifier - .fillMaxWidth() - .background( - color = Color.Gray.copy(alpha = 0.6f), - shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) - ) - .padding(8.dp) + modifier = getLandscapeBoxModifier(), + contentAlignment = getLandscapeAlignment() ) { - Column { + Column( + horizontalAlignment = getLandscapeHorizontalAlignment() + ) { // Enemy HP bar (top) LinearProgressIndicator( progress = battleSystem.opponentHP / (opponentCharacter?.baseHp?.toFloat() ?: 100f), - modifier = Modifier - .fillMaxWidth() - .height(10.dp), + modifier = getLandscapeModifier(), color = Color.Red, trackColor = Color.Gray ) @@ -757,7 +815,7 @@ fun MiddleBattleView( // Enemy HP display numbers Text( text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponentCharacter?.baseHp ?: 100}", - fontSize = 16.sp, + fontSize = getLandscapeFontSize(), color = Color.White, style = TextStyle( shadow = Shadow( @@ -962,9 +1020,7 @@ fun MiddleBattleView( // Critical bar LinearProgressIndicator( progress = battleSystem.critBarProgress / 100f, - modifier = Modifier - .fillMaxWidth() - .height(10.dp), + modifier = getLandscapeModifier(), color = Color.Yellow, trackColor = Color.Gray ) @@ -973,21 +1029,16 @@ fun MiddleBattleView( // Player HP bar and text with background box Box( - modifier = Modifier - .fillMaxWidth() - .background( - color = Color.Gray.copy(alpha = 0.6f), - shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) - ) - .padding(8.dp) + modifier = getLandscapeBoxModifier(), + contentAlignment = getLandscapeAlignment() ) { - Column { + Column( + horizontalAlignment = getLandscapeHorizontalAlignment() + ) { // Player HP bar LinearProgressIndicator( progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), - modifier = Modifier - .fillMaxWidth() - .height(10.dp), + modifier = getLandscapeModifier(), color = Color.Green, trackColor = Color.Gray ) @@ -997,7 +1048,7 @@ fun MiddleBattleView( // Player HP display numbers Text( text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 16.sp, + fontSize = getLandscapeFontSize(), color = Color.White, style = TextStyle( shadow = Shadow( @@ -1193,31 +1244,26 @@ fun PlayerBattleView( // Health bar and text with background box Box( - modifier = Modifier - .fillMaxWidth() - .background( - color = Color.Gray.copy(alpha = 0.6f), - shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) - ) - .padding(8.dp) + modifier = getLandscapeBoxModifier(), + contentAlignment = getLandscapeAlignment() ) { - Column { - // Health bar - LinearProgressIndicator( - progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), - modifier = Modifier - .fillMaxWidth() - .height(10.dp), - color = Color.Green, - trackColor = Color.Gray - ) + Column( + horizontalAlignment = getLandscapeHorizontalAlignment() + ) { + // Health bar + LinearProgressIndicator( + progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), + modifier = getLandscapeModifier(), + color = Color.Green, + trackColor = Color.Gray + ) Spacer(modifier = Modifier.height(4.dp)) - // Health display numbers - Text( - text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 16.sp, + // Health display numbers + Text( + text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", + fontSize = getLandscapeFontSize(), color = Color.White, style = TextStyle( shadow = Shadow( @@ -1363,9 +1409,7 @@ fun PlayerBattleView( // Critical bar LinearProgressIndicator( progress = battleSystem.critBarProgress / 100f, - modifier = Modifier - .fillMaxWidth() - .height(10.dp), + modifier = getLandscapeModifier(), color = Color.Yellow, trackColor = Color.Gray ) @@ -1422,22 +1466,22 @@ fun PlayerBattleView( // Match is still ongoing - update HP and continue println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") - // Set pending damage based on API result + // Set pending damage based on API result if (apiResult.playerAttackDamage > 0) { - // Player attack hit - enemy takes damage at end of player animation - println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") - onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage - battleSystem.setAttackHitState(true) + // Player attack hit - enemy takes damage at end of player animation + println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") + onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage + battleSystem.setAttackHitState(true) // Also check if enemy counter-attacks and hits if (apiResult.opponentAttackDamage > 0) { println("Enemy counter-attack hits! Player takes ${apiResult.opponentAttackDamage} damage") onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), apiResult.playerAttackDamage.toFloat()) // Both take damage } - } else { + } else { // Player attack missed - enemy counter-attacks println("Player attack missed! Enemy counter-attacks") - battleSystem.setAttackHitState(false) + battleSystem.setAttackHitState(false) // Set up counter-attack - determine if it hits based on API result val counterAttackHits = apiResult.opponentAttackDamage > 0 println("Setting up counter-attack: counterAttackHits=$counterAttackHits, opponentAttackDamage=${apiResult.opponentAttackDamage}") @@ -1458,7 +1502,7 @@ fun PlayerBattleView( battleSystem.setupCounterAttack(finalCounterAttackHits) // Set the opponent attack hit state for Phase 3 battleSystem.handleOpponentAttackResult(finalCounterAttackHits) - } + } } 2 -> { // Match is over - transition to results screen @@ -1514,28 +1558,23 @@ fun EnemyBattleView( MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize()) // Top section: Enemy HP bar and HP numbers - Column( + Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { // Enemy HP bar and text with background box Box( - modifier = Modifier - .fillMaxWidth() - .background( - color = Color.Gray.copy(alpha = 0.6f), - shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp) - ) - .padding(8.dp) + modifier = getLandscapeBoxModifier(), + contentAlignment = getLandscapeAlignment() ) { - Column { + Column( + horizontalAlignment = getLandscapeHorizontalAlignment() + ) { // Enemy HP bar LinearProgressIndicator( progress = battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), - modifier = Modifier - .fillMaxWidth() - .height(10.dp), + modifier = getLandscapeModifier(), color = Color.Red, trackColor = Color.Gray ) @@ -1545,7 +1584,7 @@ fun EnemyBattleView( // Enemy HP display numbers Text( text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}", - fontSize = 16.sp, + fontSize = getLandscapeFontSize(), color = Color.White, style = TextStyle( shadow = Shadow( @@ -1561,25 +1600,25 @@ fun EnemyBattleView( // Middle section: Enemy Digimon Box( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), + modifier = Modifier + .fillMaxSize() + .padding(16.dp), contentAlignment = Alignment.Center - ) { + ) { // Enemy Digimon - Box( - modifier = Modifier - .fillMaxWidth() - .size(80.dp), - contentAlignment = Alignment.CenterEnd - ) { - // Determine animation type based on battle state + Box( + modifier = Modifier + .fillMaxWidth() + .size(80.dp), + contentAlignment = Alignment.CenterEnd + ) { + // Determine animation type based on battle state val animationType = when { battleSystem.isOpponentDodging -> DigimonAnimationType.WALK // Use walk animation for dodge battleSystem.isOpponentHitDelayed -> DigimonAnimationType.SLEEP // Use sleep animation for hit effect (injured sprite) battleSystem.attackPhase == 2 -> DigimonAnimationType.IDLE // Player attack on enemy screen - else -> DigimonAnimationType.IDLE - } + else -> DigimonAnimationType.IDLE + } // Calculate vertical offset for dodge animation val verticalOffset = if (battleSystem.isOpponentDodging) { @@ -1608,65 +1647,65 @@ fun EnemyBattleView( } else { 0.dp } - - AnimatedSpriteImage( - characterId = activeCharacter?.charaId ?: "dim011_mon01", - animationType = animationType, + + AnimatedSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + animationType = animationType, modifier = Modifier .size(80.dp) .offset( x = hitOffset, y = verticalOffset ), - contentScale = ContentScale.Fit, + contentScale = ContentScale.Fit, reloadMappings = false, animationOffset = 375L // Offset enemy animation by half the idle duration - ) - - // Attack sprite visibility and positioning based on attack phase - val shouldShowAttack = when (battleSystem.attackPhase) { + ) + + // Attack sprite visibility and positioning based on attack phase + val shouldShowAttack = when (battleSystem.attackPhase) { 2 -> true // Player attack on enemy screen - else -> false - } - - if (shouldShowAttack) { + else -> false + } + + if (shouldShowAttack) { val xOffset = (attackAnimationProgress * 400 - 350).dp // Player attack on enemy screen - start more to the left // Use player's character ID for player attack val characterId = playerCharacter?.charaId ?: "dim011_mon01" - - // Handle sprite transition - LaunchedEffect(characterId, battleSystem.attackPhase) { - if ((previousCharacterId != null && previousCharacterId != characterId) || - (previousAttackPhase != null && previousAttackPhase != battleSystem.attackPhase)) { - // Character ID or attack phase changed, start transition - isTransitioning = true - delay(100) // Brief invisibility period - isTransitioning = false - } - previousCharacterId = characterId - previousAttackPhase = battleSystem.attackPhase + + // Handle sprite transition + LaunchedEffect(characterId, battleSystem.attackPhase) { + if ((previousCharacterId != null && previousCharacterId != characterId) || + (previousAttackPhase != null && previousAttackPhase != battleSystem.attackPhase)) { + // Character ID or attack phase changed, start transition + isTransitioning = true + delay(100) // Brief invisibility period + isTransitioning = false } - + previousCharacterId = characterId + previousAttackPhase = battleSystem.attackPhase + } + println("EnemyBattleView - Attack sprite - Phase: ${battleSystem.attackPhase}, Progress: $attackAnimationProgress, X Offset: $xOffset, CurrentView: ${battleSystem.currentView}") - + if (!isTransitioning && !hideEnemyAttackSprite) { - AttackSpriteImage( - characterId = characterId, - isLarge = true, - modifier = Modifier - .size(60.dp) - .offset( - x = xOffset, - y = 0.dp - ) + AttackSpriteImage( + characterId = characterId, + isLarge = true, + modifier = Modifier + .size(60.dp) + .offset( + x = xOffset, + y = 0.dp + ) .scale(-1f, 1f), // Flip player attacks - contentScale = ContentScale.Fit - ) - } + contentScale = ContentScale.Fit + ) } } } + } } } @@ -1735,10 +1774,10 @@ fun BattlesScreen() { println("BATTLESCREEN: LaunchedEffect triggered - checking sprite files...") val spriteFileManager = SpriteFileManager(context) if (!spriteFileManager.checkSpriteFilesExist()) { - println("BATTLESCREEN: Copying sprite files to internal storage...") - spriteFileManager.copySpriteFilesToInternalStorage() + println("BATTLESCREEN: Copying sprite files to external storage...") + spriteFileManager.copySpriteFilesToExternalStorage() } else { - println("BATTLESCREEN: Sprite files already exist in internal storage") + println("BATTLESCREEN: Sprite files already exist in external storage") } } @@ -2048,9 +2087,9 @@ fun BattlesScreen() { topBar = { // Only show TopBanner when not in battle mode if (currentView != "battle-main" && currentView != "battle-results") { - TopBanner( - text = "Online battles" - ) + TopBanner( + text = "Online battles" + ) } } ) { contentPadding -> @@ -2077,6 +2116,7 @@ fun BattlesScreen() { championButton() ultimateButton() megaButton() + /* Button( onClick = { showSpriteTester = true @@ -2098,6 +2138,8 @@ fun BattlesScreen() { ) { Text("Clear Sprite Files") } + + */ } } } @@ -2387,10 +2429,11 @@ fun AnimatedBattleBackground( println("DEBUG: Screen dimensions = ${screenWidth.value}x${screenHeight.value}dp") } - // Load background image from internal storage + // Load background image from external storage LaunchedEffect(Unit) { try { - val backgroundFile = File(context.filesDir, "battle_sprites/extracted_battlebgs/BattleBg_0015_BattleBg_0012.png") + val externalDir = Environment.getExternalStorageDirectory() + val backgroundFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/BattleBg_0015_BattleBg_0012.png") if (backgroundFile.exists()) { backgroundBitmap = BitmapFactory.decodeFile(backgroundFile.absolutePath) println("Successfully loaded battle background: ${backgroundFile.absolutePath}") @@ -2469,11 +2512,13 @@ fun MultiLayerAnimatedBattleBackground( println("DEBUG: Multi-layer screen dimensions = ${screenWidth.value}x${screenHeight.value}dp") } - // Load all three background layers from internal storage + // Load all three background layers from external storage LaunchedEffect(Unit) { try { + val externalDir = Environment.getExternalStorageDirectory() + // Back layer (BattleBg_0018_BattleBg_0013.png) - val backLayerFile = File(context.filesDir, "battle_sprites/extracted_battlebgs/BattleBg_0018_BattleBg_0013.png") + val backLayerFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/BattleBg_0018_BattleBg_0013.png") if (backLayerFile.exists()) { backLayerBitmap = BitmapFactory.decodeFile(backLayerFile.absolutePath) println("Successfully loaded back layer background: ${backLayerFile.absolutePath}") @@ -2482,7 +2527,7 @@ fun MultiLayerAnimatedBattleBackground( } // Middle layer (BattleBg_0015_BattleBg_0012.png) - val middleLayerFile = File(context.filesDir, "battle_sprites/extracted_battlebgs/BattleBg_0015_BattleBg_0012.png") + val middleLayerFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/BattleBg_0015_BattleBg_0012.png") if (middleLayerFile.exists()) { middleLayerBitmap = BitmapFactory.decodeFile(middleLayerFile.absolutePath) println("Successfully loaded middle layer background: ${middleLayerFile.absolutePath}") @@ -2491,7 +2536,7 @@ fun MultiLayerAnimatedBattleBackground( } // Front layer (BattleBg_0005_BattleBg_0011.png) - val frontLayerFile = File(context.filesDir, "battle_sprites/extracted_battlebgs/BattleBg_0005_BattleBg_0011.png") + val frontLayerFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/BattleBg_0005_BattleBg_0011.png") if (frontLayerFile.exists()) { frontLayerBitmap = BitmapFactory.decodeFile(frontLayerFile.absolutePath) println("Successfully loaded front layer background: ${frontLayerFile.absolutePath}") From ba03be808ed13183809a6ca2d0bf7dcea3bc8f8b Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 8 Aug 2025 07:51:51 -0400 Subject: [PATCH 60/65] Removed attack bar and button from player view. --- .../vbhelper/screens/BattlesScreen.kt | 134 ------------------ 1 file changed, 134 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index c9e2f79..6591404 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -1398,141 +1398,7 @@ fun PlayerBattleView( } } - // Bottom section: Critical bar and Attack button - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - .align(Alignment.BottomCenter), - horizontalAlignment = Alignment.CenterHorizontally - ) { - // Critical bar - LinearProgressIndicator( - progress = battleSystem.critBarProgress / 100f, - modifier = getLandscapeModifier(), - color = Color.Yellow, - trackColor = Color.Gray - ) - Spacer(modifier = Modifier.height(16.dp)) - - // Attack button - Button( - onClick = { - println("Attack button clicked!") - - // Get crit bar progress as float (0.0f to 100.0f) - val critBarProgressFloat = battleSystem.critBarProgress.toFloat() - - // Determine player and opponent stages - val playerStage = when (activeCharacter?.stage) { - 0 -> 0 // rookie - 1 -> 1 // champion - 2 -> 2 // ultimate - 3 -> 3 // mega - else -> 0 - } - - val opponentStage = when (opponent?.stage) { - 0 -> 0 // rookie - 1 -> 1 // champion - 2 -> 2 // ultimate - 3 -> 3 // mega - else -> 0 - } - - // Send API call with all parameters - context?.let { ctx -> - // Start player attack animation - battleSystem.startPlayerAttack() - - RetrofitHelper().getPVPWinner( - ctx, - 1, - 2, - activeCharacter?.name ?: "Player", - playerStage, - opponentStage, - opponent?.name ?: "Opponent", - opponentStage - ) { apiResult -> - // Handle API response here - println("API Result: $apiResult") - lastApiResult = apiResult // Store for debug display - - // Update HP based on API response - when (apiResult.state) { - 1 -> { - // Match is still ongoing - update HP and continue - println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") - - // Set pending damage based on API result - if (apiResult.playerAttackDamage > 0) { - // Player attack hit - enemy takes damage at end of player animation - println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") - onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage - battleSystem.setAttackHitState(true) - - // Also check if enemy counter-attacks and hits - if (apiResult.opponentAttackDamage > 0) { - println("Enemy counter-attack hits! Player takes ${apiResult.opponentAttackDamage} damage") - onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), apiResult.playerAttackDamage.toFloat()) // Both take damage - } - } else { - // Player attack missed - enemy counter-attacks - println("Player attack missed! Enemy counter-attacks") - battleSystem.setAttackHitState(false) - // Set up counter-attack - determine if it hits based on API result - val counterAttackHits = apiResult.opponentAttackDamage > 0 - println("Setting up counter-attack: counterAttackHits=$counterAttackHits, opponentAttackDamage=${apiResult.opponentAttackDamage}") - println("Full API response: status=${apiResult.status}, state=${apiResult.state}, playerAttackHit=${apiResult.playerAttackHit}, playerAttackDamage=${apiResult.playerAttackDamage}, opponentAttackDamage=${apiResult.opponentAttackDamage}, playerHP=${apiResult.playerHP}, opponentHP=${apiResult.opponentHP}") - println("DEBUG: Using playerAttackDamage > 0 instead of playerAttackHit for hit detection") - - // Use opponentAttackDamage to determine counter-attack hit - val finalCounterAttackHits = counterAttackHits - println("Using opponentAttackDamage > 0 for counter-attack: $finalCounterAttackHits") - - if (finalCounterAttackHits) { - println("Counter-attack hits! Player takes ${apiResult.opponentAttackDamage} damage") - onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), 0f) // Player takes damage - } else { - println("Counter-attack misses! Player dodges") - onSetPendingDamage(0f, 0f) // No damage - } - battleSystem.setupCounterAttack(finalCounterAttackHits) - // Set the opponent attack hit state for Phase 3 - battleSystem.handleOpponentAttackResult(finalCounterAttackHits) - } - } - 2 -> { - // Match is over - transition to results screen - println("Match is over! Winner: ${apiResult.winner}") - battleSystem.updateHPFromAPI(apiResult.playerHP.toFloat(), apiResult.opponentHP.toFloat()) - onAttackClick() // This will transition to battle-results screen - } - -1 -> { - // Error occurred - println("API Error: ${apiResult.status}") - battleSystem.resetAttackState() - battleSystem.enableAttackButton() - } - } - } - } - }, - enabled = battleSystem.isAttackButtonEnabled, - modifier = Modifier - .fillMaxWidth() - .height(50.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.Red, - disabledContainerColor = Color.Gray - ), - shape = RoundedCornerShape(8.dp) - ) { - Text("Attack", color = Color.White, fontSize = 18.sp) - } - } } } From 1bbaf66c242f73f29347e1e082279bd545f154ae Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 8 Aug 2025 09:24:30 -0400 Subject: [PATCH 61/65] Removed exit button from view until battle is over. --- .../vbhelper/screens/BattlesScreen.kt | 61 +------------------ 1 file changed, 3 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 6591404..d2c2c1c 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -761,39 +761,12 @@ fun MiddleBattleView( modifier = Modifier.fillMaxSize() ) - // Top section: Exit button, HP bars, and HP numbers + // Top section: HP bars and HP numbers Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { - // Exit button at the top-right - Box( - modifier = Modifier.fillMaxWidth() - ) { - Button( - onClick = { /* TODO: Add exit functionality */ }, - modifier = Modifier.align(Alignment.TopEnd), - colors = ButtonDefaults.buttonColors(containerColor = Color.Red) - ) { - Text("Exit", color = Color.White, fontSize = 14.sp) - } - } - - Spacer(modifier = Modifier.height(16.dp)) - - // Debug display - /* - if (lastApiResult != null) { - Text( - text = "Debug: state=${lastApiResult!!.state}, playerAttackHit=${lastApiResult!!.playerAttackHit}, opponentDamage=${lastApiResult!!.opponentAttackDamage}", - color = Color.Red, - fontSize = 10.sp - ) - Spacer(modifier = Modifier.height(8.dp)) - } - */ - // Enemy HP bar and text with background box Box( modifier = getLandscapeBoxModifier(), @@ -1209,39 +1182,12 @@ fun PlayerBattleView( // Multi-layer animated battle background MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize()) - // Top section: Exit button, HP bar, and HP numbers + // Top section: HP bar and HP numbers Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { - // Exit button at the top-right - Box( - modifier = Modifier.fillMaxWidth() - ) { - Button( - onClick = { /* TODO: Add exit functionality */ }, - modifier = Modifier.align(Alignment.TopEnd), - colors = ButtonDefaults.buttonColors(containerColor = Color.Red) - ) { - Text("Exit", color = Color.White, fontSize = 12.sp) - } - } - - Spacer(modifier = Modifier.height(8.dp)) - - // Debug display - /* - if (lastApiResult != null) { - Text( - text = "Debug: state=${lastApiResult!!.state}, playerAttackHit=${lastApiResult!!.playerAttackHit}, opponentDamage=${lastApiResult!!.opponentAttackDamage}", - color = Color.Red, - fontSize = 10.sp - ) - Spacer(modifier = Modifier.height(8.dp)) - } - */ - // Health bar and text with background box Box( modifier = getLandscapeBoxModifier(), @@ -1995,6 +1941,7 @@ fun BattlesScreen() { ) { Text("Sprite Animation Tester") } + */ Button( onClick = { val spriteFileManager = SpriteFileManager(context) @@ -2004,8 +1951,6 @@ fun BattlesScreen() { ) { Text("Clear Sprite Files") } - - */ } } } From 6a6369ae9eccd93efe229dd11ee3d45bbb745a48 Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 8 Aug 2025 12:17:47 -0400 Subject: [PATCH 62/65] Adjusted attack hit sprite location in landscape view. --- .../vbhelper/battle/HitEffectComposables.kt | 19 +++++++++++++++++-- .../vbhelper/screens/BattlesScreen.kt | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt index 5110d1c..1f3210e 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/HitEffectComposables.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -25,6 +26,8 @@ fun HitEffectOverlay( if (!isVisible) return val context = LocalContext.current + val configuration = LocalConfiguration.current + val isLandscapeMode = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE val hitEffectManager = remember { HitEffectSpriteManager(context) } val coroutineScope = rememberCoroutineScope() @@ -92,10 +95,22 @@ fun HitEffectOverlay( .offset( x = if (isPlayerScreen) { // On player screen, position further to the left - (-sprite.width * scale / 2 - 100).dp + if (isLandscapeMode) { + // In landscape mode, move even further left for player screen + (-sprite.width * scale / 2 - 300).dp + } else { + // In portrait mode, use original positioning + (-sprite.width * scale / 2 - 100).dp + } } else { // On enemy screen, position further to the right - (-sprite.width * scale / 2 + 150).dp + if (isLandscapeMode) { + // In landscape mode, move even further right for enemy screen + (-sprite.width * scale / 2 + 350).dp + } else { + // In portrait mode, use original positioning + (-sprite.width * scale / 2 + 150).dp + } }, y = (-sprite.height * scale / 2 + 40).dp // Position lower on screen (was -60, now +40) ), diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index d2c2c1c..373b8eb 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -1941,7 +1941,6 @@ fun BattlesScreen() { ) { Text("Sprite Animation Tester") } - */ Button( onClick = { val spriteFileManager = SpriteFileManager(context) @@ -1951,6 +1950,7 @@ fun BattlesScreen() { ) { Text("Clear Sprite Files") } + */ } } } From 16cd7abce8cc306cd708d9814c5b8acd19a7a2fe Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 8 Aug 2025 12:37:32 -0400 Subject: [PATCH 63/65] Added multiple background sets for battle. --- .../vbhelper/screens/BattlesScreen.kt | 92 ++++++++++++++----- 1 file changed, 70 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 373b8eb..8812e0f 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -204,7 +204,8 @@ fun BattleScreen( activeCharacter: APIBattleCharacter?, opponentCharacter: APIBattleCharacter?, onAttackClick: () -> Unit, - context: android.content.Context? = null + context: android.content.Context? = null, + selectedBackgroundSet: Int = 0 ) { val battleSystem = remember { ArenaBattleSystem() } val coroutineScope = rememberCoroutineScope() @@ -617,7 +618,8 @@ fun BattleScreen( }, coroutineScope = coroutineScope, hidePlayerAttackSprite = hidePlayerAttackSprite, - hideEnemyAttackSprite = hideEnemyAttackSprite + hideEnemyAttackSprite = hideEnemyAttackSprite, + selectedBackgroundSet = selectedBackgroundSet ) } 1 -> { @@ -638,7 +640,8 @@ fun BattleScreen( pendingOpponentDamage = opponentDamage }, coroutineScope = coroutineScope, - hidePlayerAttackSprite = hidePlayerAttackSprite + hidePlayerAttackSprite = hidePlayerAttackSprite, + selectedBackgroundSet = selectedBackgroundSet ) } 2 -> { @@ -650,7 +653,8 @@ fun BattleScreen( attackAnimationProgress = battleSystem.attackProgress, activeCharacter = opponentCharacter, playerCharacter = activeCharacter, - hideEnemyAttackSprite = hideEnemyAttackSprite + hideEnemyAttackSprite = hideEnemyAttackSprite, + selectedBackgroundSet = selectedBackgroundSet ) } } @@ -745,7 +749,8 @@ fun MiddleBattleView( onSetPendingDamage: (Float, Float) -> Unit, coroutineScope: kotlinx.coroutines.CoroutineScope, hidePlayerAttackSprite: Boolean, - hideEnemyAttackSprite: Boolean + hideEnemyAttackSprite: Boolean, + selectedBackgroundSet: Int = 0 ) { // Track previous character ID to detect transitions var previousCharacterId by remember { mutableStateOf(null) } @@ -758,7 +763,8 @@ fun MiddleBattleView( ) { // Animated background - positioned underneath all other sprites MultiLayerAnimatedBattleBackground( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), + backgroundSetIndex = selectedBackgroundSet ) // Top section: HP bars and HP numbers @@ -1168,7 +1174,8 @@ fun PlayerBattleView( opponent: APIBattleCharacter?, onSetPendingDamage: (Float, Float) -> Unit, coroutineScope: kotlinx.coroutines.CoroutineScope, - hidePlayerAttackSprite: Boolean + hidePlayerAttackSprite: Boolean, + selectedBackgroundSet: Int = 0 ) { // Track previous character ID to detect transitions var previousCharacterId by remember { mutableStateOf(null) } @@ -1180,7 +1187,7 @@ fun PlayerBattleView( modifier = Modifier.fillMaxSize() ) { // Multi-layer animated battle background - MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize()) + MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize(), backgroundSetIndex = selectedBackgroundSet) // Top section: HP bar and HP numbers Column( @@ -1356,7 +1363,8 @@ fun EnemyBattleView( attackAnimationProgress: Float, activeCharacter: APIBattleCharacter? = null, playerCharacter: APIBattleCharacter? = null, - hideEnemyAttackSprite: Boolean + hideEnemyAttackSprite: Boolean, + selectedBackgroundSet: Int = 0 ) { // Track previous character ID to detect transitions var previousCharacterId by remember { mutableStateOf(null) } @@ -1367,7 +1375,7 @@ fun EnemyBattleView( modifier = Modifier.fillMaxSize() ) { // Multi-layer animated battle background - MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize()) + MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize(), backgroundSetIndex = selectedBackgroundSet) // Top section: Enemy HP bar and HP numbers Column( @@ -1537,6 +1545,9 @@ fun BattlesScreen() { var selectedStage by remember { mutableStateOf("") } var currentStage by remember { mutableStateOf("rookie") } + // Random background set selection + var selectedBackgroundSet by remember { mutableStateOf(0) } + // Sprite animation tester state var showSpriteTester by remember { mutableStateOf(false) } var spriteTesterView by remember { mutableStateOf("entry") } // "entry" or "testing" @@ -1970,6 +1981,8 @@ fun BattlesScreen() { onClick = { activeCharacter?.let { selectedOpponent = opponent + // Randomly select background set (0, 1, or 2) + selectedBackgroundSet = kotlin.random.Random.nextInt(3) RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 0, 0, opponent.name, 0) { apiResult -> currentView = "battle-main" } @@ -2008,6 +2021,8 @@ fun BattlesScreen() { onClick = { activeCharacter?.let { selectedOpponent = opponent + // Randomly select background set (0, 1, or 2) + selectedBackgroundSet = kotlin.random.Random.nextInt(3) RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 1, 0, opponent.name, 1) { apiResult -> currentView = "battle-main" } @@ -2046,6 +2061,8 @@ fun BattlesScreen() { onClick = { activeCharacter?.let { selectedOpponent = opponent + // Randomly select background set (0, 1, or 2) + selectedBackgroundSet = kotlin.random.Random.nextInt(3) RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 2, 0, opponent.name, 2) { apiResult -> currentView = "battle-main" } @@ -2084,6 +2101,8 @@ fun BattlesScreen() { onClick = { activeCharacter?.let { selectedOpponent = opponent + // Randomly select background set (0, 1, or 2) + selectedBackgroundSet = kotlin.random.Random.nextInt(3) RetrofitHelper().getPVPWinner(context, 0, 2, it.name, 3, 0, opponent.name, 3) { apiResult -> currentView = "battle-main" } @@ -2118,7 +2137,8 @@ fun BattlesScreen() { // This will be called when the battle is over currentView = "battle-results" }, - context = context + context = context, + selectedBackgroundSet = selectedBackgroundSet ) } @@ -2298,9 +2318,36 @@ fun AnimatedBattleBackground( } } +// Data class to define background sets +data class BackgroundSet( + val backLayer: String, + val middleLayer: String, + val frontLayer: String +) + +// Define the three background sets +val backgroundSets = listOf( + BackgroundSet( + backLayer = "BattleBg_0018_BattleBg_0013.png", + middleLayer = "BattleBg_0015_BattleBg_0012.png", + frontLayer = "BattleBg_0005_BattleBg_0011.png" + ), + BackgroundSet( + backLayer = "BattleBg_0014_BattleBg_0013.png", + middleLayer = "BattleBg_0010_BattleBg_0012.png", + frontLayer = "BattleBg_0011_BattleBg_0011.png" + ), + BackgroundSet( + backLayer = "BattleBg_0019_BattleBg_0013.png", + middleLayer = "BattleBg_0004_BattleBg_0012.png", + frontLayer = "BattleBg_0009_BattleBg_0011.png" + ) +) + @Composable fun MultiLayerAnimatedBattleBackground( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + backgroundSetIndex: Int = 0 ) { val context = LocalContext.current var backLayerBitmap by remember { mutableStateOf(null) } @@ -2324,33 +2371,34 @@ fun MultiLayerAnimatedBattleBackground( } // Load all three background layers from external storage - LaunchedEffect(Unit) { + LaunchedEffect(backgroundSetIndex) { try { val externalDir = Environment.getExternalStorageDirectory() + val selectedSet = backgroundSets[backgroundSetIndex] - // Back layer (BattleBg_0018_BattleBg_0013.png) - val backLayerFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/BattleBg_0018_BattleBg_0013.png") + // Back layer + val backLayerFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/${selectedSet.backLayer}") if (backLayerFile.exists()) { backLayerBitmap = BitmapFactory.decodeFile(backLayerFile.absolutePath) - println("Successfully loaded back layer background: ${backLayerFile.absolutePath}") + println("Successfully loaded back layer background (Set ${backgroundSetIndex + 1}): ${backLayerFile.absolutePath}") } else { println("Back layer background file not found: ${backLayerFile.absolutePath}") } - // Middle layer (BattleBg_0015_BattleBg_0012.png) - val middleLayerFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/BattleBg_0015_BattleBg_0012.png") + // Middle layer + val middleLayerFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/${selectedSet.middleLayer}") if (middleLayerFile.exists()) { middleLayerBitmap = BitmapFactory.decodeFile(middleLayerFile.absolutePath) - println("Successfully loaded middle layer background: ${middleLayerFile.absolutePath}") + println("Successfully loaded middle layer background (Set ${backgroundSetIndex + 1}): ${middleLayerFile.absolutePath}") } else { println("Middle layer background file not found: ${middleLayerFile.absolutePath}") } - // Front layer (BattleBg_0005_BattleBg_0011.png) - val frontLayerFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/BattleBg_0005_BattleBg_0011.png") + // Front layer + val frontLayerFile = File(externalDir, "VBHelper/battle_sprites/extracted_battlebgs/${selectedSet.frontLayer}") if (frontLayerFile.exists()) { frontLayerBitmap = BitmapFactory.decodeFile(frontLayerFile.absolutePath) - println("Successfully loaded front layer background: ${frontLayerFile.absolutePath}") + println("Successfully loaded front layer background (Set ${backgroundSetIndex + 1}): ${frontLayerFile.absolutePath}") } else { println("Front layer background file not found: ${frontLayerFile.absolutePath}") } From 5ed7d117f557b8472340e98b845f4f71e92890a2 Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 8 Aug 2025 13:20:40 -0400 Subject: [PATCH 64/65] Fixed background scaling issues in landscape view. --- .../vbhelper/screens/BattlesScreen.kt | 148 +++++++++--------- 1 file changed, 71 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 8812e0f..6c0407f 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -903,13 +903,18 @@ fun MiddleBattleView( } } - // Horizontal line separator - Box( - modifier = Modifier - .fillMaxWidth() - .height(2.dp) - .background(Color.Black) - ) + // Horizontal line separator (hidden in landscape mode) + val lineConfiguration = LocalConfiguration.current + val isLandscapeMode = lineConfiguration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE + + if (!isLandscapeMode) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .background(Color.Black) + ) + } // Player Digimon (bottom half) Box( @@ -2295,25 +2300,22 @@ fun AnimatedBattleBackground( backgroundBitmap?.let { bitmap -> Box(modifier = modifier.fillMaxSize()) { - // First image (main) - Image( - bitmap = bitmap.asImageBitmap(), - contentDescription = "Animated Battle Background 1", - modifier = Modifier - .fillMaxSize() - .offset(x = xOffset.dp), - contentScale = ContentScale.FillBounds - ) + // Calculate how many times to repeat the image to fill the screen width + val configuration = LocalConfiguration.current + val isLandscapeMode = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE + val imageWidth = if (isLandscapeMode) screenWidth.value * 1.5f else screenWidth.value + val repeatCount = (imageWidth / screenWidth.value).toInt() + 2 // Add 2 for seamless looping - // Second image (for seamless loop) - Image( - bitmap = bitmap.asImageBitmap(), - contentDescription = "Animated Battle Background 2", - modifier = Modifier - .fillMaxSize() - .offset(x = (xOffset + screenWidth.value).dp), - contentScale = ContentScale.FillBounds - ) + repeat(repeatCount) { index -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Animated Battle Background ${index + 1}", + modifier = Modifier + .fillMaxSize() + .offset(x = (xOffset + (index * screenWidth.value)).dp), + contentScale = ContentScale.FillBounds + ) + } } } } @@ -2407,26 +2409,32 @@ fun MultiLayerAnimatedBattleBackground( } } - // Animate all three layers with different speeds + // Animate all three layers with different speeds (slower in landscape mode) + val bgConfiguration = LocalConfiguration.current + val isLandscapeMode = bgConfiguration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE + LaunchedEffect(screenWidth) { if (screenWidth > 0.dp) { while (true) { delay(50) // Update every 50ms for smooth animation + // Adjust speed based on orientation + val speedMultiplier = if (isLandscapeMode) 0.5f else 1f + // Back layer moves slowest (parallax effect) - backLayerXOffset -= 0.5f + backLayerXOffset -= 0.5f * speedMultiplier if (backLayerXOffset <= -screenWidth.value) { backLayerXOffset = 0f } // Middle layer moves at medium speed - middleLayerXOffset -= 1f + middleLayerXOffset -= 1f * speedMultiplier if (middleLayerXOffset <= -screenWidth.value) { middleLayerXOffset = 0f } // Front layer moves fastest - frontLayerXOffset -= 1.5f + frontLayerXOffset -= 1.5f * speedMultiplier if (frontLayerXOffset <= -screenWidth.value) { frontLayerXOffset = 0f } @@ -2435,64 +2443,50 @@ fun MultiLayerAnimatedBattleBackground( } Box(modifier = modifier.fillMaxSize()) { + // Calculate how many times to repeat the image to fill the screen width + val imageWidth = if (isLandscapeMode) screenWidth.value * 1.5f else screenWidth.value + val repeatCount = (imageWidth / screenWidth.value).toInt() + 2 // Add 2 for seamless looping + // Back layer (underneath everything) backLayerBitmap?.let { bitmap -> - Image( - bitmap = bitmap.asImageBitmap(), - contentDescription = "Back Layer Battle Background 1", - modifier = Modifier - .fillMaxSize() - .offset(x = backLayerXOffset.dp), - contentScale = ContentScale.FillBounds - ) - Image( - bitmap = bitmap.asImageBitmap(), - contentDescription = "Back Layer Battle Background 2", - modifier = Modifier - .fillMaxSize() - .offset(x = (backLayerXOffset + screenWidth.value).dp), - contentScale = ContentScale.FillBounds - ) + repeat(repeatCount) { index -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Back Layer Battle Background ${index + 1}", + modifier = Modifier + .fillMaxSize() + .offset(x = (backLayerXOffset + (index * screenWidth.value)).dp), + contentScale = ContentScale.FillBounds + ) + } } // Middle layer middleLayerBitmap?.let { bitmap -> - Image( - bitmap = bitmap.asImageBitmap(), - contentDescription = "Middle Layer Battle Background 1", - modifier = Modifier - .fillMaxSize() - .offset(x = middleLayerXOffset.dp), - contentScale = ContentScale.FillBounds - ) - Image( - bitmap = bitmap.asImageBitmap(), - contentDescription = "Middle Layer Battle Background 2", - modifier = Modifier - .fillMaxSize() - .offset(x = (middleLayerXOffset + screenWidth.value).dp), - contentScale = ContentScale.FillBounds - ) + repeat(repeatCount) { index -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Middle Layer Battle Background ${index + 1}", + modifier = Modifier + .fillMaxSize() + .offset(x = (middleLayerXOffset + (index * screenWidth.value)).dp), + contentScale = ContentScale.FillBounds + ) + } } // Front layer (on top of other backgrounds) frontLayerBitmap?.let { bitmap -> - Image( - bitmap = bitmap.asImageBitmap(), - contentDescription = "Front Layer Battle Background 1", - modifier = Modifier - .fillMaxSize() - .offset(x = frontLayerXOffset.dp), - contentScale = ContentScale.FillBounds - ) - Image( - bitmap = bitmap.asImageBitmap(), - contentDescription = "Front Layer Battle Background 2", - modifier = Modifier - .fillMaxSize() - .offset(x = (frontLayerXOffset + screenWidth.value).dp), - contentScale = ContentScale.FillBounds - ) + repeat(repeatCount) { index -> + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Front Layer Battle Background ${index + 1}", + modifier = Modifier + .fillMaxSize() + .offset(x = (frontLayerXOffset + (index * screenWidth.value)).dp), + contentScale = ContentScale.FillBounds + ) + } } } } \ No newline at end of file From c122b71b4612d923bcff7ba7cfe1208a619c4410 Mon Sep 17 00:00:00 2001 From: lightheel Date: Fri, 15 Aug 2025 11:55:45 -0400 Subject: [PATCH 65/65] Changed references to be correct. --- .../java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 6c0407f..11c39a6 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -1576,7 +1576,7 @@ fun BattlesScreen() { val ultimateCharacters = listOf( APIBattleCharacter("METALGREYMON (VIRUS)","degimon_name_Dim014_005","dim014_mon05",2,2,2640, 2640, 2450.0f,800.0f), - APIBattleCharacter("MAMEMON", "degimon_name_Dim000_005", "dim000_mon05", 2, 1, 3000, 3000, 4000.0f, 1000.0f), + APIBattleCharacter("MAMEMON", "degimon_name_Dim012_011", "dim012_mon11", 2, 1, 3000, 3000, 4000.0f, 1000.0f), APIBattleCharacter("DORUGREYMON","degimon_name_dim137_mon09","dim137_mon09",2,3,5000, 5000, 6400.0f,1400.0f) )