diff --git a/app/src/main/assets/En/skill_strengthened.png b/app/src/main/assets/En/skill_strengthened.png new file mode 100644 index 000000000..02cbfbd4d Binary files /dev/null and b/app/src/main/assets/En/skill_strengthened.png differ diff --git a/app/src/main/assets/En/skill_unstrengthened.png b/app/src/main/assets/En/skill_unstrengthened.png new file mode 100644 index 000000000..62bfe3e9b Binary files /dev/null and b/app/src/main/assets/En/skill_unstrengthened.png differ diff --git a/app/src/main/assets/Jp/skill_strengthened.png b/app/src/main/assets/Jp/skill_strengthened.png new file mode 100644 index 000000000..02cbfbd4d Binary files /dev/null and b/app/src/main/assets/Jp/skill_strengthened.png differ diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/exit/BattleExit.kt b/app/src/main/java/io/github/fate_grand_automata/ui/exit/BattleExit.kt index fbc59068c..a0abea214 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/exit/BattleExit.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/exit/BattleExit.kt @@ -77,6 +77,7 @@ private fun AutoBattle.ExitReason.text(): String = when (this) { AutoBattle.ExitReason.FirstClearRewards -> stringResource(R.string.first_clear_rewards) AutoBattle.ExitReason.Paused -> stringResource(R.string.script_paused) AutoBattle.ExitReason.StopAfterThisRun -> stringResource(R.string.stop_after_this_run) + is AutoBattle.ExitReason.StrengthenedSkillEmpty -> stringResource(R.string.strengthened_skill_empty, skill, requirement) } @Composable diff --git a/app/src/main/java/io/github/fate_grand_automata/ui/pref_support/PreferredSupportScreen.kt b/app/src/main/java/io/github/fate_grand_automata/ui/pref_support/PreferredSupportScreen.kt index 537171cbe..dfa2946a9 100644 --- a/app/src/main/java/io/github/fate_grand_automata/ui/pref_support/PreferredSupportScreen.kt +++ b/app/src/main/java/io/github/fate_grand_automata/ui/pref_support/PreferredSupportScreen.kt @@ -8,18 +8,23 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.AlertDialog import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults.cardElevation +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -71,6 +76,21 @@ private fun PreferredSupport( val prefServants by config.preferredServants.remember() val prefCEs by config.preferredCEs.remember() + // todo: remove this note later when the feature is working fine on all servers + var showStrengthenedNote by androidx.compose.runtime.remember { mutableStateOf(false) } + if (showStrengthenedNote) { + AlertDialog( + onDismissRequest = { showStrengthenedNote = false }, + confirmButton = { + TextButton(onClick = { showStrengthenedNote = false }) { + Text(stringResource(android.R.string.ok)) + } + }, +// title = { Text(stringResource(R.string.note)) }, + text = { Text(stringResource(R.string.p_strengthened_skills_note)) } + ) + } + LazyColumn { item { Heading(stringResource(R.string.p_support_mode_preferred)) @@ -124,6 +144,34 @@ private fun PreferredSupport( ) ) } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.weight(1f) + ) { + Text(stringResource(R.string.p_strengthened_skills)) + Icon( + painter = painterResource(R.drawable.ic_info), + contentDescription = "Info", + modifier = Modifier + .padding(start = 8.dp) + .size(20.dp) + .clickable { showStrengthenedNote = true }, + tint = MaterialTheme.colorScheme.secondary + ) + } + + StrengthenedSkills( + strengthenedSkills = listOf( + config.skill1Strengthened, + config.skill2Strengthened, + config.skill3Strengthened + ) + ) + } } } } @@ -289,4 +337,37 @@ private fun MaxSkills( } } } +} + +@Composable +private fun StrengthenedSkills( + strengthenedSkills: List> +) { + Row(verticalAlignment = Alignment.CenterVertically) { + strengthenedSkills.forEachIndexed { index, pref -> + if (index != 0) { + Text("/", modifier = Modifier.padding(horizontal = 8.dp)) + } + + var strengthened by pref.remember() + + Card( + elevation = cardElevation(5.dp), + colors = CardDefaults.cardColors( + containerColor = if (strengthened > 0) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.surfaceVariant, + contentColor = if (strengthened > 0) MaterialTheme.colorScheme.onSecondary else MaterialTheme.colorScheme.onSurfaceVariant + ) + ) { + Box( + contentAlignment = Alignment.Center, +// change to '(rankUp + 1) % 4' in future if there's 3 rank up quests on the same skill + modifier = Modifier + .clickable { strengthened = (strengthened + 1) % 3 } + .size(40.dp) + ) { + Text(strengthened.toString()) + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/values/localized.xml b/app/src/main/res/values/localized.xml index 75aacc6b7..d6ca06e4f 100644 --- a/app/src/main/res/values/localized.xml +++ b/app/src/main/res/values/localized.xml @@ -114,6 +114,9 @@ Skills" "Friend name images can be added using 'Support Image Maker' script" "Also check All" "Max Skills" + "Strengthened Skills" + "This feature has been tested on JP and NA servers. Contact GitHub/Discord if it is not working on your server." + "Strengthening %2$d may not be available for Skill %1$d. Please check your configuration." "Game Server" "Auto-detect" "Danger mode requires Auto-targeting" diff --git a/prefs/src/main/java/io/github/fate_grand_automata/prefs/SupportPreferences.kt b/prefs/src/main/java/io/github/fate_grand_automata/prefs/SupportPreferences.kt index ec19cebe7..5b905106b 100644 --- a/prefs/src/main/java/io/github/fate_grand_automata/prefs/SupportPreferences.kt +++ b/prefs/src/main/java/io/github/fate_grand_automata/prefs/SupportPreferences.kt @@ -34,6 +34,10 @@ internal class SupportPreferences( override val skill2Max by prefs.skill2Max override val skill3Max by prefs.skill3Max + override val skill1Strengthened by prefs.skill1Strengthened + override val skill2Strengthened by prefs.skill2Strengthened + override val skill3Strengthened by prefs.skill3Strengthened + override val grandServant by prefs.grandServant override val bondCEEffect by prefs.bondCEEffect override val requireBothNormalAndRewardMatch by prefs.requireBothNormalAndRewardMatch diff --git a/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/SupportPrefsCore.kt b/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/SupportPrefsCore.kt index 0bb492c89..888201850 100644 --- a/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/SupportPrefsCore.kt +++ b/prefs/src/main/java/io/github/fate_grand_automata/prefs/core/SupportPrefsCore.kt @@ -36,6 +36,10 @@ class SupportPrefsCore( val skill2Max = maker.bool("support_skill_max_2") val skill3Max = maker.bool("support_skill_max_3") + val skill1Strengthened = maker.int("support_skill_strengthened_1") + val skill2Strengthened = maker.int("support_skill_strengthened_2") + val skill3Strengthened = maker.int("support_skill_strengthened_3") + val grandServant = maker.bool("support_grand_servant") val bondCEEffect = maker.enum( "support_bond_ce_effect", diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/Images.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/Images.kt index e06078b59..3bcd2128a 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/Images.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/Images.kt @@ -94,4 +94,6 @@ enum class Images(val path: String) { GrandCeLabel("grand_ce_label.png"), BondCeEffectDefault("bond_ce_effect_default.png"), BondCeEffectNP("bond_ce_effect_np.png"), + SkillStrengthened("skill_strengthened.png"), + SkillUnstrengthened("skill_unstrengthened.png"), } \ No newline at end of file diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/entrypoints/AutoBattle.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/entrypoints/AutoBattle.kt index 8f163d919..bc37fb345 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/entrypoints/AutoBattle.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/entrypoints/AutoBattle.kt @@ -74,6 +74,8 @@ class AutoBattle @Inject constructor( class CardPriorityParseError(val msg: String) : ExitReason() data object Paused : ExitReason() data object StopAfterThisRun : ExitReason() + // Inside sealed class ExitReason + class StrengthenedSkillEmpty(val skill: Int, val requirement: Int) : ExitReason() } internal class BattleExitException(val reason: ExitReason) : Exception(reason.cause) diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/locations/SupportScreenLocations.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/locations/SupportScreenLocations.kt index ed4d96c3b..9ad3edb17 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/locations/SupportScreenLocations.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/locations/SupportScreenLocations.kt @@ -66,6 +66,8 @@ class SupportScreenLocations @Inject constructor( val listSwipeStart = Location(-59, if (canLongSwipe) 1000 else 1190) + supportOffset val listSwipeEnd = Location(-89, if (canLongSwipe) 300 else 660) + supportOffset + val strengthenedSkillRegion = Region(0, 0, 24, 24) + supportOffset + fun locate(supportClass: SupportClass) = when (supportClass) { SupportClass.None -> 0 SupportClass.All -> 184 diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/ISupportPreferences.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/ISupportPreferences.kt index 6ab1b3871..1ea361529 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/ISupportPreferences.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/prefs/ISupportPreferences.kt @@ -21,6 +21,10 @@ interface ISupportPreferences { val skill2Max: Boolean val skill3Max: Boolean + val skill1Strengthened: Int + val skill2Strengthened: Int + val skill3Strengthened: Int + val grandServant: Boolean val bondCEEffect: BondCEEffectEnum val requireBothNormalAndRewardMatch: Boolean diff --git a/scripts/src/main/java/io/github/fate_grand_automata/scripts/supportSelection/ServantSelection.kt b/scripts/src/main/java/io/github/fate_grand_automata/scripts/supportSelection/ServantSelection.kt index ae31b4c62..5f6019d37 100644 --- a/scripts/src/main/java/io/github/fate_grand_automata/scripts/supportSelection/ServantSelection.kt +++ b/scripts/src/main/java/io/github/fate_grand_automata/scripts/supportSelection/ServantSelection.kt @@ -5,6 +5,7 @@ import io.github.fate_grand_automata.scripts.IFgoAutomataApi import io.github.fate_grand_automata.scripts.Images import io.github.fate_grand_automata.scripts.ScriptLog import io.github.fate_grand_automata.scripts.prefs.ISupportPreferences +import io.github.fate_grand_automata.scripts.entrypoints.AutoBattle import io.github.lib_automata.Location import io.github.lib_automata.Pattern import io.github.lib_automata.Region @@ -49,6 +50,17 @@ class ServantSelection @Inject constructor( !skillCheckNeeded || checkMaxedSkills(needMaxedSkills, whichSkillsAreMaxed(bounds.region)) } + .filter { + val needStrengthenedSkills = listOf( + supportPrefs.skill1Strengthened.coerceIn(0..2), + supportPrefs.skill2Strengthened.coerceIn(0..2), + supportPrefs.skill3Strengthened.coerceIn(0..2) + ) + + val skillStrengthenedCheckNeeded = needStrengthenedSkills.any { it > 0} + !skillStrengthenedCheckNeeded || checkStrengthenedSkills(bounds.region, needStrengthenedSkills).all{ it } + } + return matched.isNotEmpty() } @@ -102,7 +114,7 @@ class ServantSelection @Inject constructor( skillRegion.exists(images[Images.SkillTen], similarity = 0.68) } } - + private fun checkMaxedSkills(expectedSkills: List, actualSkills: List): Boolean { val result = expectedSkills .zip(actualSkills) { expected, actual -> @@ -118,4 +130,38 @@ class ServantSelection @Inject constructor( return result.all { it } } -} \ No newline at end of file + + /** + * Check if the skill is strengthened(rank-up quest cleared) + * Currently restricted to level 2 (2 rank-up quests) for each skill, can modify in UI to allow more + */ + private fun checkStrengthenedSkills(bounds: Region, needStrengthenedSkills: List): List { + val skillMargin = 90 + val rankUpMargin = 18 + + /** + * When the servant portrait is found near the bottom of the screen + * return false to skip detection of 'Images.SkillUnstrengthened' + * to avoid false exception + */ + if (bounds.y > 1000) return List(needStrengthenedSkills.size) { false } + + return needStrengthenedSkills.mapIndexed { index, requirement -> + if (requirement <= 0) return@mapIndexed true + + val strengthenedSkillLocation = Location( + bounds.x + 1650 + index * skillMargin, + bounds.y + 351 - (requirement - 1) * rankUpMargin + ) + val strengthenedSkillRegion = locations.support.strengthenedSkillRegion.copy(x = strengthenedSkillLocation.x, y = strengthenedSkillLocation.y) + when{ + strengthenedSkillRegion.exists(images[Images.SkillStrengthened], similarity = 0.68) -> true + strengthenedSkillRegion.exists(images[Images.SkillUnstrengthened], similarity = 0.68) -> false + else -> throw AutoBattle.BattleExitException( + // 'Images.SkillUnstrengthened' not found, exit script and prompt user to check configuration + AutoBattle.ExitReason.StrengthenedSkillEmpty(index + 1, requirement) + ) + } + } + } +}