diff --git a/AutoAFK2.py b/AutoAFK2.py
index 1ddc57c..d75e3dc 100644
--- a/AutoAFK2.py
+++ b/AutoAFK2.py
@@ -24,7 +24,7 @@
 first_stage_won = False
 
 # placeholder gets replaced during build process with release tag.
-version = '3.1.1'
+version = '3.2.1'
 
 # Current time in UTC for tracking which towers/events are open
 currenttimeutc = datetime.now(timezone.utc)
@@ -50,6 +50,7 @@
 parser.add_argument("-afkt", action='store_true', help="Run AFK Talent Stages")
 parser.add_argument("-test", action='store_true', help="Used for testing functions")
 parser.add_argument("-charms", action='store_true', help="Run the Dura's Trials function")
+parser.add_argument("-proxy", action='store_true', help="Run the Chain AFK Proxy function")
 parser.add_argument("-fs", "--formation_skip", action='store_true', help="Don't load formations")
 # Configurations
 parser.add_argument("-s", "--server", choices=['global', 'vn'], default='global', help="Select alernative game servers")
@@ -158,7 +159,8 @@ def handle_exception(exc_type, exc_value, exc_traceback):
 # Boot up activities before tasks are ran
 connect_and_launch(port=config.get('ADVANCED', 'port'), server=globals()['server'])
 resolutionCheck()
-waitUntilGameActive()
+if not args['proxy']:
+    waitUntilGameActive()
 
 # TODO single SA battle and daily GS collection
 def dailies():
@@ -583,30 +585,36 @@ def level_up():
     logger.info('Levelling available heroes')
     safe_open_and_close(name=inspect.currentframe().f_code.co_name, state='open')
 
-    # Open Heroes Hall
-    clickXY(650, 1850, seconds=3)
-
     # Clicks the central button when leveling uses Dust rather than XP, then presses back to return to the selected hero screen
     def dust_level():
         if isVisible('buttons/level_up', region=(500, 1725, 260, 100), seconds=0):  # Region in the centre for the 10th level 'Level Up' button
             click('buttons/level_up', region=regions['bottom_third'], seconds=4)
-            # click('buttons/back', region=regions['bottom_third'])
-
-    # Click to open hero
-    while isVisible('buttons/levelup_double', region=regions['levelup']) or isVisible('buttons/levelup_single', region=regions['levelup']):
-        logger.info('Hero found!')
-        click('buttons/levelup_double', region=regions['levelup'], suppress=True, retry=1)
-        click('buttons/levelup_single', region=regions['levelup'], suppress=True, retry=1)
-        # Keep clicking to level
-        while isVisible('buttons/levelup_double', region=regions['levelup_hero'], seconds=0):
-            dust_level()
-            swipe(800, 1800, 800, 1800, 5000)  # Hacky way to hold it down
-            dust_level()
-        while isVisible('buttons/levelup_single', region=regions['levelup_hero'], seconds=0):
-            dust_level()
-            swipe(800, 1800, 800, 1800, 5000)  # Hacky way to hold it down
-            dust_level()
-        click('buttons/back', region=regions['bottom_third'], seconds=3, suppress=True, retry=1) # Back to Hero Box
+
+    # Open Heroes Hall
+    clickXY(650, 1850, seconds=3)
+
+    # Level up all if enabled
+    if config.getboolean('ADVANCED', 'use_level_up_all'):
+        while isVisible('buttons/level_up_all_active', region=regions['bottom_third'], confidence=0.92):
+            click('buttons/level_up_all_active', region=regions['bottom_third'], confidence=0.92) # sligtly higher confidence as inactive is the same but greyscale
+            logger.info('Level up all clicked!')
+    # Else we level the heros individually
+    else:
+        # Click to open hero
+        while isVisible('buttons/levelup_double', region=regions['levelup']) or isVisible('buttons/levelup_single', region=regions['levelup']):
+            logger.info('Hero found!')
+            click('buttons/levelup_double', region=regions['levelup'], suppress=True, retry=1)
+            click('buttons/levelup_single', region=regions['levelup'], suppress=True, retry=1)
+            # Keep clicking to level
+            while isVisible('buttons/levelup_double', region=regions['levelup_hero'], seconds=0):
+                dust_level()
+                swipe(800, 1800, 800, 1800, 5000)  # Hacky way to hold it down
+                dust_level()
+            while isVisible('buttons/levelup_single', region=regions['levelup_hero'], seconds=0):
+                dust_level()
+                swipe(800, 1800, 800, 1800, 5000)  # Hacky way to hold it down
+                dust_level()
+            click('buttons/back', region=regions['bottom_third'], seconds=3, suppress=True, retry=1) # Back to Hero Box
 
     logger.info('Done!')
 
