mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-10-01 15:41:36 +00:00
Compare commits
1
Commits
af27fc4933
..
logger
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa8641c874 |
@@ -10,10 +10,3 @@ 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/com.bandai.vitalbraceletarena.apk
|
||||||
|
|
||||||
app/src/test/resources/com/github/nacabaro/vbhelper/source/classes.dex
|
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
|
|
||||||
app/src/main/assets/extracted_audio
|
|
||||||
|
|
||||||
API-ACR122USAM-2.01.pdf
|
|
||||||
@@ -26,7 +26,7 @@ App also comes with a dex that will update every time a new character is added,
|
|||||||
|
|
||||||
2. Once the files are extracted, look for an APK called `com.bandai.vitalbraceletarena.apk`. Copy it somewhere else, you will need it.
|
2. Once the files are extracted, look for an APK called `com.bandai.vitalbraceletarena.apk`. Copy it somewhere else, you will need it.
|
||||||
|
|
||||||
2. Install an APK release for VB Helper. You will find the releases [here](http://github.com/nacabaro/vbhelper/releases). Download the latest release and install its APK.
|
2. Install an APK release for VB Arena. You will find the releases [here](http://github.com/nacabaro/vbhelper/releases). Download the latest release and install its APK.
|
||||||
|
|
||||||
Note, in the current stage of the project, you will have to delete the old application from your device. If the app keeps crashing after installing, clear application data and storage.
|
Note, in the current stage of the project, you will have to delete the old application from your device. If the app keeps crashing after installing, clear application data and storage.
|
||||||
|
|
||||||
@@ -66,4 +66,4 @@ App also comes with a dex that will update every time a new character is added,
|
|||||||
|
|
||||||
- `lightheel` for working on the online component in the application, both server and battle client.
|
- `lightheel` for working on the online component in the application, both server and battle client.
|
||||||
|
|
||||||
- `shvstrz` for the app icon.
|
- `shvstrz` for the app icon.
|
||||||
+1
-19
@@ -17,7 +17,7 @@ android {
|
|||||||
minSdk = 28
|
minSdk = 28
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "Alpha 0.6.3"
|
versionName = "Alpha 0.6.3.2"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -56,9 +56,6 @@ protobuf {
|
|||||||
artifact = "com.google.protobuf:protoc:4.27.0"
|
artifact = "com.google.protobuf:protoc:4.27.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates the java Protobuf-lite code for the Protobufs in this project. See
|
|
||||||
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
|
|
||||||
// for more information.
|
|
||||||
generateProtoTasks {
|
generateProtoTasks {
|
||||||
all().forEach { task ->
|
all().forEach { task ->
|
||||||
task.builtins {
|
task.builtins {
|
||||||
@@ -103,19 +100,4 @@ dependencies {
|
|||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
|
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
|
||||||
implementation("androidx.navigation:navigation-compose:2.7.0")
|
|
||||||
implementation("com.google.android.material:material:1.2.0")
|
|
||||||
implementation(libs.protobuf.javalite)
|
|
||||||
implementation("androidx.compose.material:material")
|
|
||||||
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,6 @@
|
|||||||
|
|
||||||
<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.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
|
||||||
tools:ignore="ScopedStorage" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".di.VBHelper"
|
android:name=".di.VBHelper"
|
||||||
@@ -20,7 +15,6 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.VBHelper"
|
android:theme="@style/Theme.VBHelper"
|
||||||
android:usesCleartextTraffic="true"
|
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@@ -32,19 +26,6 @@
|
|||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="vbhelper" android:host="auth" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="http" android:host="localhost" android:port="8080" android:pathPrefix="/authenticate" />
|
|
||||||
<data android:scheme="http" android:host="127.0.0.1" android:port="8080" android:pathPrefix="/authenticate" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.github.nacabaro.vbhelper
|
package com.github.nacabaro.vbhelper
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
@@ -68,13 +67,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i("MainActivity", "Activity onCreated")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
Log.i("MainActivity", "onPause")
|
|
||||||
for(activityListener in onActivityLifecycleListeners) {
|
for(activityListener in onActivityLifecycleListeners) {
|
||||||
activityListener.value.onPause()
|
activityListener.value.onPause()
|
||||||
}
|
}
|
||||||
@@ -82,7 +78,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
Log.i("MainActivity", "Resume")
|
|
||||||
for(activityListener in onActivityLifecycleListeners) {
|
for(activityListener in onActivityLifecycleListeners) {
|
||||||
activityListener.value.onResume()
|
activityListener.value.onResume()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
data class APIBattleCharacter(
|
|
||||||
val name: String,
|
|
||||||
val namekey: String,
|
|
||||||
val charaId: String,
|
|
||||||
val stage: Int,
|
|
||||||
val attribute: Int,
|
|
||||||
val baseHp: Int,
|
|
||||||
val currentHp: Int,
|
|
||||||
val baseBp: Float,
|
|
||||||
val baseAp: Float,
|
|
||||||
val displayName: String? = null
|
|
||||||
)
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
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
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AnimatedSpriteImage(
|
|
||||||
characterId: String,
|
|
||||||
animationType: DigimonAnimationType = DigimonAnimationType.IDLE,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
contentScale: ContentScale = ContentScale.Fit,
|
|
||||||
reloadMappings: Boolean = false,
|
|
||||||
animationOffset: Long = 0L // New parameter for offsetting animation timing
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val spriteManager = remember { IndividualSpriteManager(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<android.graphics.Bitmap?>(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 {
|
|
||||||
animationStateMachine.playIdleAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change animation when animationType changes
|
|
||||||
LaunchedEffect(animationType) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
if (animationType == DigimonAnimationType.IDLE) {
|
|
||||||
animationStateMachine.playIdleAnimation()
|
|
||||||
} else {
|
|
||||||
animationStateMachine.playAnimation(animationType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update sprite when animation state changes
|
|
||||||
LaunchedEffect(animationStateMachine.currentFrameNumber) {
|
|
||||||
val frameNumber = animationStateMachine.getCurrentFrame()
|
|
||||||
|
|
||||||
bitmap = spriteManager.loadSpriteFrame(characterId, frameNumber)
|
|
||||||
|
|
||||||
if (bitmap == null) {
|
|
||||||
println("Failed to load animated sprite frame: $frameNumber for character: $characterId")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap?.let { bmp ->
|
|
||||||
Image(
|
|
||||||
bitmap = bmp.asImageBitmap(),
|
|
||||||
contentDescription = "Animated Sprite: $characterId - ${animationStateMachine.currentAnimation}",
|
|
||||||
modifier = modifier,
|
|
||||||
contentScale = contentScale
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
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
|
|
||||||
import androidx.compose.runtime.State
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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() {
|
|
||||||
_attackPhase = 1
|
|
||||||
_attackProgress = 0f
|
|
||||||
_isPlayerAttacking = true
|
|
||||||
_isAttackButtonEnabled = false
|
|
||||||
_currentView = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startOpponentAttack() {
|
|
||||||
_attackPhase = 3
|
|
||||||
_attackProgress = 0f
|
|
||||||
_isPlayerAttacking = false
|
|
||||||
_currentView = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fun advanceAttackPhase() {
|
|
||||||
_attackPhase++
|
|
||||||
_attackProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAttackProgress(progress: Float) {
|
|
||||||
_attackProgress = progress
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAttackHitState(isHit: Boolean) {
|
|
||||||
_attackIsHit = isHit
|
|
||||||
}
|
|
||||||
|
|
||||||
fun switchToView(view: Int) {
|
|
||||||
_currentView = view
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableAttackButton() {
|
|
||||||
_isAttackButtonEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun applyDamage(isPlayer: Boolean, damage: Float) {
|
|
||||||
if (isPlayer) {
|
|
||||||
_playerHP = (_playerHP - damage).coerceAtLeast(0f)
|
|
||||||
} else {
|
|
||||||
_opponentHP = (_opponentHP - damage).coerceAtLeast(0f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateHPFromAPI(playerHP: Float, opponentHP: Float) {
|
|
||||||
_playerHP = playerHP
|
|
||||||
_opponentHP = opponentHP
|
|
||||||
}
|
|
||||||
|
|
||||||
fun initializeHP(playerHP: Float, opponentHP: Float) {
|
|
||||||
_playerHP = playerHP
|
|
||||||
_opponentHP = opponentHP
|
|
||||||
}
|
|
||||||
|
|
||||||
fun completeAttackAnimation(playerDamage: Float = 0f, opponentDamage: Float = 0f) {
|
|
||||||
if (playerDamage > 0f) {
|
|
||||||
applyDamage(true, playerDamage)
|
|
||||||
}
|
|
||||||
if (opponentDamage > 0f) {
|
|
||||||
applyDamage(false, opponentDamage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resetAttackState() {
|
|
||||||
_attackPhase = 0
|
|
||||||
_attackProgress = 0f
|
|
||||||
_isPlayerAttacking = false
|
|
||||||
_attackIsHit = false
|
|
||||||
_currentView = 0
|
|
||||||
_isDodging = false
|
|
||||||
_dodgeProgress = 0f
|
|
||||||
_dodgeDirection = 1f
|
|
||||||
_isHit = false
|
|
||||||
_hitProgress = 0f
|
|
||||||
_isPlayerDodging = false
|
|
||||||
_isOpponentDodging = false
|
|
||||||
_playerDodgeProgress = 0f
|
|
||||||
_playerDodgeDirection = 1f
|
|
||||||
_opponentDodgeProgress = 0f
|
|
||||||
_opponentDodgeDirection = 1f
|
|
||||||
_isPlayerHit = false
|
|
||||||
_isOpponentHit = false
|
|
||||||
_isPlayerHitDelayed = false
|
|
||||||
_isOpponentHitDelayed = false
|
|
||||||
_isPlayerShakeDelayed = false
|
|
||||||
_isOpponentShakeDelayed = false
|
|
||||||
_shouldCounterAttack = false
|
|
||||||
_counterAttackIsHit = false
|
|
||||||
_opponentAttackIsHit = false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkBattleOver(): Boolean {
|
|
||||||
return _playerHP <= 0f || _opponentHP <= 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endBattle() {
|
|
||||||
_isBattleOver = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateCritBarProgress(progress: Int) {
|
|
||||||
_critBarProgress = progress
|
|
||||||
//Log.d(TAG, "Updated crit bar progress: $progress")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dodge animation methods
|
|
||||||
fun startDodge() {
|
|
||||||
_isDodging = true
|
|
||||||
_dodgeProgress = 0f
|
|
||||||
_dodgeDirection = 1f // Start moving up
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDodgeProgress(progress: Float) {
|
|
||||||
_dodgeProgress = progress
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDodgeDirection(direction: Float) {
|
|
||||||
_dodgeDirection = direction
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endDodge() {
|
|
||||||
_isDodging = false
|
|
||||||
_dodgeProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hit animation methods
|
|
||||||
fun startHit() {
|
|
||||||
_isHit = true
|
|
||||||
_hitProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setHitProgress(progress: Float) {
|
|
||||||
_hitProgress = progress
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endHit() {
|
|
||||||
_isHit = false
|
|
||||||
_hitProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player-specific dodge methods
|
|
||||||
fun startPlayerDodge() {
|
|
||||||
_isPlayerDodging = true
|
|
||||||
_playerDodgeProgress = 0f
|
|
||||||
_playerDodgeDirection = 1f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endPlayerDodge() {
|
|
||||||
_isPlayerDodging = false
|
|
||||||
_playerDodgeProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPlayerDodgeProgress(progress: Float) {
|
|
||||||
_playerDodgeProgress = progress
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPlayerDodgeDirection(direction: Float) {
|
|
||||||
_playerDodgeDirection = direction
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opponent-specific dodge methods
|
|
||||||
fun startOpponentDodge() {
|
|
||||||
_isOpponentDodging = true
|
|
||||||
_opponentDodgeProgress = 0f
|
|
||||||
_opponentDodgeDirection = 1f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endOpponentDodge() {
|
|
||||||
_isOpponentDodging = false
|
|
||||||
_opponentDodgeProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setOpponentDodgeProgress(progress: Float) {
|
|
||||||
_opponentDodgeProgress = progress
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setOpponentDodgeDirection(direction: Float) {
|
|
||||||
_opponentDodgeDirection = direction
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player-specific hit methods
|
|
||||||
fun startPlayerHit() {
|
|
||||||
_isPlayerHit = true
|
|
||||||
_hitProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startPlayerHitDelayed() {
|
|
||||||
_isPlayerHitDelayed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endPlayerHit() {
|
|
||||||
_isPlayerHit = false
|
|
||||||
_hitProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endPlayerHitDelayed() {
|
|
||||||
_isPlayerHitDelayed = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opponent-specific hit methods
|
|
||||||
fun startOpponentHit() {
|
|
||||||
_isOpponentHit = true
|
|
||||||
_hitProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startOpponentHitDelayed() {
|
|
||||||
_isOpponentHitDelayed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endOpponentHit() {
|
|
||||||
_isOpponentHit = false
|
|
||||||
_hitProgress = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endOpponentHitDelayed() {
|
|
||||||
_isOpponentHitDelayed = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delayed shake methods
|
|
||||||
fun startPlayerShakeDelayed() {
|
|
||||||
_isPlayerShakeDelayed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endPlayerShakeDelayed() {
|
|
||||||
_isPlayerShakeDelayed = false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startOpponentShakeDelayed() {
|
|
||||||
_isOpponentShakeDelayed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun endOpponentShakeDelayed() {
|
|
||||||
_isOpponentShakeDelayed = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counter-attack methods
|
|
||||||
fun setupCounterAttack(isHit: Boolean) {
|
|
||||||
_shouldCounterAttack = true
|
|
||||||
_counterAttackIsHit = isHit
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startCounterAttack() {
|
|
||||||
_attackPhase = 3
|
|
||||||
_attackProgress = 0f
|
|
||||||
_isPlayerAttacking = false
|
|
||||||
_currentView = 1
|
|
||||||
_opponentAttackIsHit = _counterAttackIsHit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
data class CharacterData(
|
|
||||||
val name: String,
|
|
||||||
val charaId: String,
|
|
||||||
val smalefilename: String,
|
|
||||||
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<String>
|
|
||||||
)
|
|
||||||
|
|
||||||
class AttackSpriteManager(private val context: Context) {
|
|
||||||
private val gson = Gson()
|
|
||||||
private val characterDataCache = mutableMapOf<String, CharacterData>()
|
|
||||||
|
|
||||||
// Get the external storage directory for attack sprites
|
|
||||||
private fun getAttackTexturesBaseDir(): File {
|
|
||||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
|
||||||
return File(externalDir, "VBHelper/battle_sprites/extracted_atksprites")
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
// Determine which attack file to use
|
|
||||||
val attackFileName = if (isLarge) {
|
|
||||||
characterData.laugeFileName
|
|
||||||
} else {
|
|
||||||
characterData.smalefilename
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if no attack file
|
|
||||||
if (attackFileName == "0") {
|
|
||||||
println("AttackSpriteManager: Skipping attack file (filename is '0')")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the attack sprite from external storage
|
|
||||||
val attackFile = File(getAttackTexturesBaseDir(), "$attackFileName.png")
|
|
||||||
|
|
||||||
return if (attackFile.exists()) {
|
|
||||||
val bitmap = BitmapFactory.decodeFile(attackFile.absolutePath)
|
|
||||||
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? {
|
|
||||||
// Check cache first
|
|
||||||
if (characterDataCache.containsKey(characterId)) {
|
|
||||||
return characterDataCache[characterId]
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Load character data from JSON file in external storage
|
|
||||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
|
||||||
val characterDataFile = File(externalDir, "VBHelper/battle_sprites/extracted_digimon_stats/character_data/CharacterData.json")
|
|
||||||
|
|
||||||
if (!characterDataFile.exists()) {
|
|
||||||
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 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: "<UnknownObject<Character> 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
|
|
||||||
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,
|
|
||||||
smalefilename = "atk_s_02", // Default small attack
|
|
||||||
laugeFileName = "atk_l_04" // Default large attack
|
|
||||||
)
|
|
||||||
|
|
||||||
characterDataCache[characterId] = characterData
|
|
||||||
return characterData
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("AttackSpriteManager: Exception in getCharacterData: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.Response
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OkHttp interceptor that adds Authorization header to API requests.
|
|
||||||
* Skips adding header for auth endpoints.
|
|
||||||
*/
|
|
||||||
class AuthInterceptor(private val token: String) : Interceptor {
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
|
||||||
val originalRequest = chain.request()
|
|
||||||
|
|
||||||
// Skip adding auth header for auth endpoints
|
|
||||||
if (originalRequest.url.encodedPath.startsWith("/api/auth")) {
|
|
||||||
return chain.proceed(originalRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add authentication header for game endpoints
|
|
||||||
// Use X-Session-Token header (preferred) or Authorization: Bearer
|
|
||||||
val authenticatedRequest = originalRequest.newBuilder()
|
|
||||||
.header("X-Session-Token", token)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// Debug: Log which header is being used (first few chars of token for security)
|
|
||||||
val tokenPreview = if (token.length > 8) "${token.take(4)}...${token.takeLast(4)}" else "***"
|
|
||||||
println("AuthInterceptor: Adding X-Session-Token header (token: $tokenPreview)")
|
|
||||||
|
|
||||||
return chain.proceed(authenticatedRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.http.Body
|
|
||||||
import retrofit2.http.POST
|
|
||||||
|
|
||||||
interface AuthService {
|
|
||||||
@POST("api/auth/validate")
|
|
||||||
fun validate(@Body request: AuthenticateRequest): Call<AuthenticateResponse>
|
|
||||||
|
|
||||||
@POST("api/auth/login")
|
|
||||||
fun login(@Body request: AuthenticateRequest): Call<AuthenticateResponse>
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
data class AuthenticateRequest(
|
|
||||||
val userToken: String
|
|
||||||
)
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
data class AdditionalInfo(
|
|
||||||
val avatar: String? = null,
|
|
||||||
val id: Long? = null,
|
|
||||||
val name: String? = null,
|
|
||||||
val status: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class UserInfo(
|
|
||||||
val userId: String? = null,
|
|
||||||
val additionalInfo: AdditionalInfo? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AuthenticateResponse(
|
|
||||||
val success: Boolean,
|
|
||||||
val message: String? = null,
|
|
||||||
val userInfo: UserInfo? = null,
|
|
||||||
val sessionToken: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
|
||||||
import com.github.nacabaro.vbhelper.source.AuthRepository
|
|
||||||
|
|
||||||
private const val BATTLE_AUTH_PREFERENCES_NAME = "battle_auth_preferences"
|
|
||||||
val Context.battleAuthStore: androidx.datastore.core.DataStore<Preferences> by preferencesDataStore(
|
|
||||||
name = BATTLE_AUTH_PREFERENCES_NAME
|
|
||||||
)
|
|
||||||
|
|
||||||
class BattleAuthContainer(private val context: Context) {
|
|
||||||
val authRepository: AuthRepository = AuthRepository(context.battleAuthStore)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
data class SpriteMapping(
|
|
||||||
val atlas_name: String,
|
|
||||||
val atlas_file: String,
|
|
||||||
val texture: TextureInfo,
|
|
||||||
val sprites: List<String>
|
|
||||||
)
|
|
||||||
|
|
||||||
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<String, Bitmap>()
|
|
||||||
|
|
||||||
// Get the external storage directory for sprite files
|
|
||||||
private fun getSpriteBaseDir(): File {
|
|
||||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
|
||||||
return File(externalDir, "VBHelper/battle_sprites/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
|
|
||||||
val spriteBaseDir = getSpriteBaseDir()
|
|
||||||
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 PNG texture file directly using the atlas name
|
|
||||||
val textureFile = File(spriteBaseDir, "extracted_textures/${atlasName}.png")
|
|
||||||
|
|
||||||
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/${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)
|
|
||||||
|
|
||||||
// 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(),
|
|
||||||
correctedY,
|
|
||||||
spriteData.texture_rect.width.toInt(),
|
|
||||||
spriteData.texture_rect.height.toInt()
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Extracted sprite dimensions: ${finalBitmap.width}x${finalBitmap.height}")
|
|
||||||
|
|
||||||
// Cache the result
|
|
||||||
spriteCache[cacheKey] = finalBitmap
|
|
||||||
|
|
||||||
return finalBitmap
|
|
||||||
|
|
||||||
} 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<String> {
|
|
||||||
try {
|
|
||||||
val spritesDir = File(getSpriteBaseDir(), "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<String> {
|
|
||||||
try {
|
|
||||||
val texturesDir = File(getSpriteBaseDir(), "extracted_textures")
|
|
||||||
if (!texturesDir.exists()) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val textureFiles = texturesDir.listFiles { file ->
|
|
||||||
file.name.endsWith(".png")
|
|
||||||
} ?: emptyArray()
|
|
||||||
|
|
||||||
return textureFiles.map { file ->
|
|
||||||
// Extract atlas name from filename (e.g., "dim000_mon01.png" -> "dim000_mon01")
|
|
||||||
file.name.substringBefore(".png")
|
|
||||||
}.sorted()
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
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
|
|
||||||
import android.content.Context
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
enum class DigimonAnimationType {
|
|
||||||
IDLE,
|
|
||||||
IDLE2,
|
|
||||||
WALK,
|
|
||||||
WALK2,
|
|
||||||
RUN,
|
|
||||||
RUN2,
|
|
||||||
WORKOUT,
|
|
||||||
WORKOUT2,
|
|
||||||
HAPPY,
|
|
||||||
SLEEP,
|
|
||||||
ATTACK,
|
|
||||||
FLEE
|
|
||||||
}
|
|
||||||
|
|
||||||
data class AnimationState(
|
|
||||||
val type: DigimonAnimationType,
|
|
||||||
val frameNumber: Int, // 1-12 for individual PNG files
|
|
||||||
val duration: Long = 100L, // Duration in milliseconds
|
|
||||||
val loop: Boolean = true
|
|
||||||
)
|
|
||||||
|
|
||||||
class DigimonAnimationStateMachine(
|
|
||||||
private val characterId: String,
|
|
||||||
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>(DigimonAnimationType.IDLE)
|
|
||||||
private set
|
|
||||||
|
|
||||||
var currentFrameNumber by mutableStateOf(1)
|
|
||||||
private set
|
|
||||||
|
|
||||||
var isPlaying by mutableStateOf(false)
|
|
||||||
private set
|
|
||||||
|
|
||||||
// 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
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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(
|
|
||||||
DigimonAnimationType.IDLE to 750L,
|
|
||||||
DigimonAnimationType.IDLE2 to 750L,
|
|
||||||
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 1500L,
|
|
||||||
DigimonAnimationType.ATTACK to 650L,
|
|
||||||
DigimonAnimationType.FLEE to 150L
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
init {
|
|
||||||
println("Initialized DigimonAnimationStateMachine for character: $characterId with frame offset: $initialFrameOffset, timing offset: $timingOffset")
|
|
||||||
println("Available animation types: ${animationTypeToFrames.keys}")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
suspend fun playAnimation(animationType: DigimonAnimationType) {
|
|
||||||
if (currentAnimation == animationType && isPlaying) {
|
|
||||||
return // Already playing this animation
|
|
||||||
}
|
|
||||||
|
|
||||||
currentAnimation = animationType
|
|
||||||
isPlaying = true
|
|
||||||
|
|
||||||
val frameNumbers = animationTypeToFrames[animationType] ?: listOf(1)
|
|
||||||
val duration = animationDurations[animationType] ?: 100L
|
|
||||||
|
|
||||||
// For non-looping animations like ATTACK, play once and return to IDLE
|
|
||||||
if (animationType == DigimonAnimationType.ATTACK) {
|
|
||||||
currentFrameNumber = frameNumbers.firstOrNull() ?: 1
|
|
||||||
delay(duration)
|
|
||||||
playAnimation(DigimonAnimationType.IDLE)
|
|
||||||
} else {
|
|
||||||
// For looping animations, cycle through frames
|
|
||||||
var frameIndex = 0
|
|
||||||
while (isPlaying && currentAnimation == animationType) {
|
|
||||||
val frameNumber = frameNumbers[frameIndex % frameNumbers.size]
|
|
||||||
currentFrameNumber = frameNumber
|
|
||||||
delay(duration)
|
|
||||||
frameIndex++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
val duration = animationDurations[DigimonAnimationType.IDLE] ?: 500L
|
|
||||||
|
|
||||||
// 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
|
|
||||||
delay(duration)
|
|
||||||
frameIndex++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopAnimation() {
|
|
||||||
isPlaying = false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCurrentFrame(): Int {
|
|
||||||
return currentFrameNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
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.platform.LocalConfiguration
|
|
||||||
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 configuration = LocalConfiguration.current
|
|
||||||
val isLandscapeMode = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE
|
|
||||||
val hitEffectManager = remember { HitEffectSpriteManager(context) }
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
var currentFrame by remember { mutableStateOf(0) }
|
|
||||||
var currentSprite by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
|
|
||||||
var animationProgress by remember { mutableStateOf(0f) }
|
|
||||||
var scale by remember { mutableStateOf(0.5f) }
|
|
||||||
var alpha by remember { mutableStateOf(1f) }
|
|
||||||
|
|
||||||
LaunchedEffect(isVisible) {
|
|
||||||
if (isVisible) {
|
|
||||||
|
|
||||||
// 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"
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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)
|
|
||||||
),
|
|
||||||
contentScale = ContentScale.Fit
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
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<String, Bitmap>()
|
|
||||||
|
|
||||||
// Get the external storage directory for hit effect sprites
|
|
||||||
private fun getHitSpritesDir(): File {
|
|
||||||
val externalDir = android.os.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)
|
|
||||||
* @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 hitSpritesDir = getHitSpritesDir()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(getHitSpritesDir(), "$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 all available hit sprites
|
|
||||||
* @return List of hit sprite names (without .png extension)
|
|
||||||
*/
|
|
||||||
fun getAvailableHitSprites(): List<String> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get available damage effect spritesheet names
|
|
||||||
* @return List of available damage effect spritesheet names
|
|
||||||
*/
|
|
||||||
fun getAvailableDamageEffectSpritesheets(): List<String> {
|
|
||||||
try {
|
|
||||||
if (!getHitSpritesDir().exists()) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val dmgFiles = getHitSpritesDir().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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
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<String, Bitmap>()
|
|
||||||
|
|
||||||
// Get the external storage directory for sprite files
|
|
||||||
private fun getSpriteBaseDir(): File {
|
|
||||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
|
||||||
return File(externalDir, "VBHelper/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
|
|
||||||
val spriteBaseDir = getSpriteBaseDir()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<Int> {
|
|
||||||
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 character IDs
|
|
||||||
* @return List of character IDs that have sprite directories
|
|
||||||
*/
|
|
||||||
fun getAvailableCharacters(): List<String> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(getSpriteBaseDir(), characterId)
|
|
||||||
if (!characterDir.exists()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val spriteFiles = characterDir.listFiles { file ->
|
|
||||||
file.name.startsWith("${characterId}_") && file.name.endsWith(".png")
|
|
||||||
} ?: emptyArray()
|
|
||||||
|
|
||||||
return spriteFiles.isNotEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.http.GET
|
|
||||||
import retrofit2.http.Query
|
|
||||||
|
|
||||||
interface OpponentService {
|
|
||||||
@GET("api/opponents")
|
|
||||||
// This method returns a Call object with a generic
|
|
||||||
// type of DataModel, which represents
|
|
||||||
// the data model for the response.
|
|
||||||
fun getopponents(@Query("stage") stage: String): Call<OpponentsDataModel>
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
data class OpponentsDataModel (
|
|
||||||
val opponentsList: ArrayList<APIBattleCharacter>
|
|
||||||
):java.io.Serializable
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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,
|
|
||||||
val opponentCharaId: String? = null, // Server provides opponent's charaId from the match
|
|
||||||
val playerMaxHP: Int? = null, // Server should provide max HP for resumed matches
|
|
||||||
val opponentMaxHP: Int? = null // Server should provide max HP for resumed matches
|
|
||||||
):java.io.Serializable
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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: Long,
|
|
||||||
@Query("playerDigi") playerDigi: String,
|
|
||||||
@Query("playerStage") playerStage: Int,
|
|
||||||
@Query("critBar") critBar: Int,
|
|
||||||
@Query("opponentDigi") opponentDigi: String,
|
|
||||||
@Query("opponentStage") opponentStage: Int,
|
|
||||||
@Query("action") action: String? = null // Optional: "quit" or "rejoin"
|
|
||||||
): Call<PVPDataModel>
|
|
||||||
}
|
|
||||||
@@ -1,377 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import retrofit2.Retrofit
|
|
||||||
import android.widget.Toast
|
|
||||||
import retrofit2.*
|
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import com.github.nacabaro.vbhelper.battle.BattleAuthContainer
|
|
||||||
|
|
||||||
class RetrofitHelper {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an OkHttpClient with authentication interceptor for game endpoints.
|
|
||||||
* Requires a non-null, non-empty token.
|
|
||||||
*/
|
|
||||||
private fun createAuthenticatedClient(token: String): OkHttpClient {
|
|
||||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
|
||||||
level = HttpLoggingInterceptor.Level.BODY
|
|
||||||
}
|
|
||||||
|
|
||||||
return OkHttpClient.Builder()
|
|
||||||
.addInterceptor(AuthInterceptor(token))
|
|
||||||
.addInterceptor(loggingInterceptor)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the session token from AuthRepository for API calls.
|
|
||||||
* Falls back to nacatech token if session token is not available (backward compatibility).
|
|
||||||
*/
|
|
||||||
private fun getAuthToken(context: Context): String? {
|
|
||||||
return try {
|
|
||||||
val authContainer = BattleAuthContainer(context)
|
|
||||||
runBlocking {
|
|
||||||
// Prefer session token, fall back to nacatech token for backward compatibility
|
|
||||||
val sessionToken = authContainer.authRepository.sessionToken.first()
|
|
||||||
if (!sessionToken.isNullOrEmpty()) {
|
|
||||||
println("RetrofitHelper: Using sessionToken for API call")
|
|
||||||
sessionToken
|
|
||||||
} else {
|
|
||||||
// Fallback to nacatech token (slower, but works)
|
|
||||||
val nacatechToken = authContainer.authRepository.authToken.first()
|
|
||||||
if (!nacatechToken.isNullOrEmpty()) {
|
|
||||||
println("RetrofitHelper: No sessionToken found, falling back to nacatechToken")
|
|
||||||
}
|
|
||||||
nacatechToken
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("RetrofitHelper: Error getting auth token: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Retrofit instance with authentication for game endpoints.
|
|
||||||
*/
|
|
||||||
private fun createAuthenticatedRetrofit(context: Context): Retrofit? {
|
|
||||||
val token = getAuthToken(context)
|
|
||||||
if (token.isNullOrEmpty()) {
|
|
||||||
println("RetrofitHelper: No auth token available")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val client = createAuthenticatedClient(token)
|
|
||||||
return Retrofit.Builder()
|
|
||||||
.baseUrl("http://battle.io-void.com:8080/")
|
|
||||||
.client(client)
|
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles HTTP error responses (401, 403, 429).
|
|
||||||
* For 401/403, clears authentication state to trigger re-authentication.
|
|
||||||
*/
|
|
||||||
private fun handleErrorResponse(context: Context, response: Response<*>, errorMessage: String) {
|
|
||||||
when (response.code()) {
|
|
||||||
401 -> {
|
|
||||||
println("RetrofitHelper: Authentication failed (401) - token may be expired")
|
|
||||||
clearAuthAndNotify(context, "Authentication failed. Please log in again.")
|
|
||||||
}
|
|
||||||
403 -> {
|
|
||||||
println("RetrofitHelper: Access forbidden (403) - token may be expired or invalid")
|
|
||||||
// 403 could mean expired token, so clear auth state to trigger re-authentication
|
|
||||||
clearAuthAndNotify(context, "Session expired. Please log in again.")
|
|
||||||
}
|
|
||||||
429 -> {
|
|
||||||
println("RetrofitHelper: Rate limit exceeded (429)")
|
|
||||||
Toast.makeText(context, "Too many requests. Please wait a moment.", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
println("RetrofitHelper: API error (${response.code()}): $errorMessage")
|
|
||||||
Toast.makeText(context, "Request failed: ${response.code()}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears authentication state and shows a message.
|
|
||||||
* This will trigger BattlesScreen to detect the auth state change and open the login page.
|
|
||||||
*/
|
|
||||||
private fun clearAuthAndNotify(context: Context, message: String) {
|
|
||||||
try {
|
|
||||||
val authContainer = BattleAuthContainer(context)
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
authContainer.authRepository.logout()
|
|
||||||
println("RetrofitHelper: Cleared authentication state due to expired/invalid token")
|
|
||||||
}
|
|
||||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("RetrofitHelper: Error clearing auth state: ${e.message}")
|
|
||||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOpponents(context: Context, stage: String, callback: (OpponentsDataModel) -> Unit) {
|
|
||||||
//println("RetrofitHelper: Starting API call for stage: $stage")
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create an authenticated Retrofit instance
|
|
||||||
val retrofit = createAuthenticatedRetrofit(context)
|
|
||||||
if (retrofit == null) {
|
|
||||||
println("RetrofitHelper: Cannot create authenticated Retrofit - no token available")
|
|
||||||
Toast.makeText(context, "Authentication required. Please log in.", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an ApiService instance from the Retrofit instance.
|
|
||||||
val service: OpponentService = retrofit.create<OpponentService>(OpponentService::class.java)
|
|
||||||
//println("RetrofitHelper: Service created")
|
|
||||||
|
|
||||||
// Call the getopponents() method of the ApiService
|
|
||||||
// to make an API request.
|
|
||||||
val call: Call<OpponentsDataModel> = 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<OpponentsDataModel> {
|
|
||||||
override fun onFailure(call: Call<OpponentsDataModel>, 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<OpponentsDataModel>, response: Response<OpponentsDataModel>) {
|
|
||||||
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 {
|
|
||||||
val errorBody = response.errorBody()?.string()
|
|
||||||
println("RetrofitHelper: Response not successful - Error: $errorBody")
|
|
||||||
handleErrorResponse(context, response, errorBody ?: "Unknown error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("RetrofitHelper: Exception in getOpponents: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
Toast.makeText(context, "Request failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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://battle.io-void.com:8080/").addConverterFactory(
|
|
||||||
GsonConverterFactory.create()).build()
|
|
||||||
|
|
||||||
// Create an ApiService instance from the Retrofit instance.
|
|
||||||
val service: CombatService = retrofit.create<CombatService>(CombatService::class.java)
|
|
||||||
|
|
||||||
// Call the getwinner() method of the ApiService
|
|
||||||
// to make an API request.
|
|
||||||
val call: Call<CombatDataModel> = service.getwinner(stage)
|
|
||||||
|
|
||||||
// Use the enqueue() method of the Call object to
|
|
||||||
// make an asynchronous API request.
|
|
||||||
call.enqueue(object : Callback<CombatDataModel> {
|
|
||||||
// This is an anonymous inner class that implements the Callback interface.
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<CombatDataModel>, 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<CombatDataModel>, response: Response<CombatDataModel>) {
|
|
||||||
// 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://battle.io-void.com:8080/").addConverterFactory(
|
|
||||||
GsonConverterFactory.create()).build()
|
|
||||||
|
|
||||||
// Create an ApiService instance from the Retrofit instance.
|
|
||||||
val service: BattleService = retrofit.create<BattleService>(BattleService::class.java)
|
|
||||||
|
|
||||||
// Call the getwinner() method of the ApiService
|
|
||||||
// to make an API request.
|
|
||||||
val call: Call<BattleDataModel> = service.getwinner(playerDigi, playerStage, opponentDigi, opponentStage)
|
|
||||||
|
|
||||||
// Use the enqueue() method of the Call object to
|
|
||||||
// make an asynchronous API request.
|
|
||||||
call.enqueue(object : Callback<BattleDataModel> {
|
|
||||||
// This is an anonymous inner class that implements the Callback interface.
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<BattleDataModel>, 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<BattleDataModel>, response: Response<BattleDataModel>) {
|
|
||||||
// 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: Long, playerDigi: String, playerStage: Int, critBar: Int, opponentDigi: String, opponentStage: Int, callback: (PVPDataModel) -> Unit) {
|
|
||||||
getPVPWinner(context, apiStage, playerID, playerDigi, playerStage, critBar, opponentDigi, opponentStage, null, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPVPWinner(context: Context, apiStage: Int, playerID: Long, playerDigi: String, playerStage: Int, critBar: Int, opponentDigi: String, opponentStage: Int, action: String?, callback: (PVPDataModel) -> Unit) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create an authenticated Retrofit instance
|
|
||||||
val retrofit = createAuthenticatedRetrofit(context)
|
|
||||||
if (retrofit == null) {
|
|
||||||
println("RetrofitHelper: Cannot create authenticated Retrofit - no token available")
|
|
||||||
Toast.makeText(context, "Authentication required. Please log in.", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an ApiService instance from the Retrofit instance.
|
|
||||||
val service: PVPService = retrofit.create<PVPService>(PVPService::class.java)
|
|
||||||
|
|
||||||
// Call the getwinner() method of the ApiService
|
|
||||||
// to make an API request.
|
|
||||||
val call: Call<PVPDataModel> = service.getwinner(apiStage, playerID, playerDigi, playerStage, critBar, opponentDigi, opponentStage, action)
|
|
||||||
|
|
||||||
// Use the enqueue() method of the Call object to
|
|
||||||
// make an asynchronous API request.
|
|
||||||
call.enqueue(object : Callback<PVPDataModel> {
|
|
||||||
// This is an anonymous inner class that implements the Callback interface.
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<PVPDataModel>, t: Throwable) {
|
|
||||||
// This method is called when the API request fails.
|
|
||||||
println("RetrofitHelper: PVP API call failed: ${t.message}")
|
|
||||||
t.printStackTrace()
|
|
||||||
Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(call: Call<PVPDataModel>, response: Response<PVPDataModel>) {
|
|
||||||
// This method is called when the API response is received successfully.
|
|
||||||
println("RetrofitHelper: PVP API response received - Code: ${response.code()}")
|
|
||||||
|
|
||||||
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)
|
|
||||||
} else {
|
|
||||||
val errorBody = response.errorBody()?.string()
|
|
||||||
println("RetrofitHelper: PVP API response not successful - Code: ${response.code()}, Error: $errorBody")
|
|
||||||
handleErrorResponse(context, response, errorBody ?: "Unknown error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("RetrofitHelper: Exception in getPVPWinner: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
Toast.makeText(context, "Request failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun authenticate(context: Context, token: String, callback: (AuthenticateResponse) -> Unit) {
|
|
||||||
//println("RetrofitHelper: Starting validate API call with token: $token")
|
|
||||||
|
|
||||||
if (token.isEmpty()) {
|
|
||||||
println("RetrofitHelper: ERROR - Token is empty!")
|
|
||||||
Toast.makeText(context, "Authentication failed: Token is empty", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Add logging interceptor to see the actual HTTP request
|
|
||||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
|
||||||
level = HttpLoggingInterceptor.Level.BODY
|
|
||||||
}
|
|
||||||
val okHttpClient = OkHttpClient.Builder()
|
|
||||||
.addInterceptor(loggingInterceptor)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val retrofit: Retrofit = Retrofit.Builder()
|
|
||||||
.baseUrl("http://battle.io-void.com:8080/")
|
|
||||||
.client(okHttpClient)
|
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val service: AuthService = retrofit.create<AuthService>(AuthService::class.java)
|
|
||||||
val request = AuthenticateRequest(userToken = token)
|
|
||||||
// Use login endpoint instead of validate to get sessionToken
|
|
||||||
val call: Call<AuthenticateResponse> = service.login(request)
|
|
||||||
|
|
||||||
call.enqueue(object : Callback<AuthenticateResponse> {
|
|
||||||
override fun onFailure(call: Call<AuthenticateResponse>, t: Throwable) {
|
|
||||||
println("RetrofitHelper: Validate API call failed: ${t.message}")
|
|
||||||
t.printStackTrace()
|
|
||||||
Toast.makeText(context, "Authentication failed: ${t.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(call: Call<AuthenticateResponse>, response: Response<AuthenticateResponse>) {
|
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
val authResponse: AuthenticateResponse? = response.body()
|
|
||||||
if (authResponse != null) {
|
|
||||||
callback(authResponse)
|
|
||||||
} else {
|
|
||||||
println("RetrofitHelper: Validation failed: Invalid response body")
|
|
||||||
Toast.makeText(context, "Authentication failed: Invalid response", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val errorBody = response.errorBody()?.string()
|
|
||||||
println("RetrofitHelper: Validate response not successful - Code: ${response.code()}, Error: $errorBody")
|
|
||||||
Toast.makeText(context, "Authentication failed: ${response.code()}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("RetrofitHelper: Exception in validate: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
Toast.makeText(context, "Authentication failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.battle
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.FileInputStream
|
|
||||||
|
|
||||||
class SpriteFileManager(private val context: Context) {
|
|
||||||
|
|
||||||
// Get the external storage directory where files are already located
|
|
||||||
fun getExternalSpriteBaseDir(): File {
|
|
||||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
|
||||||
return File(externalDir, "VBHelper/battle_sprites")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the internal storage directory for sprite files
|
|
||||||
private fun getInternalSpriteBaseDir(): File {
|
|
||||||
return File(context.filesDir, "battle_sprites")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copySpriteFilesToInternalStorage() {
|
|
||||||
try {
|
|
||||||
println("Starting sprite file copy process from external storage to internal storage...")
|
|
||||||
|
|
||||||
val externalDir = getExternalSpriteBaseDir()
|
|
||||||
val internalDir = getInternalSpriteBaseDir()
|
|
||||||
|
|
||||||
// Check if external directory exists
|
|
||||||
if (!externalDir.exists()) {
|
|
||||||
println("External sprite directory does not exist: ${externalDir.absolutePath}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
println("External sprite directory exists: ${externalDir.absolutePath}")
|
|
||||||
println("Copying to internal storage: ${internalDir.absolutePath}")
|
|
||||||
|
|
||||||
// Create internal directory if it doesn't exist
|
|
||||||
if (!internalDir.exists()) {
|
|
||||||
val created = internalDir.mkdirs()
|
|
||||||
println("Created internal sprite directory: $created")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy all subdirectories from external to internal storage
|
|
||||||
val externalFiles = externalDir.listFiles()
|
|
||||||
if (externalFiles != null) {
|
|
||||||
println("Found ${externalFiles.size} items in external directory")
|
|
||||||
externalFiles.forEach { item ->
|
|
||||||
val targetItem = File(internalDir, item.name)
|
|
||||||
if (item.isDirectory) {
|
|
||||||
println("Copying directory: ${item.name}")
|
|
||||||
copyDirectory(item, targetItem)
|
|
||||||
} else {
|
|
||||||
println("Copying file: ${item.name}")
|
|
||||||
copyFile(item, targetItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Sprite files copied successfully to internal storage: ${internalDir.absolutePath}")
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("Error copying sprite files to internal storage: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copySpriteFilesToExternalStorage() {
|
|
||||||
try {
|
|
||||||
println("Starting sprite file copy process to external storage...")
|
|
||||||
|
|
||||||
// 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(", ")}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(", ")}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the base directory for battle_sprites in external storage
|
|
||||||
val battleSpritesDir = getExternalSpriteBaseDir()
|
|
||||||
if (!battleSpritesDir.exists()) {
|
|
||||||
battleSpritesDir.mkdirs()
|
|
||||||
println("Created battle_sprites directory in external storage: ${battleSpritesDir.absolutePath}")
|
|
||||||
} else {
|
|
||||||
println("battle_sprites directory already exists in external storage: ${battleSpritesDir.absolutePath}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
println("Copying $sourcePath to ${targetDir.absolutePath}")
|
|
||||||
copyAssetDirectory(sourcePath, targetDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Sprite files copied successfully to external storage: ${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 to external storage: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun copyAssetDirectory(assetPath: String, targetDir: File) {
|
|
||||||
try {
|
|
||||||
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)
|
|
||||||
|
|
||||||
// Create subdirectories if needed
|
|
||||||
if (targetFile.parentFile != null && !targetFile.parentFile!!.exists()) {
|
|
||||||
targetFile.parentFile!!.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)
|
|
||||||
}
|
|
||||||
} 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun copyDirectory(sourceDir: File, targetDir: File) {
|
|
||||||
if (!targetDir.exists()) {
|
|
||||||
targetDir.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
val files = sourceDir.listFiles()
|
|
||||||
if (files != null) {
|
|
||||||
files.forEach { file ->
|
|
||||||
val targetFile = File(targetDir, file.name)
|
|
||||||
if (file.isDirectory) {
|
|
||||||
copyDirectory(file, targetFile)
|
|
||||||
} else {
|
|
||||||
copyFile(file, targetFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun copyFile(sourceFile: File, targetFile: File) {
|
|
||||||
try {
|
|
||||||
val inputStream = FileInputStream(sourceFile)
|
|
||||||
val outputStream = FileOutputStream(targetFile)
|
|
||||||
|
|
||||||
inputStream.copyTo(outputStream)
|
|
||||||
inputStream.close()
|
|
||||||
outputStream.close()
|
|
||||||
|
|
||||||
println("Copied: ${sourceFile.name} -> ${targetFile.absolutePath}")
|
|
||||||
} catch (e: IOException) {
|
|
||||||
println("Error copying file ${sourceFile.name}: ${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 battleSpritesDir = getExternalSpriteBaseDir()
|
|
||||||
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
|
|
||||||
|
|
||||||
/*
|
|
||||||
println("Checking sprite files exist in external storage:")
|
|
||||||
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 battleSpritesDir = getInternalSpriteBaseDir()
|
|
||||||
|
|
||||||
if (battleSpritesDir.exists()) {
|
|
||||||
deleteDirectory(battleSpritesDir)
|
|
||||||
println("Cleared battle_sprites directory from internal storage")
|
|
||||||
}
|
|
||||||
|
|
||||||
} 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SpriteImage(
|
|
||||||
characterId: String,
|
|
||||||
frameNumber: Int,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
contentScale: ContentScale = ContentScale.Fit
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val spriteManager = remember { IndividualSpriteManager(context) }
|
|
||||||
|
|
||||||
var bitmap by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
|
|
||||||
|
|
||||||
LaunchedEffect(characterId, frameNumber) {
|
|
||||||
println("Loading sprite frame: $frameNumber for character: $characterId")
|
|
||||||
bitmap = spriteManager.loadSpriteFrame(characterId, frameNumber)
|
|
||||||
if (bitmap == null) {
|
|
||||||
println("Failed to load sprite frame: $frameNumber for character: $characterId")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap?.let { bmp ->
|
|
||||||
Image(
|
|
||||||
bitmap = bmp.asImageBitmap(),
|
|
||||||
contentDescription = "Sprite: $characterId frame $frameNumber",
|
|
||||||
modifier = modifier,
|
|
||||||
contentScale = contentScale
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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<Bitmap?>(null) }
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
bitmap = loadedBitmap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap?.let { bmp ->
|
|
||||||
Image(
|
|
||||||
bitmap = bmp.asImageBitmap(),
|
|
||||||
contentDescription = "Attack Sprite",
|
|
||||||
modifier = modifier,
|
|
||||||
contentScale = contentScale
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
object AppLogger {
|
||||||
|
private val logs = StringBuilder()
|
||||||
|
|
||||||
|
fun d(tag: String, message: String) {
|
||||||
|
Log.d(tag, message)
|
||||||
|
append("DEBUG", tag, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun e(tag: String, message: String, tr: Throwable? = null) {
|
||||||
|
Log.e(tag, message, tr)
|
||||||
|
append("ERROR", tag, message + (tr?.stackTraceToString() ?: ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun append(level: String, tag: String, message: String) {
|
||||||
|
logs.append("${System.currentTimeMillis()} [$level][$tag] $message\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLogs(): String = logs.toString()
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
logs.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenContr
|
|||||||
import com.github.nacabaro.vbhelper.screens.cardScreen.CardAdventureScreen
|
import com.github.nacabaro.vbhelper.screens.cardScreen.CardAdventureScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
|
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
|
||||||
import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen
|
import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.settingsScreen.LoggerScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
|
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
|
||||||
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
|
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
|
||||||
import com.github.nacabaro.vbhelper.source.StorageRepository
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
@@ -182,6 +183,11 @@ fun AppNavigation(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
composable(NavigationItems.Logger.route) {
|
||||||
|
LoggerScreen(
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,4 +98,10 @@ sealed class NavigationItems(
|
|||||||
R.drawable.baseline_data_24,
|
R.drawable.baseline_data_24,
|
||||||
R.string.nav_credits
|
R.string.nav_credits
|
||||||
)
|
)
|
||||||
|
|
||||||
|
object Logger : NavigationItems(
|
||||||
|
"Logger",
|
||||||
|
R.drawable.baseline_bug_report,
|
||||||
|
R.string.nav_logs
|
||||||
|
)
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens.cardScreen
|
package com.github.nacabaro.vbhelper.screens.cardScreen
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -98,8 +97,8 @@ fun CardsScreen(
|
|||||||
selectedCard.value = null
|
selectedCard.value = null
|
||||||
},
|
},
|
||||||
onRename = { newName ->
|
onRename = { newName ->
|
||||||
Log.d("CardsScreen", "New name: $newName")
|
AppLogger.d("CardsScreen", "New name: $newName")
|
||||||
Log.d("CardsScreen", "Card: ${selectedCard.value.toString()}")
|
AppLogger.d("CardsScreen", "Card: ${selectedCard.value.toString()}")
|
||||||
cardScreenController
|
cardScreenController
|
||||||
.renameCard(
|
.renameCard(
|
||||||
cardId = selectedCard.value!!.cardId,
|
cardId = selectedCard.value!!.cardId,
|
||||||
|
|||||||
+4
-5
@@ -6,7 +6,6 @@ import android.nfc.Tag
|
|||||||
import android.nfc.tech.NfcA
|
import android.nfc.tech.NfcA
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@@ -127,18 +126,18 @@ class ScanScreenControllerImpl(
|
|||||||
handleTag(secrets) { tagCommunicator ->
|
handleTag(secrets) { tagCommunicator ->
|
||||||
try {
|
try {
|
||||||
if (nfcCharacter is VBNfcCharacter) {
|
if (nfcCharacter is VBNfcCharacter) {
|
||||||
Log.d("SendCharacter", "VBNfcCharacter")
|
AppLogger.d("SendCharacter", "VBNfcCharacter")
|
||||||
val castNfcCharacter: VBNfcCharacter = nfcCharacter
|
val castNfcCharacter: VBNfcCharacter = nfcCharacter
|
||||||
tagCommunicator.sendCharacter(castNfcCharacter)
|
tagCommunicator.sendCharacter(castNfcCharacter)
|
||||||
} else if (nfcCharacter is BENfcCharacter) {
|
} else if (nfcCharacter is BENfcCharacter) {
|
||||||
Log.d("SendCharacter", "BENfcCharacter")
|
AppLogger.d("SendCharacter", "BENfcCharacter")
|
||||||
val castNfcCharacter: BENfcCharacter = nfcCharacter
|
val castNfcCharacter: BENfcCharacter = nfcCharacter
|
||||||
tagCommunicator.sendCharacter(castNfcCharacter)
|
tagCommunicator.sendCharacter(castNfcCharacter)
|
||||||
}
|
}
|
||||||
onComplete.invoke()
|
onComplete.invoke()
|
||||||
componentActivity.getString(R.string.scan_sent_character_success)
|
componentActivity.getString(R.string.scan_sent_character_success)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e("TAG", e.stackTraceToString())
|
AppLogger.e("TAG", e.stackTraceToString())
|
||||||
componentActivity.getString(R.string.scan_error_generic)
|
componentActivity.getString(R.string.scan_error_generic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +177,7 @@ class ScanScreenControllerImpl(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val character = nfcGenerator.characterToNfc(characterId)
|
val character = nfcGenerator.characterToNfc(characterId)
|
||||||
Log.d("CharacterType", character.toString())
|
AppLogger.d("CharacterType", character.toString())
|
||||||
return character
|
return character
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -2,7 +2,6 @@ package com.github.nacabaro.vbhelper.screens.scanScreen.converters
|
|||||||
|
|
||||||
import android.icu.util.Calendar
|
import android.icu.util.Calendar
|
||||||
import android.icu.util.TimeZone
|
import android.icu.util.TimeZone
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import com.github.cfogrady.vbnfc.be.BENfcCharacter
|
import com.github.cfogrady.vbnfc.be.BENfcCharacter
|
||||||
import com.github.cfogrady.vbnfc.be.FirmwareVersion
|
import com.github.cfogrady.vbnfc.be.FirmwareVersion
|
||||||
@@ -161,7 +160,7 @@ class ToNfcConverter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
nfcVitalsHistory.map {
|
nfcVitalsHistory.map {
|
||||||
Log.d("NFC", it.toString())
|
AppLogger.d("NFC", it.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nfcVitalsHistory
|
return nfcVitalsHistory
|
||||||
@@ -247,7 +246,7 @@ class ToNfcConverter(
|
|||||||
val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC"))
|
val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC"))
|
||||||
calendar.time = date
|
calendar.time = date
|
||||||
|
|
||||||
Log.d(
|
AppLogger.d(
|
||||||
"TransformationHistory",
|
"TransformationHistory",
|
||||||
"Year: ${calendar.get(Calendar.YEAR)}, " +
|
"Year: ${calendar.get(Calendar.YEAR)}, " +
|
||||||
"Month: ${calendar.get(Calendar.MONTH) + 1}, " +
|
"Month: ${calendar.get(Calendar.MONTH) + 1}, " +
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.settingsScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.R
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoggerScreen(
|
||||||
|
navController: NavController
|
||||||
|
) {
|
||||||
|
Scaffold (
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = stringResource(R.string.logs_title),
|
||||||
|
onBackClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) { contentPadding ->
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = AppLogger.getLogs(),
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-2
@@ -1,7 +1,6 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens.settingsScreen
|
package com.github.nacabaro.vbhelper.screens.settingsScreen
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -24,6 +23,7 @@ import androidx.navigation.NavController
|
|||||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
import com.github.nacabaro.vbhelper.R
|
import com.github.nacabaro.vbhelper.R
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -80,10 +80,16 @@ fun SettingsScreen(
|
|||||||
) {
|
) {
|
||||||
val browserIntent = Intent(
|
val browserIntent = Intent(
|
||||||
Intent.ACTION_VIEW,
|
Intent.ACTION_VIEW,
|
||||||
Uri.parse("https://github.com/nacabaro/vbhelper/")
|
"https://github.com/nacabaro/vbhelper/".toUri()
|
||||||
)
|
)
|
||||||
context.startActivity(browserIntent)
|
context.startActivity(browserIntent)
|
||||||
}
|
}
|
||||||
|
SettingsEntry(
|
||||||
|
title = stringResource(R.string.settings_logs_title),
|
||||||
|
description = stringResource(R.string.settings_logs_desc)
|
||||||
|
) {
|
||||||
|
navController.navigate(NavigationItems.Logger.route)
|
||||||
|
}
|
||||||
|
|
||||||
SettingsSection(title = stringResource(R.string.settings_section_data))
|
SettingsSection(title = stringResource(R.string.settings_section_data))
|
||||||
SettingsEntry(
|
SettingsEntry(
|
||||||
|
|||||||
+3
-4
@@ -1,6 +1,5 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens.settingsScreen.controllers
|
package com.github.nacabaro.vbhelper.screens.settingsScreen.controllers
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.github.cfogrady.vb.dim.card.BemCard
|
import com.github.cfogrady.vb.dim.card.BemCard
|
||||||
import com.github.cfogrady.vb.dim.card.DimCard
|
import com.github.cfogrady.vb.dim.card.DimCard
|
||||||
import com.github.cfogrady.vb.dim.card.DimReader
|
import com.github.cfogrady.vb.dim.card.DimReader
|
||||||
@@ -154,7 +153,7 @@ class CardImportController(
|
|||||||
cardId: Long,
|
cardId: Long,
|
||||||
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
|
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
|
||||||
) {
|
) {
|
||||||
Log.d("importAdventureMissions", "Importing adventure missions")
|
AppLogger.d("importAdventureMissions", "Importing adventure missions")
|
||||||
if (card is BemCard) {
|
if (card is BemCard) {
|
||||||
card.adventureLevels.levels.forEach {
|
card.adventureLevels.levels.forEach {
|
||||||
database
|
database
|
||||||
@@ -190,13 +189,13 @@ class CardImportController(
|
|||||||
cardId: Long,
|
cardId: Long,
|
||||||
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
|
card: com.github.cfogrady.vb.dim.card.Card<*, *, *, *, *, *>
|
||||||
) {
|
) {
|
||||||
Log.d("importCardFusions", "Importing card fusions")
|
AppLogger.d("importCardFusions", "Importing card fusions")
|
||||||
if (card is DimCard) {
|
if (card is DimCard) {
|
||||||
card
|
card
|
||||||
.attributeFusions
|
.attributeFusions
|
||||||
.entries
|
.entries
|
||||||
.forEach {
|
.forEach {
|
||||||
Log.d("importCardFusions", "Importing fusion: ${it.attribute1Fusion}")
|
AppLogger.d("importCardFusions", "Importing fusion: ${it.attribute1Fusion}")
|
||||||
if (it.attribute1Fusion != 65535 && it.characterIndex != 65535) {
|
if (it.attribute1Fusion != 65535 && it.characterIndex != 65535) {
|
||||||
database
|
database
|
||||||
.cardFusionsDao()
|
.cardFusionsDao()
|
||||||
|
|||||||
+2
-3
@@ -2,7 +2,6 @@ package com.github.nacabaro.vbhelper.screens.settingsScreen.controllers
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@@ -41,7 +40,7 @@ class DatabaseManagementController(
|
|||||||
componentActivity.finishAffinity()
|
componentActivity.finishAffinity()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ScanScreenController", "Error exporting database $e")
|
AppLogger.e("ScanScreenController", "Error exporting database $e")
|
||||||
componentActivity.runOnUiThread {
|
componentActivity.runOnUiThread {
|
||||||
Toast.makeText(componentActivity, "Error exporting database: ${e.message}", Toast.LENGTH_LONG).show()
|
Toast.makeText(componentActivity, "Error exporting database: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
@@ -84,7 +83,7 @@ class DatabaseManagementController(
|
|||||||
componentActivity.finishAffinity()
|
componentActivity.finishAffinity()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ScanScreenController", "Error importing database $e")
|
AppLogger.e("ScanScreenController", "Error importing database $e")
|
||||||
componentActivity.runOnUiThread {
|
componentActivity.runOnUiThread {
|
||||||
Toast.makeText(componentActivity, "Error importing database: ${e.message}", Toast.LENGTH_LONG).show()
|
Toast.makeText(componentActivity, "Error importing database: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens.spriteViewer
|
package com.github.nacabaro.vbhelper.screens.spriteViewer
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -13,7 +12,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -27,8 +25,6 @@ fun SpriteViewer(
|
|||||||
) {
|
) {
|
||||||
val spriteList = remember { mutableStateListOf<Bitmap>() }
|
val spriteList = remember { mutableStateListOf<Bitmap>() }
|
||||||
|
|
||||||
Log.d("SpriteViewer", "spriteList: $spriteList")
|
|
||||||
|
|
||||||
LaunchedEffect(spriteViewerController) {
|
LaunchedEffect(spriteViewerController) {
|
||||||
val sprites = spriteViewerController.getAllSprites()
|
val sprites = spriteViewerController.getAllSprites()
|
||||||
val bitmapData = spriteViewerController.convertToBitmap(sprites)
|
val bitmapData = spriteViewerController.convertToBitmap(sprites)
|
||||||
|
|||||||
+13
-16
@@ -1,13 +1,11 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens.spriteViewer
|
package com.github.nacabaro.vbhelper.screens.spriteViewer
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
|
||||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
import com.github.nacabaro.vbhelper.domain.characters.Sprite
|
import com.github.nacabaro.vbhelper.domain.characters.Sprite
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import androidx.core.graphics.createBitmap
|
||||||
|
|
||||||
class SpriteViewerControllerImpl(
|
class SpriteViewerControllerImpl(
|
||||||
private val context: ComponentActivity
|
private val context: ComponentActivity
|
||||||
@@ -24,41 +22,40 @@ class SpriteViewerControllerImpl(
|
|||||||
val bitmapList = mutableListOf<Bitmap>()
|
val bitmapList = mutableListOf<Bitmap>()
|
||||||
|
|
||||||
for (sprite in sprites) {
|
for (sprite in sprites) {
|
||||||
Log.d("SpriteViewer", "sprite: $sprite")
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle1))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle1))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle2))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle2))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk1))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk1))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk2))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk2))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun1))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun1))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun2))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun2))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain1))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain1))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain2))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain2))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteHappy))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteHappy))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteSleep))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteSleep))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteAttack))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteAttack))
|
||||||
})
|
})
|
||||||
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
bitmapList.add(createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteDodge))
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteDodge))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens.storageScreen
|
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.gestures.Orientation
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.gestures.scrollable
|
import androidx.compose.foundation.gestures.scrollable
|
||||||
@@ -149,7 +148,7 @@ fun StorageScreen(
|
|||||||
.deleteCharacter(
|
.deleteCharacter(
|
||||||
characterId = selectedCharacter!!,
|
characterId = selectedCharacter!!,
|
||||||
onCompletion = {
|
onCompletion = {
|
||||||
Log.d("StorageScreen", "Character deleted")
|
AppLogger.d("StorageScreen", "Character deleted")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
selectedCharacter = null
|
selectedCharacter = null
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.source
|
|
||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
|
||||||
import androidx.datastore.preferences.core.edit
|
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
|
||||||
import androidx.datastore.preferences.core.longPreferencesKey
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
|
|
||||||
class AuthRepository(
|
|
||||||
private val dataStore: DataStore<Preferences>
|
|
||||||
) {
|
|
||||||
private companion object {
|
|
||||||
val IS_AUTHENTICATED = booleanPreferencesKey("is_authenticated")
|
|
||||||
val AUTH_TOKEN = stringPreferencesKey("auth_token") // Nacatech token (for re-authentication)
|
|
||||||
val SESSION_TOKEN = stringPreferencesKey("session_token") // Session token (for API calls)
|
|
||||||
val USER_ID = longPreferencesKey("user_id")
|
|
||||||
}
|
|
||||||
|
|
||||||
val isAuthenticated: Flow<Boolean> = dataStore.data
|
|
||||||
.map { preferences ->
|
|
||||||
preferences[IS_AUTHENTICATED] ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
val authToken: Flow<String?> = dataStore.data
|
|
||||||
.map { preferences ->
|
|
||||||
preferences[AUTH_TOKEN]
|
|
||||||
}
|
|
||||||
|
|
||||||
val sessionToken: Flow<String?> = dataStore.data
|
|
||||||
.map { preferences ->
|
|
||||||
preferences[SESSION_TOKEN]
|
|
||||||
}
|
|
||||||
|
|
||||||
val userId: Flow<Long?> = dataStore.data
|
|
||||||
.map { preferences ->
|
|
||||||
preferences[USER_ID]
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun setAuthenticated(isAuthenticated: Boolean, nacatechToken: String? = null, sessionToken: String? = null, userId: Long? = null) {
|
|
||||||
dataStore.edit { preferences ->
|
|
||||||
preferences[IS_AUTHENTICATED] = isAuthenticated
|
|
||||||
if (nacatechToken != null) {
|
|
||||||
preferences[AUTH_TOKEN] = nacatechToken
|
|
||||||
}
|
|
||||||
if (sessionToken != null) {
|
|
||||||
preferences[SESSION_TOKEN] = sessionToken
|
|
||||||
}
|
|
||||||
if (userId != null) {
|
|
||||||
preferences[USER_ID] = userId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun logout() {
|
|
||||||
dataStore.edit { preferences ->
|
|
||||||
preferences[IS_AUTHENTICATED] = false
|
|
||||||
preferences.remove(AUTH_TOKEN)
|
|
||||||
preferences.remove(SESSION_TOKEN)
|
|
||||||
preferences.remove(USER_ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:pathData="M480,760q66,0 113,-47t47,-113v-160q0,-66 -47,-113t-113,-47q-66,0 -113,47t-47,113v160q0,66 47,113t113,47ZM400,640h160v-80L400,560v80ZM400,480h160v-80L400,400v80ZM480,520ZM480,840q-65,0 -120.5,-32T272,720L160,720v-80h84q-3,-20 -3.5,-40t-0.5,-40h-80v-80h80q0,-20 0.5,-40t3.5,-40h-84v-80h112q14,-23 31.5,-43t40.5,-35l-64,-66 56,-56 86,86q28,-9 57,-9t57,9l88,-86 56,56 -66,66q23,15 41.5,34.5T688,320h112v80h-84q3,20 3.5,40t0.5,40h80v80h-80q0,20 -0.5,40t-3.5,40h84v80L688,720q-32,56 -87.5,88T480,840Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
||||||
@@ -225,5 +225,9 @@
|
|||||||
<string name="home_special_mission_delete_main">Are you sure you want to delete this special mission with this progress?</string>
|
<string name="home_special_mission_delete_main">Are you sure you want to delete this special mission with this progress?</string>
|
||||||
<string name="home_special_mission_delete_dismiss">Dismiss</string>
|
<string name="home_special_mission_delete_dismiss">Dismiss</string>
|
||||||
<string name="home_special_mission_delete_remove">Remove</string>
|
<string name="home_special_mission_delete_remove">Remove</string>
|
||||||
|
<string name="nav_logs">Logs</string>
|
||||||
|
<string name="settings_logs_title">Log viewer</string>
|
||||||
|
<string name="settings_logs_desc">Check logs generated within the app</string>
|
||||||
|
<string name="logs_title">Application logs</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user