StarRailCopilot/module/combat/combat.py
LmeSzinc e128142678 Fix: Mis-detection
- Mis-detect BATTLE_STATUS_B when battle executing
- Mis-detect in_stage on potato device
- Optimize enemy_genre select for 12-2 leveling
2020-06-17 17:56:10 +08:00

375 lines
13 KiB
Python

import numpy as np
from module.base.timer import Timer
from module.base.utils import color_bar_percentage
from module.combat.assets import *
from module.combat.combat_auto import CombatAuto
from module.combat.combat_manual import CombatManual
from module.combat.emotion import Emotion
from module.combat.hp_balancer import HPBalancer
from module.combat.submarine import SubmarineCall
from module.handler.enemy_searching import EnemySearchingHandler
from module.logger import logger
from module.map.assets import MAP_OFFENSIVE
from module.retire.retirement import Retirement
from module.ui.assets import BACK_ARROW
class Combat(HPBalancer, EnemySearchingHandler, Retirement, SubmarineCall, CombatAuto, CombatManual):
_automation_set_timer = Timer(1)
_emotion: Emotion
battle_status_click_interval = 0
@property
def emotion(self):
if not hasattr(self, '_emotion'):
self._emotion = Emotion(config=self.config)
return self._emotion
def combat_appear(self):
"""
Returns:
bool: If enter combat.
"""
if self.config.ENABLE_MAP_FLEET_LOCK and not self.is_in_map():
if self.is_combat_loading():
return True
if self.appear(BATTLE_PREPARATION):
return True
if self.appear(BATTLE_PREPARATION_WITH_OVERLAY) and self.handle_combat_automation_confirm():
return True
return False
def map_offensive(self):
while 1:
self.device.screenshot()
if self.appear_then_click(MAP_OFFENSIVE, interval=1):
continue
if self.handle_combat_low_emotion():
continue
if self.handle_retirement():
continue
# Break
if self.combat_appear():
break
def is_combat_loading(self):
"""
Returns:
bool:
"""
left = color_bar_percentage(self.device.image, area=LOADING_BAR.area, prev_color=(99, 150, 255))
right = color_bar_percentage(self.device.image, area=LOADING_BAR.area, prev_color=(225, 225, 225), reverse=True)
if 0.15 < left < 0.95 and right > 0.15 and left + right <= 1.2:
logger.attr('Loading', f'{int(left * 100)}%({int(right * 100)}%)')
return True
return False
def is_combat_executing(self):
"""
Returns:
bool:
"""
return self.appear(PAUSE) and np.max(self.device.image.crop(PAUSE_DOUBLE_CHECK.area)) < 153
def handle_combat_automation_confirm(self):
if self.appear(AUTOMATION_CONFIRM_CHECK, interval=1):
self.appear_then_click(AUTOMATION_CONFIRM, offset=True)
return True
return False
def combat_preparation(self, balance_hp=False, emotion_reduce=False, auto=True, fleet_index=1):
"""
Args:
balance_hp (bool):
emotion_reduce (bool):
auto (bool):
fleet_index (int):
"""
logger.info('Combat preparation.')
if emotion_reduce:
self.emotion.wait(fleet=fleet_index)
if balance_hp:
self.hp_balance()
while 1:
self.device.screenshot()
if self.appear(BATTLE_PREPARATION):
if self.handle_combat_automation_set(auto=auto):
continue
if self.handle_retirement():
if self.config.ENABLE_HP_BALANCE:
self.wait_until_appear(BATTLE_PREPARATION)
# When re-entering battle_preparation page, the emergency icon is active by default, even if
# nothing to use. After a short animation, everything shows as usual.
self.device.sleep(0.5) # Wait animation.
continue
if self.handle_combat_low_emotion():
continue
if self.handle_emergency_repair_use():
continue
if self.appear_then_click(BATTLE_PREPARATION, interval=2):
continue
if self.handle_combat_automation_confirm():
continue
if self.handle_story_skip():
continue
# End
if self.is_combat_executing():
if emotion_reduce:
self.emotion.reduce(fleet_index)
break
def handle_combat_automation_set(self, auto):
"""
Args:
auto (bool): If use auto.
Returns:
bool:
"""
if not self._automation_set_timer.reached():
return False
if self.appear(AUTOMATION_ON):
logger.info('[Automation] ON')
if not auto:
self.device.click(AUTOMATION_SWITCH)
self.device.sleep(1)
self._automation_set_timer.reset()
return True
if self.appear(AUTOMATION_OFF):
logger.info('[Automation] OFF')
if auto:
self.device.click(AUTOMATION_SWITCH)
self.device.sleep(1)
self._automation_set_timer.reset()
return True
if self.appear_then_click(AUTOMATION_CONFIRM, offset=True):
self._automation_set_timer.reset()
return True
return False
def handle_emergency_repair_use(self):
if not self.config.ENABLE_HP_BALANCE:
return False
if self.appear_then_click(EMERGENCY_REPAIR_CONFIRM, offset=True):
self.device.sleep(0.5) # Animation: hp increase and emergency_repair amount decrease.
return True
if self.appear(BATTLE_PREPARATION) and self.appear(EMERGENCY_REPAIR_AVAILABLE):
logger.info('EMERGENCY_REPAIR_AVAILABLE')
if not len(self.hp):
return False
if np.min(np.array(self.hp)[np.array(self.hp) > 0.001]) < self.config.EMERGENCY_REPAIR_SINGLE_THRESHOLD \
or np.max(self.hp[:3]) < self.config.EMERGENCY_REPAIR_HOLE_THRESHOLD \
or np.max(self.hp[3:]) < self.config.EMERGENCY_REPAIR_HOLE_THRESHOLD:
logger.info('Use emergency repair')
self.device.click(EMERGENCY_REPAIR_AVAILABLE)
return True
return False
def combat_execute(self, auto=True, call_submarine_at_boss=False, save_get_items=False):
"""
Args:
auto (bool):
call_submarine_at_boss (bool):
save_get_items (bool)
"""
logger.info('Combat execute')
self.submarine_call_reset()
self.combat_auto_reset()
self.combat_manual_reset()
confirm_timer = Timer(10)
confirm_timer.start()
self.device.screenshot_interval_set(self.config.COMBAT_SCREENSHOT_INTERVAL)
while 1:
self.device.screenshot()
if not confirm_timer.reached() and self.appear_then_click(AUTOMATION_CONFIRM, offset=True):
continue
if self.handle_story_skip():
continue
if self.handle_combat_auto():
continue
if self.handle_combat_manual():
continue
if not auto 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
# End
if self.handle_battle_status(save_get_items=save_get_items):
self.device.screenshot_interval_set(0)
break
def handle_battle_status(self, save_get_items=False):
"""
Args:
save_get_items (bool):
Returns:
bool:
"""
if self.is_combat_executing():
return False
if self.appear_then_click(BATTLE_STATUS_S, screenshot=save_get_items, genre='status', interval=self.battle_status_click_interval):
if not save_get_items:
self.device.sleep((0.25, 0.5))
return True
if self.appear_then_click(BATTLE_STATUS_A, screenshot=save_get_items, genre='status', interval=self.battle_status_click_interval):
logger.warning('Battle status: A')
if not save_get_items:
self.device.sleep((0.25, 0.5))
return True
if self.appear_then_click(BATTLE_STATUS_B, screenshot=save_get_items, genre='status', interval=self.battle_status_click_interval):
logger.warning('Battle Status B')
if not save_get_items:
self.device.sleep((0.25, 0.5))
return True
return False
def handle_get_items(self, save_get_items=False):
"""
Args:
save_get_items (bool):
Returns:
bool:
"""
if self.appear_then_click(GET_ITEMS_1, screenshot=save_get_items, genre='get_items', offset=5,
interval=self.battle_status_click_interval):
self.interval_reset(BATTLE_STATUS_S)
self.interval_reset(BATTLE_STATUS_A)
self.interval_reset(BATTLE_STATUS_B)
return True
if self.appear_then_click(GET_ITEMS_2, screenshot=save_get_items, genre='get_items', offset=5,
interval=self.battle_status_click_interval):
self.interval_reset(BATTLE_STATUS_S)
self.interval_reset(BATTLE_STATUS_A)
self.interval_reset(BATTLE_STATUS_B)
return True
return False
def handle_exp_info(self):
"""
Returns:
bool:
"""
if self.appear_then_click(EXP_INFO_S):
self.device.sleep((0.25, 0.5))
return True
if self.appear_then_click(EXP_INFO_A):
self.device.sleep((0.25, 0.5))
return True
if self.appear_then_click(EXP_INFO_B):
self.device.sleep((0.25, 0.5))
return True
return False
def handle_get_ship(self, save_get_items=False):
"""
Args:
save_get_items (bool):
Returns:
bool:
"""
if self.appear_then_click(GET_SHIP, screenshot=save_get_items, genre='get_ship'):
return True
return False
def combat_status(self, save_get_items=False, expected_end=None):
"""
Args:
save_get_items (bool):
expected_end (str): with_searching, no_searching, in_stage.
"""
logger.info('Combat status')
logger.attr('expected_end', expected_end)
exp_info = False # This is for the white screen bug in game
while 1:
self.device.screenshot()
# Combat status
if not exp_info and self.handle_get_ship(save_get_items=save_get_items):
continue
if self.handle_get_items(save_get_items=save_get_items):
continue
if self.handle_battle_status(save_get_items=save_get_items):
continue
if self.handle_popup_confirm():
continue
if self.handle_exp_info():
exp_info = True
continue
if self.handle_urgent_commission(save_get_items=save_get_items):
continue
if self.handle_story_skip():
continue
# End
if self.handle_in_stage():
break
if expected_end is None:
if self.handle_in_map_with_enemy_searching():
break
if isinstance(expected_end, str):
if expected_end == 'in_stage' and self.handle_in_stage():
break
if expected_end == 'with_searching' and self.handle_in_map_with_enemy_searching():
break
if expected_end == 'no_searching' and self.handle_in_map_no_enemy_searching():
break
if expected_end == 'in_ui' and self.appear(BACK_ARROW, offset=(20, 20)):
break
if callable(expected_end):
if expected_end():
break
def combat(self, balance_hp=None, emotion_reduce=None, func=None, call_submarine_at_boss=None, save_get_items=None,
expected_end=None, fleet_index=1):
"""
Execute a combat.
"""
balance_hp = balance_hp if balance_hp is not None else self.config.ENABLE_HP_BALANCE
emotion_reduce = emotion_reduce if emotion_reduce is not None else self.config.ENABLE_EMOTION_REDUCE
auto = self.config.COMBAT_AUTO_MODE == 'combat_auto'
call_submarine_at_boss = call_submarine_at_boss if call_submarine_at_boss is not None else self.config.SUBMARINE_CALL_AT_BOSS
save_get_items = save_get_items if save_get_items is not None else self.config.ENABLE_SAVE_GET_ITEMS
self.battle_status_click_interval = 3 if save_get_items else 0
# if not hasattr(self, 'emotion'):
# self.emotion = Emotion(config=self.config)
self.combat_preparation(
balance_hp=balance_hp, emotion_reduce=emotion_reduce, auto=auto, fleet_index=fleet_index)
self.combat_execute(
auto=auto, call_submarine_at_boss=call_submarine_at_boss, save_get_items=save_get_items)
self.combat_status(
save_get_items=save_get_items, expected_end=expected_end)
self.handle_map_after_combat_story()