AzurLaneAutoScript icon indicating copy to clipboard operation
AzurLaneAutoScript copied to clipboard

刷紧急委托的小优化

Open Air111 opened this issue 11 months ago • 0 comments

你的功能请求是否与问题有关?

目前刷紧急委托功能的“心情控制”主要依赖于红脸弹窗。选择更换舰船时,当检测到红脸弹窗,会放弃本次作战,更换舰船,然后开始新的一次作战。这会浪费一点点石油,尤其是在低耗时进图的10油占到了整个作战的接近一半。粗略计算下,用alas刷3油a3的物资油耗比会下降到接近4油c2的水平。

我的优化试图让紧急委托也能通过预防来进行心情控制,提早更换舰船以避免石油的浪费。

解决方案

仿照其他作战的心情控制机制固然可以解决,但除了我暂时不想研究这部分代码外还有一个潜在的问题,就是前后排心情消耗速度不同。当可更换前排不够多时,可以把它们都放在后宅。然而后排没法也没必要放在后宅。

我的解决方法是每经过一定次数的作战进行一次更换舰船的尝试,这个次数是经过保守计算得到的:

  • 定义一个每战心情消耗,先假设它为10不变。
  • 假设作战速度足够快,那么经过150/10=15次作战后需要更换舰船。因此该次数初始化为15
  • 每次尝试更换舰船时,获取更换后的舰船的心情(可能与更换前的一致),将RunCount设为min(RunCount, ship.emotion // 10)

StopCondition_RunCount在刷紧急委托任务中未暴露在前端,对它的相关处理做少量的修改,可以使其归零时进行更换舰船的尝试,而不结束刷紧急委托任务

丑陋的实现:

#module\campaign\gems_farming.py

from module.campaign.campaign_base import CampaignBase
from module.campaign.run import CampaignRun
from module.combat.assets import BATTLE_PREPARATION
from module.equipment.assets import *
from module.equipment.equipment_change import EquipmentChange
from module.equipment.fleet_equipment import OCR_FLEET_INDEX
from module.exception import CampaignEnd
from module.handler.assets import AUTO_SEARCH_MAP_OPTION_OFF
from module.logger import logger
from module.map.assets import FLEET_PREPARATION, MAP_PREPARATION
from module.retire.assets import DOCK_CHECK, TEMPLATE_BOGUE, TEMPLATE_HERMES, TEMPLATE_LANGLEY, TEMPLATE_RANGER
from module.retire.dock import Dock
from module.retire.scanner import ShipScanner
from module.ui.page import page_fleet
from module.ui.assets import BACK_ARROW

SIM_VALUE = 0.95
GF_EMOTION_PER_ROUND = 10
GF_RUN_COUNT = 150 // GF_EMOTION_PER_ROUND


class GemsCampaignOverride(CampaignBase):

    def handle_combat_low_emotion(self):
        """
        Overwrite info_handler.handle_combat_low_emotion()
        If change vanguard is enabled, withdraw combat and change flagship and vanguard
        """
        if self.config.GemsFarming_ChangeVanguard == 'disabled':
            result = self.handle_popup_confirm('IGNORE_LOW_EMOTION')
            if result:
                # Avoid clicking AUTO_SEARCH_MAP_OPTION_OFF
                self.interval_reset(AUTO_SEARCH_MAP_OPTION_OFF)
            return result

        if self.handle_popup_cancel('IGNORE_LOW_EMOTION'):
            self.config.GEMS_EMOTION_TRIGGRED = True
            logger.hr('EMOTION WITHDRAW')

            while 1:
                self.device.screenshot()

                if self.handle_story_skip():
                    continue
                if self.handle_popup_cancel('IGNORE_LOW_EMOTION'):
                    continue

                if self.appear(BATTLE_PREPARATION, offset=(20, 20), interval=2):
                    self.device.click(BACK_ARROW)
                    continue
                if self.handle_auto_search_exit():
                    continue
                if self.is_in_stage():
                    break

                if self.is_in_map():
                    self.withdraw()
                    break

                if self.appear(FLEET_PREPARATION, offset=(20, 50), interval=2) \
                        or self.appear(MAP_PREPARATION, offset=(20, 20), interval=2):
                    self.enter_map_cancel()
                    break
            raise CampaignEnd('Emotion withdraw')


class GemsFarming(CampaignRun, Dock, EquipmentChange):

    def load_campaign(self, name, folder='campaign_main'):
        super().load_campaign(name, folder)

        class GemsCampaign(GemsCampaignOverride, self.module.Campaign):
            pass

        self.campaign = GemsCampaign(device=self.campaign.device, config=self.campaign.config)
        self.campaign.config.override(Emotion_Mode='ignore')
        self.campaign.config.override(EnemyPriority_EnemyScaleBalanceWeight='S1_enemy_first')

    @property
    def change_flagship(self):
        return 'ship' in self.config.GemsFarming_ChangeFlagship

    @property
    def change_flagship_equip(self):
        return 'equip' in self.config.GemsFarming_ChangeFlagship

    @property
    def change_vanguard(self):
        return 'ship' in self.config.GemsFarming_ChangeVanguard

    @property
    def change_vanguard_equip(self):
        return 'equip' in self.config.GemsFarming_ChangeVanguard

    def _fleet_detail_enter(self):
        """
        Enter GEMS_FLEET page
        """
        self.ui_ensure(page_fleet)
        _fleet_to_change = self.config.Fleet_Fleet1
        if self.config.Fleet_FleetOrder == 'fleet1_all_fleet2_standby':
            _fleet_to_change = self.config.Fleet_Fleet1
        elif self.config.Fleet_FleetOrder == 'fleet1_standby_fleet2_all':
            _fleet_to_change = self.config.Fleet_Fleet2
        self.ui_ensure_index(_fleet_to_change, letter=OCR_FLEET_INDEX,
                             next_button=FLEET_NEXT, prev_button=FLEET_PREV, skip_first_screenshot=True)

    def _ship_detail_enter(self, button):
        self._fleet_detail_enter()
        self.equip_enter(button)

    def flagship_change(self):
        """
        Change flagship and flagship's equipment
        If config.GemsFarming_CommonCV == 'any', only change auxiliary equipment

        Returns:
            bool: True if flagship changed.
        """

        if self.config.GemsFarming_CommonCV == 'any':
            index_list = range(3, 5)
        else:
            index_list = range(0, 5)
        logger.hr('Change flagship', level=1)
        logger.attr('ChangeFlagship', self.config.GemsFarming_ChangeFlagship)
        if self.change_flagship_equip:
            logger.hr('Record flagship equipment', level=2)
            self._ship_detail_enter(FLEET_ENTER_FLAGSHIP)
            self.record_equipment(index_list=index_list)
            self._equip_take_off_one()
            self.ui_back(page_fleet.check_button)

        logger.hr('Change flagship', level=2)
        self._fleet_detail_enter()
        success = self.flagship_change_execute()

        if self.change_flagship_equip:
            logger.hr('Equip flagship equipment', level=2)
            self._ship_detail_enter(FLEET_ENTER_FLAGSHIP)
            self._equip_take_off_one()

            self.equipment_take_on(index_list=index_list)
            self.ui_back(page_fleet.check_button)

        return success

    def vanguard_change(self):
        """
        Change vanguard and vanguard's equipment

        Returns:
            bool: True if vanguard changed
        """
        logger.hr('Change vanguard', level=1)
        logger.attr('ChangeVanguard', self.config.GemsFarming_ChangeVanguard)
        if self.change_vanguard_equip:
            logger.hr('Record vanguard equipment', level=2)
            self._ship_detail_enter(FLEET_ENTER)
            self.record_equipment()
            self._equip_take_off_one()
            self.ui_back(page_fleet.check_button)

        logger.hr('Change vanguard', level=2)
        self._fleet_detail_enter()
        success = self.vanguard_change_execute()

        if self.change_vanguard_equip:
            logger.hr('Equip vanguard equipment', level=2)
            self._ship_detail_enter(FLEET_ENTER)
            self._equip_take_off_one()

            self.equipment_take_on()
            self.ui_back(page_fleet.check_button)

        return success

    def _ship_change_confirm(self, button):

        self.dock_select_one(button)
        self.dock_filter_set()
        self.dock_select_confirm(check_button=page_fleet.check_button)

    def get_common_rarity_cv(self):
        """
        Get a common rarity cv by config.GemsFarming_CommonCV
        If config.GemsFarming_CommonCV == 'any', return a common lv1 ~ lv33 cv
        Returns:
            Ship:
        """

        logger.hr('FINDING FLAGSHIP')

        scanner = ShipScanner(
            level=(1, 31), emotion=(GF_EMOTION_PER_ROUND, 150), fleet=self.config.Fleet_Fleet1, status='free')
        scanner.disable('rarity')

        if self.config.GemsFarming_CommonCV == 'any':
            logger.info('')

            self.dock_sort_method_dsc_set(False)

            ships = scanner.scan(self.device.image)
            if ships:
                # Don't need to change current
                return ships

            scanner.set_limitation(fleet=0)
            return scanner.scan(self.device.image, output=False)

        else:
            template = {
                'BOGUE': TEMPLATE_BOGUE,
                'HERMES': TEMPLATE_HERMES,
                'LANGLEY': TEMPLATE_LANGLEY,
                'RANGER': TEMPLATE_RANGER
            }[f'{self.config.GemsFarming_CommonCV.upper()}']

            self.dock_sort_method_dsc_set()

            ships = scanner.scan(self.device.image)
            if ships:
                # Don't need to change current
                return ships

            scanner.set_limitation(fleet=0)
            candidates = [ship for ship in scanner.scan(self.device.image, output=False)
                          if template.match(self.image_crop(ship.button), similarity=SIM_VALUE)]

            if candidates:
                return candidates

            logger.info('No specific CV was found, try reversed order.')
            self.dock_sort_method_dsc_set(False)

            candidates = [ship for ship in scanner.scan(self.device.image)
                          if template.match(self.image_crop(ship.button), similarity=SIM_VALUE)]

            return candidates

    def get_common_rarity_dd(self):
        """
        Get a common rarity dd with level is 100 (70 for servers except CN) and emotion > 10
        Returns:
            Ship:
        """
        logger.hr('FINDING VANGUARD')

        if self.config.SERVER in ['cn']:
            max_level = 100
        else:
            max_level = 70

        scanner = ShipScanner(level=(max_level, max_level), emotion=(GF_EMOTION_PER_ROUND, 150),
                              fleet=self.config.Fleet_Fleet1, status='free')
        scanner.disable('rarity')

        ships = scanner.scan(self.device.image)
        if ships:
            # Don't need to change current
            return ships

        scanner.set_limitation(fleet=0)
        return scanner.scan(self.device.image, output=False)

    def flagship_change_execute(self):
        """
        Returns:
            bool: If success.

        Pages:
            in: page_fleet
            out: page_fleet
        """
        self.ui_click(FLEET_ENTER_FLAGSHIP,
                      appear_button=page_fleet.check_button, check_button=DOCK_CHECK, retry_wait=2, skip_first_screenshot=True)
        self.dock_filter_set(
            index='cv', rarity='common', extra='enhanceable', sort='total')
        self.dock_favourite_set(False)

        ships = self.get_common_rarity_cv()
        if ships:
            ship = min(ships, key=lambda s: (s.level, -s.emotion))
            self.config.StopCondition_RunCount = min(ship.emotion // GF_EMOTION_PER_ROUND, self.config.StopCondition_RunCount)
            self._ship_change_confirm(ship.button)

            logger.info('Change flagship success')
            return True
        else:
            logger.info('Change flagship failed, no CV in common rarity.')
            self.dock_filter_set()
            self.ui_back(check_button=page_fleet.check_button)
            return False

    def vanguard_change_execute(self):
        """
        Returns:
            bool: If success.

        Pages:
            in: page_fleet
            out: page_fleet
        """
        self.ui_click(FLEET_ENTER,
                      appear_button=page_fleet.check_button, check_button=DOCK_CHECK, retry_wait=2, skip_first_screenshot=True)
        self.dock_filter_set(
            index='dd', rarity='common', faction='eagle', extra='can_limit_break')
        self.dock_favourite_set(False)

        ships = self.get_common_rarity_dd()
        if ships:
            ship = max(ships, key=lambda s: s.emotion)
            self.config.StopCondition_RunCount = min(ship.emotion // GF_EMOTION_PER_ROUND, self.config.StopCondition_RunCount)
            self._ship_change_confirm(ship.button)

            logger.info('Change vanguard ship success')
            return True
        else:
            logger.info('Change vanguard ship failed, no DD in common rarity.')
            self.dock_filter_set()
            self.ui_back(check_button=page_fleet.check_button)
            return False

    _trigger_lv32 = False
    _trigger_emotion = False
    _trigger_count = False

    def triggered_stop_condition(self, oil_check=True):
        # Lv32 limit
        if self.change_flagship and self.campaign.config.LV32_TRIGGERED:
            self._trigger_lv32 = True
            logger.hr('TRIGGERED LV32 LIMIT')
            return True

        if self.campaign.map_is_auto_search and self.campaign.config.GEMS_EMOTION_TRIGGRED:
            self._trigger_emotion = True
            logger.hr('TRIGGERED EMOTION LIMIT')
            return True
        
        if self.run_limit and self.config.StopCondition_RunCount <= 0:
            self._trigger_count = True
            logger.hr('Triggered stop condition: Run count')
            self.config.StopCondition_RunCount = GF_RUN_COUNT
            return True

        return super().triggered_stop_condition(oil_check=oil_check)

    def run(self, name, folder='campaign_main', mode='normal', total=0):
        """
        Args:
            name (str): Name of .py file.
            folder (str): Name of the file folder under campaign.
            mode (str): `normal` or `hard`
            total (int):
        """
        self.config.STOP_IF_REACH_LV32 = self.change_flagship
        self.config.RETIRE_KEEP_COMMON_CV = True
        self.config.StopCondition_RunCount = GF_RUN_COUNT
        success = True
        if self.change_flagship:
            success = self.flagship_change()
        if self.change_vanguard:
            success = success and self.vanguard_change()
        if not success:
            self.config.task_delay(minute=30)
            self.config.task_stop()

        while 1:
            self._trigger_lv32 = False
            is_limit = self.config.StopCondition_RunCount

            try:
                super().run(name=name, folder=folder, total=total)
            except CampaignEnd as e:
                if e.args[0] == 'Emotion withdraw':
                    self._trigger_emotion = True
                else:
                    raise e

            # End
            if self._trigger_lv32 or self._trigger_emotion or self._trigger_count:
                success = True
                if self.change_flagship:
                    success = self.flagship_change()
                if self.change_vanguard:
                    success = success and self.vanguard_change()

                self._trigger_lv32 = False
                self._trigger_emotion = False
                self._trigger_count = False
                self.campaign.config.LV32_TRIGGERED = False
                self.campaign.config.GEMS_EMOTION_TRIGGRED = False

                # Scheduler
                if self.config.task_switched():
                    self.campaign.ensure_auto_search_exit()
                    self.config.task_stop()
                elif not success:
                    self.campaign.ensure_auto_search_exit()
                    self.config.task_delay(minute=30)
                    self.config.task_stop()

                continue
            else:
                break

其他内容

我的解决方案显著的缺点是会进行更多次的更换舰船操作,浪费多一点时间

关于“每战心情消耗”:

  • 理论上来说刷紧急委托的图都是4-6战的,保守一点可以把它设为12
  • 虽然2-4只需4战,但有比较大的概率因路被堵而需要5战
  • 这个选项可以暴露在前端,但用户也不一定理解

Air111 avatar Mar 01 '24 19:03 Air111