This commit is contained in:
wess09 2026-05-11 18:55:23 +08:00
commit b32c8ab898
15 changed files with 199 additions and 151 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -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 Tulipas Seas Rerun | 复刻扬起郁金之旗 | Toward Tulipas Seas Rerun | チュリッパの海へ(復刻) | - |
| 20260417 | event 20260417 cn | Vacation Lane Beachside Brilliance | - | - | - | 假日航線閃耀海濱 |
| 20260417 | event 20201126 cn | Vacation Lane Rerun | - | - | - | 復刻假日航線 |

View File

@ -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

View File

@ -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'})

View File

@ -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:

View File

@ -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"

View File

@ -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'})

View File

@ -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()

View File

@ -6,6 +6,10 @@ class OilExhausted(Exception):
pass
class OilMaxed(Exception):
pass
class MapDetectionError(Exception):
pass