@@ -657,16 +665,16 @@ def claim_and_collect(italics=True):
         # if isVisible('buttons/claim_all_italics', click=True, region=regions['bottom_third']):
         #     clickXY(1000, 1800) # Clear Loot
 
-    # Fabled Road
-    logger.info('    Checking Fabled Road')
-    if isVisible('buttons/fabled_road_active', region=regions['bottom_third'], seconds=2, grayscale=True) or isVisible('buttons/fabled_road_inactive', region=regions['bottom_third'], click=True, seconds=2, grayscale=True):
+    # # Fabled Road
+    # logger.info('    Checking Fabled Road')
+    # if isVisible('buttons/fabled_road_active', region=regions['bottom_third'], seconds=2, grayscale=True) or isVisible('buttons/fabled_road_inactive', region=regions['bottom_third'], click=True, seconds=2, grayscale=True):
+    #     claim_and_collect()
+
+    # Seasonal Noble Path
+    logger.info('    Checking Season Noble Path')
+    if isVisible('buttons/noble_season_active', region=regions['bottom_third'], seconds=2, grayscale=True) or isVisible('buttons/noble_season_inactive', region=regions['bottom_third'], click=True, seconds=2, grayscale=True):
         claim_and_collect()
 
-    # # Seasonal Noble Path
-    # logger.info('    Checking Season Noble Path')
-    # if isVisible('buttons/noble_season_active', region=regions['bottom_third'], seconds=2, grayscale=True) or isVisible('buttons/noble_season_inactive', region=regions['bottom_third'], click=True, seconds=2, grayscale=True):
-    #     claim_and_collect()
-    #
     # # Noble Path
     # logger.info('    Checking Noble Path')
     # if isVisible('buttons/noble_path_active', region=regions['bottom_third'], seconds=2, grayscale=True) or isVisible('buttons/noble_path_inactive', region=regions['bottom_third'], click=True, seconds=2, grayscale=True):
@@ -818,11 +826,22 @@ def blind_push(mode, tower=None, load_formation=True):
                         formation_handler()
                         while True:
                             click("buttons/battle", suppress=True, region=regions['bottom_buttons'])
-                            click("buttons/retry", suppress=True, region=regions['bottom_buttons'])
                             if isVisible("buttons/next", click=True, seconds=4, region=regions['bottom_buttons']):
                                 logger.info(faction.capitalize() + ' win detected, moving to next floor\n')
+                                globals()['stage_defeats'] = 0
                                 formation_handler()
-                                click("buttons/battle", seconds=3, suppress=True)
+                            if isVisible("buttons/retry", click=True, region=regions['bottom_buttons']):
+                                # Increment defeats
+                                globals()['stage_defeats'] += 1
+                                # If were past the defeat cap handle formation change, else standard log output
+                                if globals()['stage_defeats'] >= 1 and globals()['stage_defeats'] % config.getint('PUSHING', 'defeat_limit') == 0:
+                                    globals()['formation'] = (globals()['stage_defeats'] / config.getint('PUSHING', 'defeat_limit')) + 1  # number of defeats / defeat_limit, plus 1 as we start on formation #1
+                                    logger.info(str(globals()['stage_defeats']) + ' defeats, trying next formation')
+                                    wait()
+                                    formation_handler(globals()['formation'])
+                                else:
+                                    logger.info('Defeat #' + str(globals()['stage_defeats']) + '! Retrying')
+
 
         if safe_open_and_close(name=inspect.currentframe().f_code.co_name, state='close'):
             logger.info('Towers pushed!\n')
@@ -946,8 +965,8 @@ def blind_push(mode, tower=None, load_formation=True):
                 logger.info('Defeat #' + str(globals()['stage_defeats']) + '! Retrying')
                 clickXY(730, 1800, seconds=3)
                 blind_push('afkstages', load_formation=False)
-            # Battle button indicated victory, we run the victory logic
-            if isVisible('buttons/battle', region=regions['bottom_buttons']):
+            # If there's no 'Retry' button post battle, we assume victory (as normal and talent stages have different buttons here to continue)
+            else:
                 globals()['stage_defeats'] = 0  # Reset defeats
                 globals()['formation'] = 1  # Reset formation
                 logger.info('Victory! Stage passed\n')
@@ -1032,16 +1051,51 @@ def blind_push(mode, tower=None, load_formation=True):
             recover()
 
 def open_afk_stages(afkstages=True):
-    clickXY(100, 1800, seconds=4)  # Open AFK Rewards
+
+    # open afk stage screen without prompting loot if it's >1h uncollected
+    clickXY(450, 1825, seconds=3)
+    click('buttons/afk_stage', region=regions['battle_modes'], seconds=4)
+
     if afkstages is True: # Standard Stage
         logger.info('Opening AFK Stages')
         logger.info('Changing formations after ' + str(config.getint('PUSHING', 'defeat_limit')) + ' defeats\n')
