1
0
mirror of https://github.com/sui-feng-cb/AzurLaneAutoScript1.git synced 2026-08-15 03:40:37 +08:00

Add: [CN, JP] island restaurant

This commit is contained in:
guoh064
2026-06-14 03:13:06 +08:00
parent 4c148eeb00
commit 845d5223f6
19 changed files with 496 additions and 0 deletions

View File

@@ -9,6 +9,18 @@ ISLAND_DOCK_CHECK = Button(area={'cn': (126, 20, 220, 46), 'en': (126, 20, 220,
ISLAND_DOCK_SORTING_CLICK = Button(area={'cn': (115, 608, 266, 641), 'en': (115, 608, 266, 641), 'jp': (115, 608, 266, 641), 'tw': (115, 608, 266, 641)}, color={'cn': (104, 104, 103), 'en': (104, 104, 103), 'jp': (104, 104, 103), 'tw': (104, 104, 103)}, button={'cn': (115, 608, 266, 641), 'en': (115, 608, 266, 641), 'jp': (115, 608, 266, 641), 'tw': (115, 608, 266, 641)}, file={'cn': './assets/cn/island_handler/ISLAND_DOCK_SORTING_CLICK.png', 'en': './assets/cn/island_handler/ISLAND_DOCK_SORTING_CLICK.png', 'jp': './assets/cn/island_handler/ISLAND_DOCK_SORTING_CLICK.png', 'tw': './assets/cn/island_handler/ISLAND_DOCK_SORTING_CLICK.png'})
ISLAND_DOCK_SORT_ASC = Button(area={'cn': (140, 614, 153, 634), 'en': (140, 614, 153, 634), 'jp': (140, 614, 153, 634), 'tw': (140, 614, 153, 634)}, color={'cn': (140, 140, 137), 'en': (140, 140, 137), 'jp': (140, 140, 137), 'tw': (140, 140, 137)}, button={'cn': (140, 614, 153, 634), 'en': (140, 614, 153, 634), 'jp': (140, 614, 153, 634), 'tw': (140, 614, 153, 634)}, file={'cn': './assets/cn/island_handler/ISLAND_DOCK_SORT_ASC.png', 'en': './assets/cn/island_handler/ISLAND_DOCK_SORT_ASC.png', 'jp': './assets/cn/island_handler/ISLAND_DOCK_SORT_ASC.png', 'tw': './assets/cn/island_handler/ISLAND_DOCK_SORT_ASC.png'})
ISLAND_DOCK_SORT_DESC = Button(area={'cn': (141, 616, 152, 634), 'en': (141, 616, 152, 634), 'jp': (141, 616, 152, 634), 'tw': (141, 616, 152, 634)}, color={'cn': (154, 153, 151), 'en': (154, 153, 151), 'jp': (154, 153, 151), 'tw': (154, 153, 151)}, button={'cn': (141, 616, 152, 634), 'en': (141, 616, 152, 634), 'jp': (141, 616, 152, 634), 'tw': (141, 616, 152, 634)}, file={'cn': './assets/cn/island_handler/ISLAND_DOCK_SORT_DESC.png', 'en': './assets/cn/island_handler/ISLAND_DOCK_SORT_DESC.png', 'jp': './assets/cn/island_handler/ISLAND_DOCK_SORT_DESC.png', 'tw': './assets/cn/island_handler/ISLAND_DOCK_SORT_DESC.png'})
ISLAND_RESTAURANT_CHECK = Button(area={'cn': (122, 18, 219, 46), 'en': (122, 18, 219, 46), 'jp': (122, 20, 219, 47), 'tw': (122, 18, 219, 46)}, color={'cn': (107, 110, 120), 'en': (107, 110, 120), 'jp': (99, 103, 114), 'tw': (107, 110, 120)}, button={'cn': (122, 18, 219, 46), 'en': (122, 18, 219, 46), 'jp': (122, 20, 219, 47), 'tw': (122, 18, 219, 46)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_CHECK.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_CHECK.png', 'jp': './assets/jp/island_handler/ISLAND_RESTAURANT_CHECK.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_CHECK.png'})
ISLAND_RESTAURANT_EVENT_BUFF = Button(area={'cn': (223, 451, 289, 475), 'en': (223, 451, 289, 475), 'jp': (223, 451, 289, 475), 'tw': (223, 451, 289, 475)}, color={'cn': (147, 165, 144), 'en': (147, 165, 144), 'jp': (147, 165, 144), 'tw': (147, 165, 144)}, button={'cn': (223, 451, 289, 475), 'en': (223, 451, 289, 475), 'jp': (223, 451, 289, 475), 'tw': (223, 451, 289, 475)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_BUFF.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_BUFF.png', 'jp': './assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_BUFF.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_BUFF.png'})
ISLAND_RESTAURANT_EVENT_CHECK = Button(area={'cn': (60, 116, 166, 162), 'en': (60, 116, 166, 162), 'jp': (60, 116, 166, 162), 'tw': (60, 116, 166, 162)}, color={'cn': (192, 191, 191), 'en': (192, 191, 191), 'jp': (192, 191, 191), 'tw': (192, 191, 191)}, button={'cn': (60, 116, 166, 162), 'en': (60, 116, 166, 162), 'jp': (60, 116, 166, 162), 'tw': (60, 116, 166, 162)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_CHECK.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_CHECK.png', 'jp': './assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_CHECK.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_CHECK.png'})
ISLAND_RESTAURANT_RECEIVE = Button(area={'cn': (595, 612, 685, 638), 'en': (595, 612, 685, 638), 'jp': (592, 611, 687, 641), 'tw': (595, 612, 685, 638)}, color={'cn': (131, 213, 252), 'en': (131, 213, 252), 'jp': (113, 207, 252), 'tw': (131, 213, 252)}, button={'cn': (595, 612, 685, 638), 'en': (595, 612, 685, 638), 'jp': (592, 611, 687, 641), 'tw': (595, 612, 685, 638)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_RECEIVE.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_RECEIVE.png', 'jp': './assets/jp/island_handler/ISLAND_RESTAURANT_RECEIVE.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_RECEIVE.png'})
ISLAND_RESTAURANT_RECOMMEND = Button(area={'cn': (288, 612, 379, 639), 'en': (288, 612, 379, 639), 'jp': (284, 612, 383, 640), 'tw': (288, 612, 379, 639)}, color={'cn': (103, 131, 150), 'en': (103, 131, 150), 'jp': (89, 120, 140), 'tw': (103, 131, 150)}, button={'cn': (288, 612, 379, 639), 'en': (288, 612, 379, 639), 'jp': (284, 612, 383, 640), 'tw': (288, 612, 379, 639)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_RECOMMEND.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_RECOMMEND.png', 'jp': './assets/jp/island_handler/ISLAND_RESTAURANT_RECOMMEND.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_RECOMMEND.png'})
ISLAND_RESTAURANT_RESTING = Button(area={'cn': (605, 612, 675, 639), 'en': (605, 612, 675, 639), 'jp': (604, 613, 676, 640), 'tw': (605, 612, 675, 639)}, color={'cn': (181, 181, 179), 'en': (181, 181, 179), 'jp': (179, 179, 177), 'tw': (181, 181, 179)}, button={'cn': (605, 612, 675, 639), 'en': (605, 612, 675, 639), 'jp': (604, 613, 676, 640), 'tw': (605, 612, 675, 639)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_RESTING.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_RESTING.png', 'jp': './assets/jp/island_handler/ISLAND_RESTAURANT_RESTING.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_RESTING.png'})
ISLAND_RESTAURANT_RESULT = Button(area={'cn': (509, 225, 534, 244), 'en': (509, 225, 534, 244), 'jp': (509, 225, 534, 244), 'tw': (509, 225, 534, 244)}, color={'cn': (157, 158, 158), 'en': (157, 158, 158), 'jp': (157, 158, 158), 'tw': (157, 158, 158)}, button={'cn': (509, 225, 534, 244), 'en': (509, 225, 534, 244), 'jp': (509, 225, 534, 244), 'tw': (509, 225, 534, 244)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_RESULT.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_RESULT.png', 'jp': './assets/cn/island_handler/ISLAND_RESTAURANT_RESULT.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_RESULT.png'})
ISLAND_RESTAURANT_RUNNING = Button(area={'cn': (507, 613, 532, 638), 'en': (507, 613, 532, 638), 'jp': (507, 613, 532, 638), 'tw': (507, 613, 532, 638)}, color={'cn': (173, 173, 171), 'en': (173, 173, 171), 'jp': (173, 173, 171), 'tw': (173, 173, 171)}, button={'cn': (507, 613, 532, 638), 'en': (507, 613, 532, 638), 'jp': (507, 613, 532, 638), 'tw': (507, 613, 532, 638)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_RUNNING.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_RUNNING.png', 'jp': './assets/cn/island_handler/ISLAND_RESTAURANT_RUNNING.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_RUNNING.png'})
ISLAND_RESTAURANT_SCROLL_BOTTOM = Button(area={'cn': (1040, 197, 1043, 228), 'en': (1040, 197, 1043, 228), 'jp': (1040, 197, 1043, 228), 'tw': (1040, 197, 1043, 228)}, color={'cn': (159, 159, 158), 'en': (159, 159, 158), 'jp': (159, 159, 158), 'tw': (159, 159, 158)}, button={'cn': (1040, 197, 1043, 228), 'en': (1040, 197, 1043, 228), 'jp': (1040, 197, 1043, 228), 'tw': (1040, 197, 1043, 228)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_BOTTOM.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_BOTTOM.png', 'jp': './assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_BOTTOM.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_BOTTOM.png'})
ISLAND_RESTAURANT_SCROLL_TOP = Button(area={'cn': (1040, 387, 1043, 416), 'en': (1040, 387, 1043, 416), 'jp': (1040, 387, 1043, 416), 'tw': (1040, 387, 1043, 416)}, color={'cn': (145, 146, 146), 'en': (145, 146, 146), 'jp': (145, 146, 146), 'tw': (145, 146, 146)}, button={'cn': (1040, 387, 1043, 416), 'en': (1040, 387, 1043, 416), 'jp': (1040, 387, 1043, 416), 'tw': (1040, 387, 1043, 416)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_TOP.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_TOP.png', 'jp': './assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_TOP.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_TOP.png'})
ISLAND_RESTAURANT_SELECT_CHARACTER = Button(area={'cn': (330, 266, 367, 306), 'en': (330, 266, 367, 306), 'jp': (330, 266, 367, 306), 'tw': (330, 266, 367, 306)}, color={'cn': (202, 200, 203), 'en': (202, 200, 203), 'jp': (202, 200, 203), 'tw': (202, 200, 203)}, button={'cn': (330, 266, 367, 306), 'en': (330, 266, 367, 306), 'jp': (330, 266, 367, 306), 'tw': (330, 266, 367, 306)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_SELECT_CHARACTER.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_SELECT_CHARACTER.png', 'jp': './assets/cn/island_handler/ISLAND_RESTAURANT_SELECT_CHARACTER.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_SELECT_CHARACTER.png'})
ISLAND_RESTAURANT_START = Button(area={'cn': (715, 612, 809, 639), 'en': (715, 612, 809, 639), 'jp': (713, 612, 810, 640), 'tw': (715, 612, 809, 639)}, color={'cn': (122, 211, 253), 'en': (122, 211, 253), 'jp': (115, 208, 252), 'tw': (122, 211, 253)}, button={'cn': (715, 612, 809, 639), 'en': (715, 612, 809, 639), 'jp': (713, 612, 810, 640), 'tw': (715, 612, 809, 639)}, file={'cn': './assets/cn/island_handler/ISLAND_RESTAURANT_START.png', 'en': './assets/cn/island_handler/ISLAND_RESTAURANT_START.png', 'jp': './assets/jp/island_handler/ISLAND_RESTAURANT_START.png', 'tw': './assets/cn/island_handler/ISLAND_RESTAURANT_START.png'})
ISLAND_TECHNOLOGY_TAB1 = Button(area={'cn': (20, 575, 109, 644), 'en': (20, 575, 109, 644), 'jp': (20, 575, 109, 644), 'tw': (20, 575, 109, 644)}, color={'cn': (76, 109, 130), 'en': (76, 109, 130), 'jp': (76, 109, 130), 'tw': (76, 109, 130)}, button={'cn': (20, 575, 109, 644), 'en': (20, 575, 109, 644), 'jp': (20, 575, 109, 644), 'tw': (20, 575, 109, 644)}, file={'cn': './assets/cn/island_handler/ISLAND_TECHNOLOGY_TAB1.png', 'en': './assets/cn/island_handler/ISLAND_TECHNOLOGY_TAB1.png', 'jp': './assets/cn/island_handler/ISLAND_TECHNOLOGY_TAB1.png', 'tw': './assets/cn/island_handler/ISLAND_TECHNOLOGY_TAB1.png'})
TEMPLATE_ISLAND_DOCK_CARD_ANCHOR = Template(file={'cn': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_CARD_ANCHOR.png', 'en': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_CARD_ANCHOR.png', 'jp': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_CARD_ANCHOR.png', 'tw': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_CARD_ANCHOR.png'})
TEMPLATE_ISLAND_DOCK_GRADE_A = Template(file={'cn': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_A.png', 'en': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_A.png', 'jp': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_A.png', 'tw': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_A.png'})

View File

@@ -0,0 +1,484 @@
from datetime import datetime, timedelta
import cv2
import numpy as np
from yaml import safe_load
import module.config.server as server
from module.base.button import ButtonGrid
from module.base.decorator import cached_property, del_cached_property
from module.base.timer import Timer
from module.base.utils import color_similar, color_similarity_2d, load_image
from module.config.utils import get_server_next_update
from module.island.assets import ISLAND_CLICK_SAFE_AREA
from module.island.data import DIC_ISLAND_ITEM, DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE
from module.island.utils import (
get_stuck_season_order_requirements,
load_hard_floor_items,
normalize_item_keys,
)
from module.island_handler.assets import *
from module.island_handler.dock import IslandDock
from module.island_handler.dock_scanner import CharacterScanner
from module.island_handler.restaurant_config import (
RESTAURANT_IDS,
WAITRESS_ANY,
WAITRESS_NONE,
get_config_key,
get_restaurant_config,
get_selected_named_waitresses,
get_waitress_effect,
get_waitress_slots,
)
from module.logger import logger
from module.ocr.ocr import Digit
from module.statistics.item import Item, ItemGrid
from module.statistics.utils import load_folder
RESTAURANT_SWIPE_AREA = (583, 208, 1023, 400)
ISLAND_RESTAURANT_ITEM_ORDER_PRICE = {
DIC_ISLAND_ITEM[item_id]['name']['en'].replace(' ', '_'): {
'id': item_id,
'order_price': DIC_ISLAND_ITEM[item_id]['order_price'],
}
for menu in DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE.values()
for item_id in menu.keys()
}
class WaitressOccupied(Exception):
pass
class RestaurantItem(Item):
IMAGE_SHAPE = (83, 87)
def predict_valid(self):
mask = color_similarity_2d(self.image, (207, 209, 211))
cv2.inRange(mask, 0, 201, dst=mask)
sum_ = np.count_nonzero(mask)
return sum_ > 400
class RestaurantItemAmount(Digit):
def pre_process(self, image):
image = super().pre_process(image)
image = cv2.copyMakeBorder(image, 4, 2, 0, 0, cv2.BORDER_CONSTANT, value=(255, 255, 255))
return image
class RestaurantItemGrid(ItemGrid):
item_class = RestaurantItem
similarity = 0.75
def __init__(self, grid: ButtonGrid):
super().__init__(
grid,
templates={},
template_area=(12, 21, 72, 67),
amount_area=(42, 67, 77, 85),
tag_area=(66, 2, 72, 5)
)
if server.server == 'jp':
self.amount_ocr = RestaurantItemAmount([], letter=(220, 220, 220), lang='azur_lane', threshold=128, name='Amount_ocr')
else:
self.amount_ocr = Digit([], lang='cnocr', threshold=160, name='Amount_ocr')
self.load_template_folder('./assets/island/restaurant')
@staticmethod
def predict_tag(image):
color = cv2.mean(np.array(image))[:3]
if color_similar(color, (79, 201, 108), threshold=30):
return 'bonus'
return None
def predict(self, image, name=True, amount=True, cost=False, price=True, tag=True):
super().predict(image, name=True, amount=True, cost=False, price=False, tag=True)
for item in self.items:
item.id = ISLAND_RESTAURANT_ITEM_ORDER_PRICE.get(item.name, {}).get('id', 0)
item.price = ISLAND_RESTAURANT_ITEM_ORDER_PRICE.get(item.name, {}).get('order_price', 0)
items = self.items
grids = self.grids
if len(items):
min_row = grids[0, 0].area[1]
row = [str(item) for item in items if item.button[1] == min_row]
logger.info(f'Item row 1: {row}')
row = [str(item) for item in items if item.button[1] != min_row]
logger.info(f'Item row 2: {row}')
return items
class IslandRestaurant(IslandDock):
working_restaurant_id = None
@staticmethod
def get_initial_capacity_from_grade(grade):
if grade == 'bronze':
return 5
elif grade in ['silver', 'gold', 'diamond']:
return 6
else:
raise ValueError(f"Invalid grade: {grade}")
@cached_property
def restaurant_capacity(self):
capacity = {}
for restaurant_id in RESTAURANT_IDS:
config_data = get_restaurant_config(restaurant_id)
grade = self.config.cross_get(
get_config_key(restaurant_id, config_data['grade_key'])
)
slots = get_waitress_slots(self.config, restaurant_id)
capacity_delta, _ = get_waitress_effect(restaurant_id, slots)
capacity[restaurant_id] = self.get_initial_capacity_from_grade(grade) + capacity_delta
return capacity
@staticmethod
def get_quantity_from_grade(grade):
if grade in ['bronze', 'silver']:
return 2
elif grade == 'gold':
return 3
elif grade == 'diamond':
return 4
else:
raise ValueError(f"Invalid grade: {grade}")
@cached_property
def restaurant_quantity(self):
quantity = {}
for restaurant_id in RESTAURANT_IDS:
config_data = get_restaurant_config(restaurant_id)
grade = self.config.cross_get(
get_config_key(restaurant_id, config_data['grade_key'])
)
quantity[restaurant_id] = self.get_quantity_from_grade(grade)
return quantity
def is_in_island_restaurant(self):
return self.appear(ISLAND_RESTAURANT_CHECK, offset=(0, 20))
@cached_property
def restaurant_has_event(self):
return self.appear(ISLAND_RESTAURANT_EVENT_CHECK, offset=(20, 20))
# 364 and 214
@cached_property
def _restaurant_offset_x(self):
if self.restaurant_has_event:
return 150
else:
return 0
@cached_property
def _restaurant_offset(self):
return (self._restaurant_offset_x - 20, -20, self._restaurant_offset_x + 20, 20)
def receive_revenue(self):
confirm_timer = Timer(1, count=3)
for _ in self.loop():
if self.appear_then_click(ISLAND_RESTAURANT_RECEIVE, offset=self._restaurant_offset, interval=2):
confirm_timer.reset()
continue
if self.appear(ISLAND_RESTAURANT_RESULT, offset=(20, 20), interval=2):
self.device.click(ISLAND_CLICK_SAFE_AREA)
confirm_timer.reset()
continue
if self.handle_island_additional():
confirm_timer.reset()
continue
# End
if (self.appear(ISLAND_RESTAURANT_RECOMMEND, offset=self._restaurant_offset)
or self.restaurant_resting()):
if confirm_timer.reached():
return True
@cached_property
def restaurant_grid(self):
return ButtonGrid(
origin=(583 + self._restaurant_offset_x, 208), delta=(89.5, 92),
button_shape=(83, 87), grid_shape=(5, 2), name='restaurant_grid'
)
def swipe_top_to_bottom(self):
if not self.match_template_color(ISLAND_RESTAURANT_SCROLL_TOP, offset=self._restaurant_offset):
return False
box = (RESTAURANT_SWIPE_AREA[0] + self._restaurant_offset_x, RESTAURANT_SWIPE_AREA[1],
RESTAURANT_SWIPE_AREA[2] + self._restaurant_offset_x, RESTAURANT_SWIPE_AREA[3])
self.device.swipe_vector((0, -150), box=box, padding=-5)
for _ in self.loop(timeout=0.8):
pass
return True
def swipe_bottom_to_top(self):
if not self.match_template_color(ISLAND_RESTAURANT_SCROLL_BOTTOM, offset=self._restaurant_offset):
return False
box = (RESTAURANT_SWIPE_AREA[0] + self._restaurant_offset_x, RESTAURANT_SWIPE_AREA[1],
RESTAURANT_SWIPE_AREA[2] + self._restaurant_offset_x, RESTAURANT_SWIPE_AREA[3])
self.device.swipe_vector((0, 150), box=box, padding=-5)
for _ in self.loop(timeout=0.8):
pass
return True
def scan_item_grid(self, grid: ButtonGrid):
item_grid = RestaurantItemGrid(grid)
if self.config.SHOP_EXTRACT_TEMPLATE:
item_grid.extract_template(self.device.image, folder='./assets/island/restaurant')
item_grid.predict(self.device.image, tag=True)
return item_grid.items
def scan_all_items(self):
items = []
items_set = set()
self.swipe_bottom_to_top()
result = self.scan_item_grid(self.restaurant_grid)
for item in result:
if item.name not in items_set:
items.append(item)
items_set.add(item.name)
if self.swipe_top_to_bottom():
result = self.scan_item_grid(self.restaurant_grid.move((0, 24)))
for item in result:
if item.name not in items_set:
items.append(item)
items_set.add(item.name)
return items
@cached_property
def event_buff(self):
if not self.restaurant_has_event:
return 0
ocr = Digit(ISLAND_RESTAURANT_EVENT_BUFF, lang='cnocr', letter=(67, 71, 23), threshold=160, alphabet='0123IDB')
result = ocr.ocr(self.device.image)
if not result in [10, 20, 30]:
logger.warning(f'Unexpected event buff OCR result: {result}, default to 10')
result = 10
return result
def get_sell_plan(self):
capacity = self.restaurant_capacity[self.working_restaurant_id]
menu_key = get_restaurant_config(self.working_restaurant_id)['menu_key']
menu_text = self.config.cross_get(
get_config_key(self.working_restaurant_id, menu_key),
default="{}",
)
menu = normalize_item_keys(safe_load(menu_text))
protected_items = self.restaurant_protected_items
def total_revenue_estimate(item):
amount = min(item.amount, capacity)
if item.tag == 'bonus':
return item.price * amount * (1 + self.event_buff / 100)
return item.price * amount
def has_sellable_capacity(item):
return item.amount >= capacity + protected_items.get(item.id, 0)
items = self.scan_all_items()
menu_items = [
item for item in items
if item.id in menu
# Sell one full waitress-capacity tranche while preserving manual
# hard floors and remaining season-order requirements. Reserves and
# daily buffers are soft and may be consumed by restaurants.
and has_sellable_capacity(item)
]
surplus_items = [
item for item in items
if item.id not in menu
and has_sellable_capacity(item)
]
sellable_items = menu_items + surplus_items
quantity = self.restaurant_quantity[self.working_restaurant_id]
items = sorted(sellable_items, key=total_revenue_estimate, reverse=True)
if len(items) < quantity:
quantity = len(items)
plan = items[:quantity]
logger.info(f'Sell plan: {[str(item) for item in plan]}')
return plan
@cached_property
def restaurant_protected_items(self):
hard_floor_items = normalize_item_keys(load_hard_floor_items(
self.config.cross_get("IslandProduction.IslandProduction.HardFloorItems", "")
))
stuck_season_order_id = self.config.cross_get(
"IslandOrder.IslandOrder.StuckSeasonOrderId", 0
)
season_order_items = normalize_item_keys(
get_stuck_season_order_requirements(stuck_season_order_id)
)
item_ids = set(hard_floor_items) | set(season_order_items)
return {
item_id: max(hard_floor_items.get(item_id, 0), 0)
+ max(season_order_items.get(item_id, 0), 0)
for item_id in item_ids
}
@cached_property
def waitress_lists(self):
return {
restaurant_id: list(get_waitress_slots(self.config, restaurant_id))
for restaurant_id in RESTAURANT_IDS
}
def unavailable_waitress_list(self):
current_waitresses = get_selected_named_waitresses(
get_waitress_slots(self.config, self.working_restaurant_id)
)
lst = set()
for restaurant_id in RESTAURANT_IDS:
if restaurant_id == self.working_restaurant_id:
continue
lst.update(
get_selected_named_waitresses(
get_waitress_slots(self.config, restaurant_id)
)
)
return sorted(lst - current_waitresses)
def restaurant_running(self):
return self.appear(ISLAND_RESTAURANT_RUNNING, offset=self._restaurant_offset)
def restaurant_resting(self):
return self.appear(ISLAND_RESTAURANT_RESTING, offset=self._restaurant_offset)
def choose_waitress(self):
waitress_list = list(get_waitress_slots(self.config, self.working_restaurant_id))
active_waitresses = [
waitress for waitress in waitress_list
if waitress != WAITRESS_NONE
]
if not active_waitresses:
return False
unavailable_waitress_list = set(self.unavailable_waitress_list())
named_waitresses = [
waitress for waitress in active_waitresses
if waitress not in (WAITRESS_ANY, WAITRESS_NONE)
]
all_named_waitresses = set(named_waitresses)
selected_waitresses = set()
if unavailable_waitress_list:
logger.warning(f"Unavailable waitress list: {sorted(unavailable_waitress_list)}")
for _ in self.loop():
if self.appear_then_click(ISLAND_RESTAURANT_SELECT_CHARACTER, offset=self._restaurant_offset, interval=2):
continue
if self.is_in_island_dock():
break
success = True
for waitress in named_waitresses:
if waitress in unavailable_waitress_list:
logger.warning(
f"Waitress {waitress} is already assigned to another restaurant, "
f"skip it for restaurant {self.working_restaurant_id}"
)
unavailable_waitress_list.remove(waitress)
continue
candidate = self.island_dock_find_character(waitress)
if candidate is not None and candidate.status == 'free':
self.island_dock_select_one(candidate.button)
selected_waitresses.add(candidate.identity)
continue
if candidate is not None:
time_until_update = get_server_next_update("00:00") - datetime.now()
if time_until_update >= timedelta(hours=8):
logger.warning(
f"Waitress {waitress} not available, delaying restaurant "
f"{self.working_restaurant_id} for 8 hours"
)
self.ui_back(check_button=self.is_in_island_restaurant)
raise WaitressOccupied(
f"Waitress {waitress} is occupied, delaying restaurant "
f"{self.working_restaurant_id} for 8 hours"
)
self.ensure_dock_page_at_top()
fallback_blacklist = (
unavailable_waitress_list
| selected_waitresses
| all_named_waitresses
)
selected = self.island_dock_select_character_with_blacklist(fallback_blacklist)
if selected is None:
success = False
else:
selected_waitresses.add(selected)
for _ in range(active_waitresses.count(WAITRESS_ANY)):
blacklist = unavailable_waitress_list | selected_waitresses | all_named_waitresses
selected = self.island_dock_select_character_with_blacklist(blacklist)
if selected is None:
success = False
else:
selected_waitresses.add(selected)
if not success:
logger.warning("Failed to choose waitress")
self.ui_back(check_button=self.is_in_island_restaurant)
else:
self.island_dock_select_confirm(check_button=self.is_in_island_restaurant, skip_first=False)
return success
def select_dishes(self):
plan = self.get_sell_plan()
if not plan:
return False
self.swipe_bottom_to_top()
result = self.scan_item_grid(self.restaurant_grid)
plan_to_click = []
for item in result:
if item in plan:
plan_to_click.append(item)
for _ in self.loop():
for item in plan_to_click:
if self.is_button_selected(item._button):
plan.remove(item)
plan_to_click.remove(item)
else:
self.device.click(item._button)
if not plan_to_click:
break
if not plan:
return True
if self.swipe_top_to_bottom():
result = self.scan_item_grid(self.restaurant_grid.move((0, 24)))
plan_to_click = []
for item in result:
if item in plan:
plan_to_click.append(item)
for _ in self.loop():
for item in plan_to_click:
if self.is_button_selected(item._button):
plan.remove(item)
plan_to_click.remove(item)
else:
self.device.click(item._button)
if not plan_to_click:
break
return not plan
def restaurant_start(self):
for _ in self.loop():
if self.handle_island_additional():
continue
if self.appear_then_click(ISLAND_RESTAURANT_START, offset=self._restaurant_offset, interval=2):
continue
if self.restaurant_running():
return True
def run(self):
if self.restaurant_running():
logger.info("Restaurant is already running, skip this round")
return False
self.receive_revenue()
if self.restaurant_resting():
logger.info("Restaurant is resting, finish this round")
return False
if not self.choose_waitress():
logger.warning("Failed to choose waitress, skip this round")
return False
if not self.select_dishes():
logger.warning("Failed to select all dishes, skip this round")
return False
if not self.restaurant_start():
logger.warning("Failed to start restaurant, skip this round")
return False
return True