diff --git a/assets/cn/commission/OIL_MAXED.png b/assets/cn/commission/OIL_MAXED.png new file mode 100644 index 000000000..9baa15826 Binary files /dev/null and b/assets/cn/commission/OIL_MAXED.png differ diff --git a/assets/cn/dorm/DORM_BUY_FOOD_CHECK.png b/assets/cn/dorm/DORM_BUY_FOOD_CHECK.png new file mode 100644 index 000000000..f01963a7d Binary files /dev/null and b/assets/cn/dorm/DORM_BUY_FOOD_CHECK.png differ diff --git a/assets/cn/dorm/DORM_BUY_FOOD_CONFIRM.png b/assets/cn/dorm/DORM_BUY_FOOD_CONFIRM.png new file mode 100644 index 000000000..140efbeb2 Binary files /dev/null and b/assets/cn/dorm/DORM_BUY_FOOD_CONFIRM.png differ diff --git a/assets/cn/dorm/DORM_BUY_FOOD_ENTER.png b/assets/cn/dorm/DORM_BUY_FOOD_ENTER.png new file mode 100644 index 000000000..686498803 Binary files /dev/null and b/assets/cn/dorm/DORM_BUY_FOOD_ENTER.png differ diff --git a/assets/cn/dorm/FOOD_MINUS.png b/assets/cn/dorm/FOOD_MINUS.png new file mode 100644 index 000000000..a0b727069 Binary files /dev/null and b/assets/cn/dorm/FOOD_MINUS.png differ diff --git a/assets/cn/dorm/FOOD_PLUS.png b/assets/cn/dorm/FOOD_PLUS.png new file mode 100644 index 000000000..df703a514 Binary files /dev/null and b/assets/cn/dorm/FOOD_PLUS.png differ diff --git a/assets/cn/dorm/OCR_DORM_BUY_FOOD_AMOUNT.png b/assets/cn/dorm/OCR_DORM_BUY_FOOD_AMOUNT.png new file mode 100644 index 000000000..3b83c00b8 Binary files /dev/null and b/assets/cn/dorm/OCR_DORM_BUY_FOOD_AMOUNT.png differ diff --git a/campaign/Readme.md b/campaign/Readme.md index 228ec530f..159346b2a 100644 --- a/campaign/Readme.md +++ b/campaign/Readme.md @@ -291,3 +291,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/dev_tools/utils.py b/dev_tools/utils.py index ce6e8f6d1..07abb7d2d 100644 --- a/dev_tools/utils.py +++ b/dev_tools/utils.py @@ -45,7 +45,53 @@ class LuaLoader: def filepath(self, path): return os.path.join(self.folder, self.server, path) - def _load_file(self, file): + 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: file (str): @@ -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 = {} matched = re.findall('function \(\)(.*?)end[()]', text, re.S) if matched: @@ -106,7 +162,7 @@ class LuaLoader: return result - def load(self, path): + def load(self, path, keyword=None): """ Load a lua file to python dictionary, handling the differences @@ -121,9 +177,9 @@ 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) + result = self._load_file(path, keyword=keyword) print(f'{len(result.keys())} items loaded') return result diff --git a/module/commission/assets.py b/module/commission/assets.py index 0282d861f..eca4cf071 100644 --- a/module/commission/assets.py +++ b/module/commission/assets.py @@ -13,5 +13,6 @@ COMMISSION_URGENT = Button(area={'cn': (35, 231, 68, 281), 'en': (28, 221, 76, 2 EXP_INFO_S_REWARD = Button(area={'cn': (498, 140, 557, 154), 'en': (1138, 40, 1266, 145), 'jp': (498, 140, 557, 154), 'tw': (498, 140, 557, 154)}, color={'cn': (233, 241, 127), 'en': (89, 115, 159), 'jp': (233, 241, 127), 'tw': (233, 241, 127)}, button={'cn': (498, 140, 557, 154), 'en': (1138, 40, 1266, 145), 'jp': (498, 140, 557, 154), 'tw': (498, 140, 557, 154)}, file={'cn': './assets/cn/commission/EXP_INFO_S_REWARD.png', 'en': './assets/en/commission/EXP_INFO_S_REWARD.png', 'jp': './assets/jp/commission/EXP_INFO_S_REWARD.png', 'tw': './assets/tw/commission/EXP_INFO_S_REWARD.png'}) FUEL_MAXED = Button(area={'cn': (390, 266, 536, 346), 'en': (388, 311, 478, 336), 'jp': (388, 311, 478, 336), 'tw': (388, 311, 478, 336)}, color={'cn': (130, 109, 92), 'en': (87, 85, 94), 'jp': (87, 85, 94), 'tw': (87, 85, 94)}, button={'cn': (390, 266, 536, 346), 'en': (388, 311, 478, 336), 'jp': (388, 311, 478, 336), 'tw': (388, 311, 478, 336)}, file={'cn': './assets/cn/commission/FUEL_MAXED.png', 'en': './assets/en/commission/FUEL_MAXED.png', 'jp': './assets/jp/commission/FUEL_MAXED.png', 'tw': './assets/tw/commission/FUEL_MAXED.png'}) OCR_FUEL_MAXED = Button(area={'cn': (428, 308, 482, 337), 'en': (428, 308, 482, 337), 'jp': (428, 308, 482, 337), 'tw': (428, 308, 482, 337)}, color={'cn': (99, 96, 101), 'en': (99, 96, 101), 'jp': (99, 96, 101), 'tw': (99, 96, 101)}, button={'cn': (428, 308, 482, 337), 'en': (428, 308, 482, 337), 'jp': (428, 308, 482, 337), 'tw': (428, 308, 482, 337)}, file={'cn': './assets/cn/commission/OCR_FUEL_MAXED.png', 'en': './assets/cn/commission/OCR_FUEL_MAXED.png', 'jp': './assets/cn/commission/OCR_FUEL_MAXED.png', 'tw': './assets/cn/commission/OCR_FUEL_MAXED.png'}) +OIL_MAXED = Button(area={'cn': (428, 310, 534, 335), 'en': (428, 310, 534, 335), 'jp': (428, 310, 534, 335), 'tw': (428, 310, 534, 335)}, color={'cn': (106, 103, 110), 'en': (106, 103, 110), 'jp': (106, 103, 110), 'tw': (106, 103, 110)}, button={'cn': (428, 310, 534, 335), 'en': (428, 310, 534, 335), 'jp': (428, 310, 534, 335), 'tw': (428, 310, 534, 335)}, file={'cn': './assets/cn/commission/OIL_MAXED.png', 'en': './assets/cn/commission/OIL_MAXED.png', 'jp': './assets/cn/commission/OIL_MAXED.png', 'tw': './assets/cn/commission/OIL_MAXED.png'}) REWARD_1 = Button(area={'cn': (383, 285, 503, 297), 'en': (403, 274, 504, 290), 'jp': (432, 273, 476, 294), 'tw': (383, 285, 503, 297)}, color={'cn': (238, 168, 81), 'en': (241, 198, 145), 'jp': (241, 188, 122), 'tw': (238, 168, 81)}, button={'cn': (383, 285, 503, 297), 'en': (392, 262, 515, 303), 'jp': (403, 271, 514, 303), 'tw': (383, 285, 503, 297)}, file={'cn': './assets/cn/commission/REWARD_1.png', 'en': './assets/en/commission/REWARD_1.png', 'jp': './assets/jp/commission/REWARD_1.png', 'tw': './assets/tw/commission/REWARD_1.png'}) REWARD_SAVE_CLICK = Button(area={'cn': (415, 184, 496, 214), 'en': (415, 184, 496, 214), 'jp': (415, 184, 496, 214), 'tw': (415, 184, 496, 214)}, color={'cn': (152, 150, 168), 'en': (152, 150, 168), 'jp': (152, 150, 168), 'tw': (152, 150, 168)}, button={'cn': (415, 184, 496, 214), 'en': (415, 184, 496, 214), 'jp': (415, 184, 496, 214), 'tw': (415, 184, 496, 214)}, file={'cn': './assets/cn/commission/REWARD_SAVE_CLICK.png', 'en': './assets/en/commission/REWARD_SAVE_CLICK.png', 'jp': './assets/jp/commission/REWARD_SAVE_CLICK.png', 'tw': './assets/tw/commission/REWARD_SAVE_CLICK.png'}) diff --git a/module/commission/commission.py b/module/commission/commission.py index 99cb50be2..47b11eda9 100644 --- a/module/commission/commission.py +++ b/module/commission/commission.py @@ -12,7 +12,8 @@ from module.commission.preset import DICT_FILTER_PRESET, SHORTEST_FILTER 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.exception import GameStuckError +from module.dorm.dorm import RewardDorm +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 @@ -617,7 +618,7 @@ class RewardCommission(UI, InfoHandler): except Exception as e: logger.warning(f'Commission income recording failed: {e}') - def commission_receive(self, skip_first_screenshot=True): + def _commission_receive(self, skip_first_screenshot=True): logger.hr('Reward receive') reward = False @@ -680,23 +681,9 @@ class RewardCommission(UI, InfoHandler): self.interval_reset(GET_SHIP) continue - if self.appear(FUEL_MAXED, offset=(20, 20), interval=1): - logger.info("Fuel maxed, skip reward receive") - - # Force-write buy-food flag to config file. - # This avoids losing the flag when multiple config updates happen in one loop. - self.config.modified['Dorm.Dorm.BuyFood'] = True - self.config.save() - self.config.task_call('Dorm') - self.config.task_delay(minute=1) - # Write again after task_delay(), because it triggers an immediate update(). - self.config.modified['Dorm.Dorm.BuyFood'] = True - self.config.save() - logger.info( - f"Dorm buy-food flag set to: {self.config.cross_get('Dorm.Dorm.BuyFood')}" - ) - self.config.task_stop() - break + if self.config.SERVER in ['cn']: + if self.appear(OIL_MAXED, offset=(20, 20), interval=3): + raise OilMaxed for button in [GET_SHIP]: if click_timer.reached() and self.appear(button, interval=1): @@ -717,6 +704,26 @@ 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: + return self._commission_receive() + 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 b214207f5..7ba10b6cd 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -2147,8 +2147,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" @@ -2642,8 +2642,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" @@ -3541,8 +3541,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" @@ -3976,8 +3976,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" @@ -4411,8 +4411,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" @@ -6764,8 +6764,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" @@ -7216,8 +7216,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" @@ -7668,8 +7668,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" @@ -8120,8 +8120,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" @@ -8562,8 +8562,8 @@ "event_20260326_cn" ], "option_tw": [ - "event_20260417_cn", "event_20201126_cn", + "event_20260417_cn", "event_20220915_cn", "event_20260326_cn", "event_20220728_cn" diff --git a/module/dorm/assets.py b/module/dorm/assets.py index df2c2ebc1..8e758f506 100644 --- a/module/dorm/assets.py +++ b/module/dorm/assets.py @@ -4,6 +4,9 @@ from module.base.template import Template # This file was automatically generated by dev_tools/button_extract.py. # Don't modify it manually. +DORM_BUY_FOOD_CHECK = Button(area={'cn': (607, 427, 675, 459), 'en': (607, 427, 675, 459), 'jp': (607, 427, 675, 459), 'tw': (607, 427, 675, 459)}, color={'cn': (184, 182, 182), 'en': (184, 182, 182), 'jp': (184, 182, 182), 'tw': (184, 182, 182)}, button={'cn': (607, 427, 675, 459), 'en': (607, 427, 675, 459), 'jp': (607, 427, 675, 459), 'tw': (607, 427, 675, 459)}, file={'cn': './assets/cn/dorm/DORM_BUY_FOOD_CHECK.png', 'en': './assets/cn/dorm/DORM_BUY_FOOD_CHECK.png', 'jp': './assets/cn/dorm/DORM_BUY_FOOD_CHECK.png', 'tw': './assets/cn/dorm/DORM_BUY_FOOD_CHECK.png'}) +DORM_BUY_FOOD_CONFIRM = Button(area={'cn': (751, 499, 792, 520), 'en': (751, 499, 792, 520), 'jp': (751, 499, 792, 520), 'tw': (751, 499, 792, 520)}, color={'cn': (255, 238, 186), 'en': (255, 238, 186), 'jp': (255, 238, 186), 'tw': (255, 238, 186)}, button={'cn': (751, 499, 792, 520), 'en': (751, 499, 792, 520), 'jp': (751, 499, 792, 520), 'tw': (751, 499, 792, 520)}, file={'cn': './assets/cn/dorm/DORM_BUY_FOOD_CONFIRM.png', 'en': './assets/cn/dorm/DORM_BUY_FOOD_CONFIRM.png', 'jp': './assets/cn/dorm/DORM_BUY_FOOD_CONFIRM.png', 'tw': './assets/cn/dorm/DORM_BUY_FOOD_CONFIRM.png'}) +DORM_BUY_FOOD_ENTER = Button(area={'cn': (866, 375, 888, 398), 'en': (866, 375, 888, 398), 'jp': (866, 375, 888, 398), 'tw': (866, 375, 888, 398)}, color={'cn': (119, 110, 71), 'en': (119, 110, 71), 'jp': (119, 110, 71), 'tw': (119, 110, 71)}, button={'cn': (866, 375, 888, 398), 'en': (866, 375, 888, 398), 'jp': (866, 375, 888, 398), 'tw': (866, 375, 888, 398)}, file={'cn': './assets/cn/dorm/DORM_BUY_FOOD_ENTER.png', 'en': './assets/cn/dorm/DORM_BUY_FOOD_ENTER.png', 'jp': './assets/cn/dorm/DORM_BUY_FOOD_ENTER.png', 'tw': './assets/cn/dorm/DORM_BUY_FOOD_ENTER.png'}) DORM_FEED_CHECK = Button(area={'cn': (162, 342, 274, 370), 'en': (162, 342, 274, 370), 'jp': (162, 342, 274, 370), 'tw': (162, 342, 274, 370)}, color={'cn': (182, 179, 171), 'en': (182, 179, 171), 'jp': (182, 179, 171), 'tw': (182, 179, 171)}, button={'cn': (162, 342, 274, 370), 'en': (162, 342, 274, 370), 'jp': (162, 342, 274, 370), 'tw': (162, 342, 274, 370)}, file={'cn': './assets/cn/dorm/DORM_FEED_CHECK.png', 'en': './assets/en/dorm/DORM_FEED_CHECK.png', 'jp': './assets/jp/dorm/DORM_FEED_CHECK.png', 'tw': './assets/tw/dorm/DORM_FEED_CHECK.png'}) DORM_FEED_ENTER = Button(area={'cn': (254, 581, 300, 605), 'en': (298, 581, 344, 605), 'jp': (254, 581, 300, 605), 'tw': (254, 581, 300, 605)}, color={'cn': (204, 192, 177), 'en': (204, 192, 176), 'jp': (204, 192, 177), 'tw': (204, 192, 177)}, button={'cn': (254, 581, 300, 605), 'en': (298, 581, 344, 605), 'jp': (254, 581, 300, 605), 'tw': (254, 581, 300, 605)}, file={'cn': './assets/cn/dorm/DORM_FEED_ENTER.png', 'en': './assets/en/dorm/DORM_FEED_ENTER.png', 'jp': './assets/jp/dorm/DORM_FEED_ENTER.png', 'tw': './assets/tw/dorm/DORM_FEED_ENTER.png'}) DORM_FURNITURE_BUY_ALL = Button(area={'cn': (818, 621, 1072, 677), 'en': (819, 621, 1072, 677), 'jp': (899, 636, 991, 661), 'tw': (818, 621, 1072, 677)}, color={'cn': (249, 202, 66), 'en': (248, 201, 66), 'jp': (215, 171, 65), 'tw': (248, 201, 66)}, button={'cn': (818, 621, 1072, 677), 'en': (819, 621, 1072, 677), 'jp': (899, 636, 991, 661), 'tw': (818, 621, 1072, 677)}, file={'cn': './assets/cn/dorm/DORM_FURNITURE_BUY_ALL.png', 'en': './assets/en/dorm/DORM_FURNITURE_BUY_ALL.png', 'jp': './assets/jp/dorm/DORM_FURNITURE_BUY_ALL.png', 'tw': './assets/tw/dorm/DORM_FURNITURE_BUY_ALL.png'}) @@ -21,11 +24,10 @@ DORM_MANAGE = Button(area={'cn': (949, 600, 1005, 659), 'en': (949, 600, 1005, 6 DORM_MANAGE_CHECK = Button(area={'cn': (1128, 116, 1150, 135), 'en': (1128, 116, 1150, 135), 'jp': (1128, 116, 1150, 135), 'tw': (1128, 116, 1150, 135)}, color={'cn': (173, 147, 77), 'en': (173, 147, 77), 'jp': (173, 147, 77), 'tw': (173, 147, 77)}, button={'cn': (1128, 116, 1150, 135), 'en': (1128, 116, 1150, 135), 'jp': (1128, 116, 1150, 135), 'tw': (1128, 116, 1150, 135)}, file={'cn': './assets/cn/dorm/DORM_MANAGE_CHECK.png', 'en': './assets/en/dorm/DORM_MANAGE_CHECK.png', 'jp': './assets/jp/dorm/DORM_MANAGE_CHECK.png', 'tw': './assets/tw/dorm/DORM_MANAGE_CHECK.png'}) DORM_QUICK_COLLECT = Button(area={'cn': (1191, 497, 1251, 519), 'en': (1191, 497, 1251, 519), 'jp': (1191, 497, 1251, 519), 'tw': (1191, 497, 1251, 519)}, color={'cn': (243, 194, 138), 'en': (243, 194, 138), 'jp': (243, 194, 138), 'tw': (243, 194, 138)}, button={'cn': (1191, 497, 1251, 519), 'en': (1191, 497, 1251, 519), 'jp': (1191, 497, 1251, 519), 'tw': (1191, 497, 1251, 519)}, file={'cn': './assets/cn/dorm/DORM_QUICK_COLLECT.png', 'en': './assets/en/dorm/DORM_QUICK_COLLECT.png', 'jp': './assets/jp/dorm/DORM_QUICK_COLLECT.png', 'tw': './assets/tw/dorm/DORM_QUICK_COLLECT.png'}) DORM_RED_DOT = Button(area={'cn': (528, 339, 543, 356), 'en': (528, 339, 543, 356), 'jp': (528, 339, 543, 356), 'tw': (528, 339, 543, 356)}, color={'cn': (214, 126, 114), 'en': (214, 126, 114), 'jp': (214, 126, 114), 'tw': (214, 126, 114)}, button={'cn': (528, 339, 543, 356), 'en': (528, 339, 543, 356), 'jp': (528, 339, 543, 356), 'tw': (528, 339, 543, 356)}, file={'cn': './assets/cn/dorm/DORM_RED_DOT.png', 'en': './assets/en/dorm/DORM_RED_DOT.png', 'jp': './assets/jp/dorm/DORM_RED_DOT.png', 'tw': './assets/tw/dorm/DORM_RED_DOT.png'}) -FOOD_BUY_ADD_10 = Button(area={'cn': (851, 369, 902, 400), 'en': (0, 0, 899, 400), 'jp': (0, 0, 899, 400), 'tw': (0, 0, 899, 400)}, color={'cn': (224, 224, 225), 'en': (1, 1, 1), 'jp': (1, 1, 1), 'tw': (1, 1, 1)}, button={'cn': (851, 369, 902, 400), 'en': (0, 0, 899, 400), 'jp': (0, 0, 899, 400), 'tw': (0, 0, 899, 400)}, file={'cn': './assets/cn/dorm/FOOD_BUY_ADD_10.png', 'en': './assets/en/dorm/FOOD_BUY_ADD_10.png', 'jp': './assets/jp/dorm/FOOD_BUY_ADD_10.png', 'tw': './assets/tw/dorm/FOOD_BUY_ADD_10.png'}) -FOOD_BUY_CONFIRM = Button(area={'cn': (688, 490, 860, 530), 'en': (656, 489, 886, 531), 'jp': (656, 489, 886, 531), 'tw': (656, 489, 886, 531)}, color={'cn': (255, 216, 93), 'en': (255, 215, 90), 'jp': (255, 215, 90), 'tw': (255, 215, 90)}, button={'cn': (688, 490, 860, 530), 'en': (656, 489, 886, 531), 'jp': (656, 489, 886, 531), 'tw': (656, 489, 886, 531)}, file={'cn': './assets/cn/dorm/FOOD_BUY_CONFIRM.png', 'en': './assets/en/dorm/FOOD_BUY_CONFIRM.png', 'jp': './assets/jp/dorm/FOOD_BUY_CONFIRM.png', 'tw': './assets/tw/dorm/FOOD_BUY_CONFIRM.png'}) -FOOD_BUY_COST = Button(area={'cn': (607, 426, 692, 459), 'en': (605, 429, 691, 458), 'jp': (605, 429, 691, 458), 'tw': (605, 429, 691, 458)}, color={'cn': (191, 189, 188), 'en': (183, 180, 180), 'jp': (183, 180, 180), 'tw': (183, 180, 180)}, button={'cn': (607, 426, 692, 459), 'en': (605, 429, 691, 458), 'jp': (605, 429, 691, 458), 'tw': (605, 429, 691, 458)}, file={'cn': './assets/cn/dorm/FOOD_BUY_COST.png', 'en': './assets/en/dorm/FOOD_BUY_COST.png', 'jp': './assets/jp/dorm/FOOD_BUY_COST.png', 'tw': './assets/tw/dorm/FOOD_BUY_COST.png'}) +FOOD_MINUS = Button(area={'cn': (532, 370, 554, 397), 'en': (532, 370, 554, 397), 'jp': (532, 370, 554, 397), 'tw': (532, 370, 554, 397)}, color={'cn': (246, 246, 247), 'en': (246, 246, 247), 'jp': (246, 246, 247), 'tw': (246, 246, 247)}, button={'cn': (532, 370, 554, 397), 'en': (532, 370, 554, 397), 'jp': (532, 370, 554, 397), 'tw': (532, 370, 554, 397)}, file={'cn': './assets/cn/dorm/FOOD_MINUS.png', 'en': './assets/cn/dorm/FOOD_MINUS.png', 'jp': './assets/cn/dorm/FOOD_MINUS.png', 'tw': './assets/cn/dorm/FOOD_MINUS.png'}) +FOOD_PLUS = Button(area={'cn': (807, 370, 826, 397), 'en': (807, 370, 826, 397), 'jp': (807, 370, 826, 397), 'tw': (807, 370, 826, 397)}, color={'cn': (248, 248, 248), 'en': (248, 248, 248), 'jp': (248, 248, 248), 'tw': (248, 248, 248)}, button={'cn': (807, 370, 826, 397), 'en': (807, 370, 826, 397), 'jp': (807, 370, 826, 397), 'tw': (807, 370, 826, 397)}, file={'cn': './assets/cn/dorm/FOOD_PLUS.png', 'en': './assets/cn/dorm/FOOD_PLUS.png', 'jp': './assets/cn/dorm/FOOD_PLUS.png', 'tw': './assets/cn/dorm/FOOD_PLUS.png'}) +OCR_DORM_BUY_FOOD_AMOUNT = Button(area={'cn': (653, 374, 706, 395), 'en': (653, 374, 706, 395), 'jp': (653, 374, 706, 395), 'tw': (653, 374, 706, 395)}, color={'cn': (203, 203, 204), 'en': (203, 203, 204), 'jp': (203, 203, 204), 'tw': (203, 203, 204)}, button={'cn': (653, 374, 706, 395), 'en': (653, 374, 706, 395), 'jp': (653, 374, 706, 395), 'tw': (653, 374, 706, 395)}, file={'cn': './assets/cn/dorm/OCR_DORM_BUY_FOOD_AMOUNT.png', 'en': './assets/cn/dorm/OCR_DORM_BUY_FOOD_AMOUNT.png', 'jp': './assets/cn/dorm/OCR_DORM_BUY_FOOD_AMOUNT.png', 'tw': './assets/cn/dorm/OCR_DORM_BUY_FOOD_AMOUNT.png'}) OCR_DORM_FILL = Button(area={'cn': (813, 271, 987, 296), 'en': (813, 271, 987, 296), 'jp': (813, 271, 987, 296), 'tw': (813, 271, 987, 296)}, color={'cn': (222, 213, 193), 'en': (222, 213, 193), 'jp': (222, 213, 193), 'tw': (222, 213, 193)}, button={'cn': (813, 271, 987, 296), 'en': (813, 271, 987, 296), 'jp': (813, 271, 987, 296), 'tw': (813, 271, 987, 296)}, file={'cn': './assets/cn/dorm/OCR_DORM_FILL.png', 'en': './assets/en/dorm/OCR_DORM_FILL.png', 'jp': './assets/jp/dorm/OCR_DORM_FILL.png', 'tw': './assets/tw/dorm/OCR_DORM_FILL.png'}) OCR_DORM_FURNITURE_COIN = Button(area={'cn': (897, 20, 988, 49), 'en': (897, 20, 988, 49), 'jp': (897, 20, 988, 49), 'tw': (897, 20, 988, 49)}, color={'cn': (203, 197, 194), 'en': (203, 197, 194), 'jp': (203, 197, 194), 'tw': (203, 197, 194)}, button={'cn': (897, 20, 988, 49), 'en': (897, 20, 988, 49), 'jp': (897, 20, 988, 49), 'tw': (897, 20, 988, 49)}, file={'cn': './assets/cn/dorm/OCR_DORM_FURNITURE_COIN.png', 'en': './assets/en/dorm/OCR_DORM_FURNITURE_COIN.png', 'jp': './assets/jp/dorm/OCR_DORM_FURNITURE_COIN.png', 'tw': './assets/tw/dorm/OCR_DORM_FURNITURE_COIN.png'}) OCR_DORM_FURNITURE_PRICE = Button(area={'cn': (819, 417, 896, 442), 'en': (819, 417, 896, 442), 'jp': (819, 417, 896, 442), 'tw': (819, 417, 896, 442)}, color={'cn': (227, 223, 220), 'en': (227, 223, 220), 'jp': (227, 223, 220), 'tw': (227, 223, 220)}, button={'cn': (819, 417, 896, 442), 'en': (819, 417, 896, 442), 'jp': (819, 417, 896, 442), 'tw': (819, 417, 896, 442)}, file={'cn': './assets/cn/dorm/OCR_DORM_FURNITURE_PRICE.png', 'en': './assets/en/dorm/OCR_DORM_FURNITURE_PRICE.png', 'jp': './assets/jp/dorm/OCR_DORM_FURNITURE_PRICE.png', 'tw': './assets/tw/dorm/OCR_DORM_FURNITURE_PRICE.png'}) OCR_DORM_SLOT = Button(area={'cn': (112, 662, 155, 694), 'en': (112, 662, 155, 694), 'jp': (112, 662, 155, 694), 'tw': (112, 662, 155, 694)}, color={'cn': (217, 217, 217), 'en': (217, 217, 217), 'jp': (217, 217, 217), 'tw': (217, 217, 217)}, button={'cn': (112, 662, 155, 694), 'en': (112, 662, 155, 694), 'jp': (112, 662, 155, 694), 'tw': (112, 662, 155, 694)}, file={'cn': './assets/cn/dorm/OCR_DORM_SLOT.png', 'en': './assets/en/dorm/OCR_DORM_SLOT.png', 'jp': './assets/jp/dorm/OCR_DORM_SLOT.png', 'tw': './assets/tw/dorm/OCR_DORM_SLOT.png'}) -OCR_FUEL_COST = Button(area={'cn': (644, 428, 705, 457), 'en': (644, 428, 705, 457), 'jp': (644, 428, 705, 457), 'tw': (644, 428, 705, 457)}, color={'cn': (210, 205, 205), 'en': (210, 205, 205), 'jp': (210, 205, 205), 'tw': (210, 205, 205)}, button={'cn': (644, 428, 705, 457), 'en': (644, 428, 705, 457), 'jp': (644, 428, 705, 457), 'tw': (644, 428, 705, 457)}, file={'cn': './assets/cn/dorm/OCR_FUEL_COST.png', 'en': './assets/en/dorm/OCR_FUEL_COST.png', 'jp': './assets/jp/dorm/OCR_FUEL_COST.png', 'tw': './assets/tw/dorm/OCR_FUEL_COST.png'}) diff --git a/module/dorm/dorm.py b/module/dorm/dorm.py index 61643b49b..c3c954599 100644 --- a/module/dorm/dorm.py +++ b/module/dorm/dorm.py @@ -1,7 +1,7 @@ import time import typing as t -from module.base.button import ButtonGrid, Button +from module.base.button import ButtonGrid from module.base.decorator import Config, cached_property from module.base.filter import Filter from module.base.mask import Mask @@ -21,22 +21,8 @@ MASK_DORM = Mask(file='./assets/mask/MASK_DORM.png') DORM_CAMERA_SWIPE = (300, 250) DORM_CAMERA_RANDOM = (-20, -20, 20, 20) OCR_SLOT = DigitCounter(OCR_DORM_SLOT, letter=(107, 89, 82), threshold=128, name='OCR_DORM_SLOT') -OCR_FUEL_COST = Digit(OCR_FUEL_COST, letter=(107, 89, 90), threshold=128, name='OCR_DORM_FUEL_COST') -BTN_BUY_CURRY = Button( - area={ - 'cn': (862, 370, 892, 400), - 'en': (862, 370, 892, 400), - 'jp': (862, 370, 892, 400), - 'tw': (862, 370, 892, 400) - }, - button={ - 'cn': (862, 370, 892, 400), - 'en': (862, 370, 892, 400), - 'jp': (862, 370, 892, 400), - 'tw': (862, 370, 892, 400) - }, - color={'cn': (1, 1, 1), 'en': (1, 1, 1), 'jp': (1, 1, 1), 'tw': (1, 1, 1)} -) +OCR_BUY_FOOD_AMOUNT = Digit(OCR_DORM_BUY_FOOD_AMOUNT, letter=(96, 96, 100), threshold=128, name='OCR_DORM_BUY_FOOD_AMOUNT') + class OcrDormFood(DigitCounter): def pre_process(self, image): @@ -214,7 +200,7 @@ class RewardDorm(UI): f'does not support DOWN/UP events, use multi-click instead') self.device.multi_click(button, count) - def dorm_view_reset(self, skip_first_screenshot=True): + def dorm_view_reset(self): """ Use Dorm manage and Back to reset dorm view. @@ -223,12 +209,7 @@ class RewardDorm(UI): out: page_dorm """ logger.info('Dorm view reset') - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - + for _ in self.loop(): # End if self.appear(DORM_MANAGE_CHECK, offset=(20, 20)): break @@ -241,13 +222,7 @@ class RewardDorm(UI): if self.appear_then_click(DORM_FURNITURE_CONFIRM, offset=(30, 30), interval=3): continue - skip_first_screenshot = True - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - + for _ in self.loop(): if self.appear(DORM_MANAGE, offset=(20, 20)): break @@ -266,17 +241,11 @@ class RewardDorm(UI): logger.hr('Dorm collect') self.ensure_no_info_bar() - skip_first_screenshot = True # Set a timer to avoid Alas failing to detect the info_bar by accident. timeout = Timer(1.5, count=3).start() - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - + for _ in self.loop(): # Handle all popups if self.ui_additional(get_ship=False): continue @@ -327,11 +296,7 @@ class RewardDorm(UI): skip_first_screenshot = True self.popup_interval_clear() - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() + for _ in self.loop(skip_first=skip_first_screenshot): # End if self.appear(DORM_FEED_CHECK, offset=(20, 20)): break @@ -369,13 +334,7 @@ class RewardDorm(UI): timeout = Timer(1.5, count=3).start() food: t.List[Food] = [] fill: int = 0 - skip_first_screenshot = True - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - + for _ in self.loop(): # End if timeout.reached(): logger.warning('Get dorm food timeout, probably because food is empty') @@ -403,28 +362,6 @@ class RewardDorm(UI): return False - def buy_food(self): - """ - Buy 11 navy curries, should only be here when fuel is maxed. - - Returns: - bool: If food purchase was confirmed. - """ - while 1: - self.device.screenshot() - cost = OCR_FUEL_COST.ocr(self.device.image) - logger.info(f'Current dorm food fuel cost: {cost}') - if self.appear(FOOD_BUY_COST) and cost > 100: - self.appear_then_click(FOOD_BUY_CONFIRM) - return True - elif self.appear(FOOD_BUY_ADD_10) and cost < 100: - self.device.click(FOOD_BUY_ADD_10) - elif cost > 1000: - logger.warning('Incorrect cost for dorm food, abort') - return False - else: - self.device.click(BTN_BUY_CURRY) - def dorm_feed(self): """ Returns: @@ -443,19 +380,14 @@ class RewardDorm(UI): logger.warning('Dorm feed run count reached') return 10 - def dorm_feed_enter(self, skip_first_screenshot=False): + def dorm_feed_enter(self): """ Pages: in: DORM_CHECK out: DORM_FEED_CHECK """ self.interval_clear(DORM_CHECK) - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - + for _ in self.loop(skip_first=False): # End if self.appear(DORM_FEED_CHECK, offset=(20, 20)): break @@ -479,19 +411,14 @@ class RewardDorm(UI): logger.info(f'{DORM_FURNITURE_SHOP_FIRST_SELECTED} -> {DORM_FURNITURE_SHOP_QUIT}') continue - def dorm_feed_quit(self, skip_first_screenshot=True): + def dorm_feed_quit(self): """ Pages: in: DORM_FEED_CHECK out: DORM_CHECK """ self.interval_clear(DORM_FEED_CHECK) - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - + for _ in self.loop(): # End if self.appear(DORM_CHECK): break @@ -506,13 +433,83 @@ class RewardDorm(UI): self.interval_clear(DORM_CHECK) continue - def dorm_run(self, feed=True, collect=True, buy_furniture=False, buy_food=False): + def dorm_buy_food_enter(self): + """ + Pages: + in: DORM_FEED_CHECK + out: DORM_BUY_FOOD_CHECK + """ + self.interval_clear(DORM_FEED_CHECK) + for _ in self.loop(): + # End + if self.appear(DORM_BUY_FOOD_CHECK, offset=(20, 20)): + break + + if self.match_template_color(DORM_FEED_CHECK, offset=(20, 20), interval=5): + self.device.click(DORM_BUY_FOOD_ENTER) + continue + + def dorm_buy_food(self, amount): + """ + Pages: + in: DORM_BUY_FOOD_CHECK + out: DORM_BUY_FOOD_CHECK + """ + logger.hr('Dorm buy food') + index_offset = (20, 20) + # In case either -/+ shift position, use + # shipyard ocr trick to accurately parse + self.appear(FOOD_PLUS, offset=index_offset) + self.appear(FOOD_MINUS, offset=index_offset) + + self.ui_ensure_index(amount, letter=OCR_BUY_FOOD_AMOUNT, prev_button=FOOD_MINUS, next_button=FOOD_PLUS, + skip_first_screenshot=True) + return True + + def dorm_buy_food_confirm(self): + """ + Pages: + in: DORM_BUY_FOOD_CHECK + out: DORM_FEED_CHECK + """ + self.interval_clear(DORM_BUY_FOOD_CONFIRM) + for _ in self.loop(): + # End + if self.match_template_color(DORM_FEED_CHECK, offset=(20, 20)): + break + + if self.appear_then_click(DORM_BUY_FOOD_CONFIRM, offset=(20, 20), interval=5): + continue + + 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 not buy_food: + if not feed and not collect and not buy_furniture: return self.ui_ensure(page_dormmenu) @@ -527,17 +524,10 @@ class RewardDorm(UI): # Feed first to handle DORM_INFO # DORM_INFO may cover dorm coins and loves - if feed or buy_food: + if feed: logger.hr('Dorm feed', level=1) self.dorm_feed_enter() - if buy_food: - logger.hr('Dorm buy food', level=2) - if self.buy_food(): - self.config.Dorm_BuyFood = False - else: - logger.warning('Dorm food purchase failed, keep Dorm_BuyFood=True for retry') - if feed: - self.dorm_feed() + self.dorm_feed() self.dorm_feed_quit() if collect: @@ -548,11 +538,8 @@ class RewardDorm(UI): logger.hr('Dorm buy furniture', level=1) BuyFurniture(self.config, self.device).run() - def get_dorm_ship_amount(self, skip_first_screenshot=True): + def get_dorm_ship_amount(self): """ - Args: - skip_first_screenshot: - Returns: int: Number of ships in dorm @@ -561,12 +548,7 @@ class RewardDorm(UI): """ timeout = Timer(2, count=4).start() current = 0 - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - + for _ in self.loop(): # Handle popups if self.appear_then_click(DORM_FURNITURE_CONFIRM, offset=(30, 30), interval=3): timeout.reset() @@ -632,18 +614,13 @@ class RewardDorm(UI): out: page_dorm """ if not self.config.Dorm_Feed and not self.config.Dorm_Collect \ - and not self.config.BuyFurniture_Enable \ - and not self.config.Dorm_BuyFood: + and not self.config.BuyFurniture_Enable: self.config.Scheduler_Enable = False self.config.task_stop() - buy_food = bool(self.config.cross_get(keys='Dorm.Dorm.BuyFood', default=self.config.Dorm_BuyFood)) - if buy_food != bool(self.config.Dorm_BuyFood): - logger.warning(f'Dorm_BuyFood attr/data mismatch: attr={self.config.Dorm_BuyFood}, data={buy_food}') self.dorm_run(feed=self.config.Dorm_Feed, collect=self.config.Dorm_Collect, - buy_furniture=self.config.BuyFurniture_Enable, - buy_food=buy_food) + buy_furniture=self.config.BuyFurniture_Enable) # Scheduler ships = self.get_dorm_ship_amount() diff --git a/module/exception.py b/module/exception.py index 7e525a637..26f718095 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