-        clickXY(715, 1600, seconds=3)  # Battle
-        clickXY(715, 1600, seconds=2)  # Battle (again since first can claim afk rewards when its >1h)
+        # 3 clicks, first can collect loot, second can prompt 'Are you sure?' popup, 3rd opens stages for sure.
+        clickXY(715, 1600, seconds=2)  # AFK Stage button
+        click('buttons/confirm', suppress=True)
     else: # Talent Stage
-        logger.info('Opening Talent Stages\n')
-        clickXY(370, 1600, seconds=3)  # Battle
-        clickXY(370, 1600, seconds=2)  # Battle (again since first can claim afk rewards when its >1h)
+        logger.info('Opening Talent Stages')
+        logger.info('Changing formations after ' + str(config.getint('PUSHING', 'defeat_limit')) + ' defeats\n')
+        clickXY(370, 1600, seconds=2)  # AFK Stage button
+        click('buttons/confirm', suppress=True)
+
+def afk_stage_chain_proxy():
+    formation_handler()
+    click('buttons/battle', retry=1, suppress=True, seconds=0, region=regions['bottom_third'])
+    click('buttons/confirm', retry=1, suppress=True, seconds=0)
+    while True:
+        # Victory Logic
+        if isVisible('buttons/next', retry=1, click=True, seconds=3):
+            click('buttons/battle', retry=1, suppress=True, seconds=5, region=regions['bottom_third'])
+            logger.info('Victory!\n')
+            globals()['stage_defeats'] = 0
+            formation_handler()
+            click('buttons/battle', retry=1, suppress=True, seconds=0, region=regions['bottom_third'])
+            click('buttons/confirm', retry=1, suppress=True, seconds=0)
+        # Defeat logic
+        if isVisible('buttons/retry', retry=1, click=True, seconds=4, region=(650, 1750, 200, 150)):
+            # Increment defeats
+            globals()['stage_defeats'] += 1
+            # If were past the defeat cap handle formation change, else standard log output
+            if globals()['stage_defeats'] >= 1 and globals()['stage_defeats'] % config.getint('PUSHING', 'defeat_limit') == 0:
+                globals()['formation'] = (globals()['stage_defeats'] / config.getint('PUSHING', 'defeat_limit')) + 1  # number of defeats / defeat_limit, plus 1 as we start on formation #1
+                logger.info(str(globals()['stage_defeats']) + ' defeats, trying next formation')
+                formation_handler(globals()['formation'])
+                click('buttons/battle', retry=1, suppress=True, seconds=0)
+                click('buttons/confirm', retry=1, suppress=True, seconds=0)
+            else:
+                logger.info('Defeat #' + str(globals()['stage_defeats']) + '! Retrying')
+                click('buttons/battle', suppress=True, seconds=0, region=regions['bottom_third'])
+                click('buttons/confirm', suppress=True, seconds=0)
 
 def handle_charms():
 
@@ -1154,21 +1208,18 @@ def go_back(exit_mode=False):
 def quest_push():
     logger.info('Pushing Quests!\n')
     # The order of these is important
-    buttons = ['buttons/battle', 'buttons/skip', 'buttons/dialogue_option', 'buttons/confirm', 'buttons/red_dialogue', 'buttons/interact',
-               'buttons/dialogue', 'buttons/tap_and_hold', 'buttons/enter', 'buttons/chest', 'buttons/battle_button', 'labels/questrewards',
-               'buttons/woi_ship', 'labels/tap_to_close', 'buttons/track', 'labels/woi', 'labels/woi2']
-    while True:
+    buttons = ['buttons/battle', 'buttons/skip', 'buttons/dialogue_option', 'buttons/confirm', 'buttons/red_dialogue', 'buttons/dialogue_coe',
+               'buttons/blue_dialogue', 'buttons/interact', 'buttons/dialogue', 'buttons/tap_and_hold', 'buttons/enter', 'buttons/chest',
+               'buttons/battle_button', 'labels/questrewards', 'labels/tap_to_close', 'buttons/track', 'labels/coe']
 
-        click_array(buttons, suppress=True, confidence=0.95)
+    while True:
+        click_array(buttons, suppress=True, confidence=0.91)
         if isVisible('buttons/tap_and_hold', region=regions['chat_window'], seconds=0):
             logger.info('Holding button')
             swipe(550, 1250, 550, 1250, 4000)  # Hacky way to hold it down
