Refactor: Guild module

- Simplify guild exchange with Filter
- Expose submarine mode
This commit is contained in:
LmeSzinc 2021-09-21 00:56:33 +08:00
parent ca5a14a2dd
commit c8c1cd9789
10 changed files with 143 additions and 294 deletions

View File

@ -20,6 +20,7 @@ from module.logger import logger, log_file
from module.research.research import RewardResearch
from module.reward.reward import Reward
from module.tactical.tactical_class import RewardTacticalClass
from module.guild.guild_reward import RewardGuild
class AzurLaneAutoScript:
@ -115,6 +116,9 @@ class AzurLaneAutoScript:
def reward(self):
Reward(config=self.config, device=self.device).run()
def guild(self):
RewardGuild(config=self.config, device=self.device).run()
def main(self):
CampaignRun(config=self.config, device=self.device).run(
name=self.config.Campaign_Name,

View File

@ -252,3 +252,23 @@ Reward:
CollectOil: true
CollectCoin: true
CollectMission: true
Guild:
Scheduler:
Enable: false
NextRun: 2020-01-01 00:00:00
Command: Guild
SuccessInterval: 40-60
FailureInterval: 40-60
ServerUpdate: 00:00, 12:00, 18:00, 21:00
Logistics:
Enable: true
ExchangeFilter: |-
PlateTorpedoT1 > PlateAntiAirT1 > PlatePlaneT1 > PlateGunT1 > PlateGeneralT1
> PlateTorpedoT2 > PlateAntiAirT2 > PlatePlaneT2 > PlateGunT2 > PlateGeneralT2
> PlateTorpedoT3 > PlateAntiAirT3 > PlatePlaneT3 > PlateGunT3 > PlateGeneralT3
> OxyCola > Coolant > Merit > Coins > Oil
Operation:
Enable: true
JoinThreshold: 1
AttackBoss: true
BossFleetRecommend: false

View File

@ -196,11 +196,11 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan
return False
def combat_execute(self, auto='combat_auto', call_submarine_at_boss=False, drop=None):
def combat_execute(self, auto='combat_auto', submarine='do_not_use', drop=None):
"""
Args:
auto (str): Combat auto mode.
call_submarine_at_boss (bool):
auto (str): ['combat_auto', 'combat_manual', 'stand_still_in_the_middle', 'hide_in_bottom_left']
submarine (str): ['do_not_use', 'hunt_only', 'every_combat']
drop (DropImage):
"""
logger.info('Combat execute')
@ -226,11 +226,8 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan
if auto != 'combat_auto' and self.auto_mode_checked and self.is_combat_executing():
if self.handle_combat_weapon_release():
continue
if call_submarine_at_boss:
pass
else:
if self.handle_submarine_call():
continue
if self.handle_submarine_call(submarine):
continue
if self.handle_popup_confirm('COMBAT_EXECUTE'):
continue
@ -408,7 +405,7 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan
if expected_end():
break
def combat(self, balance_hp=None, emotion_reduce=None, auto_mode=None, call_submarine_at_boss=None,
def combat(self, balance_hp=None, emotion_reduce=None, auto_mode=None, submarine_mode=None,
save_get_items=None, expected_end=None, fleet_index=1):
"""
Execute a combat.
@ -418,7 +415,7 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan
balance_hp (bool):
emotion_reduce (bool):
auto_mode (str): combat_auto, combat_manual, stand_still_in_the_middle, hide_in_bottom_left
call_submarine_at_boss (bool):
submarine_mode (str): do_not_use, hunt_only, every_combat
save_get_items (bool):
expected_end (str, callable):
fleet_index (int): 1 or 2
@ -427,7 +424,9 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan
emotion_reduce = emotion_reduce if emotion_reduce is not None else self.config.Emotion_CalculateEmotion
if auto_mode is None:
auto_mode = self.config.Fleet_Fleet1Mode if fleet_index == 1 else self.config.Fleet_Fleet2Mode
call_submarine_at_boss = call_submarine_at_boss if call_submarine_at_boss is not None else False
if submarine_mode is None:
if self.config.Submarine_Fleet:
submarine_mode = self.config.Submarine_Mode
self.battle_status_click_interval = 7 if save_get_items else 0
# if not hasattr(self, 'emotion'):
@ -441,7 +440,7 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan
self.combat_preparation(
balance_hp=balance_hp, emotion_reduce=emotion_reduce, auto=auto_mode, fleet_index=fleet_index)
self.combat_execute(
auto=auto_mode, call_submarine_at_boss=call_submarine_at_boss, drop=drop)
auto=auto_mode, submarine=submarine_mode, drop=drop)
self.combat_status(
drop=drop, expected_end=expected_end)
self.handle_map_after_combat_story()

View File

@ -16,14 +16,14 @@ class SubmarineCall(ModuleBase):
self.submarine_call_timer.reset()
self.submarine_call_flag = False
def handle_submarine_call(self):
def handle_submarine_call(self, submarine='do_not_use'):
"""
Returns:
bool: If call.
str: If call.
"""
if self.submarine_call_flag:
return False
if not self.config.Submarine_Fleet or self.config.Submarine_Mode in ['do_not_use', 'hunt_only']:
if submarine in ['do_not_use', 'hunt_only']:
self.submarine_call_flag = True
return False
if self.submarine_call_timer.reached():

View File

@ -238,7 +238,7 @@ GemsFarming:
option: [0, 1, 2]
Mode:
value: do_not_use
option: ['do_not_use','hunt_only', 'every_combat']
option: ['do_not_use', 'hunt_only', 'every_combat']
Emotion:
CalculateEmotion: true
IgnoreLowEmotionWarn: false
@ -360,3 +360,25 @@ Reward:
CollectOil: true
CollectCoin: true
CollectMission: true
Guild:
_info:
Menu: Reward
Scheduler:
Enable: false
NextRun: 2020-01-01 00:00:00
Command: Guild
SuccessInterval: 40-60
FailureInterval: 40-60
ServerUpdate: 00:00, 12:00, 18:00, 21:00
Logistics:
Enable: true
ExchangeFilter: |-
PlateTorpedoT1 > PlateAntiAirT1 > PlatePlaneT1 > PlateGunT1 > PlateGeneralT1
> PlateTorpedoT2 > PlateAntiAirT2 > PlatePlaneT2 > PlateGunT2 > PlateGeneralT2
> PlateTorpedoT3 > PlateAntiAirT3 > PlatePlaneT3 > PlateGunT3 > PlateGeneralT3
> OxyCola > Coolant > Merit > Coins > Oil
Operation:
Enable: true
JoinThreshold: 1
AttackBoss: true
BossFleetRecommend: false

View File

@ -124,3 +124,11 @@ class GeneratedConfig:
Reward_CollectOil = True
Reward_CollectCoin = True
Reward_CollectMission = True
# Func `Guild`
Logistics_Enable = True
Logistics_ExchangeFilter = 'PlateTorpedoT1 > PlateAntiAirT1 > PlatePlaneT1 > PlateGunT1 > PlateGeneralT1\n> PlateTorpedoT2 > PlateAntiAirT2 > PlatePlaneT2 > PlateGunT2 > PlateGeneralT2\n> PlateTorpedoT3 > PlateAntiAirT3 > PlatePlaneT3 > PlateGunT3 > PlateGeneralT3\n> OxyCola > Coolant > Merit > Coins > Oil'
Operation_Enable = True
Operation_JoinThreshold = 1
Operation_AttackBoss = True
Operation_BossFleetRecommend = False

View File

@ -6,8 +6,7 @@ class ManualConfig:
SCHEDULER_PRIORITY = """
Restart
> Research > Commission > Tactical > Dorm > Reward
> GuildLogistics > GuildOperation
> Research > Commission > Tactical > Dorm > Guild > Reward
> Meowfficer > Gacha > Shop
> OpsiObscure
> Exercise > Daily > Hard > OpsiAsh

View File

@ -1,27 +1,11 @@
from datetime import datetime, timedelta
from module.base.decorator import cached_property
from module.base.utils import ensure_time
from module.guild.assets import GUILD_RED_DOT
from module.guild.lobby import GuildLobby
from module.guild.logistics import GuildLogistics, RECORD_OPTION_LOGISTICS, RECORD_SINCE_LOGISTICS
from module.guild.operations import GuildOperations, RECORD_OPTION_DISPATCH, RECORD_SINCE_DISPATCH
from module.logger import logger
from module.ui.ui import page_guild
GUILD_RECORD = ('RewardRecord', 'guild')
from module.guild.logistics import GuildLogistics
from module.guild.operations import GuildOperations
from module.ui.ui import page_guild, page_main
class RewardGuild(GuildLobby, GuildLogistics, GuildOperations):
@cached_property
def guild_interval(self):
return int(ensure_time(self.config.GUILD_INTERVAL, precision=3) * 60)
def guild_interval_reset(self):
""" Call this method after guild run executed """
del self.__dict__['guild_interval']
def handle_guild(self):
def run(self):
"""
ALAS handler function for guild reward loop
@ -32,43 +16,28 @@ class RewardGuild(GuildLobby, GuildLogistics, GuildOperations):
in: page_main
out: page_main
"""
if not self.config.ENABLE_GUILD_LOGISTICS and not self.config.ENABLE_GUILD_OPERATIONS:
return False
now = datetime.now()
guild_record = datetime.strptime(self.config.config.get(*GUILD_RECORD), self.config.TIME_FORMAT)
update = guild_record + timedelta(seconds=self.guild_interval)
attr = f'{GUILD_RECORD[0]}_{GUILD_RECORD[1]}'
logger.attr(f'{attr}', f'Record time: {guild_record}')
logger.attr(f'{attr}', f'Next update: {update}')
if not now > update:
return False
if not self.appear(GUILD_RED_DOT, offset=(30, 30)):
logger.info('Guild red dot not appears, skip current check')
self.config.record_save(option=GUILD_RECORD)
return False
if not self.config.Logistics_Enable and not self.config.Operation_Enable:
self.config.Scheduler_Enable = False
self.config.task_stop()
self.ui_ensure(page_guild)
success = True
# Lobby
self.guild_lobby()
# Logistics
if self.config.ENABLE_GUILD_LOGISTICS \
and not self.config.record_executed_since(option=RECORD_OPTION_LOGISTICS, since=RECORD_SINCE_LOGISTICS):
# Save record when guild mission has been finished this week
# If finished, only need to check guild logostics once a day
# If not finished, check it in every guild reward
if self.guild_logistics():
self.config.record_save(option=RECORD_OPTION_LOGISTICS)
if self.config.Logistics_Enable:
success &= self.guild_logistics()
# Operation
if self.config.ENABLE_GUILD_OPERATIONS \
and not self.config.record_executed_since(option=RECORD_OPTION_DISPATCH, since=RECORD_SINCE_DISPATCH):
# Check guild operation 4 times a day
self.guild_operations()
if self.config.Operation_Enable:
success &= self.guild_operations()
self.guild_interval_reset()
self.config.record_save(option=GUILD_RECORD)
self.ui_goto_main()
return True
self.ui_goto(page_main)
# Scheduler
if success:
self.config.task_delay(server_update=True)
else:
self.config.task_delay(success=False, server_update=True)

View File

@ -1,5 +1,8 @@
import re
from module.base.button import ButtonGrid
from module.base.decorator import cached_property, Config
from module.base.filter import Filter
from module.base.timer import Timer
from module.base.utils import *
from module.combat.assets import GET_ITEMS_1
@ -10,34 +13,11 @@ from module.logger import logger
from module.ocr.ocr import Digit
from module.statistics.item import ItemGrid
RECORD_OPTION_LOGISTICS = ('RewardRecord', 'logistics')
RECORD_SINCE_LOGISTICS = (0,)
EXCHANGE_GRIDS = ButtonGrid(
origin=(470, 470), delta=(198.5, 0), button_shape=(83, 83), grid_shape=(3, 1), name='EXCHANGE_GRIDS')
EXCHANGE_BUTTONS = ButtonGrid(
origin=(440, 609), delta=(198.5, 0), button_shape=(144, 31), grid_shape=(3, 1), name='EXCHANGE_BUTTONS')
DEFAULT_ITEM_PRIORITY = [
't1',
't2',
't3',
'oxycola',
'coolant',
'coins',
'oil',
'merit'
]
DEFAULT_PLATE_PRIORITY = [
'torpedo',
'antiair',
'plane',
'gun',
'general'
]
GRADES = [s for s in DEFAULT_ITEM_PRIORITY if len(s) == 2]
EXCHANGE_FILTER = Filter(regex=re.compile('^(.*?)$'), attr=('name',))
class ExchangeLimitOcr(Digit):
@ -348,179 +328,37 @@ class GuildLogistics(GuildBase):
logger.info(f'supply_checked: {supply_checked}, mission_checked: {mission_checked}, '
f'exchange_checked: {exchange_checked}, mission_finished: {self._guild_logistics_mission_finished}')
return all([supply_checked, mission_checked, exchange_checked, self._guild_logistics_mission_finished])
@staticmethod
def _guild_exchange_priorities_helper(title, string_priority, default_priority):
"""
Helper for _guild_exchange_priorities for repeated usage
Use defaults if configurations are found
invalid in any manner
Pages:
in: GUILD_LOGISTICS
out: GUILD_LOGISTICS
"""
# Parse the string to a list, perform any special processing when applicable
priority_parsed = [s.strip().lower() for s in string_priority.split('>')]
priority_parsed = list(filter(''.__ne__, priority_parsed))
priority = priority_parsed.copy()
[priority.remove(s) for s in priority_parsed if s not in default_priority]
# If after all that processing, result is empty list, then use default
if len(priority) == 0:
priority = default_priority
# logger.info(f'{title:10}: {priority}')
return priority
def _guild_exchange_priorities(self):
"""
Set up priorities lists and dictionaries
based on configurations
Pages:
in: GUILD_LOGISTICS
out: GUILD_LOGISTICS
"""
# Items
item_priority = self._guild_exchange_priorities_helper(
'Item', self.config.GUILD_LOGISTICS_ITEM_ORDER_STRING, DEFAULT_ITEM_PRIORITY)
# T1 Grade Plates
t1_priority = self._guild_exchange_priorities_helper(
'T1 Plate', self.config.GUILD_LOGISTICS_PLATE_T1_ORDER_STRING, DEFAULT_PLATE_PRIORITY)
# T2 Grade Plates
t2_priority = self._guild_exchange_priorities_helper(
'T2 Plate', self.config.GUILD_LOGISTICS_PLATE_T2_ORDER_STRING, DEFAULT_PLATE_PRIORITY)
# T3 Grade Plates
t3_priority = self._guild_exchange_priorities_helper(
'T3 Plate', self.config.GUILD_LOGISTICS_PLATE_T3_ORDER_STRING, DEFAULT_PLATE_PRIORITY)
# Build dictionary
grade_to_plate_priorities = dict()
grade_to_plate_priorities['t1'] = t1_priority
grade_to_plate_priorities['t2'] = t2_priority
grade_to_plate_priorities['t3'] = t3_priority
return item_priority, grade_to_plate_priorities
# Azur Lane receives new guild missions now
# No longer consider `self._guild_logistics_mission_finished` as a check
return all([supply_checked, mission_checked, exchange_checked])
def _guild_exchange_scan(self):
"""
Image scan of available options
to be exchanged. Summarizes matching
templates and whether red text present
in a list of tuples
Image scan of available options.
Not exchangeable items are tagged enough=False.
Returns:
list[Item]:
Pages:
in: GUILD_LOGISTICS
out: GUILD_LOGISTICS
"""
# Scan the available exchange items that are selectable
self.exchange_items._load_image(self.device.image)
name = [self.exchange_items.match_template(item.image) for item in self.exchange_items.items]
name = [str(item).lower() for item in name]
items = self.exchange_items.predict(self.device.image, name=True, amount=False)
# Loop EXCHANGE_GRIDS to detect for red text in bottom right area
# indicating player lacks inventory for that item
in_red_list = []
for button in EXCHANGE_GRIDS.buttons:
for item, button in zip(items, EXCHANGE_GRIDS.buttons):
area = area_offset((35, 64, 83, 83), button.area[0:2])
if self.image_color_count(area, color=(255, 93, 90), threshold=221, count=20):
in_red_list.append(True)
item.enough = False
else:
in_red_list.append(False)
item.enough = True
# Zip contents of both lists into tuples
return zip(name, in_red_list)
@staticmethod
def _guild_exchange_check(options, item_priority, grade_to_plate_priorities):
"""
Sift through all exchangeable options
Record details on each to determine
selection order
Pages:
in: GUILD_LOGISTICS
out: GUILD_LOGISTICS
"""
# Contains the details of all options
choices = dict()
for i, (option, in_red) in enumerate(options):
# Options already sorted sequentially
# Button indexes are in sync
btn = EXCHANGE_BUTTONS[i, 0]
# Defaults set absurd values, which tells ALAS to skip option
item_weight = len(DEFAULT_ITEM_PRIORITY)
plate_weight = len(DEFAULT_PLATE_PRIORITY)
can_exchange = False
# Player lacks inventory of this item
# so leave this choice under all defaults
# to skip
if not in_red:
# Plate perhaps, extract last
# 2 characters to ensure
grade = option[-2:]
if grade in GRADES:
item_weight = item_priority.index(grade)
can_exchange = True
plate_priority = grade_to_plate_priorities.get(grade)
plate_name = option[5:-2]
if plate_name in plate_priority:
plate_weight = plate_priority.index(plate_name)
# Did weight update?
# If not, then this choice given less priority
# also set to absurd cost to avoid using
if plate_weight == len(DEFAULT_PLATE_PRIORITY):
item_weight = len(DEFAULT_ITEM_PRIORITY)
can_exchange = False
# Else normal item, check normally
# Plates are skipped since only grade in priority
if option in item_priority:
item_weight = item_priority.index(option)
can_exchange = True
choices[f'{i + 1}'] = [item_weight, plate_weight, i + 1, can_exchange, btn]
logger.info(f'Choice #{i + 1} - Name: {option:15}, Weight: {item_weight:3}, Exchangeable: {can_exchange}')
return choices
def _guild_exchange_select(self, choices):
"""
Execute exchange action on choices
The order of selection based on item weight
If none are applicable, return False
Pages:
in: GUILD_LOGISTICS
out: GUILD_LOGISTICS
"""
while len(choices):
# Select minimum by order of details
# First, item_weight then plate_weight then button_index
key = min(choices, key=choices.get)
details = choices.get(key)
# Item is exchangeable and exchange was a success
if details[3]:
self.device.click(details[4])
return True
else:
# Remove this choice since inapplicable, then choose again
choices.pop(key)
logger.warning('Failed to exchange with any of the 3 available options')
return False
text = [str(item.name) if item.enough else str(item.name) + ' (not enough)' for item in items]
logger.info(f'Exchange items: {", ".join(text)}')
return items
def _guild_exchange(self):
"""
@ -529,19 +367,28 @@ class GuildLogistics(GuildBase):
If unable to exchange at all, loop terminates
prematurely
Returns:
bool: If clicked.
Pages:
in: GUILD_LOGISTICS
out: GUILD_LOGISTICS
"""
item_priority, grade_to_plate_priorities = self._guild_exchange_priorities()
if not GUILD_EXCHANGE_LIMIT.ocr(self.device.image) > 0:
return False
options = self._guild_exchange_scan()
choices = self._guild_exchange_check(options, item_priority, grade_to_plate_priorities)
if self._guild_exchange_select(choices):
items = self._guild_exchange_scan()
EXCHANGE_FILTER.load(self.config.Logistics_ExchangeFilter)
selected = EXCHANGE_FILTER.apply(items, func=lambda item: item.enough)
logger.attr('Exchange_sort', ' > '.join([str(item.name) for item in selected]))
if len(selected):
button = EXCHANGE_BUTTONS.buttons[items.index(selected[0])]
# Just bored click, will retry in self._guild_logistics_collect
self.device.click(button)
return True
else:
logger.warning('No guild exchange items satisfy current filter, or not having enough resources')
return False
def guild_logistics(self):
@ -555,15 +402,9 @@ class GuildLogistics(GuildBase):
in: page_guild
out: page_guild, GUILD_LOGISTICS
"""
# Transition to Logistics
if not self.guild_side_navbar_ensure(bottom=3):
logger.info('Logistics sidebar not ensured, try again on next reward loop')
return False
self.guild_side_navbar_ensure(bottom=3)
self._guild_logistics_ensure()
# Run
checked = self._guild_logistics_collect()
if checked:
logger.info('All guild logistics finished today, skip checking them today')
return checked
result = self._guild_logistics_collect()
logger.info(f'Guild logistics run success: {result}')
return result

View File

@ -4,17 +4,11 @@ from module.base.utils import *
from module.guild.assets import *
from module.guild.base import GuildBase
from module.logger import logger
from module.map_detection.utils import Points
from module.ocr.ocr import DigitCounter
from module.template.assets import TEMPLATE_OPERATIONS_RED_DOT
GUILD_OPERATIONS_PROGRESS = DigitCounter(OCR_GUILD_OPERATIONS_PROGRESS, letter=(255, 247, 247), threshold=64)
RECORD_OPTION_DISPATCH = ('RewardRecord', 'operations_dispatch')
RECORD_SINCE_DISPATCH = (6, 12, 18, 21,)
RECORD_OPTION_BOSS = ('RewardRecord', 'operations_boss')
RECORD_SINCE_BOSS = (0,)
class GuildOperations(GuildBase):
def _guild_operations_ensure(self, skip_first_screenshot=True):
@ -36,7 +30,7 @@ class GuildOperations(GuildBase):
self.device.click(GUILD_OPERATIONS_CLICK_SAFE_AREA)
else:
current, remain, total = GUILD_OPERATIONS_PROGRESS.ocr(self.device.image)
threshold = total * self.config.GUILD_OPERATIONS_JOIN_THRESHOLD
threshold = total * self.config.Operation_JoinThreshold
if current <= threshold:
logger.info('Joining Operation, current progress less than '
f'threshold ({threshold:.2f})')
@ -141,11 +135,9 @@ class GuildOperations(GuildBase):
if len(entrance_1):
return True
backup = self.config.cover(DEVICE_CONTROL_METHOD='minitouch')
p1, p2 = random_rectangle_vector(
(-600, 0), box=detection_area, random_range=(-50, -50, 50, 50), padding=20)
self.device.drag(p1, p2, segments=2, shake=(0, 25), point_random=(0, 0, 0, 0), shake_random=(0, -5, 0, 5))
backup.recover()
self.device.sleep(0.3)
logger.warning('Failed to find active operation dispatch')
@ -397,7 +389,7 @@ class GuildOperations(GuildBase):
return False
continue
if self.config.ENABLE_GUILD_OPERATIONS_BOSS_RECOMMEND:
if self.config.Operation_BossFleetRecommend:
if self.info_bar_count() and self.appear_then_click(GUILD_DISPATCH_RECOMMEND_2, interval=3):
continue
@ -428,9 +420,7 @@ class GuildOperations(GuildBase):
if not self._guild_operations_boss_preparation(az):
return False
backup = self.config.cover(SUBMARINE=1, SUBMARINE_MODE='every_combat')
az.combat_execute(auto='combat_auto')
backup.recover()
az.combat_execute(auto='combat_auto', submarine='every_combat')
az.combat_status(expected_end='in_ui')
logger.info('Guild Raid Boss has been repelled')
return True
@ -448,31 +438,28 @@ class GuildOperations(GuildBase):
return appear
def guild_operations(self):
if not self.guild_side_navbar_ensure(bottom=1):
logger.info('Operations sidebar not ensured, try again on next reward loop')
return None
self.guild_side_navbar_ensure(bottom=1)
self._guild_operations_ensure()
# Determine the mode of operations, currently 3 are available
operations_mode = self._guild_operation_get_mode()
if operations_mode is None:
return
# Execute actions based on the detected mode
if operations_mode == 0:
return
result = True
elif operations_mode == 1:
self._guild_operations_dispatch()
self.config.record_save(option=RECORD_OPTION_DISPATCH)
result = True
elif operations_mode == 2:
if self._guild_operations_boss_available():
if self.config.Operation_AttackBoss:
result = self._guild_operations_boss_combat()
else:
logger.info('Auto-battle disabled, play manually to complete this Guild Task')
result = True
else:
result = True
else:
# Limit check for Guild Raid Boss to once a day
if not self.config.record_executed_since(option=RECORD_OPTION_BOSS, since=RECORD_SINCE_BOSS):
skip_record = False
if self._guild_operations_boss_available():
if self.config.ENABLE_GUILD_OPERATIONS_BOSS_AUTO:
if not self._guild_operations_boss_combat():
skip_record = True
else:
logger.info('Auto-battle disabled, play manually to complete this Guild Task')
result = False
if not skip_record:
self.config.record_save(option=RECORD_OPTION_BOSS)
logger.info(f'Guild operation run success: {result}')
return result