Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions module/campaign/campaign_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def execute_a_battle(self):

def run(self):
logger.hr(self.ENTRANCE, level=2)
self.emotion.reset_campaign()

# Enter map
self.emotion.check_reduce(self._map_battle)
Expand Down
4 changes: 2 additions & 2 deletions module/campaign/gems_farming.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

class GemsCampaignOverride(CampaignBase):

def handle_combat_low_emotion(self):
def handle_combat_low_emotion(self, fleet_index=None):
"""
Overwrite info_handler.handle_combat_low_emotion()
If change vanguard is enabled, withdraw combat and change flagship and vanguard
Expand All @@ -34,7 +34,7 @@ def handle_combat_low_emotion(self):
if result:
# Avoid clicking AUTO_SEARCH_MAP_OPTION_OFF
self.interval_reset(AUTO_SEARCH_MAP_OPTION_OFF)
return result
return 'ignore' if result else False

if self.handle_popup_cancel('IGNORE_LOW_EMOTION'):
self.config.GEMS_EMOTION_TRIGGERED = True
Expand Down
6 changes: 6 additions & 0 deletions module/campaign/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ def run(self, name, folder='campaign_main', mode='normal', total=0):
self.run_count += 1
if self.config.StopCondition_RunCount:
self.config.StopCondition_RunCount -= 1
if total and self.run_count >= total:
break
# End
if self.triggered_stop_condition(oil_check=False):
break
Expand All @@ -462,5 +464,9 @@ def run(self, name, folder='campaign_main', mode='normal', total=0):
if self.config.task_switched():
self.campaign.ensure_auto_search_exit()
self.config.task_stop()
break
# Emotion
if self.campaign.emotion.delay_after_campaign(self.campaign._map_battle):
break

self.campaign.ensure_auto_search_exit()
7 changes: 6 additions & 1 deletion module/coalition/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,12 @@ def enter_map(self, event, stage, mode):
continue

# Emotion
if self.handle_combat_low_emotion():
result = self.handle_combat_low_emotion(fleet_index=1)
if result == 'control':
self.emotion.delay_before_entering_map(
battle=self.coalition_get_battles(event, stage),
fleet_index=1)
if result:
continue

# Urgent commission
Expand Down
14 changes: 12 additions & 2 deletions module/combat/auto_search_combat.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ def map_offensive_auto_search(self, skip_first_screenshot=True):
if self.appear(AUTO_SEARCH_MAP_OPTION_ON, offset=self._auto_search_offset, interval=3) \
and self.appear_then_click(AUTO_SEARCH_MAP_OPTION_ON):
continue
if self.handle_combat_low_emotion():
result = self.handle_combat_low_emotion()
if result == 'control':
self.emotion.wait_after_low_emotion(
fleet_index=self.combat_low_emotion_fleet_index()
)
if result:
continue
if self.handle_retirement():
continue
Expand Down Expand Up @@ -180,7 +185,12 @@ def auto_search_moving(self, skip_first_screenshot=True):
break
if self.handle_auto_search_map_option():
continue
if self.handle_combat_low_emotion():
result = self.handle_combat_low_emotion()
if result == 'control':
self.emotion.wait_after_low_emotion(
fleet_index=self.combat_low_emotion_fleet_index()
)
if result:
self._auto_search_status_confirm = True
continue
if self.handle_story_skip():
Expand Down
14 changes: 12 additions & 2 deletions module/combat/combat.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ def map_offensive(self, skip_first_screenshot=True):

if self.appear_then_click(MAP_OFFENSIVE, interval=1):
continue
if self.handle_combat_low_emotion():
result = self.handle_combat_low_emotion()
if result == 'control':
self.emotion.wait_after_low_emotion(
fleet_index=self.combat_low_emotion_fleet_index()
)
if result:
self.interval_reset(MAP_OFFENSIVE)
continue
if self.handle_retirement():
Expand Down Expand Up @@ -240,7 +245,12 @@ def combat_preparation(self, balance_hp=False, emotion_reduce=False, auto='comba
continue
if self.handle_retirement():
continue
if self.handle_combat_low_emotion():
result = self.handle_combat_low_emotion(fleet_index=fleet_index)
if result == 'control':
self.emotion.wait_after_low_emotion(
fleet_index=self.combat_low_emotion_fleet_index(fleet_index=fleet_index)
)
if result:
continue
if balance_hp and self.handle_emergency_repair_use():
continue
Expand Down
191 changes: 167 additions & 24 deletions module/combat/emotion.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,28 +119,30 @@ def update(self):
recover_count = max(recover_count, 0)
self.current = min(max(self.value, 0) + self.speed * recover_count, self.max)

def get_recovered(self, expected_reduce=0):
def get_recovered(self, expected_reduce=0, control_limit=None):
"""
Args:
expected_reduce (int):
control_limit (int, None): Use configured control limit if None.