-
-# Placeholder for when I get round to it
-#def run_lab():
-#    if lab is not completed:
-#        run_lab()
-
+        if isVisible('labels/time_change', region=regions['chat_window'], seconds=0, yrelative=620, click=True):
+            logger.info('Time changed!')
+            wait(4)
 
 # Handle launch arguments
 
@@ -1222,6 +1273,11 @@ def emit(self, record):
 if args['charms']:
     handle_charms()
 
+if args['proxy']:
+    logger.info('\n')
+    logger.info('Starting up Chain Proxy farming\n')
+    afk_stage_chain_proxy()
+
 if args['afks']:
     safe_open_and_close(name=inspect.currentframe().f_code.co_name, state='open')
     open_afk_stages(afkstages=True)
@@ -1234,7 +1290,7 @@ def emit(self, record):
 
 # If no function launch argument we pop the UI
 
-options = ["Run Dailies", "Push Towers", "Push AFK Stages", "Push AFK Talent Stages", "Push Dura's Trials", "Run Quests", "Use Dream Realm attempts", "Farm Team-Up Chat"]
+options = ["Run Dailies", "Push Towers", "Push AFK Stages", "Push AFK Talent Stages", "Push Dura's Trials", "Run Quests", "Use Dream Realm attempts", "Farm Team-Up Chat", "Farm Chain AFK Proxy Request"]
 selection = SelectionMenu.get_selection(options, title='Welcome to AutoAFK2! Select an activity:', subtitle='Note that to stop a task or start a new one you have to restart the bot. Questions? Jc.2 @ Discord')
 selection += 1 # Non-zero index to make things easier to read
 
@@ -1332,4 +1388,8 @@ def emit(self, record):
     while time.time() - start_time < limit:
         team_up()
 
+if selection == 9:
+    logger.info('Starting up Chain Proxy farming')
+    logger.info('For this function you need to open the request yourself and start this function at the stage setup screen\n')
 
+    afk_stage_chain_proxy()
diff --git a/img/buttons/afk_stage.png b/img/buttons/afk_stage.png
new file mode 100644
index 0000000..71277b5
Binary files /dev/null and b/img/buttons/afk_stage.png differ
diff --git a/img/buttons/blue_dialogue.png b/img/buttons/blue_dialogue.png
new file mode 100644
index 0000000..837c64d
Binary files /dev/null and b/img/buttons/blue_dialogue.png differ
diff --git a/img/buttons/dialogue_coe.png b/img/buttons/dialogue_coe.png
new file mode 100644
index 0000000..8575b5a
Binary files /dev/null and b/img/buttons/dialogue_coe.png differ
diff --git a/img/buttons/level_up_all_active.png b/img/buttons/level_up_all_active.png
new file mode 100644
index 0000000..b0b3d18
Binary files /dev/null and b/img/buttons/level_up_all_active.png differ
diff --git a/img/buttons/red_dialogue.png b/img/buttons/red_dialogue.png
index 0134e70..5f17acf 100644
Binary files a/img/buttons/red_dialogue.png and b/img/buttons/red_dialogue.png differ
diff --git a/img/labels/coe.png b/img/labels/coe.png
new file mode 100644
index 0000000..0811838
Binary files /dev/null and b/img/labels/coe.png differ
diff --git a/img/labels/time_change.png b/img/labels/time_change.png
new file mode 100644
index 0000000..98ed028
Binary files /dev/null and b/img/labels/time_change.png differ
diff --git a/tools.py b/tools.py
index 84a228c..b9d09aa 100644
--- a/tools.py
+++ b/tools.py
@@ -20,6 +20,7 @@
 
 def connect_and_launch(port, server):
     global device
+    counter = 0
 
     # Find and connect to the device
     device = get_adb_device(port)
@@ -67,8 +68,11 @@ def connect_and_launch(port, server):
     device.shell('monkey -p ' + server + ' 1')
     wait(5) # This long wait doesn't slow anything down as the game takes 60 seconds to load anyway
     while device.shell('pidof ' + server) == '':
+        counter += 1
         device.shell('monkey -p ' + server + ' 1')
         wait(5) # This long wait doesn't slow anything down as the game takes 60 seconds to load anyway
+        if counter > 5:
+            logger.warning('Attempting to launch AFK Journey, but cannot detect AFK Journey running')
 
 def manage_adb_exe(command, device_name='127.0.0.1:5555'):
     # Get the right ADB path depending on whether we run from Pycharm or compiled .exe
@@ -278,6 +282,7 @@ def click_array(images, confidence=0.9, seconds=1, suppress=False, grayscale=Fal
             y_center = round(y + h/2)
             device.input_tap(x_center, y_center)
             wait(seconds)
+            return
         else:
             if suppress is not True:
                 logger.info('Image:' + image + ' not found!')