@@ -1129,50 +1129,76 @@ def formation_handler(
11291129 formation_number (int): The formation number to load. Defaults to 1.
11301130 already_open (bool): Whether the formations menu is already open. Defaults to False.
11311131 """
1132- if self .metadata .load_formations is False :
1133- self .logger .info ("Formation loading disabled" )
1134- return
1132+ try :
1133+ if self .metadata .load_formations is False :
1134+ self .logger .info ("Formation loading disabled" )
1135+ return
1136+
1137+ if self .metadata .formation > 7 :
1138+ self .logger .info (
1139+ "Formation selected higher than 7, starting from 1 again.."
1140+ )
1141+ self .metadata .formation = 1
11351142
1136- if self .metadata .formation > 7 :
11371143 self .logger .info (
1138- "Formation selected higher than 7, starting from 1 again.."
1144+ "Loading formation #" + str ( math . trunc ( self . metadata . formation ))
11391145 )
1140- self .metadata .formation = 1
1141-
1142- self .logger .info (
1143- "Loading formation #" + str (math .trunc (self .metadata .formation ))
1144- )
1145- counter = 1
1146- unowned_counter = 0
1147- self .wait ()
1148- if already_open is False : # Sometimes we're already in the formations menu
1149- self .click ("buttons/records" , seconds = 3 )
1150- while counter != formation_number :
1151- self .click_xy (1000 , 1025 )
1152- counter += 1
1153-
1154- self .click ("buttons/copy" , seconds = 2 )
1155- # Handle 'Hero not owned' popup
1156- if self .is_visible ("labels/not_owned" ):
1157- while self .is_visible (
1158- "labels/not_owned"
1159- ): # Try next formation and check again
1160- self .logger .info ("Hero/Artifact not owned, trying next formation.." )
1161- self .click_xy (360 , 1250 )
1146+ counter = 1
1147+ unowned_counter = 0
1148+ self .wait ()
1149+
1150+ if already_open is False : # Sometimes we're already in the formations menu
1151+ # Try multiple regions to find records button
1152+ records_regions = [
1153+ self .metadata .regions ["bottom_buttons" ],
1154+ (0 , 1600 , 1080 , 320 ),
1155+ ]
1156+ records_clicked = False
1157+ for region in records_regions :
1158+ if self .is_visible ("buttons/records" , region = region , seconds = 0 , retry = 5 , confidence = 0.5 , click = True ):
1159+ records_clicked = True
1160+ break
1161+ if not records_clicked :
1162+ self .click ("buttons/records" , seconds = 3 )
1163+ self .wait (3 )
1164+
1165+ while counter != formation_number :
11621166 self .click_xy (1000 , 1025 )
1163- self .click ("buttons/copy" )
1164- self .metadata .formation += 1
1165- unowned_counter += 1
1166- if unowned_counter > 7 :
1167- self .logger .info ("All formations contained an unowned hero!" )
1168- self .click_location (
1169- "neutral"
1170- ) # Close windows back to battle screen
1171- self .click_location (
1172- "neutral"
1173- ) # Close windows back to battle screen
1174- break
1175- self .click ("buttons/confirm" , suppress = True , seconds = 0 )
1167+ counter += 1
1168+ self .wait (0.5 ) # Small wait between clicks
1169+
1170+ if not self .is_visible ("buttons/copy" , seconds = 0 , retry = 5 , click = True ):
1171+ self .click ("buttons/copy" , seconds = 2 )
1172+
1173+ self .wait (1 ) # Wait for any popups
1174+
1175+ # Handle 'Hero not owned' popup
1176+ if self .is_visible ("labels/not_owned" , seconds = 0 , retry = 1 ):
1177+ while self .is_visible (
1178+ "labels/not_owned" ,
1179+ seconds = 0 ,
1180+ retry = 1 ,
1181+ ): # Try next formation and check again
1182+ self .logger .info ("Hero/Artifact not owned, trying next formation.." )
1183+ self .click_xy (360 , 1250 )
1184+ self .click_xy (1000 , 1025 )
1185+ self .click ("buttons/copy" )
1186+ self .metadata .formation += 1
1187+ unowned_counter += 1
1188+ if unowned_counter > 7 :
1189+ self .logger .info ("All formations contained an unowned hero!" )
1190+ self .click_location ("neutral" )
1191+ self .click_location ("neutral" )
1192+ break
1193+
1194+ # Note: In updated UI, there's no confirm button after copy - formation is loaded directly
1195+ self .wait (1 )
1196+ self .click_location ("neutral" )
1197+ self .wait (0.5 )
1198+ except Exception as e :
1199+ self .logger .error (f"Error in formation_handler: { e } " , exc_info = True )
1200+ self .save_screenshot ("formation_handler_error" )
1201+ raise
11761202
11771203 def blind_push (
11781204 self ,
@@ -1510,12 +1536,21 @@ def blind_push(
15101536 # For pushing afk stages
15111537 if mode == "afkstages" :
15121538 timeout = 0
1513- if self .is_visible (
1514- "buttons/records" ,
1515- region = self .metadata .regions ["bottom_buttons" ],
1516- seconds = 0 ,
1517- retry = 20 ,
1518- ):
1539+
1540+ # Try multiple regions to detect AFK Stages screen
1541+ records_regions = [
1542+ (self .metadata .regions ["bottom_buttons" ], 20 ),
1543+ ((0 , 1600 , 1080 , 320 ), 10 ),
1544+ ((600 , 600 , 480 , 400 ), 10 ),
1545+ ((0 , 0 , 1080 , 1920 ), 5 ),
1546+ ]
1547+ records_found = False
1548+ for region , retries in records_regions :
1549+ if self .is_visible ("buttons/records" , region = region , seconds = 0 , retry = retries , confidence = 0.5 ):
1550+ records_found = True
1551+ break
1552+
1553+ if records_found :
15191554
15201555 # Change formation if we we beat the 2nd round or have defeat >10 times in a row
15211556 if (
@@ -1547,8 +1582,6 @@ def blind_push(
15471582 elif load_formation is True :
15481583 self .formation_handler (self .metadata .formation )
15491584
1550- # Season 3 single stage code
1551-
15521585 # Start Battle
15531586 self .click (
15541587 "buttons/battle" ,
@@ -1562,39 +1595,67 @@ def blind_push(
15621595 ) # Long wait to stop false positives from the back button on the battle selection screen
15631596
15641597 # Wait til we see the back button in the post battle screen before running next checks
1598+ timeout = 0
15651599 while not self .is_visible (
15661600 "buttons/back" ,
15671601 region = self .metadata .regions ["bottom_buttons" ],
1568- seconds = 2 ,
1602+ seconds = 0 ,
1603+ retry = 1 ,
15691604 ):
1605+ self .wait (2 ) # Wait 2 seconds between each check
15701606 timeout += 1
1571- if (
1572- timeout > 30
1573- ): # If nothing at 30 seconds start clicking in case battery saver mode is active
1574- self .click_location ("neutral" )
1575- if (
1576- timeout > 60
1577- ): # Still nothing at 60 seconds? Quit as somethings gone wrong
1578- self .logger .info ("Battle timeout error!" )
1607+ if timeout > 45 : # Battle is 90 seconds, if still not done, something went wrong
1608+ self .logger .error ("Battle timeout error! Could not detect battle completion after 90 seconds." )
1609+ self .save_screenshot ("battle_timeout_error" )
15791610 break
15801611
15811612 # Post battle screen detection
15821613 result = ""
1583- while result == "" :
1584- # Loop the different scenarios until we get an image match ('retry' is defeat, 'battle' is normal stage victory, 'talent_trials' is talent stage victory)
1585- images = [
1586- "buttons/retry" ,
1587- "buttons/battle" ,
1588- "buttons/talent_trials" ,
1589- ]
1590- result = self .is_visible_array (
1591- images ,
1592- confidence = 0.9 ,
1593- seconds = 0 ,
1594- retry = 1 ,
1595- click = True ,
1596- region = self .metadata .regions ["bottom_buttons" ],
1597- )
1614+ result_timeout = 0
1615+ max_result_timeout = 20 # Maximum 20 attempts (40 seconds)
1616+
1617+ images = ["buttons/retry" , "buttons/battle" , "buttons/talent_trials" , "buttons/p_challenge" ]
1618+ search_regions = [
1619+ (0 , 1500 , 1080 , 420 ),
1620+ self .metadata .regions ["bottom_buttons" ],
1621+ (0 , 0 , 1080 , 1920 ),
1622+ ]
1623+
1624+ while result == "" and result_timeout < max_result_timeout :
1625+ for region in search_regions :
1626+ result = self .is_visible_array (
1627+ images ,
1628+ seconds = 0 ,
1629+ retry = 1 ,
1630+ click = True ,
1631+ region = region ,
1632+ )
1633+
1634+ if result != "" and result != "not_found" :
1635+ break
1636+
1637+ if result == "not_found" or result == "" :
1638+ result = "" # Reset to continue loop
1639+ result_timeout += 1
1640+ self .wait (2 )
1641+ else :
1642+ break
1643+
1644+ if result == "" or result == "not_found" :
1645+ self .logger .error ("Could not detect battle result! Trying individual button detection..." )
1646+ self .save_screenshot ("battle_result_detection_error" )
1647+
1648+ # Try individual button detection
1649+ buttons_to_check = ["buttons/retry" , "buttons/battle" , "buttons/talent_trials" , "buttons/p_challenge" ]
1650+ for btn in buttons_to_check :
1651+ if self .is_visible (btn , seconds = 0 , retry = 1 , region = (0 , 0 , 1080 , 1920 )):
1652+ result = btn
1653+ break
1654+
1655+ if result == "" or result == "not_found" :
1656+ self .logger .error ("Could not detect any battle result button. Exiting..." )
1657+ self .logger .error ("Please check the screenshot: battle_result_detection_error.png" )
1658+ return
15981659
15991660 # Retry button indicates defeat, we run the defeat logic
16001661 if result == "buttons/retry" :
@@ -1604,17 +1665,40 @@ def blind_push(
16041665 )
16051666 self .blind_push ("afkstages" , load_formation = False )
16061667
1607- # The other two mean we have a victory
1608- elif result == "buttons/battle" or result == "buttons/talent_trials" :
1668+ # Victory buttons: battle, talent_trials, or p_challenge
1669+ elif result == "buttons/battle" or result == "buttons/talent_trials" or result == "buttons/p_challenge" :
16091670 self .metadata .stage_defeats = 0 # Reset defeats
16101671 self .metadata .formation = 1 # Reset formation
16111672 self .logger .info ("Victory! Stage passed\n " )
16121673 self .metadata .first_stage_won = False
16131674 self .blind_push ("afkstages" , load_formation = True )
1614- else :
1615- self .logger .info ("Something went wrong opening AFK Stages!" )
1675+ if not records_found :
1676+ self .logger .error (
1677+ "Failed to detect AFK Stages screen! "
1678+ "Expected 'buttons/records' button not found after multiple search attempts. "
1679+ "This usually means the stage selection screen did not open correctly."
1680+ )
1681+ self .logger .debug (
1682+ "Searched for 'buttons/records' in: "
1683+ f"1. bottom_buttons region: { self .metadata .regions ['bottom_buttons' ]} , "
1684+ f"2. extended region: (0, 1500, 1080, 420), "
1685+ f"3. full screen: (0, 0, 1080, 1920)"
1686+ )
1687+ self .logger .info ("Checking if we're on a different screen..." )
1688+
1689+ # Try to detect what screen we're actually on
1690+ if self .is_visible ("labels/sunandstars" , region = self .metadata .regions ["sunandstars" ], seconds = 0 , retry = 1 ):
1691+ self .logger .warning ("Detected main screen instead of AFK Stages screen. Stage selection may have failed." )
1692+ elif self .is_visible ("buttons/back" , region = self .metadata .regions ["bottom_buttons" ], seconds = 0 , retry = 1 ):
1693+ self .logger .warning ("Detected back button but not records button. May be on wrong screen." )
1694+
16161695 self .save_screenshot ("afk_stage_error" )
1617- self .recover ()
1696+ self .logger .info ("Attempting to recover to main screen..." )
1697+ recovery_result = self .recover ()
1698+ if recovery_result :
1699+ self .logger .info ("Recovery successful, returned to main screen" )
1700+ else :
1701+ self .logger .error ("Recovery failed, could not return to main screen" )
16181702
16191703 def open_afk_stages (self , afkstages : bool = True ) -> None :
16201704 """Opens the AFK or Talent Stages based on the provided flag.
@@ -1627,6 +1711,8 @@ def open_afk_stages(self, afkstages: bool = True) -> None:
16271711 afkstages (bool): If True, opens the standard AFK Stages. If False, opens the
16281712 Talent Stages.
16291713 """
1714+ stage_type = "AFK Stages" if afkstages else "Talent Stages"
1715+
16301716 # Open afk stage screen without prompting loot if it's >1h uncollected
16311717 self .click_xy (450 , 1825 , seconds = 3 )
16321718 self .click (
@@ -1650,8 +1736,11 @@ def open_afk_stages(self, afkstages: bool = True) -> None:
16501736 + str (self .config .getint ("PUSHING" , "defeat_limit" ))
16511737 + " defeats\n "
16521738 )
1653- self .click_xy (370 , 1600 , seconds = 2 ) # AFK Stage button
1739+ self .click_xy (370 , 1600 , seconds = 2 ) # Talent Stage button
16541740 self .click ("buttons/confirm" , suppress = True )
1741+
1742+ # Give the screen time to load after clicking
1743+ self .wait (2 )
16551744
16561745 def afk_stage_chain_proxy (self ) -> None :
16571746 """Starts an AFK Stage chain by attempting to start the stage and then
0 commit comments