Returns:
datetime.datetime: When will emotion >= control limit.
datetime.datetime: When will emotion >= control limit + expected reduce.
If already recovered, return time in the past.
"""
if self.control == 'keep_exp_bonus' and self.recover == 'not_in_dormitory':
limit = self.limit if control_limit is None else control_limit
if control_limit is None and self.control == 'keep_exp_bonus' and self.recover == 'not_in_dormitory':
logger.critical(f'Fleet {self.fleet} Emotion Control=\"Keep Happy Bonus\" and '
f'Fleet {self.fleet} Recover Location=\"Docks\" can not be used together, '
'please check your emotion settings')
raise RequestHumanTakeover
# In 14-4 with 2X book, expected emotion reduce is 32, can't keep happy bonus (>120),
# otherwise will infinite task delay
if self.control == 'keep_exp_bonus' and expected_reduce >= 29:
if control_limit is None and self.control == 'keep_exp_bonus' and expected_reduce >= 29:
expected_reduce = 29
logger.info(f'Fleet {self.fleet} expected_reduce is limited to 29 '
f'when Emotion Control=\"Keep Happy Bonus\"')

recover_count = (self.limit + expected_reduce - self.current) // self.speed
recover_count = (limit + expected_reduce - self.current) // self.speed
recovered = (int(datetime.now().timestamp()) // 360 + recover_count + 1) * 360
return datetime.fromtimestamp(recovered)

Expand All @@ -158,6 +160,7 @@ def __init__(self, config):
self.fleet_1 = FleetEmotion(self.config, fleet=1)
self.fleet_2 = FleetEmotion(self.config, fleet=2)
self.fleets = [self.fleet_1, self.fleet_2]
self.reset_campaign()

@property
def is_calculate(self):
Expand Down Expand Up @@ -204,19 +207,35 @@ def reduce_per_battle_before_entering(self):
else:
return 2

def check_reduce(self, battle):
"""
Check emotion before entering a campaign.

Args:
battle (int): Battles in this campaign

Raise:
ScriptEnd: Delay current task to prevent emotion control in the future.
"""
if not self.is_calculate:
return

def reset_campaign(self):
self.low_emotion_triggered = False
self.fleets_used_in_campaign = set()

@staticmethod
def _valid_fleet_index(fleet_index):
try:
fleet_index = int(fleet_index)
except (TypeError, ValueError):
return None

if fleet_index in [1, 2]:
return fleet_index
return None

def _fleet_indexes(self, fleet_index=None):
if isinstance(fleet_index, (list, tuple, set)):
indexes = [
index for index in [self._valid_fleet_index(index) for index in fleet_index]
if index is not None
]
return sorted(set(indexes)) if indexes else [1, 2]

index = self._valid_fleet_index(fleet_index)
if index is not None:
return [index]
return [1, 2]

def _expected_reduce(self, battle):
method = self.config.Fleet_FleetOrder

if method == 'fleet1_mob_fleet2_boss':
Expand All @@ -232,39 +251,161 @@ def check_reduce(self, battle):

battle = tuple(np.array(battle) * self.reduce_per_battle_before_entering)
logger.info(f'Expect emotion reduce: {battle}')
return battle

def _get_recovered(self, battle):
battle = self._expected_reduce(battle)
self.update()
self.record()
self.show()
return max([f.get_recovered(b) for f, b in zip(self.fleets, battle)])

def delay_before_entering_map(self, battle, fleet_index=None):
"""
Delay current task when low emotion is reported before the map is entered.

Args:
battle (int): Battles in this campaign.
fleet_index (int, None): 1 or 2. Unknown fleets reset both ledgers.

Raise:
ScriptEnd: Stop current task normally so scheduler can continue.
"""
logger.hr('Emotion control')
self.reset_low_emotion(fleet_index=fleet_index)
recovered = self._get_recovered(battle)
logger.info(f'Delay current task until emotion recovers at {recovered}')
self.config.task_delay(target=recovered)
raise ScriptEnd('Emotion control')

def delay_after_campaign(self, battle):
"""
Delay current task after a map ended if low emotion control happened in this map.

Args:
battle (int): Battles in this campaign.

