diff --git a/assets/cn/campaign/EVENT_20201126_ENTRANCE_TEMP.png b/assets/cn/campaign/EVENT_20201126_ENTRANCE_TEMP.png new file mode 100644 index 000000000..4420fac7b Binary files /dev/null and b/assets/cn/campaign/EVENT_20201126_ENTRANCE_TEMP.png differ diff --git a/campaign/Readme.md b/campaign/Readme.md index 298f434f5..b22feb792 100644 --- a/campaign/Readme.md +++ b/campaign/Readme.md @@ -294,3 +294,4 @@ To add a new event, add a new row in here, and run `python -m module.config.conf | 20260417 | event 20201126 cn | Vacation Lane Rerun | 复刻假日航线 | Vacation Lane Rerun | バケーションレーン(復刻) | - | | 20260417 | event 20250424 cn | Toward Tulipa’s Seas Rerun | 复刻扬起郁金之旗 | Toward Tulipa’s Seas Rerun | チュリッパの海へ(復刻) | - | | 20260417 | event 20260417 cn | Vacation Lane – Beachside Brilliance | - | - | - | 假日航線閃耀海濱 | +| 20260417 | event 20201126 cn | Vacation Lane Rerun | - | - | - | 復刻假日航線 | diff --git a/campaign/event_20201126_cn/campaign_base.py b/campaign/event_20201126_cn/campaign_base.py index 41382e4c2..c8a631b1c 100644 --- a/campaign/event_20201126_cn/campaign_base.py +++ b/campaign/event_20201126_cn/campaign_base.py @@ -1,9 +1,11 @@ from module.base.button import Button -from module.campaign.assets import EVENT_20201126_PT_ICON, EVENT_20201126_DETAIL, EVENT_20201126_DETAIL_CHECK, EVENT_20201126_DETAIL_WHITE, EVENT_20201126_ENTRANCE +from module.campaign.assets import EVENT_20201126_DETAIL, EVENT_20201126_DETAIL_CHECK, EVENT_20201126_DETAIL_WHITE, \ + EVENT_20201126_ENTRANCE, EVENT_20201126_ENTRANCE_TEMP, EVENT_20201126_PT_ICON from module.campaign.campaign_base import CampaignBase as CampaignBase_ from module.exception import CampaignNameError from module.logger import logger from module.ui.page import page_campaign_menu, page_event, page_main_white +from module.ui_white.assets import MAIN_GOTO_CAMPAIGN_WHITE EVENT_ANIMATION = Button(area=(49, 229, 119, 400), color=(118, 215, 240), button=(49, 229, 119, 400), name='EVENT_ANIMATION') @@ -24,6 +26,11 @@ class CampaignBase(CampaignBase_): return True self.ui_ensure(page_campaign_menu) if self.is_event_entrance_available(): + if self.config.SERVER == 'tw': + self.ui_click(EVENT_20201126_ENTRANCE_TEMP, check_button=EVENT_20201126_PT_ICON, + appear_button=MAIN_GOTO_CAMPAIGN_WHITE, offset=(40, 20)) + return True + self.ui_goto_main() if self.ui_page_appear(page_main_white): self.ui_click(EVENT_20201126_DETAIL_WHITE, check_button=EVENT_20201126_DETAIL_CHECK) diff --git a/dev_tools/utils.py b/dev_tools/utils.py index e85e9682a..c8d75fb36 100644 --- a/dev_tools/utils.py +++ b/dev_tools/utils.py @@ -45,6 +45,52 @@ class LuaLoader: def filepath(self, path): return os.path.join(self.folder, self.server, path) + def _find_matching_brace(self, text, start_index): + depth = 0 + in_string = None + escape = False + for i in range(start_index, len(text)): + ch = text[i] + if in_string: + if escape: + escape = False + elif ch == '\\': + escape = True + elif ch == in_string: + in_string = None + else: + if ch in ('"', "'"): + in_string = ch + elif ch == '{': + depth += 1 + elif ch == '}': + depth -= 1 + if depth == 0: + return i + return -1 + + def _infer_base_name(self, file, keyword): + if keyword: + keyword = keyword.strip() + if keyword.startswith('pg.base.'): + return keyword[len('pg.base.') :] + if keyword.startswith('pg.'): + return keyword[len('pg.') :] + return keyword + return os.path.splitext(os.path.basename(file))[0] + + def _load_pg_base_entries(self, text, base_name): + pattern = rf"pg\.base\.{re.escape(base_name)}\[(\d+)\]\s*=\s*\{{" + result = {} + for m in re.finditer(pattern, text): + start = m.end() - 1 + end = self._find_matching_brace(text, start) + if end == -1: + continue + table_text = text[start:end + 1] + result[int(m.group(1))] = slpp.decode(table_text) + return result + def _load_file(self, file, keyword=None): """ Args: @@ -56,6 +102,16 @@ class LuaLoader: with open(self.filepath(file), 'r', encoding='utf-8') as f: text = f.read() + if 'pg.base.' in text: + base_name = self._infer_base_name(file, keyword) + if not base_name: + m = re.search(r"pg\.base\.([A-Za-z0-9_]+)\[", text) + base_name = m.group(1) if m else None + if base_name: + result = self._load_pg_base_entries(text, base_name) + if result: + return result + result = {} if text.startswith('_G'): text = '{' + text + '}' @@ -86,7 +142,7 @@ class LuaLoader: if os.path.isdir(self.filepath(path)): result = {} for file in tqdm(os.listdir(self.filepath(path))): - result.update(self._load_file(f'./{path}/{file}')) + result.update(self._load_file(f'./{path}/{file}', keyword=keyword)) else: result = self._load_file(path, keyword=keyword) diff --git a/module/campaign/assets.py b/module/campaign/assets.py index 90e2c507c..28f1edac2 100644 --- a/module/campaign/assets.py +++ b/module/campaign/assets.py @@ -15,6 +15,7 @@ EVENT_20201126_DETAIL = Button(area={'cn': (617, 470, 659, 510), 'en': (617, 470 EVENT_20201126_DETAIL_CHECK = Button(area={'cn': (1198, 20, 1244, 65), 'en': (1198, 20, 1244, 65), 'jp': (1198, 20, 1244, 65), 'tw': (1198, 20, 1244, 65)}, color={'cn': (176, 138, 121), 'en': (176, 138, 121), 'jp': (176, 138, 121), 'tw': (176, 138, 121)}, button={'cn': (1198, 20, 1244, 65), 'en': (1198, 20, 1244, 65), 'jp': (1198, 20, 1244, 65), 'tw': (1198, 20, 1244, 65)}, file={'cn': './assets/cn/campaign/EVENT_20201126_DETAIL_CHECK.png', 'en': './assets/cn/campaign/EVENT_20201126_DETAIL_CHECK.png', 'jp': './assets/cn/campaign/EVENT_20201126_DETAIL_CHECK.png', 'tw': './assets/cn/campaign/EVENT_20201126_DETAIL_CHECK.png'}) EVENT_20201126_DETAIL_WHITE = Button(area={'cn': (969, 94, 1030, 156), 'en': (969, 94, 1030, 156), 'jp': (969, 94, 1030, 156), 'tw': (969, 94, 1030, 156)}, color={'cn': (178, 148, 165), 'en': (178, 148, 165), 'jp': (178, 148, 165), 'tw': (178, 148, 165)}, button={'cn': (969, 94, 1030, 156), 'en': (969, 94, 1030, 156), 'jp': (969, 94, 1030, 156), 'tw': (969, 94, 1030, 156)}, file={'cn': './assets/cn/campaign/EVENT_20201126_DETAIL_WHITE.png', 'en': './assets/cn/campaign/EVENT_20201126_DETAIL_WHITE.png', 'jp': './assets/cn/campaign/EVENT_20201126_DETAIL_WHITE.png', 'tw': './assets/cn/campaign/EVENT_20201126_DETAIL_WHITE.png'}) EVENT_20201126_ENTRANCE = Button(area={'cn': (927, 599, 1054, 631), 'en': (927, 599, 1054, 631), 'jp': (927, 599, 1054, 631), 'tw': (927, 599, 1054, 631)}, color={'cn': (255, 255, 255), 'en': (255, 255, 255), 'jp': (255, 255, 255), 'tw': (255, 255, 255)}, button={'cn': (927, 599, 1054, 631), 'en': (927, 599, 1054, 631), 'jp': (927, 599, 1054, 631), 'tw': (927, 599, 1054, 631)}, file={'cn': './assets/cn/campaign/EVENT_20201126_ENTRANCE.png', 'en': './assets/cn/campaign/EVENT_20201126_ENTRANCE.png', 'jp': './assets/cn/campaign/EVENT_20201126_ENTRANCE.png', 'tw': './assets/cn/campaign/EVENT_20201126_ENTRANCE.png'}) +EVENT_20201126_ENTRANCE_TEMP = Button(area={'cn': (1022, 375, 1100, 398), 'en': (1022, 375, 1100, 398), 'jp': (1022, 375, 1100, 398), 'tw': (1022, 375, 1100, 398)}, color={'cn': (143, 197, 241), 'en': (143, 197, 241), 'jp': (143, 197, 241), 'tw': (143, 197, 241)}, button={'cn': (1022, 375, 1100, 398), 'en': (1022, 375, 1100, 398), 'jp': (1022, 375, 1100, 398), 'tw': (1022, 375, 1100, 398)}, file={'cn': './assets/cn/campaign/EVENT_20201126_ENTRANCE_TEMP.png', 'en': './assets/cn/campaign/EVENT_20201126_ENTRANCE_TEMP.png', 'jp': './assets/cn/campaign/EVENT_20201126_ENTRANCE_TEMP.png', 'tw': './assets/cn/campaign/EVENT_20201126_ENTRANCE_TEMP.png'}) EVENT_20201126_PT_ICON = Button(area={'cn': (1108, 102, 1133, 127), 'en': (1108, 102, 1133, 127), 'jp': (1108, 102, 1133, 127), 'tw': (1108, 102, 1133, 127)}, color={'cn': (231, 121, 159), 'en': (231, 121, 159), 'jp': (231, 121, 159), 'tw': (231, 121, 159)}, button={'cn': (1108, 102, 1133, 127), 'en': (1108, 102, 1133, 127), 'jp': (1108, 102, 1133, 127), 'tw': (1108, 102, 1133, 127)}, file={'cn': './assets/cn/campaign/EVENT_20201126_PT_ICON.png', 'en': './assets/cn/campaign/EVENT_20201126_PT_ICON.png', 'jp': './assets/cn/campaign/EVENT_20201126_PT_ICON.png', 'tw': './assets/cn/campaign/EVENT_20201126_PT_ICON.png'}) EVENT_20221124_ENTRANCE = Button(area={'cn': (1037, 162, 1077, 195), 'en': (1037, 162, 1077, 195), 'jp': (1037, 162, 1077, 195), 'tw': (1037, 162, 1077, 195)}, color={'cn': (207, 168, 148), 'en': (207, 168, 148), 'jp': (207, 168, 148), 'tw': (207, 168, 148)}, button={'cn': (1037, 162, 1077, 195), 'en': (1037, 162, 1077, 195), 'jp': (1037, 162, 1077, 195), 'tw': (1037, 162, 1077, 195)}, file={'cn': './assets/cn/campaign/EVENT_20221124_ENTRANCE.png', 'en': './assets/cn/campaign/EVENT_20221124_ENTRANCE.png', 'jp': './assets/cn/campaign/EVENT_20221124_ENTRANCE.png', 'tw': './assets/cn/campaign/EVENT_20221124_ENTRANCE.png'}) EVENT_20221124_PT_ICON = Button(area={'cn': (1106, 109, 1135, 130), 'en': (1071, 109, 1101, 129), 'jp': (1106, 109, 1135, 130), 'tw': (1106, 109, 1135, 130)}, color={'cn': (151, 116, 139), 'en': (152, 115, 138), 'jp': (151, 116, 139), 'tw': (151, 116, 139)}, button={'cn': (1106, 109, 1135, 130), 'en': (1071, 109, 1101, 129), 'jp': (1106, 109, 1135, 130), 'tw': (1106, 109, 1135, 130)}, file={'cn': './assets/cn/campaign/EVENT_20221124_PT_ICON.png', 'en': './assets/en/campaign/EVENT_20221124_PT_ICON.png', 'jp': './assets/cn/campaign/EVENT_20221124_PT_ICON.png', 'tw': './assets/cn/campaign/EVENT_20221124_PT_ICON.png'}) diff --git a/module/commission/commission.py b/module/commission/commission.py index 959d04082..9f6a69b85 100644 --- a/module/commission/commission.py +++ b/module/commission/commission.py @@ -12,7 +12,7 @@ from module.commission.project import COMMISSION_FILTER, Commission from module.config.config_generated import GeneratedConfig from module.config.utils import get_server_last_update, get_server_next_update, nearest_future from module.dorm.dorm import RewardDorm -from module.exception import GameStuckError +from module.exception import GameStuckError, OilMaxed, RequestHumanTakeover from module.handler.info_handler import InfoHandler from module.logger import logger from module.map.map_grids import SelectedGrids @@ -491,7 +491,7 @@ class RewardCommission(UI, InfoHandler): if not self.daily_choose and not self.urgent_choose: logger.info('No commission chose') - def commission_receive(self, skip_first_screenshot=True): + def _commission_receive(self, skip_first_screenshot=True): """ Args: skip_first_screenshot: @@ -559,11 +559,7 @@ class RewardCommission(UI, InfoHandler): # handle oil maxed if self.config.SERVER in ['cn']: if self.appear(OIL_MAXED, offset=(20, 20), interval=3): - logger.info("Oil maxed, buy food to consume oil") - RewardDorm(self.config, self.device).dorm_run( - feed=False, collect=False, buy_furniture=False, buy_food=10) - self.ui_ensure(page_reward) - continue + raise OilMaxed # Check GET_SHIP at last to handle random white background at page_main for button in [GET_SHIP]: if click_timer.reached() and self.appear(button, interval=1): @@ -581,6 +577,27 @@ class RewardCommission(UI, InfoHandler): return reward + def commission_receive(self): + """ + Returns: + bool: If rewarded. + + Pages: + in: page_reward + out: page_commission + """ + for _ in range(3): + try: + reward = self._commission_receive() + return reward + except OilMaxed: + logger.info("Oil maxed, buy food to consume oil") + RewardDorm(self.config, self.device).dorm_food_run(amount=10) + self.ui_ensure(page_reward) + + logger.critical(f'Failed to handle oil maxed after 3 trial') + raise RequestHumanTakeover + def run(self): """ Pages: diff --git a/module/config/argument/args.json b/module/config/argument/args.json index dcb5a644b..2ef1661d7 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -1995,6 +1995,7 @@ "event_20260417_cn" ], "option_tw": [ + "event_20201126_cn", "event_20260417_cn" ], "option_bold": [ @@ -2423,6 +2424,7 @@ "event_20260417_cn" ], "option_tw": [ + "event_20201126_cn", "event_20260417_cn" ], "option_bold": [ @@ -2845,6 +2847,7 @@ "event_20260417_cn" ], "option_tw": [ + "event_20201126_cn", "event_20260417_cn" ], "option_bold": [ @@ -4670,6 +4673,7 @@ "event_20260417_cn" ], "option_tw": [ + "event_20201126_cn", "event_20260417_cn" ], "option_bold": [ @@ -5110,6 +5114,7 @@ "event_20260417_cn" ], "option_tw": [ + "event_20201126_cn", "event_20260417_cn" ], "option_bold": [ @@ -5550,6 +5555,7 @@ "event_20260417_cn" ], "option_tw": [ + "event_20201126_cn", "event_20260417_cn" ], "option_bold": [ @@ -5990,6 +5996,7 @@ "event_20260417_cn" ], "option_tw": [ + "event_20201126_cn", "event_20260417_cn" ], "option_bold": [ @@ -6420,6 +6427,7 @@ "event_20260417_cn" ], "option_tw": [ + "event_20201126_cn", "event_20260417_cn" ], "option_bold": [ diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index cdb6a019d..a318c06ba 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -742,7 +742,7 @@ "event_20201002_en": "Counterattack Within the Fjord", "event_20201012_cn": "復刻劃破海空之翼", "event_20201029_cn": "激唱的UNIVERSE", - "event_20201126_cn": "假日航線", + "event_20201126_cn": "復刻假日航線", "event_20201229_cn": "復刻-負象限作戰", "event_20210121_cn": "復刻神聖的悲喜劇", "event_20210225_cn": "復刻破曉冰華", diff --git a/module/dorm/dorm.py b/module/dorm/dorm.py index b854a441a..c3c954599 100644 --- a/module/dorm/dorm.py +++ b/module/dorm/dorm.py @@ -481,13 +481,35 @@ class RewardDorm(UI): if self.appear_then_click(DORM_BUY_FOOD_CONFIRM, offset=(20, 20), interval=5): continue - def dorm_run(self, feed=True, collect=True, buy_furniture=False, buy_food=0): + def dorm_food_run(self, amount): + """ + Args: + amount (int): amount of food to buy + + Pages: + in: Any page + out: page_dorm + """ + if amount <= 0: + return + + self.ui_ensure(page_dormmenu) + self.handle_info_bar() + self.ui_goto(page_dorm, skip_first_screenshot=True) + logger.hr('Dorm buy food', level=1) + self.dorm_feed_enter() + self.dorm_buy_food_enter() + self.dorm_buy_food(amount=amount) + self.dorm_buy_food_confirm() + self.dorm_feed_quit() + + def dorm_run(self, feed=True, collect=True, buy_furniture=False): """ Pages: in: Any page out: page_dorm """ - if not feed and not collect and not buy_furniture and buy_food <= 0: + if not feed and not collect and not buy_furniture: return self.ui_ensure(page_dormmenu) @@ -516,19 +538,8 @@ class RewardDorm(UI): logger.hr('Dorm buy furniture', level=1) BuyFurniture(self.config, self.device).run() - if buy_food > 0: - logger.hr('Dorm buy food', level=1) - self.dorm_feed_enter() - self.dorm_buy_food_enter() - self.dorm_buy_food(amount=buy_food) - self.dorm_buy_food_confirm() - self.dorm_feed_quit() - def get_dorm_ship_amount(self): """ - Args: - skip_first_screenshot: - Returns: int: Number of ships in dorm diff --git a/module/exception.py b/module/exception.py index 5d7654fd9..5b08fe05d 100644 --- a/module/exception.py +++ b/module/exception.py @@ -6,6 +6,10 @@ class OilExhausted(Exception): pass +class OilMaxed(Exception): + pass + + class MapDetectionError(Exception): pass diff --git a/module/handler/info_handler.py b/module/handler/info_handler.py index 1a0c77363..6ea8ac246 100644 --- a/module/handler/info_handler.py +++ b/module/handler/info_handler.py @@ -337,8 +337,8 @@ class InfoHandler(ModuleBase): list[Button]: List of story options, from upper to bottom. If no option found, return an empty list. """ # Area to detect the options, should include at least 3 options. - story_option_area = (315, 130, 965, 555) - story_detect_area = (330, 130, 355, 555) + story_option_area = (330, 135, 980, 555) + story_detect_area = (330, 135, 355, 555) story_option_color = (247, 247, 247) image = color_similarity_2d(self.image_crop(story_detect_area, copy=False), color=story_option_color)