Moved sprites to load from external Android storage.

Setup landscape view for battle.
This commit is contained in:
lightheel 2025-08-08 07:44:36 -04:00
parent f0760f9ed0
commit bb1c29bbb4
7 changed files with 280 additions and 226 deletions

View File

@ -5,6 +5,8 @@
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" /> <uses-feature android:name="android.hardware.nfc" android:required="true" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application <application
android:name=".di.VBHelper" android:name=".di.VBHelper"

View File

@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.battle
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Environment
import com.google.gson.Gson import com.google.gson.Gson
import java.io.File import java.io.File
@ -31,8 +32,10 @@ class AttackSpriteManager(private val context: Context) {
private val gson = Gson() private val gson = Gson()
private val characterDataCache = mutableMapOf<String, CharacterData>() private val characterDataCache = mutableMapOf<String, CharacterData>()
// Base path for attack textures (updated for new folder structure) // Get the external storage directory for attack sprites
private val attackTexturesPath = "battle_sprites/extracted_atksprites" private fun getAttackTexturesPath(): String {
return "VBHelper/battle_sprites/extracted_atksprites"
}
fun getAttackSprite(characterId: String, isLarge: Boolean = false): Bitmap? { fun getAttackSprite(characterId: String, isLarge: Boolean = false): Bitmap? {
println("AttackSpriteManager: Getting attack sprite for characterId=$characterId, isLarge=$isLarge") println("AttackSpriteManager: Getting attack sprite for characterId=$characterId, isLarge=$isLarge")
@ -55,9 +58,10 @@ class AttackSpriteManager(private val context: Context) {
return null return null
} }
// Load the attack sprite // Load the attack sprite from external storage
val attackFilePath = "$attackTexturesPath/$attackFileName.png" val externalDir = Environment.getExternalStorageDirectory()
val attackFile = File(context.filesDir, attackFilePath) val attackFilePath = "${getAttackTexturesPath()}/$attackFileName.png"
val attackFile = File(externalDir, attackFilePath)
println("AttackSpriteManager: Attack file path = ${attackFile.absolutePath}") println("AttackSpriteManager: Attack file path = ${attackFile.absolutePath}")
println("AttackSpriteManager: Attack file exists = ${attackFile.exists()}") println("AttackSpriteManager: Attack file exists = ${attackFile.exists()}")
@ -85,8 +89,9 @@ class AttackSpriteManager(private val context: Context) {
} }
try { try {
// Load character data from JSON file // Load character data from JSON file in external storage
val characterDataFile = File(context.filesDir, "battle_sprites/extracted_digimon_stats/character_data/CharacterData.json") 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 path = ${characterDataFile.absolutePath}")
println("AttackSpriteManager: Character data file exists = ${characterDataFile.exists()}") println("AttackSpriteManager: Character data file exists = ${characterDataFile.exists()}")

View File

@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.battle
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Environment
import com.google.gson.Gson import com.google.gson.Gson
import java.io.File import java.io.File
@ -37,8 +38,11 @@ class BattleSpriteManager(private val context: Context) {
private val gson = Gson() private val gson = Gson()
private val spriteCache = mutableMapOf<String, Bitmap>() private val spriteCache = mutableMapOf<String, Bitmap>()
// Base directory where your sprites are stored // Get the external storage directory for sprite files
private val spriteBaseDir = File(context.filesDir, "battle_sprites/extracted_assets") private fun getSpriteBaseDir(): File {
val externalDir = Environment.getExternalStorageDirectory()
return File(externalDir, "VBHelper/battle_sprites/extracted_assets")
}
fun loadSprite(spriteName: String, atlasName: String): Bitmap? { fun loadSprite(spriteName: String, atlasName: String): Bitmap? {
val cacheKey = "${spriteName}_${atlasName}" val cacheKey = "${spriteName}_${atlasName}"
@ -49,6 +53,7 @@ class BattleSpriteManager(private val context: Context) {
} }
// Debug: Check if base directory exists // Debug: Check if base directory exists
val spriteBaseDir = getSpriteBaseDir()
if (!spriteBaseDir.exists()) { if (!spriteBaseDir.exists()) {
println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}") println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}")
return null return null
@ -130,7 +135,7 @@ class BattleSpriteManager(private val context: Context) {
// Helper method to get available sprites for an atlas // Helper method to get available sprites for an atlas
fun getAvailableSprites(atlasName: String): List<String> { fun getAvailableSprites(atlasName: String): List<String> {
try { try {
val spritesDir = File(spriteBaseDir, "sprites") val spritesDir = File(getSpriteBaseDir(), "sprites")
if (!spritesDir.exists()) { if (!spritesDir.exists()) {
return emptyList() return emptyList()
} }
@ -154,7 +159,7 @@ class BattleSpriteManager(private val context: Context) {
// Helper method to get available atlases // Helper method to get available atlases
fun getAvailableAtlases(): List<String> { fun getAvailableAtlases(): List<String> {
try { try {
val texturesDir = File(spriteBaseDir, "extracted_textures") val texturesDir = File(getSpriteBaseDir(), "extracted_textures")
if (!texturesDir.exists()) { if (!texturesDir.exists()) {
return emptyList() return emptyList()
} }

View File

@ -4,13 +4,17 @@ import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Rect import android.graphics.Rect
import android.os.Environment
import java.io.File import java.io.File
class HitEffectSpriteManager(private val context: Context) { class HitEffectSpriteManager(private val context: Context) {
private val spriteCache = mutableMapOf<String, Bitmap>() private val spriteCache = mutableMapOf<String, Bitmap>()
// Base directory where hit effect sprites are stored // Get the external storage directory for hit effect sprites
private val hitSpritesDir = File(context.filesDir, "battle_sprites/extracted_hit_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) * 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 { try {
val hitSpritesDir = getHitSpritesDir()
val spriteFile = File(hitSpritesDir, "$spriteName.png") val spriteFile = File(hitSpritesDir, "$spriteName.png")
if (!spriteFile.exists()) { if (!spriteFile.exists()) {
@ -68,7 +73,7 @@ class HitEffectSpriteManager(private val context: Context) {
} }
try { try {
val spritesheetFile = File(hitSpritesDir, "$spritesheetName.png") val spritesheetFile = File(getHitSpritesDir(), "$spritesheetName.png")
if (!spritesheetFile.exists()) { if (!spritesheetFile.exists()) {
println("Damage effect spritesheet not found: ${spritesheetFile.absolutePath}") println("Damage effect spritesheet not found: ${spritesheetFile.absolutePath}")
@ -118,28 +123,21 @@ class HitEffectSpriteManager(private val context: Context) {
} }
/** /**
* Get available hit sprite names * Get all available hit sprites
* @return List of available hit sprite names * @return List of hit sprite names (without .png extension)
*/ */
fun getAvailableHitSprites(): List<String> { fun getAvailableHitSprites(): List<String> {
try { val hitSpritesDir = getHitSpritesDir()
if (!hitSpritesDir.exists()) {
return emptyList() if (!hitSpritesDir.exists()) {
}
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() 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<String> { fun getAvailableDamageEffectSpritesheets(): List<String> {
try { try {
if (!hitSpritesDir.exists()) { if (!getHitSpritesDir().exists()) {
return emptyList() return emptyList()
} }
val dmgFiles = hitSpritesDir.listFiles { file -> val dmgFiles = getHitSpritesDir().listFiles { file ->
file.name.startsWith("dmg_ef") && file.name.endsWith(".png") file.name.startsWith("dmg_ef") && file.name.endsWith(".png")
} ?: emptyArray() } ?: emptyArray()

View File

@ -3,13 +3,17 @@ package com.github.nacabaro.vbhelper.battle
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Environment
import java.io.File import java.io.File
class IndividualSpriteManager(private val context: Context) { class IndividualSpriteManager(private val context: Context) {
private val spriteCache = mutableMapOf<String, Bitmap>() private val spriteCache = mutableMapOf<String, Bitmap>()
// Base directory where individual sprite PNGs are stored // Get the external storage directory for sprite files
private val spriteBaseDir = File(context.filesDir, "battle_sprites/extracted_assets/sprites") 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 * Load a specific sprite frame for a character
@ -26,6 +30,7 @@ class IndividualSpriteManager(private val context: Context) {
} }
// Debug: Check if base directory exists // Debug: Check if base directory exists
val spriteBaseDir = getSpriteBaseDir()
if (!spriteBaseDir.exists()) { if (!spriteBaseDir.exists()) {
println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}") println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}")
return null return null
@ -68,51 +73,38 @@ class IndividualSpriteManager(private val context: Context) {
* @return List of frame numbers (1-12) that exist for this character * @return List of frame numbers (1-12) that exist for this character
*/ */
fun getAvailableFrames(characterId: String): List<Int> { fun getAvailableFrames(characterId: String): List<Int> {
try { val spriteBaseDir = getSpriteBaseDir()
val characterDir = File(spriteBaseDir, characterId) val characterDir = File(spriteBaseDir, characterId)
if (!characterDir.exists()) {
println("Character directory not found: ${characterDir.absolutePath}") if (!characterDir.exists()) {
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() 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 * @return List of character IDs that have sprite directories
*/ */
fun getAvailableCharacters(): List<String> { fun getAvailableCharacters(): List<String> {
try { val spriteBaseDir = getSpriteBaseDir()
if (!spriteBaseDir.exists()) {
return emptyList() if (!spriteBaseDir.exists()) {
}
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() 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 * @return true if the character has sprite files, false otherwise
*/ */
fun hasCharacterSprites(characterId: String): Boolean { fun hasCharacterSprites(characterId: String): Boolean {
val characterDir = File(spriteBaseDir, characterId) val characterDir = File(getSpriteBaseDir(), characterId)
if (!characterDir.exists()) { if (!characterDir.exists()) {
return false return false
} }

View File

@ -1,15 +1,22 @@
package com.github.nacabaro.vbhelper.battle package com.github.nacabaro.vbhelper.battle
import android.content.Context import android.content.Context
import android.os.Environment
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
class SpriteFileManager(private val context: Context) { 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 { try {
println("Starting sprite file copy process...") println("Starting sprite file copy process to external storage...")
// Debug: List what's in the assets directory // Debug: List what's in the assets directory
val assetManager = context.assets val assetManager = context.assets
@ -47,17 +54,17 @@ class SpriteFileManager(private val context: Context) {
} }
} }
// Create the base directory for battle_sprites // Create the base directory for battle_sprites in external storage
val battleSpritesDir = File(context.filesDir, "battle_sprites") val battleSpritesDir = getSpriteBaseDir()
if (!battleSpritesDir.exists()) { if (!battleSpritesDir.exists()) {
battleSpritesDir.mkdirs() battleSpritesDir.mkdirs()
println("Created battle_sprites directory: ${battleSpritesDir.absolutePath}") println("Created battle_sprites directory in external storage: ${battleSpritesDir.absolutePath}")
} else { } 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 // Copy all subdirectories from battle_sprites assets to external storage
println("Copying all battle_sprites subdirectories...") println("Copying all battle_sprites subdirectories to external storage...")
battleSpritesFiles?.forEach { subdir -> battleSpritesFiles?.forEach { subdir ->
val sourcePath = "battle_sprites/$subdir" val sourcePath = "battle_sprites/$subdir"
val targetDir = File(battleSpritesDir, subdir) val targetDir = File(battleSpritesDir, subdir)
@ -65,7 +72,7 @@ class SpriteFileManager(private val context: Context) {
copyAssetDirectory(sourcePath, targetDir) 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 // Verify that attack sprites were copied
val atkspritesDir = File(battleSpritesDir, "extracted_atksprites") val atkspritesDir = File(battleSpritesDir, "extracted_atksprites")
@ -95,7 +102,7 @@ class SpriteFileManager(private val context: Context) {
} }
} catch (e: Exception) { } catch (e: Exception) {
println("Error copying sprite files: ${e.message}") println("Error copying sprite files to external storage: ${e.message}")
e.printStackTrace() e.printStackTrace()
} }
} }
@ -180,7 +187,7 @@ class SpriteFileManager(private val context: Context) {
} }
fun checkSpriteFilesExist(): Boolean { fun checkSpriteFilesExist(): Boolean {
val battleSpritesDir = File(context.filesDir, "battle_sprites") val battleSpritesDir = getSpriteBaseDir()
val extractedAssetsDir = File(battleSpritesDir, "extracted_assets") val extractedAssetsDir = File(battleSpritesDir, "extracted_assets")
val extractedStatsDir = File(battleSpritesDir, "extracted_digimon_stats") val extractedStatsDir = File(battleSpritesDir, "extracted_digimon_stats")
val atkspritesDir = File(battleSpritesDir, "extracted_atksprites") val atkspritesDir = File(battleSpritesDir, "extracted_atksprites")
@ -204,7 +211,7 @@ class SpriteFileManager(private val context: Context) {
fun clearSpriteFiles() { fun clearSpriteFiles() {
try { try {
val battleSpritesDir = File(context.filesDir, "battle_sprites") val battleSpritesDir = getSpriteBaseDir()
if (battleSpritesDir.exists()) { if (battleSpritesDir.exists()) {
deleteDirectory(battleSpritesDir) deleteDirectory(battleSpritesDir)

View File

@ -66,9 +66,72 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Environment
import java.io.File import java.io.File
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalConfiguration 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 @Composable
fun AnimatedDamageNumber( fun AnimatedDamageNumber(
@ -266,13 +329,13 @@ fun BattleScreen(
delay(16) // 60 FPS delay(16) // 60 FPS
} }
println("Phase 2 completed, applying damage and starting Phase 3") 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 // Hide damage number and reset pending damage after animation
if (showOpponentDamageNumber) { if (showOpponentDamageNumber) {
delay(800) // Wait for damage number animation (scale up + hold + fade out) delay(800) // Wait for damage number animation (scale up + hold + fade out)
showOpponentDamageNumber = false showOpponentDamageNumber = false
pendingOpponentDamage = 0f pendingOpponentDamage = 0f
println("DEBUG: Hiding opponent damage number and resetting pending damage") 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("Phase 3 completed, applying damage and resetting")
println("DEBUG: pendingPlayerDamage = $pendingPlayerDamage") println("DEBUG: pendingPlayerDamage = $pendingPlayerDamage")
battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage) battleSystem.completeAttackAnimation(playerDamage = pendingPlayerDamage)
// Hide damage number and reset pending damage after animation // Hide damage number and reset pending damage after animation
if (showPlayerDamageNumber) { if (showPlayerDamageNumber) {
delay(800) // Wait for damage number animation (scale up + hold + fade out) delay(800) // Wait for damage number animation (scale up + hold + fade out)
showPlayerDamageNumber = false showPlayerDamageNumber = false
pendingPlayerDamage = 0f pendingPlayerDamage = 0f
println("DEBUG: Hiding player damage number and resetting pending damage") println("DEBUG: Hiding player damage number and resetting pending damage")
} }
battleSystem.resetAttackState() battleSystem.resetAttackState()
battleSystem.enableAttackButton() battleSystem.enableAttackButton()
// Check if battle is over // Check if battle is over
if (battleSystem.checkBattleOver()) { if (battleSystem.checkBattleOver()) {
@ -733,21 +796,16 @@ fun MiddleBattleView(
// Enemy HP bar and text with background box // Enemy HP bar and text with background box
Box( Box(
modifier = Modifier modifier = getLandscapeBoxModifier(),
.fillMaxWidth() contentAlignment = getLandscapeAlignment()
.background(
color = Color.Gray.copy(alpha = 0.6f),
shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp)
)
.padding(8.dp)
) { ) {
Column { Column(
horizontalAlignment = getLandscapeHorizontalAlignment()
) {
// Enemy HP bar (top) // Enemy HP bar (top)
LinearProgressIndicator( LinearProgressIndicator(
progress = battleSystem.opponentHP / (opponentCharacter?.baseHp?.toFloat() ?: 100f), progress = battleSystem.opponentHP / (opponentCharacter?.baseHp?.toFloat() ?: 100f),
modifier = Modifier modifier = getLandscapeModifier(),
.fillMaxWidth()
.height(10.dp),
color = Color.Red, color = Color.Red,
trackColor = Color.Gray trackColor = Color.Gray
) )
@ -757,7 +815,7 @@ fun MiddleBattleView(
// Enemy HP display numbers // Enemy HP display numbers
Text( Text(
text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponentCharacter?.baseHp ?: 100}", text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${opponentCharacter?.baseHp ?: 100}",
fontSize = 16.sp, fontSize = getLandscapeFontSize(),
color = Color.White, color = Color.White,
style = TextStyle( style = TextStyle(
shadow = Shadow( shadow = Shadow(
@ -962,9 +1020,7 @@ fun MiddleBattleView(
// Critical bar // Critical bar
LinearProgressIndicator( LinearProgressIndicator(
progress = battleSystem.critBarProgress / 100f, progress = battleSystem.critBarProgress / 100f,
modifier = Modifier modifier = getLandscapeModifier(),
.fillMaxWidth()
.height(10.dp),
color = Color.Yellow, color = Color.Yellow,
trackColor = Color.Gray trackColor = Color.Gray
) )
@ -973,21 +1029,16 @@ fun MiddleBattleView(
// Player HP bar and text with background box // Player HP bar and text with background box
Box( Box(
modifier = Modifier modifier = getLandscapeBoxModifier(),
.fillMaxWidth() contentAlignment = getLandscapeAlignment()
.background(
color = Color.Gray.copy(alpha = 0.6f),
shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp)
)
.padding(8.dp)
) { ) {
Column { Column(
horizontalAlignment = getLandscapeHorizontalAlignment()
) {
// Player HP bar // Player HP bar
LinearProgressIndicator( LinearProgressIndicator(
progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f),
modifier = Modifier modifier = getLandscapeModifier(),
.fillMaxWidth()
.height(10.dp),
color = Color.Green, color = Color.Green,
trackColor = Color.Gray trackColor = Color.Gray
) )
@ -997,7 +1048,7 @@ fun MiddleBattleView(
// Player HP display numbers // Player HP display numbers
Text( Text(
text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}",
fontSize = 16.sp, fontSize = getLandscapeFontSize(),
color = Color.White, color = Color.White,
style = TextStyle( style = TextStyle(
shadow = Shadow( shadow = Shadow(
@ -1193,31 +1244,26 @@ fun PlayerBattleView(
// Health bar and text with background box // Health bar and text with background box
Box( Box(
modifier = Modifier modifier = getLandscapeBoxModifier(),
.fillMaxWidth() contentAlignment = getLandscapeAlignment()
.background(
color = Color.Gray.copy(alpha = 0.6f),
shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp)
)
.padding(8.dp)
) { ) {
Column { Column(
// Health bar horizontalAlignment = getLandscapeHorizontalAlignment()
LinearProgressIndicator( ) {
progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), // Health bar
modifier = Modifier LinearProgressIndicator(
.fillMaxWidth() progress = battleSystem.playerHP / (activeCharacter?.baseHp?.toFloat() ?: 100f),
.height(10.dp), modifier = getLandscapeModifier(),
color = Color.Green, color = Color.Green,
trackColor = Color.Gray trackColor = Color.Gray
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
// Health display numbers // Health display numbers
Text( Text(
text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}", text = "HP: ${battleSystem.playerHP.toInt()}/${activeCharacter?.baseHp ?: 100}",
fontSize = 16.sp, fontSize = getLandscapeFontSize(),
color = Color.White, color = Color.White,
style = TextStyle( style = TextStyle(
shadow = Shadow( shadow = Shadow(
@ -1363,9 +1409,7 @@ fun PlayerBattleView(
// Critical bar // Critical bar
LinearProgressIndicator( LinearProgressIndicator(
progress = battleSystem.critBarProgress / 100f, progress = battleSystem.critBarProgress / 100f,
modifier = Modifier modifier = getLandscapeModifier(),
.fillMaxWidth()
.height(10.dp),
color = Color.Yellow, color = Color.Yellow,
trackColor = Color.Gray trackColor = Color.Gray
) )
@ -1422,22 +1466,22 @@ fun PlayerBattleView(
// Match is still ongoing - update HP and continue // Match is still ongoing - update HP and continue
println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}") println("Round ${apiResult.currentRound}: Player HP=${apiResult.playerHP}, Opponent HP=${apiResult.opponentHP}")
// Set pending damage based on API result // Set pending damage based on API result
if (apiResult.playerAttackDamage > 0) { if (apiResult.playerAttackDamage > 0) {
// Player attack hit - enemy takes damage at end of player animation // Player attack hit - enemy takes damage at end of player animation
println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage") println("Player attack hit! Enemy will take ${apiResult.playerAttackDamage} damage")
onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage onSetPendingDamage(0f, apiResult.playerAttackDamage.toFloat()) // Opponent takes damage
battleSystem.setAttackHitState(true) battleSystem.setAttackHitState(true)
// Also check if enemy counter-attacks and hits // Also check if enemy counter-attacks and hits
if (apiResult.opponentAttackDamage > 0) { if (apiResult.opponentAttackDamage > 0) {
println("Enemy counter-attack hits! Player takes ${apiResult.opponentAttackDamage} damage") println("Enemy counter-attack hits! Player takes ${apiResult.opponentAttackDamage} damage")
onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), apiResult.playerAttackDamage.toFloat()) // Both take damage onSetPendingDamage(apiResult.opponentAttackDamage.toFloat(), apiResult.playerAttackDamage.toFloat()) // Both take damage
} }
} else { } else {
// Player attack missed - enemy counter-attacks // Player attack missed - enemy counter-attacks
println("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 // Set up counter-attack - determine if it hits based on API result
val counterAttackHits = apiResult.opponentAttackDamage > 0 val counterAttackHits = apiResult.opponentAttackDamage > 0
println("Setting up counter-attack: counterAttackHits=$counterAttackHits, opponentAttackDamage=${apiResult.opponentAttackDamage}") println("Setting up counter-attack: counterAttackHits=$counterAttackHits, opponentAttackDamage=${apiResult.opponentAttackDamage}")
@ -1458,7 +1502,7 @@ fun PlayerBattleView(
battleSystem.setupCounterAttack(finalCounterAttackHits) battleSystem.setupCounterAttack(finalCounterAttackHits)
// Set the opponent attack hit state for Phase 3 // Set the opponent attack hit state for Phase 3
battleSystem.handleOpponentAttackResult(finalCounterAttackHits) battleSystem.handleOpponentAttackResult(finalCounterAttackHits)
} }
} }
2 -> { 2 -> {
// Match is over - transition to results screen // Match is over - transition to results screen
@ -1514,28 +1558,23 @@ fun EnemyBattleView(
MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize()) MultiLayerAnimatedBattleBackground(modifier = Modifier.fillMaxSize())
// Top section: Enemy HP bar and HP numbers // Top section: Enemy HP bar and HP numbers
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp) .padding(16.dp)
) { ) {
// Enemy HP bar and text with background box // Enemy HP bar and text with background box
Box( Box(
modifier = Modifier modifier = getLandscapeBoxModifier(),
.fillMaxWidth() contentAlignment = getLandscapeAlignment()
.background(
color = Color.Gray.copy(alpha = 0.6f),
shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp)
)
.padding(8.dp)
) { ) {
Column { Column(
horizontalAlignment = getLandscapeHorizontalAlignment()
) {
// Enemy HP bar // Enemy HP bar
LinearProgressIndicator( LinearProgressIndicator(
progress = battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f), progress = battleSystem.opponentHP / (activeCharacter?.baseHp?.toFloat() ?: 100f),
modifier = Modifier modifier = getLandscapeModifier(),
.fillMaxWidth()
.height(10.dp),
color = Color.Red, color = Color.Red,
trackColor = Color.Gray trackColor = Color.Gray
) )
@ -1545,7 +1584,7 @@ fun EnemyBattleView(
// Enemy HP display numbers // Enemy HP display numbers
Text( Text(
text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}", text = "Enemy HP: ${battleSystem.opponentHP.toInt()}/${activeCharacter?.baseHp ?: 100}",
fontSize = 16.sp, fontSize = getLandscapeFontSize(),
color = Color.White, color = Color.White,
style = TextStyle( style = TextStyle(
shadow = Shadow( shadow = Shadow(
@ -1561,25 +1600,25 @@ fun EnemyBattleView(
// Middle section: Enemy Digimon // Middle section: Enemy Digimon
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp), .padding(16.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
// Enemy Digimon // Enemy Digimon
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.size(80.dp), .size(80.dp),
contentAlignment = Alignment.CenterEnd contentAlignment = Alignment.CenterEnd
) { ) {
// Determine animation type based on battle state // Determine animation type based on battle state
val animationType = when { val animationType = when {
battleSystem.isOpponentDodging -> DigimonAnimationType.WALK // Use walk animation for dodge battleSystem.isOpponentDodging -> DigimonAnimationType.WALK // Use walk animation for dodge
battleSystem.isOpponentHitDelayed -> 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 battleSystem.attackPhase == 2 -> DigimonAnimationType.IDLE // Player attack on enemy screen
else -> DigimonAnimationType.IDLE else -> DigimonAnimationType.IDLE
} }
// Calculate vertical offset for dodge animation // Calculate vertical offset for dodge animation
val verticalOffset = if (battleSystem.isOpponentDodging) { val verticalOffset = if (battleSystem.isOpponentDodging) {
@ -1608,65 +1647,65 @@ fun EnemyBattleView(
} else { } else {
0.dp 0.dp
} }
AnimatedSpriteImage( AnimatedSpriteImage(
characterId = activeCharacter?.charaId ?: "dim011_mon01", characterId = activeCharacter?.charaId ?: "dim011_mon01",
animationType = animationType, animationType = animationType,
modifier = Modifier modifier = Modifier
.size(80.dp) .size(80.dp)
.offset( .offset(
x = hitOffset, x = hitOffset,
y = verticalOffset y = verticalOffset
), ),
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
reloadMappings = false, reloadMappings = false,
animationOffset = 375L // Offset enemy animation by half the idle duration animationOffset = 375L // Offset enemy animation by half the idle duration
) )
// Attack sprite visibility and positioning based on attack phase // Attack sprite visibility and positioning based on attack phase
val shouldShowAttack = when (battleSystem.attackPhase) { val shouldShowAttack = when (battleSystem.attackPhase) {
2 -> true // Player attack on enemy screen 2 -> true // Player attack on enemy screen
else -> false else -> false
} }
if (shouldShowAttack) { if (shouldShowAttack) {
val xOffset = (attackAnimationProgress * 400 - 350).dp // Player attack on enemy screen - start more to the left val xOffset = (attackAnimationProgress * 400 - 350).dp // Player attack on enemy screen - start more to the left
// Use player's character ID for player attack // Use player's character ID for player attack
val characterId = playerCharacter?.charaId ?: "dim011_mon01" val characterId = playerCharacter?.charaId ?: "dim011_mon01"
// Handle sprite transition // Handle sprite transition
LaunchedEffect(characterId, battleSystem.attackPhase) { LaunchedEffect(characterId, battleSystem.attackPhase) {
if ((previousCharacterId != null && previousCharacterId != characterId) || if ((previousCharacterId != null && previousCharacterId != characterId) ||
(previousAttackPhase != null && previousAttackPhase != battleSystem.attackPhase)) { (previousAttackPhase != null && previousAttackPhase != battleSystem.attackPhase)) {
// Character ID or attack phase changed, start transition // Character ID or attack phase changed, start transition
isTransitioning = true isTransitioning = true
delay(100) // Brief invisibility period delay(100) // Brief invisibility period
isTransitioning = false isTransitioning = false
}
previousCharacterId = characterId
previousAttackPhase = battleSystem.attackPhase
} }
previousCharacterId = characterId
previousAttackPhase = battleSystem.attackPhase
}
println("EnemyBattleView - 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 && !hideEnemyAttackSprite) { if (!isTransitioning && !hideEnemyAttackSprite) {
AttackSpriteImage( AttackSpriteImage(
characterId = characterId, characterId = characterId,
isLarge = true, isLarge = true,
modifier = Modifier modifier = Modifier
.size(60.dp) .size(60.dp)
.offset( .offset(
x = xOffset, x = xOffset,
y = 0.dp y = 0.dp
) )
.scale(-1f, 1f), // Flip player attacks .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...") println("BATTLESCREEN: LaunchedEffect triggered - checking sprite files...")
val spriteFileManager = SpriteFileManager(context) val spriteFileManager = SpriteFileManager(context)
if (!spriteFileManager.checkSpriteFilesExist()) { if (!spriteFileManager.checkSpriteFilesExist()) {
println("BATTLESCREEN: Copying sprite files to internal storage...") println("BATTLESCREEN: Copying sprite files to external storage...")
spriteFileManager.copySpriteFilesToInternalStorage() spriteFileManager.copySpriteFilesToExternalStorage()
} else { } 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 = { topBar = {
// Only show TopBanner when not in battle mode // Only show TopBanner when not in battle mode
if (currentView != "battle-main" && currentView != "battle-results") { if (currentView != "battle-main" && currentView != "battle-results") {
TopBanner( TopBanner(
text = "Online battles" text = "Online battles"
) )
} }
} }
) { contentPadding -> ) { contentPadding ->
@ -2077,6 +2116,7 @@ fun BattlesScreen() {
championButton() championButton()
ultimateButton() ultimateButton()
megaButton() megaButton()
/*
Button( Button(
onClick = { onClick = {
showSpriteTester = true showSpriteTester = true
@ -2098,6 +2138,8 @@ fun BattlesScreen() {
) { ) {
Text("Clear Sprite Files") Text("Clear Sprite Files")
} }
*/
} }
} }
} }
@ -2387,10 +2429,11 @@ fun AnimatedBattleBackground(
println("DEBUG: Screen dimensions = ${screenWidth.value}x${screenHeight.value}dp") println("DEBUG: Screen dimensions = ${screenWidth.value}x${screenHeight.value}dp")
} }
// Load background image from internal storage // Load background image from external storage
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
try { 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()) { if (backgroundFile.exists()) {
backgroundBitmap = BitmapFactory.decodeFile(backgroundFile.absolutePath) backgroundBitmap = BitmapFactory.decodeFile(backgroundFile.absolutePath)
println("Successfully loaded battle background: ${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") 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) { LaunchedEffect(Unit) {
try { try {
val externalDir = Environment.getExternalStorageDirectory()
// Back layer (BattleBg_0018_BattleBg_0013.png) // 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()) { if (backLayerFile.exists()) {
backLayerBitmap = BitmapFactory.decodeFile(backLayerFile.absolutePath) backLayerBitmap = BitmapFactory.decodeFile(backLayerFile.absolutePath)
println("Successfully loaded back layer background: ${backLayerFile.absolutePath}") println("Successfully loaded back layer background: ${backLayerFile.absolutePath}")
@ -2482,7 +2527,7 @@ fun MultiLayerAnimatedBattleBackground(
} }
// Middle layer (BattleBg_0015_BattleBg_0012.png) // 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()) { if (middleLayerFile.exists()) {
middleLayerBitmap = BitmapFactory.decodeFile(middleLayerFile.absolutePath) middleLayerBitmap = BitmapFactory.decodeFile(middleLayerFile.absolutePath)
println("Successfully loaded middle layer background: ${middleLayerFile.absolutePath}") println("Successfully loaded middle layer background: ${middleLayerFile.absolutePath}")
@ -2491,7 +2536,7 @@ fun MultiLayerAnimatedBattleBackground(
} }
// Front layer (BattleBg_0005_BattleBg_0011.png) // 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()) { if (frontLayerFile.exists()) {
frontLayerBitmap = BitmapFactory.decodeFile(frontLayerFile.absolutePath) frontLayerBitmap = BitmapFactory.decodeFile(frontLayerFile.absolutePath)
println("Successfully loaded front layer background: ${frontLayerFile.absolutePath}") println("Successfully loaded front layer background: ${frontLayerFile.absolutePath}")