Returns:
bool: If delayed.
"""
if not self.is_calculate or not self.low_emotion_triggered:
self.reset_campaign()
return False

indexes = sorted(self.fleets_used_in_campaign)
if not indexes:
logger.warning('Low emotion triggered but no battle fleet was recorded')
self.reset_campaign()
return False

logger.hr('Emotion control after campaign')
recovered = self._get_recovered(battle)
self.reset_campaign()
if recovered > datetime.now():
logger.info(f'Delay current task until next run emotion recovers at {recovered}')
self.config.task_delay(target=recovered)
return True
return False

def register_battle_fleet(self, fleet_index):
try:
fleet_index = int(fleet_index)
except (TypeError, ValueError):
return

if fleet_index in [1, 2]:
self.fleets_used_in_campaign.add(fleet_index)

def reset_low_emotion(self, fleet_index=None):
"""
Reset emotion ledger after the game client reports low emotion.

Args:
fleet_index (int, None): 1 or 2. Unknown fleets reset both ledgers.

Returns:
list[FleetEmotion]: Fleets whose values were reset.
"""
indexes = self._fleet_indexes(fleet_index=fleet_index)
if len(indexes) > 1 and not isinstance(fleet_index, (list, tuple, set)):
logger.warning('Unable to identify low-emotion fleet, reset both fleet ledgers')

logger.hr('Emotion control')
self.update()
fleets = [self.fleets[index - 1] for index in indexes]
for fleet in fleets:
fleet.current = 0
logger.info(f'Reset emotion fleet {fleet.fleet} to {fleet.current}')

self.record()
self.show()
recovered = max([f.get_recovered(b) for f, b in zip(self.fleets, battle)])
return fleets

def wait_after_low_emotion(self, fleet_index=None):
"""
Wait in map after backing out of the low-emotion sortie popup.
"""
self.low_emotion_triggered = True
fleets = self.reset_low_emotion(fleet_index=fleet_index)
for fleet in fleets:
self.wait(fleet_index=fleet.fleet, control_limit=0, check_task_switch=True)

def check_reduce(self, battle):
"""
Check emotion before entering a campaign.

Args:
battle (int): Battles in this campaign

Raise:
ScriptEnd: Delay current task to prevent emotion control in the future.
"""
if not self.is_calculate:
return

recovered = self._get_recovered(battle)
if recovered > datetime.now():
logger.info('Delay current task to prevent emotion control in the future')
self.config.task_delay(target=recovered)
raise ScriptEnd('Emotion control')

def wait(self, fleet_index):
def wait(self, fleet_index, control_limit=None, check_task_switch=False):
"""
Wait emotion of specific fleet.
Should be called before entering any battles.

Args:
fleet_index (int): 1 or 2.
control_limit (int, None): Use configured control limit if None.
check_task_switch (bool): If check scheduler switch while waiting.
"""
self.update()
self.record()
self.show()
fleet = self.fleets[fleet_index - 1]
recovered = fleet.get_recovered(expected_reduce=self.reduce_per_battle)
expected_reduce = self.reduce_per_battle
recovered = fleet.get_recovered(expected_reduce=expected_reduce, control_limit=control_limit)
if recovered > datetime.now():
logger.hr('Emotion wait')
logger.info(f'Emotion of fleet {fleet_index} will recover to {fleet.limit} at {recovered}')
limit = fleet.limit if control_limit is None else control_limit
logger.info(
f'Emotion of fleet {fleet_index} will recover to {limit + expected_reduce} at {recovered}'
)

wait_interval = 5 if check_task_switch else 60
log_count = 0
while 1:
if datetime.now() > recovered:
break
if check_task_switch and self.config.task_switched():
logger.info('Task switched during emotion wait')
raise ScriptEnd('Emotion control')

logger.attr('Wait until', recovered)
sleep(60)
if log_count <= 0:
logger.attr('Wait until', recovered)
log_count = 12 if check_task_switch else 1
log_count -= 1
sleep(wait_interval)

def reduce(self, fleet_index):
"""
Expand All @@ -278,6 +419,8 @@ def reduce(self, fleet_index):
logger.hr('Emotion reduce')
self.update()

fleet_index = int(fleet_index)
self.register_battle_fleet(fleet_index)
fleet = self.fleets[fleet_index - 1]
fleet.current -= self.reduce_per_battle
self.total_reduced += self.reduce_per_battle
Expand Down
7 changes: 6 additions & 1 deletion module/event_hospital/combat.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ def check_coin():
check_coin()
if self.handle_retirement():
continue
if self.handle_combat_low_emotion():
result = self.handle_combat_low_emotion(fleet_index=fleet_index)
if result == 'control':
self.emotion.wait_after_low_emotion(
fleet_index=self.combat_low_emotion_fleet_index(fleet_index=fleet_index)
)
if result:
continue
if self.appear_then_click(BATTLE_PREPARATION, offset=(30, 20), interval=2):
continue
Expand Down
Loading