mirror of
https://github.com/LmeSzinc/AzurLaneAutoScript.git
synced 2025-01-08 12:27:33 +08:00
Refactor: Guild module
- Simplify guild exchange with Filter - Expose submarine mode
This commit is contained in:
parent
ca5a14a2dd
commit
c8c1cd9789
4
alas.py
4
alas.py
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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():
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user