diff --git a/alas.py b/alas.py index 6268c638a7..b4254eb0b9 100644 --- a/alas.py +++ b/alas.py @@ -407,6 +407,30 @@ def gems_farming(self): GemsFarming(config=self.config, device=self.device).run( name=self.config.Campaign_Name, folder=self.config.Campaign_Event, mode=self.config.Campaign_Mode) + def island_production(self): + from module.island.production import IslandProduction + IslandProduction(config=self.config, device=self.device).run() + + def island_order(self): + from module.island.order import IslandOrder + IslandOrder(config=self.config, device=self.device).run() + + def island_freebie(self): + from module.island.freebie import IslandFreebie + IslandFreebie(config=self.config, device=self.device).run() + + def island_collect(self): + from module.island.collect import IslandCollect + IslandCollect(config=self.config, device=self.device).run() + + def island_season_task(self): + from module.island.season_task import IslandSeasonTask + IslandSeasonTask(config=self.config, device=self.device).run() + + def island_business(self): + from module.island.business import IslandBusiness + IslandBusiness(config=self.config, device=self.device).run() + def daemon(self): from module.daemon.daemon import AzurLaneDaemon AzurLaneDaemon(config=self.config, device=self.device, task="Daemon").run() @@ -419,6 +443,10 @@ def event_story(self): from module.eventstory.eventstory import EventStory EventStory(config=self.config, device=self.device, task="EventStory").run() + def island_production_planner(self): + from module.island_handler.production_planner import IslandProductionPlanner + IslandProductionPlanner(config=self.config, device=self.device, task="IslandProductionPlanner").run() + def azur_lane_uncensored(self): from module.daemon.uncensored import AzurLaneUncensored AzurLaneUncensored(config=self.config, device=self.device, task="AzurLaneUncensored").run() diff --git a/assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP.png b/assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP.png new file mode 100644 index 0000000000..ddbed11ba8 Binary files /dev/null and b/assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP.png differ diff --git a/assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png b/assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png new file mode 100644 index 0000000000..45aac5987f Binary files /dev/null and b/assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png differ diff --git a/assets/cn/island/ISLAND_CLICK_SAFE_AREA.png b/assets/cn/island/ISLAND_CLICK_SAFE_AREA.png new file mode 100644 index 0000000000..f1eadcba7d Binary files /dev/null and b/assets/cn/island/ISLAND_CLICK_SAFE_AREA.png differ diff --git a/assets/cn/island/ISLAND_COLLECT_LOCATION_1.png b/assets/cn/island/ISLAND_COLLECT_LOCATION_1.png new file mode 100644 index 0000000000..7941b5036d Binary files /dev/null and b/assets/cn/island/ISLAND_COLLECT_LOCATION_1.png differ diff --git a/assets/cn/island/ISLAND_COLLECT_LOCATION_2.png b/assets/cn/island/ISLAND_COLLECT_LOCATION_2.png new file mode 100644 index 0000000000..704220a08c Binary files /dev/null and b/assets/cn/island/ISLAND_COLLECT_LOCATION_2.png differ diff --git a/assets/cn/island/ISLAND_COLLECT_SELECT_CANCEL.png b/assets/cn/island/ISLAND_COLLECT_SELECT_CANCEL.png new file mode 100644 index 0000000000..344fc15af9 Binary files /dev/null and b/assets/cn/island/ISLAND_COLLECT_SELECT_CANCEL.png differ diff --git a/assets/cn/island/ISLAND_COLLECT_SELECT_CONFIRM.png b/assets/cn/island/ISLAND_COLLECT_SELECT_CONFIRM.png new file mode 100644 index 0000000000..b03d5b597d Binary files /dev/null and b/assets/cn/island/ISLAND_COLLECT_SELECT_CONFIRM.png differ diff --git a/assets/cn/island/ISLAND_COLLECT_SELECT_ENTER.png b/assets/cn/island/ISLAND_COLLECT_SELECT_ENTER.png new file mode 100644 index 0000000000..4b8a9ad749 Binary files /dev/null and b/assets/cn/island/ISLAND_COLLECT_SELECT_ENTER.png differ diff --git a/assets/cn/island/ISLAND_COLLECT_START.png b/assets/cn/island/ISLAND_COLLECT_START.png new file mode 100644 index 0000000000..3df79b8b45 Binary files /dev/null and b/assets/cn/island/ISLAND_COLLECT_START.png differ diff --git a/assets/cn/island/ISLAND_COLLECT_START_UNAVAILABLE.png b/assets/cn/island/ISLAND_COLLECT_START_UNAVAILABLE.png new file mode 100644 index 0000000000..263bea2868 Binary files /dev/null and b/assets/cn/island/ISLAND_COLLECT_START_UNAVAILABLE.png differ diff --git a/assets/cn/island/ISLAND_FREEBIE_AVAILABLE.BUTTON.png b/assets/cn/island/ISLAND_FREEBIE_AVAILABLE.BUTTON.png new file mode 100644 index 0000000000..3870fcc106 Binary files /dev/null and b/assets/cn/island/ISLAND_FREEBIE_AVAILABLE.BUTTON.png differ diff --git a/assets/cn/island/ISLAND_FREEBIE_AVAILABLE.gif b/assets/cn/island/ISLAND_FREEBIE_AVAILABLE.gif new file mode 100644 index 0000000000..98a815f822 Binary files /dev/null and b/assets/cn/island/ISLAND_FREEBIE_AVAILABLE.gif differ diff --git a/assets/cn/island/ISLAND_FREEBIE_CLAIM.png b/assets/cn/island/ISLAND_FREEBIE_CLAIM.png new file mode 100644 index 0000000000..5341bf7b11 Binary files /dev/null and b/assets/cn/island/ISLAND_FREEBIE_CLAIM.png differ diff --git a/assets/cn/island/ISLAND_FREEBIE_COOLDOWN.png b/assets/cn/island/ISLAND_FREEBIE_COOLDOWN.png new file mode 100644 index 0000000000..2a2a2ec0e8 Binary files /dev/null and b/assets/cn/island/ISLAND_FREEBIE_COOLDOWN.png differ diff --git a/assets/cn/island/ISLAND_FREEBIE_RECEIVE.png b/assets/cn/island/ISLAND_FREEBIE_RECEIVE.png new file mode 100644 index 0000000000..c2e58c15ec Binary files /dev/null and b/assets/cn/island/ISLAND_FREEBIE_RECEIVE.png differ diff --git a/assets/cn/island/ISLAND_FREEBIE_SHARE.png b/assets/cn/island/ISLAND_FREEBIE_SHARE.png new file mode 100644 index 0000000000..9a25ed8cec Binary files /dev/null and b/assets/cn/island/ISLAND_FREEBIE_SHARE.png differ diff --git a/assets/cn/island/ISLAND_FREEBIE_SHARE_ALL.png b/assets/cn/island/ISLAND_FREEBIE_SHARE_ALL.png new file mode 100644 index 0000000000..f5282da4b4 Binary files /dev/null and b/assets/cn/island/ISLAND_FREEBIE_SHARE_ALL.png differ diff --git a/assets/cn/island/ISLAND_FREEBIE_SHARE_BACK.png b/assets/cn/island/ISLAND_FREEBIE_SHARE_BACK.png new file mode 100644 index 0000000000..c2ce172ede Binary files /dev/null and b/assets/cn/island/ISLAND_FREEBIE_SHARE_BACK.png differ diff --git a/assets/cn/island/ISLAND_FREEBIE_UNAVAILABLE.png b/assets/cn/island/ISLAND_FREEBIE_UNAVAILABLE.png new file mode 100644 index 0000000000..19dc4c70c2 Binary files /dev/null and b/assets/cn/island/ISLAND_FREEBIE_UNAVAILABLE.png differ diff --git a/assets/cn/island/ISLAND_LEVEL_UP.png b/assets/cn/island/ISLAND_LEVEL_UP.png new file mode 100644 index 0000000000..fe24b48284 Binary files /dev/null and b/assets/cn/island/ISLAND_LEVEL_UP.png differ diff --git a/assets/cn/island/ISLAND_ORDER_ACCEPT.png b/assets/cn/island/ISLAND_ORDER_ACCEPT.png new file mode 100644 index 0000000000..5c77356751 Binary files /dev/null and b/assets/cn/island/ISLAND_ORDER_ACCEPT.png differ diff --git a/assets/cn/island/ISLAND_ORDER_ACCEPT_URGENT.png b/assets/cn/island/ISLAND_ORDER_ACCEPT_URGENT.png new file mode 100644 index 0000000000..624ef1d713 Binary files /dev/null and b/assets/cn/island/ISLAND_ORDER_ACCEPT_URGENT.png differ diff --git a/assets/cn/island/ISLAND_ORDER_BACKGROUND.png b/assets/cn/island/ISLAND_ORDER_BACKGROUND.png new file mode 100644 index 0000000000..7d9bd8264a Binary files /dev/null and b/assets/cn/island/ISLAND_ORDER_BACKGROUND.png differ diff --git a/assets/cn/island/ISLAND_ORDER_COOLDOWN_REMAIN_TIME.png b/assets/cn/island/ISLAND_ORDER_COOLDOWN_REMAIN_TIME.png new file mode 100644 index 0000000000..a25a735dcb Binary files /dev/null and b/assets/cn/island/ISLAND_ORDER_COOLDOWN_REMAIN_TIME.png differ diff --git a/assets/cn/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png b/assets/cn/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png new file mode 100644 index 0000000000..e983b52761 Binary files /dev/null and b/assets/cn/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png differ diff --git a/assets/cn/island/ISLAND_ORDER_LEVEL_UP.png b/assets/cn/island/ISLAND_ORDER_LEVEL_UP.png new file mode 100644 index 0000000000..dd3cab2c2f Binary files /dev/null and b/assets/cn/island/ISLAND_ORDER_LEVEL_UP.png differ diff --git a/assets/cn/island/ISLAND_ORDER_REJECT.png b/assets/cn/island/ISLAND_ORDER_REJECT.png new file mode 100644 index 0000000000..b941aee724 Binary files /dev/null and b/assets/cn/island/ISLAND_ORDER_REJECT.png differ diff --git a/assets/cn/island/ISLAND_ORDER_REQUIREMENTS_CHECK.png b/assets/cn/island/ISLAND_ORDER_REQUIREMENTS_CHECK.png new file mode 100644 index 0000000000..0c3275201d Binary files /dev/null and b/assets/cn/island/ISLAND_ORDER_REQUIREMENTS_CHECK.png differ diff --git a/assets/cn/island/ISLAND_PRODUCTION_RECEIVE.png b/assets/cn/island/ISLAND_PRODUCTION_RECEIVE.png new file mode 100644 index 0000000000..397b2ea289 Binary files /dev/null and b/assets/cn/island/ISLAND_PRODUCTION_RECEIVE.png differ diff --git a/assets/cn/island/ISLAND_PRODUCTION_RERUN.png b/assets/cn/island/ISLAND_PRODUCTION_RERUN.png new file mode 100644 index 0000000000..d70af40d89 Binary files /dev/null and b/assets/cn/island/ISLAND_PRODUCTION_RERUN.png differ diff --git a/assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png b/assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png new file mode 100644 index 0000000000..8580019bbd Binary files /dev/null and b/assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png differ diff --git a/assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png b/assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png new file mode 100644 index 0000000000..c357e69020 Binary files /dev/null and b/assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png differ diff --git a/assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png b/assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png new file mode 100644 index 0000000000..c045410060 Binary files /dev/null and b/assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png new file mode 100644 index 0000000000..193ec282d1 Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png new file mode 100644 index 0000000000..385a6aac4e Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png new file mode 100644 index 0000000000..d1fe10ecab Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png new file mode 100644 index 0000000000..9b18eeb31b Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_KOI.png b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_KOI.png new file mode 100644 index 0000000000..1bdec33ed2 Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_KOI.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png new file mode 100644 index 0000000000..eb8de3e577 Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png new file mode 100644 index 0000000000..13a1ba8fb9 Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png b/assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png new file mode 100644 index 0000000000..c1894721fe Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png b/assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png new file mode 100644 index 0000000000..4416e7a4c5 Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png b/assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png new file mode 100644 index 0000000000..5c68c50725 Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png differ diff --git a/assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png b/assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png new file mode 100644 index 0000000000..342fc33e85 Binary files /dev/null and b/assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png differ diff --git a/assets/cn/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png b/assets/cn/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png new file mode 100644 index 0000000000..8bb62e556e Binary files /dev/null and b/assets/cn/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png differ diff --git a/assets/cn/island_handler/ISLAND_DOCK_CHECK.png b/assets/cn/island_handler/ISLAND_DOCK_CHECK.png new file mode 100644 index 0000000000..81875939c2 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_DOCK_CHECK.png differ diff --git a/assets/cn/island_handler/ISLAND_DOCK_SORTING_CLICK.png b/assets/cn/island_handler/ISLAND_DOCK_SORTING_CLICK.png new file mode 100644 index 0000000000..1e77ae333d Binary files /dev/null and b/assets/cn/island_handler/ISLAND_DOCK_SORTING_CLICK.png differ diff --git a/assets/cn/island_handler/ISLAND_DOCK_SORT_ASC.png b/assets/cn/island_handler/ISLAND_DOCK_SORT_ASC.png new file mode 100644 index 0000000000..e8d65d8d0b Binary files /dev/null and b/assets/cn/island_handler/ISLAND_DOCK_SORT_ASC.png differ diff --git a/assets/cn/island_handler/ISLAND_DOCK_SORT_DESC.png b/assets/cn/island_handler/ISLAND_DOCK_SORT_DESC.png new file mode 100644 index 0000000000..0d9c7e5c0c Binary files /dev/null and b/assets/cn/island_handler/ISLAND_DOCK_SORT_DESC.png differ diff --git a/assets/cn/island_handler/ISLAND_INFO_GOTO_SHOP.png b/assets/cn/island_handler/ISLAND_INFO_GOTO_SHOP.png new file mode 100644 index 0000000000..5e222fa114 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_INFO_GOTO_SHOP.png differ diff --git a/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT.png b/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT.png new file mode 100644 index 0000000000..92f08f0376 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT.png differ diff --git a/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MAX.png b/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MAX.png new file mode 100644 index 0000000000..ddbc6ea480 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MAX.png differ diff --git a/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MINUS.png b/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MINUS.png new file mode 100644 index 0000000000..9aeaa96cb5 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MINUS.png differ diff --git a/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_PLUS.png b/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_PLUS.png new file mode 100644 index 0000000000..cb90a56377 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_PLUS.png differ diff --git a/assets/cn/island_handler/ISLAND_RECIPE_CHECK.png b/assets/cn/island_handler/ISLAND_RECIPE_CHECK.png new file mode 100644 index 0000000000..ea7fae6c31 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RECIPE_CHECK.png differ diff --git a/assets/cn/island_handler/ISLAND_RECIPE_DRAG_CHECK.png b/assets/cn/island_handler/ISLAND_RECIPE_DRAG_CHECK.png new file mode 100644 index 0000000000..218aeaaa3b Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RECIPE_DRAG_CHECK.png differ diff --git a/assets/cn/island_handler/ISLAND_RECIPE_TIME.png b/assets/cn/island_handler/ISLAND_RECIPE_TIME.png new file mode 100644 index 0000000000..38453e9874 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RECIPE_TIME.png differ diff --git a/assets/cn/island_handler/ISLAND_RECIPE_TIME_ANCHOR.png b/assets/cn/island_handler/ISLAND_RECIPE_TIME_ANCHOR.png new file mode 100644 index 0000000000..84bf3881a3 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RECIPE_TIME_ANCHOR.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_CHECK.png b/assets/cn/island_handler/ISLAND_RESTAURANT_CHECK.png new file mode 100644 index 0000000000..dc9b065ef3 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_CHECK.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_BUFF.png b/assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_BUFF.png new file mode 100644 index 0000000000..3f012df2ef Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_BUFF.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_CHECK.png b/assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_CHECK.png new file mode 100644 index 0000000000..6bc1e8b596 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_EVENT_CHECK.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_RECEIVE.png b/assets/cn/island_handler/ISLAND_RESTAURANT_RECEIVE.png new file mode 100644 index 0000000000..220a1885b0 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_RECEIVE.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_RECOMMEND.png b/assets/cn/island_handler/ISLAND_RESTAURANT_RECOMMEND.png new file mode 100644 index 0000000000..3d09a2dfe9 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_RECOMMEND.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_RESTING.png b/assets/cn/island_handler/ISLAND_RESTAURANT_RESTING.png new file mode 100644 index 0000000000..0f53050783 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_RESTING.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_RESULT.png b/assets/cn/island_handler/ISLAND_RESTAURANT_RESULT.png new file mode 100644 index 0000000000..8a70fe0f0e Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_RESULT.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_RUNNING.png b/assets/cn/island_handler/ISLAND_RESTAURANT_RUNNING.png new file mode 100644 index 0000000000..a45a782d04 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_RUNNING.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_BOTTOM.png b/assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_BOTTOM.png new file mode 100644 index 0000000000..5af275a671 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_BOTTOM.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_TOP.png b/assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_TOP.png new file mode 100644 index 0000000000..63e1ed96dc Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_SCROLL_TOP.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_SELECT_CHARACTER.png b/assets/cn/island_handler/ISLAND_RESTAURANT_SELECT_CHARACTER.png new file mode 100644 index 0000000000..fe4b612c76 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_SELECT_CHARACTER.png differ diff --git a/assets/cn/island_handler/ISLAND_RESTAURANT_START.png b/assets/cn/island_handler/ISLAND_RESTAURANT_START.png new file mode 100644 index 0000000000..9ce8adddae Binary files /dev/null and b/assets/cn/island_handler/ISLAND_RESTAURANT_START.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT.png b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT.png new file mode 100644 index 0000000000..feb06ff68c Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_ONE.png b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_ONE.png new file mode 100644 index 0000000000..5f1c32c2a6 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_ONE.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_TEN.png b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_TEN.png new file mode 100644 index 0000000000..4833803d41 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_TEN.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_ONE.png b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_ONE.png new file mode 100644 index 0000000000..3d60b16045 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_ONE.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_TEN.png b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_TEN.png new file mode 100644 index 0000000000..d6b9bd3119 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_TEN.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_BUY_CHECK.png b/assets/cn/island_handler/ISLAND_SHOP_BUY_CHECK.png new file mode 100644 index 0000000000..d6cb89c2eb Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_BUY_CHECK.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_BUY_CONFIRM.png b/assets/cn/island_handler/ISLAND_SHOP_BUY_CONFIRM.png new file mode 100644 index 0000000000..1b46361759 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_BUY_CONFIRM.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_LOADING.png b/assets/cn/island_handler/ISLAND_SHOP_LOADING.png new file mode 100644 index 0000000000..fb4868e07a Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_LOADING.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_MILL_CHECK.png b/assets/cn/island_handler/ISLAND_SHOP_MILL_CHECK.png new file mode 100644 index 0000000000..97f4697da0 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_MILL_CHECK.png differ diff --git a/assets/cn/island_handler/ISLAND_SHOP_RECOMMEND.png b/assets/cn/island_handler/ISLAND_SHOP_RECOMMEND.png new file mode 100644 index 0000000000..c8fe1507c6 Binary files /dev/null and b/assets/cn/island_handler/ISLAND_SHOP_RECOMMEND.png differ diff --git a/assets/cn/island_handler/ISLAND_TECHNOLOGY_TAB1.png b/assets/cn/island_handler/ISLAND_TECHNOLOGY_TAB1.png new file mode 100644 index 0000000000..9a0a5a7c5d Binary files /dev/null and b/assets/cn/island_handler/ISLAND_TECHNOLOGY_TAB1.png differ diff --git a/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_CARD_ANCHOR.png b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_CARD_ANCHOR.png new file mode 100644 index 0000000000..7a79ae63eb Binary files /dev/null and b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_CARD_ANCHOR.png differ diff --git a/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_A.png b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_A.png new file mode 100644 index 0000000000..5d9c8bfd48 Binary files /dev/null and b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_A.png differ diff --git a/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_B.png b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_B.png new file mode 100644 index 0000000000..497d1fc937 Binary files /dev/null and b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_B.png differ diff --git a/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_C.png b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_C.png new file mode 100644 index 0000000000..158c7748a7 Binary files /dev/null and b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_C.png differ diff --git a/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_D.png b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_D.png new file mode 100644 index 0000000000..f274bd8ee4 Binary files /dev/null and b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_D.png differ diff --git a/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_E.png b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_E.png new file mode 100644 index 0000000000..5d956274ca Binary files /dev/null and b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_E.png differ diff --git a/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png new file mode 100644 index 0000000000..43eb85c4b1 Binary files /dev/null and b/assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png differ diff --git a/assets/cn/island_handler/TEMPLATE_ISLAND_RECIPE_ANCHOR.png b/assets/cn/island_handler/TEMPLATE_ISLAND_RECIPE_ANCHOR.png new file mode 100644 index 0000000000..391be5dd15 Binary files /dev/null and b/assets/cn/island_handler/TEMPLATE_ISLAND_RECIPE_ANCHOR.png differ diff --git a/assets/cn/ui/DORMMENU_GOTO_ISLAND.png b/assets/cn/ui/DORMMENU_GOTO_ISLAND.png new file mode 100644 index 0000000000..c12990da84 Binary files /dev/null and b/assets/cn/ui/DORMMENU_GOTO_ISLAND.png differ diff --git a/assets/cn/ui/ISLAND_CHECK.png b/assets/cn/ui/ISLAND_CHECK.png new file mode 100644 index 0000000000..5cc9d2e78e Binary files /dev/null and b/assets/cn/ui/ISLAND_CHECK.png differ diff --git a/assets/cn/ui/ISLAND_COMMISSION_CHECK.png b/assets/cn/ui/ISLAND_COMMISSION_CHECK.png new file mode 100644 index 0000000000..4ce32f4259 Binary files /dev/null and b/assets/cn/ui/ISLAND_COMMISSION_CHECK.png differ diff --git a/assets/cn/ui/ISLAND_GOTO_ISLAND_MAP.png b/assets/cn/ui/ISLAND_GOTO_ISLAND_MAP.png new file mode 100644 index 0000000000..7e59589cf9 Binary files /dev/null and b/assets/cn/ui/ISLAND_GOTO_ISLAND_MAP.png differ diff --git a/assets/cn/ui/ISLAND_GOTO_ISLAND_PHONE.png b/assets/cn/ui/ISLAND_GOTO_ISLAND_PHONE.png new file mode 100644 index 0000000000..5cc9d2e78e Binary files /dev/null and b/assets/cn/ui/ISLAND_GOTO_ISLAND_PHONE.png differ diff --git a/assets/cn/ui/ISLAND_GOTO_ISLAND_SEASON.png b/assets/cn/ui/ISLAND_GOTO_ISLAND_SEASON.png new file mode 100644 index 0000000000..bf485d50a3 Binary files /dev/null and b/assets/cn/ui/ISLAND_GOTO_ISLAND_SEASON.png differ diff --git a/assets/cn/ui/ISLAND_GOTO_ISLAND_SHOP.png b/assets/cn/ui/ISLAND_GOTO_ISLAND_SHOP.png new file mode 100644 index 0000000000..f5ced938d6 Binary files /dev/null and b/assets/cn/ui/ISLAND_GOTO_ISLAND_SHOP.png differ diff --git a/assets/cn/ui/ISLAND_GOTO_ISLAND_TECHNOLOGY.png b/assets/cn/ui/ISLAND_GOTO_ISLAND_TECHNOLOGY.png new file mode 100644 index 0000000000..f521a3ab77 Binary files /dev/null and b/assets/cn/ui/ISLAND_GOTO_ISLAND_TECHNOLOGY.png differ diff --git a/assets/cn/ui/ISLAND_MANAGE_CHECK.png b/assets/cn/ui/ISLAND_MANAGE_CHECK.png new file mode 100644 index 0000000000..3b003bd9a1 Binary files /dev/null and b/assets/cn/ui/ISLAND_MANAGE_CHECK.png differ diff --git a/assets/cn/ui/ISLAND_MAP_CHECK.png b/assets/cn/ui/ISLAND_MAP_CHECK.png new file mode 100644 index 0000000000..359a0e82e1 Binary files /dev/null and b/assets/cn/ui/ISLAND_MAP_CHECK.png differ diff --git a/assets/cn/ui/ISLAND_ORDER_CHECK.png b/assets/cn/ui/ISLAND_ORDER_CHECK.png new file mode 100644 index 0000000000..c1c45b4c63 Binary files /dev/null and b/assets/cn/ui/ISLAND_ORDER_CHECK.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_CHECK.png b/assets/cn/ui/ISLAND_PHONE_CHECK.png new file mode 100644 index 0000000000..4fc9d3f773 Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_CHECK.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.BUTTON.png b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.BUTTON.png new file mode 100644 index 0000000000..4f8cfd430e Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.BUTTON.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.png b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.png new file mode 100644 index 0000000000..4fc9d3f773 Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.BUTTON.png b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.BUTTON.png new file mode 100644 index 0000000000..87e077896c Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.BUTTON.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.png b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.png new file mode 100644 index 0000000000..4fc9d3f773 Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_MANAGE.png b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_MANAGE.png new file mode 100644 index 0000000000..86eb463fdc Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_MANAGE.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.BUTTON.png b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.BUTTON.png new file mode 100644 index 0000000000..cec3089caf Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.BUTTON.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.png b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.png new file mode 100644 index 0000000000..4fc9d3f773 Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_STORAGE.png b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_STORAGE.png new file mode 100644 index 0000000000..383ae380e7 Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_STORAGE.png differ diff --git a/assets/cn/ui/ISLAND_PHONE_GOTO_MAIN.png b/assets/cn/ui/ISLAND_PHONE_GOTO_MAIN.png new file mode 100644 index 0000000000..4fc9d3f773 Binary files /dev/null and b/assets/cn/ui/ISLAND_PHONE_GOTO_MAIN.png differ diff --git a/assets/cn/ui/ISLAND_SEASON_CHECK.png b/assets/cn/ui/ISLAND_SEASON_CHECK.png new file mode 100644 index 0000000000..eee046c932 Binary files /dev/null and b/assets/cn/ui/ISLAND_SEASON_CHECK.png differ diff --git a/assets/cn/ui/ISLAND_SHOP_CHECK.png b/assets/cn/ui/ISLAND_SHOP_CHECK.png new file mode 100644 index 0000000000..0a196bb384 Binary files /dev/null and b/assets/cn/ui/ISLAND_SHOP_CHECK.png differ diff --git a/assets/cn/ui/ISLAND_STORAGE_CHECK.png b/assets/cn/ui/ISLAND_STORAGE_CHECK.png new file mode 100644 index 0000000000..7b75edab6b Binary files /dev/null and b/assets/cn/ui/ISLAND_STORAGE_CHECK.png differ diff --git a/assets/cn/ui/ISLAND_STORAGE_EXIT.png b/assets/cn/ui/ISLAND_STORAGE_EXIT.png new file mode 100644 index 0000000000..13ef852eb8 Binary files /dev/null and b/assets/cn/ui/ISLAND_STORAGE_EXIT.png differ diff --git a/assets/cn/ui/ISLAND_TECHNOLOGY_CHECK.png b/assets/cn/ui/ISLAND_TECHNOLOGY_CHECK.png new file mode 100644 index 0000000000..0a0efa57ab Binary files /dev/null and b/assets/cn/ui/ISLAND_TECHNOLOGY_CHECK.png differ diff --git a/assets/island/character/Akashi.png b/assets/island/character/Akashi.png new file mode 100644 index 0000000000..92409c96ac Binary files /dev/null and b/assets/island/character/Akashi.png differ diff --git a/assets/island/character/Amagi_chan.png b/assets/island/character/Amagi_chan.png new file mode 100644 index 0000000000..c8102ad3f3 Binary files /dev/null and b/assets/island/character/Amagi_chan.png differ diff --git a/assets/island/character/Atago.png b/assets/island/character/Atago.png new file mode 100644 index 0000000000..b545a1688f Binary files /dev/null and b/assets/island/character/Atago.png differ diff --git a/assets/island/character/August_von_Parseval.png b/assets/island/character/August_von_Parseval.png new file mode 100644 index 0000000000..45fd8b469f Binary files /dev/null and b/assets/island/character/August_von_Parseval.png differ diff --git a/assets/island/character/Chao_Ho.png b/assets/island/character/Chao_Ho.png new file mode 100644 index 0000000000..cb0f195fae Binary files /dev/null and b/assets/island/character/Chao_Ho.png differ diff --git a/assets/island/character/Chen_Hai.png b/assets/island/character/Chen_Hai.png new file mode 100644 index 0000000000..228253b289 Binary files /dev/null and b/assets/island/character/Chen_Hai.png differ diff --git a/assets/island/character/Cheshire.png b/assets/island/character/Cheshire.png new file mode 100644 index 0000000000..d073070724 Binary files /dev/null and b/assets/island/character/Cheshire.png differ diff --git a/assets/island/character/Explorer.png b/assets/island/character/Explorer.png new file mode 100644 index 0000000000..8ecfcf4e03 Binary files /dev/null and b/assets/island/character/Explorer.png differ diff --git a/assets/island/character/Fei_Yuen.png b/assets/island/character/Fei_Yuen.png new file mode 100644 index 0000000000..46e9e603e6 Binary files /dev/null and b/assets/island/character/Fei_Yuen.png differ diff --git a/assets/island/character/Friedrich_der_Grosse.png b/assets/island/character/Friedrich_der_Grosse.png new file mode 100644 index 0000000000..9d31d08e07 Binary files /dev/null and b/assets/island/character/Friedrich_der_Grosse.png differ diff --git a/assets/island/character/Helena.png b/assets/island/character/Helena.png new file mode 100644 index 0000000000..4df64a5c73 Binary files /dev/null and b/assets/island/character/Helena.png differ diff --git a/assets/island/character/Hood.png b/assets/island/character/Hood.png new file mode 100644 index 0000000000..3b65a25866 Binary files /dev/null and b/assets/island/character/Hood.png differ diff --git a/assets/island/character/Javelin.png b/assets/island/character/Javelin.png new file mode 100644 index 0000000000..2fef8ae55b Binary files /dev/null and b/assets/island/character/Javelin.png differ diff --git a/assets/island/character/Laffey.png b/assets/island/character/Laffey.png new file mode 100644 index 0000000000..027160ec5f Binary files /dev/null and b/assets/island/character/Laffey.png differ diff --git a/assets/island/character/Le_Malin.png b/assets/island/character/Le_Malin.png new file mode 100644 index 0000000000..2e534b4876 Binary files /dev/null and b/assets/island/character/Le_Malin.png differ diff --git a/assets/island/character/Manjuu.png b/assets/island/character/Manjuu.png new file mode 100644 index 0000000000..27d5fc86c9 Binary files /dev/null and b/assets/island/character/Manjuu.png differ diff --git a/assets/island/character/Navigator.png b/assets/island/character/Navigator.png new file mode 100644 index 0000000000..1c87845c04 Binary files /dev/null and b/assets/island/character/Navigator.png differ diff --git a/assets/island/character/New_Jersey.png b/assets/island/character/New_Jersey.png new file mode 100644 index 0000000000..5750c13f5f Binary files /dev/null and b/assets/island/character/New_Jersey.png differ diff --git a/assets/island/character/Oceana.png b/assets/island/character/Oceana.png new file mode 100644 index 0000000000..263dc59a67 Binary files /dev/null and b/assets/island/character/Oceana.png differ diff --git a/assets/island/character/Prinz_Eugen.png b/assets/island/character/Prinz_Eugen.png new file mode 100644 index 0000000000..8b7a9110e6 Binary files /dev/null and b/assets/island/character/Prinz_Eugen.png differ diff --git a/assets/island/character/Saratoga.png b/assets/island/character/Saratoga.png new file mode 100644 index 0000000000..2f3923e037 Binary files /dev/null and b/assets/island/character/Saratoga.png differ diff --git a/assets/island/character/Shimakaze.png b/assets/island/character/Shimakaze.png new file mode 100644 index 0000000000..aa90465638 Binary files /dev/null and b/assets/island/character/Shimakaze.png differ diff --git a/assets/island/character/Takao.png b/assets/island/character/Takao.png new file mode 100644 index 0000000000..bcc33712c8 Binary files /dev/null and b/assets/island/character/Takao.png differ diff --git a/assets/island/character/Tashkent.png b/assets/island/character/Tashkent.png new file mode 100644 index 0000000000..eac936fa09 Binary files /dev/null and b/assets/island/character/Tashkent.png differ diff --git a/assets/island/character/Unicorn.png b/assets/island/character/Unicorn.png new file mode 100644 index 0000000000..687af7dc08 Binary files /dev/null and b/assets/island/character/Unicorn.png differ diff --git a/assets/island/character/William_D_Porter.png b/assets/island/character/William_D_Porter.png new file mode 100644 index 0000000000..bd0c6fb8c2 Binary files /dev/null and b/assets/island/character/William_D_Porter.png differ diff --git a/assets/island/character/Yat_Sen.png b/assets/island/character/Yat_Sen.png new file mode 100644 index 0000000000..6960046c6f Binary files /dev/null and b/assets/island/character/Yat_Sen.png differ diff --git a/assets/island/character/Ying_Swei.png b/assets/island/character/Ying_Swei.png new file mode 100644 index 0000000000..9f781fdd5f Binary files /dev/null and b/assets/island/character/Ying_Swei.png differ diff --git a/assets/island/restaurant/Amaranth_Onigiri.png b/assets/island/restaurant/Amaranth_Onigiri.png new file mode 100644 index 0000000000..a1ebdb7b4d Binary files /dev/null and b/assets/island/restaurant/Amaranth_Onigiri.png differ diff --git a/assets/island/restaurant/Apple_Juice.png b/assets/island/restaurant/Apple_Juice.png new file mode 100644 index 0000000000..5dfc2efb7d Binary files /dev/null and b/assets/island/restaurant/Apple_Juice.png differ diff --git a/assets/island/restaurant/Apple_Pie.png b/assets/island/restaurant/Apple_Pie.png new file mode 100644 index 0000000000..2a25be90a5 Binary files /dev/null and b/assets/island/restaurant/Apple_Pie.png differ diff --git "a/assets/island/restaurant/Banana_Cr\303\252pe.png" "b/assets/island/restaurant/Banana_Cr\303\252pe.png" new file mode 100644 index 0000000000..4cc43e5818 Binary files /dev/null and "b/assets/island/restaurant/Banana_Cr\303\252pe.png" differ diff --git a/assets/island/restaurant/Banana_and_Mango_Juice.png b/assets/island/restaurant/Banana_and_Mango_Juice.png new file mode 100644 index 0000000000..37035323ce Binary files /dev/null and b/assets/island/restaurant/Banana_and_Mango_Juice.png differ diff --git a/assets/island/restaurant/Berry_and_Orange_Dessert.png b/assets/island/restaurant/Berry_and_Orange_Dessert.png new file mode 100644 index 0000000000..5d227de4e5 Binary files /dev/null and b/assets/island/restaurant/Berry_and_Orange_Dessert.png differ diff --git a/assets/island/restaurant/Buddha's_Temptation.png b/assets/island/restaurant/Buddha's_Temptation.png new file mode 100644 index 0000000000..030dd7615c Binary files /dev/null and b/assets/island/restaurant/Buddha's_Temptation.png differ diff --git a/assets/island/restaurant/Cabbage_and_Tofu_Soup.png b/assets/island/restaurant/Cabbage_and_Tofu_Soup.png new file mode 100644 index 0000000000..9ab0e9aeba Binary files /dev/null and b/assets/island/restaurant/Cabbage_and_Tofu_Soup.png differ diff --git a/assets/island/restaurant/Cheese.png b/assets/island/restaurant/Cheese.png new file mode 100644 index 0000000000..426a8806c2 Binary files /dev/null and b/assets/island/restaurant/Cheese.png differ diff --git a/assets/island/restaurant/Chicken_and_Potato_Hors_d'Oeuvre.png b/assets/island/restaurant/Chicken_and_Potato_Hors_d'Oeuvre.png new file mode 100644 index 0000000000..8d5d264eab Binary files /dev/null and b/assets/island/restaurant/Chicken_and_Potato_Hors_d'Oeuvre.png differ diff --git a/assets/island/restaurant/Citrus_Coffee.png b/assets/island/restaurant/Citrus_Coffee.png new file mode 100644 index 0000000000..afd220f38e Binary files /dev/null and b/assets/island/restaurant/Citrus_Coffee.png differ diff --git a/assets/island/restaurant/Classic_Tofu_Combo.png b/assets/island/restaurant/Classic_Tofu_Combo.png new file mode 100644 index 0000000000..f76595df70 Binary files /dev/null and b/assets/island/restaurant/Classic_Tofu_Combo.png differ diff --git a/assets/island/restaurant/Coal-Roasted_Skewer.png b/assets/island/restaurant/Coal-Roasted_Skewer.png new file mode 100644 index 0000000000..3502f777ce Binary files /dev/null and b/assets/island/restaurant/Coal-Roasted_Skewer.png differ diff --git a/assets/island/restaurant/Colorful_Fruit_Paradise.png b/assets/island/restaurant/Colorful_Fruit_Paradise.png new file mode 100644 index 0000000000..092e247597 Binary files /dev/null and b/assets/island/restaurant/Colorful_Fruit_Paradise.png differ diff --git a/assets/island/restaurant/Corn_Cup.png b/assets/island/restaurant/Corn_Cup.png new file mode 100644 index 0000000000..6851d4543e Binary files /dev/null and b/assets/island/restaurant/Corn_Cup.png differ diff --git a/assets/island/restaurant/Crayfish_Stir-Fry.png b/assets/island/restaurant/Crayfish_Stir-Fry.png new file mode 100644 index 0000000000..96886848c7 Binary files /dev/null and b/assets/island/restaurant/Crayfish_Stir-Fry.png differ diff --git a/assets/island/restaurant/Cucumber_Juice.png b/assets/island/restaurant/Cucumber_Juice.png new file mode 100644 index 0000000000..de88f12f7b Binary files /dev/null and b/assets/island/restaurant/Cucumber_Juice.png differ diff --git a/assets/island/restaurant/Double_Energy_Combo.png b/assets/island/restaurant/Double_Energy_Combo.png new file mode 100644 index 0000000000..6695498bf1 Binary files /dev/null and b/assets/island/restaurant/Double_Energy_Combo.png differ diff --git a/assets/island/restaurant/Fish_&_Chips.png b/assets/island/restaurant/Fish_&_Chips.png new file mode 100644 index 0000000000..169cfa13f3 Binary files /dev/null and b/assets/island/restaurant/Fish_&_Chips.png differ diff --git a/assets/island/restaurant/Floral_and_Fruity.png b/assets/island/restaurant/Floral_and_Fruity.png new file mode 100644 index 0000000000..9b60010997 Binary files /dev/null and b/assets/island/restaurant/Floral_and_Fruity.png differ diff --git a/assets/island/restaurant/Fruity_&_Fruitier.png b/assets/island/restaurant/Fruity_&_Fruitier.png new file mode 100644 index 0000000000..2e183d27c6 Binary files /dev/null and b/assets/island/restaurant/Fruity_&_Fruitier.png differ diff --git a/assets/island/restaurant/Hearty_Meal.png b/assets/island/restaurant/Hearty_Meal.png new file mode 100644 index 0000000000..e274705576 Binary files /dev/null and b/assets/island/restaurant/Hearty_Meal.png differ diff --git a/assets/island/restaurant/Honey_and_Lemon_Water.png b/assets/island/restaurant/Honey_and_Lemon_Water.png new file mode 100644 index 0000000000..5bb1169ee2 Binary files /dev/null and b/assets/island/restaurant/Honey_and_Lemon_Water.png differ diff --git a/assets/island/restaurant/Iced_Coffee.png b/assets/island/restaurant/Iced_Coffee.png new file mode 100644 index 0000000000..795b2f9d02 Binary files /dev/null and b/assets/island/restaurant/Iced_Coffee.png differ diff --git a/assets/island/restaurant/Latte.png b/assets/island/restaurant/Latte.png new file mode 100644 index 0000000000..3713bf946d Binary files /dev/null and b/assets/island/restaurant/Latte.png differ diff --git a/assets/island/restaurant/Lavender_Tea.png b/assets/island/restaurant/Lavender_Tea.png new file mode 100644 index 0000000000..f2423ccea6 Binary files /dev/null and b/assets/island/restaurant/Lavender_Tea.png differ diff --git a/assets/island/restaurant/Lemon_Shrimp.png b/assets/island/restaurant/Lemon_Shrimp.png new file mode 100644 index 0000000000..3957c07090 Binary files /dev/null and b/assets/island/restaurant/Lemon_Shrimp.png differ diff --git a/assets/island/restaurant/Morning_Light_Energy_Combo.png b/assets/island/restaurant/Morning_Light_Energy_Combo.png new file mode 100644 index 0000000000..66ebc605bd Binary files /dev/null and b/assets/island/restaurant/Morning_Light_Energy_Combo.png differ diff --git a/assets/island/restaurant/Omelette.png b/assets/island/restaurant/Omelette.png new file mode 100644 index 0000000000..668280b7fc Binary files /dev/null and b/assets/island/restaurant/Omelette.png differ diff --git a/assets/island/restaurant/Omurice.png b/assets/island/restaurant/Omurice.png new file mode 100644 index 0000000000..5d6455908e Binary files /dev/null and b/assets/island/restaurant/Omurice.png differ diff --git a/assets/island/restaurant/Orange_Pie.png b/assets/island/restaurant/Orange_Pie.png new file mode 100644 index 0000000000..162ee965cb Binary files /dev/null and b/assets/island/restaurant/Orange_Pie.png differ diff --git a/assets/island/restaurant/Orchard_Duo.png b/assets/island/restaurant/Orchard_Duo.png new file mode 100644 index 0000000000..8183847fbf Binary files /dev/null and b/assets/island/restaurant/Orchard_Duo.png differ diff --git a/assets/island/restaurant/Paella.png b/assets/island/restaurant/Paella.png new file mode 100644 index 0000000000..a3c7c8e120 Binary files /dev/null and b/assets/island/restaurant/Paella.png differ diff --git a/assets/island/restaurant/Rolled_Carrot_Omelette.png b/assets/island/restaurant/Rolled_Carrot_Omelette.png new file mode 100644 index 0000000000..65dbc91e54 Binary files /dev/null and b/assets/island/restaurant/Rolled_Carrot_Omelette.png differ diff --git a/assets/island/restaurant/Steak_Bowl.png b/assets/island/restaurant/Steak_Bowl.png new file mode 100644 index 0000000000..dcadc15fab Binary files /dev/null and b/assets/island/restaurant/Steak_Bowl.png differ diff --git a/assets/island/restaurant/Steamed_Fish_with_Onions.png b/assets/island/restaurant/Steamed_Fish_with_Onions.png new file mode 100644 index 0000000000..2ef0a334dd Binary files /dev/null and b/assets/island/restaurant/Steamed_Fish_with_Onions.png differ diff --git a/assets/island/restaurant/Sticky_Rice_with_Mango.png b/assets/island/restaurant/Sticky_Rice_with_Mango.png new file mode 100644 index 0000000000..b438479f74 Binary files /dev/null and b/assets/island/restaurant/Sticky_Rice_with_Mango.png differ diff --git a/assets/island/restaurant/Stir-Fried_Chicken.png b/assets/island/restaurant/Stir-Fried_Chicken.png new file mode 100644 index 0000000000..f13320d0cb Binary files /dev/null and b/assets/island/restaurant/Stir-Fried_Chicken.png differ diff --git a/assets/island/restaurant/Strawberry_Charlotte.png b/assets/island/restaurant/Strawberry_Charlotte.png new file mode 100644 index 0000000000..bf48ce9339 Binary files /dev/null and b/assets/island/restaurant/Strawberry_Charlotte.png differ diff --git "a/assets/island/restaurant/Strawberry_Honey_Frapp\303\251.png" "b/assets/island/restaurant/Strawberry_Honey_Frapp\303\251.png" new file mode 100644 index 0000000000..0a718facdf Binary files /dev/null and "b/assets/island/restaurant/Strawberry_Honey_Frapp\303\251.png" differ diff --git a/assets/island/restaurant/Strawberry_Lemon_Drink.png b/assets/island/restaurant/Strawberry_Lemon_Drink.png new file mode 100644 index 0000000000..ae86565856 Binary files /dev/null and b/assets/island/restaurant/Strawberry_Lemon_Drink.png differ diff --git a/assets/island/restaurant/Strawberry_Milkshake.png b/assets/island/restaurant/Strawberry_Milkshake.png new file mode 100644 index 0000000000..d301df4755 Binary files /dev/null and b/assets/island/restaurant/Strawberry_Milkshake.png differ diff --git a/assets/island/restaurant/Succulently_Sweet.png b/assets/island/restaurant/Succulently_Sweet.png new file mode 100644 index 0000000000..48f59a1365 Binary files /dev/null and b/assets/island/restaurant/Succulently_Sweet.png differ diff --git a/assets/island/restaurant/Sunny_Honey.png b/assets/island/restaurant/Sunny_Honey.png new file mode 100644 index 0000000000..eea5d4372a Binary files /dev/null and b/assets/island/restaurant/Sunny_Honey.png differ diff --git a/assets/island/restaurant/The_Carne-val.png b/assets/island/restaurant/The_Carne-val.png new file mode 100644 index 0000000000..f2347c90a7 Binary files /dev/null and b/assets/island/restaurant/The_Carne-val.png differ diff --git a/assets/island/restaurant/The_Wake-Up_Call.png b/assets/island/restaurant/The_Wake-Up_Call.png new file mode 100644 index 0000000000..eb71550637 Binary files /dev/null and b/assets/island/restaurant/The_Wake-Up_Call.png differ diff --git a/assets/island/restaurant/Tofu.png b/assets/island/restaurant/Tofu.png new file mode 100644 index 0000000000..1850bed96a Binary files /dev/null and b/assets/island/restaurant/Tofu.png differ diff --git a/assets/island/restaurant/Tofu_with_Minced_Meat.png b/assets/island/restaurant/Tofu_with_Minced_Meat.png new file mode 100644 index 0000000000..254b96bf35 Binary files /dev/null and b/assets/island/restaurant/Tofu_with_Minced_Meat.png differ diff --git a/assets/island/restaurant/Tomato_and_Egg_Stir-Fry.png b/assets/island/restaurant/Tomato_and_Egg_Stir-Fry.png new file mode 100644 index 0000000000..b9d24ec5b6 Binary files /dev/null and b/assets/island/restaurant/Tomato_and_Egg_Stir-Fry.png differ diff --git a/assets/island/restaurant/Vegetable_Salad.png b/assets/island/restaurant/Vegetable_Salad.png new file mode 100644 index 0000000000..71a5f65113 Binary files /dev/null and b/assets/island/restaurant/Vegetable_Salad.png differ diff --git a/assets/island/restaurant/Watermelon_Juice.png b/assets/island/restaurant/Watermelon_Juice.png new file mode 100644 index 0000000000..25f431f26b Binary files /dev/null and b/assets/island/restaurant/Watermelon_Juice.png differ diff --git a/assets/island/technology/technology_chart_2.png b/assets/island/technology/technology_chart_2.png new file mode 100644 index 0000000000..b5d2632f26 Binary files /dev/null and b/assets/island/technology/technology_chart_2.png differ diff --git a/assets/island/technology/technology_chart_3.png b/assets/island/technology/technology_chart_3.png new file mode 100644 index 0000000000..1d92217970 Binary files /dev/null and b/assets/island/technology/technology_chart_3.png differ diff --git a/assets/island/technology/technology_chart_4.png b/assets/island/technology/technology_chart_4.png new file mode 100644 index 0000000000..bfd8790f60 Binary files /dev/null and b/assets/island/technology/technology_chart_4.png differ diff --git a/assets/island/technology/technology_chart_5.png b/assets/island/technology/technology_chart_5.png new file mode 100644 index 0000000000..45dbd4068d Binary files /dev/null and b/assets/island/technology/technology_chart_5.png differ diff --git a/assets/island/technology/technology_chart_6.png b/assets/island/technology/technology_chart_6.png new file mode 100644 index 0000000000..888d3aa863 Binary files /dev/null and b/assets/island/technology/technology_chart_6.png differ diff --git a/assets/jp/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png b/assets/jp/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png new file mode 100644 index 0000000000..45aac5987f Binary files /dev/null and b/assets/jp/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png differ diff --git a/assets/jp/island/ISLAND_COLLECT_SELECT_CANCEL.png b/assets/jp/island/ISLAND_COLLECT_SELECT_CANCEL.png new file mode 100644 index 0000000000..344fc15af9 Binary files /dev/null and b/assets/jp/island/ISLAND_COLLECT_SELECT_CANCEL.png differ diff --git a/assets/jp/island/ISLAND_COLLECT_SELECT_CONFIRM.png b/assets/jp/island/ISLAND_COLLECT_SELECT_CONFIRM.png new file mode 100644 index 0000000000..b03d5b597d Binary files /dev/null and b/assets/jp/island/ISLAND_COLLECT_SELECT_CONFIRM.png differ diff --git a/assets/jp/island/ISLAND_COLLECT_SELECT_ENTER.png b/assets/jp/island/ISLAND_COLLECT_SELECT_ENTER.png new file mode 100644 index 0000000000..4b8a9ad749 Binary files /dev/null and b/assets/jp/island/ISLAND_COLLECT_SELECT_ENTER.png differ diff --git a/assets/jp/island/ISLAND_COLLECT_START.png b/assets/jp/island/ISLAND_COLLECT_START.png new file mode 100644 index 0000000000..3df79b8b45 Binary files /dev/null and b/assets/jp/island/ISLAND_COLLECT_START.png differ diff --git a/assets/jp/island/ISLAND_COLLECT_START_UNAVAILABLE.png b/assets/jp/island/ISLAND_COLLECT_START_UNAVAILABLE.png new file mode 100644 index 0000000000..263bea2868 Binary files /dev/null and b/assets/jp/island/ISLAND_COLLECT_START_UNAVAILABLE.png differ diff --git a/assets/jp/island/ISLAND_FREEBIE_CLAIM.png b/assets/jp/island/ISLAND_FREEBIE_CLAIM.png new file mode 100644 index 0000000000..5341bf7b11 Binary files /dev/null and b/assets/jp/island/ISLAND_FREEBIE_CLAIM.png differ diff --git a/assets/jp/island/ISLAND_FREEBIE_COOLDOWN.png b/assets/jp/island/ISLAND_FREEBIE_COOLDOWN.png new file mode 100644 index 0000000000..2a2a2ec0e8 Binary files /dev/null and b/assets/jp/island/ISLAND_FREEBIE_COOLDOWN.png differ diff --git a/assets/jp/island/ISLAND_FREEBIE_RECEIVE.png b/assets/jp/island/ISLAND_FREEBIE_RECEIVE.png new file mode 100644 index 0000000000..c2e58c15ec Binary files /dev/null and b/assets/jp/island/ISLAND_FREEBIE_RECEIVE.png differ diff --git a/assets/jp/island/ISLAND_FREEBIE_SHARE.png b/assets/jp/island/ISLAND_FREEBIE_SHARE.png new file mode 100644 index 0000000000..9a25ed8cec Binary files /dev/null and b/assets/jp/island/ISLAND_FREEBIE_SHARE.png differ diff --git a/assets/jp/island/ISLAND_FREEBIE_SHARE_ALL.png b/assets/jp/island/ISLAND_FREEBIE_SHARE_ALL.png new file mode 100644 index 0000000000..f5282da4b4 Binary files /dev/null and b/assets/jp/island/ISLAND_FREEBIE_SHARE_ALL.png differ diff --git a/assets/jp/island/ISLAND_ORDER_ACCEPT.png b/assets/jp/island/ISLAND_ORDER_ACCEPT.png new file mode 100644 index 0000000000..5c77356751 Binary files /dev/null and b/assets/jp/island/ISLAND_ORDER_ACCEPT.png differ diff --git a/assets/jp/island/ISLAND_ORDER_ACCEPT_URGENT.png b/assets/jp/island/ISLAND_ORDER_ACCEPT_URGENT.png new file mode 100644 index 0000000000..624ef1d713 Binary files /dev/null and b/assets/jp/island/ISLAND_ORDER_ACCEPT_URGENT.png differ diff --git a/assets/jp/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png b/assets/jp/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png new file mode 100644 index 0000000000..e983b52761 Binary files /dev/null and b/assets/jp/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png differ diff --git a/assets/jp/island/ISLAND_ORDER_REJECT.png b/assets/jp/island/ISLAND_ORDER_REJECT.png new file mode 100644 index 0000000000..b941aee724 Binary files /dev/null and b/assets/jp/island/ISLAND_ORDER_REJECT.png differ diff --git a/assets/jp/island/ISLAND_PRODUCTION_RECEIVE.png b/assets/jp/island/ISLAND_PRODUCTION_RECEIVE.png new file mode 100644 index 0000000000..397b2ea289 Binary files /dev/null and b/assets/jp/island/ISLAND_PRODUCTION_RECEIVE.png differ diff --git a/assets/jp/island/ISLAND_PRODUCTION_RERUN.png b/assets/jp/island/ISLAND_PRODUCTION_RERUN.png new file mode 100644 index 0000000000..d70af40d89 Binary files /dev/null and b/assets/jp/island/ISLAND_PRODUCTION_RERUN.png differ diff --git a/assets/jp/island/ISLAND_RECIPE_CHECK.png b/assets/jp/island/ISLAND_RECIPE_CHECK.png new file mode 100644 index 0000000000..ea7fae6c31 Binary files /dev/null and b/assets/jp/island/ISLAND_RECIPE_CHECK.png differ diff --git a/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png new file mode 100644 index 0000000000..193ec282d1 Binary files /dev/null and b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png differ diff --git a/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png new file mode 100644 index 0000000000..385a6aac4e Binary files /dev/null and b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png differ diff --git a/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png new file mode 100644 index 0000000000..d1fe10ecab Binary files /dev/null and b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png differ diff --git a/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png new file mode 100644 index 0000000000..9b18eeb31b Binary files /dev/null and b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png differ diff --git a/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_KOI.png b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_KOI.png new file mode 100644 index 0000000000..1bdec33ed2 Binary files /dev/null and b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_KOI.png differ diff --git a/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png new file mode 100644 index 0000000000..eb8de3e577 Binary files /dev/null and b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png differ diff --git a/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png new file mode 100644 index 0000000000..13a1ba8fb9 Binary files /dev/null and b/assets/jp/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png differ diff --git a/assets/jp/island/TEMPLATE_ISLAND_RECIPE_ANCHOR.png b/assets/jp/island/TEMPLATE_ISLAND_RECIPE_ANCHOR.png new file mode 100644 index 0000000000..391be5dd15 Binary files /dev/null and b/assets/jp/island/TEMPLATE_ISLAND_RECIPE_ANCHOR.png differ diff --git a/assets/jp/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png b/assets/jp/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png new file mode 100644 index 0000000000..342fc33e85 Binary files /dev/null and b/assets/jp/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png differ diff --git a/assets/jp/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png b/assets/jp/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png new file mode 100644 index 0000000000..8bb62e556e Binary files /dev/null and b/assets/jp/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png differ diff --git a/assets/jp/island_handler/ISLAND_DOCK_CHECK.png b/assets/jp/island_handler/ISLAND_DOCK_CHECK.png new file mode 100644 index 0000000000..81875939c2 Binary files /dev/null and b/assets/jp/island_handler/ISLAND_DOCK_CHECK.png differ diff --git a/assets/jp/island_handler/ISLAND_INFO_GOTO_SHOP.png b/assets/jp/island_handler/ISLAND_INFO_GOTO_SHOP.png new file mode 100644 index 0000000000..5e222fa114 Binary files /dev/null and b/assets/jp/island_handler/ISLAND_INFO_GOTO_SHOP.png differ diff --git a/assets/jp/island_handler/ISLAND_RESTAURANT_CHECK.png b/assets/jp/island_handler/ISLAND_RESTAURANT_CHECK.png new file mode 100644 index 0000000000..dc9b065ef3 Binary files /dev/null and b/assets/jp/island_handler/ISLAND_RESTAURANT_CHECK.png differ diff --git a/assets/jp/island_handler/ISLAND_RESTAURANT_RECEIVE.png b/assets/jp/island_handler/ISLAND_RESTAURANT_RECEIVE.png new file mode 100644 index 0000000000..220a1885b0 Binary files /dev/null and b/assets/jp/island_handler/ISLAND_RESTAURANT_RECEIVE.png differ diff --git a/assets/jp/island_handler/ISLAND_RESTAURANT_RECOMMEND.png b/assets/jp/island_handler/ISLAND_RESTAURANT_RECOMMEND.png new file mode 100644 index 0000000000..3d09a2dfe9 Binary files /dev/null and b/assets/jp/island_handler/ISLAND_RESTAURANT_RECOMMEND.png differ diff --git a/assets/jp/island_handler/ISLAND_RESTAURANT_RESTING.png b/assets/jp/island_handler/ISLAND_RESTAURANT_RESTING.png new file mode 100644 index 0000000000..0f53050783 Binary files /dev/null and b/assets/jp/island_handler/ISLAND_RESTAURANT_RESTING.png differ diff --git a/assets/jp/island_handler/ISLAND_RESTAURANT_START.png b/assets/jp/island_handler/ISLAND_RESTAURANT_START.png new file mode 100644 index 0000000000..9ce8adddae Binary files /dev/null and b/assets/jp/island_handler/ISLAND_RESTAURANT_START.png differ diff --git a/assets/jp/island_handler/ISLAND_SHOP_BUY_CHECK.png b/assets/jp/island_handler/ISLAND_SHOP_BUY_CHECK.png new file mode 100644 index 0000000000..d6cb89c2eb Binary files /dev/null and b/assets/jp/island_handler/ISLAND_SHOP_BUY_CHECK.png differ diff --git a/assets/jp/island_handler/ISLAND_SHOP_BUY_CONFIRM.png b/assets/jp/island_handler/ISLAND_SHOP_BUY_CONFIRM.png new file mode 100644 index 0000000000..1b46361759 Binary files /dev/null and b/assets/jp/island_handler/ISLAND_SHOP_BUY_CONFIRM.png differ diff --git a/assets/jp/island_handler/ISLAND_SHOP_RECOMMEND.png b/assets/jp/island_handler/ISLAND_SHOP_RECOMMEND.png new file mode 100644 index 0000000000..c8fe1507c6 Binary files /dev/null and b/assets/jp/island_handler/ISLAND_SHOP_RECOMMEND.png differ diff --git a/assets/jp/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png b/assets/jp/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png new file mode 100644 index 0000000000..43eb85c4b1 Binary files /dev/null and b/assets/jp/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png differ diff --git a/assets/jp/ui/ISLAND_COMMISSION_CHECK.png b/assets/jp/ui/ISLAND_COMMISSION_CHECK.png new file mode 100644 index 0000000000..4ce32f4259 Binary files /dev/null and b/assets/jp/ui/ISLAND_COMMISSION_CHECK.png differ diff --git a/assets/jp/ui/ISLAND_MANAGE_CHECK.png b/assets/jp/ui/ISLAND_MANAGE_CHECK.png new file mode 100644 index 0000000000..3b003bd9a1 Binary files /dev/null and b/assets/jp/ui/ISLAND_MANAGE_CHECK.png differ diff --git a/assets/jp/ui/ISLAND_MAP_CHECK.png b/assets/jp/ui/ISLAND_MAP_CHECK.png new file mode 100644 index 0000000000..359a0e82e1 Binary files /dev/null and b/assets/jp/ui/ISLAND_MAP_CHECK.png differ diff --git a/assets/jp/ui/ISLAND_ORDER_CHECK.png b/assets/jp/ui/ISLAND_ORDER_CHECK.png new file mode 100644 index 0000000000..c1c45b4c63 Binary files /dev/null and b/assets/jp/ui/ISLAND_ORDER_CHECK.png differ diff --git a/assets/jp/ui/ISLAND_SEASON_CHECK.png b/assets/jp/ui/ISLAND_SEASON_CHECK.png new file mode 100644 index 0000000000..eee046c932 Binary files /dev/null and b/assets/jp/ui/ISLAND_SEASON_CHECK.png differ diff --git a/assets/jp/ui/ISLAND_STORAGE_CHECK.png b/assets/jp/ui/ISLAND_STORAGE_CHECK.png new file mode 100644 index 0000000000..7b75edab6b Binary files /dev/null and b/assets/jp/ui/ISLAND_STORAGE_CHECK.png differ diff --git a/assets/jp/ui/ISLAND_TECHNOLOGY_CHECK.png b/assets/jp/ui/ISLAND_TECHNOLOGY_CHECK.png new file mode 100644 index 0000000000..0a0efa57ab Binary files /dev/null and b/assets/jp/ui/ISLAND_TECHNOLOGY_CHECK.png differ diff --git a/assets/mask/MASK_ISLAND_TECHNOLOGY.png b/assets/mask/MASK_ISLAND_TECHNOLOGY.png new file mode 100644 index 0000000000..d8a72e58fe Binary files /dev/null and b/assets/mask/MASK_ISLAND_TECHNOLOGY.png differ diff --git a/config/template.json b/config/template.json index aef8040512..53c576fadf 100644 --- a/config/template.json +++ b/config/template.json @@ -1907,6 +1907,117 @@ "Storage": {} } }, + "IslandProduction": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "IslandProduction", + "SuccessInterval": 0, + "FailureInterval": 120, + "ServerUpdate": "00:00" + }, + "IslandProduction": { + "HardFloorItems": "{}", + "ReserveItems": "{}", + "RequestBufferItems": "{}", + "DailyBufferItems": "{}", + "IdleAccumulatingItems": "{}" + }, + "Storage": { + "Storage": {} + } + }, + "IslandOrder": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "IslandOrder", + "SuccessInterval": 0, + "FailureInterval": 120, + "ServerUpdate": "00:00" + }, + "IslandOrder": { + "StuckSeasonOrderId": 0 + }, + "Storage": { + "Storage": {} + } + }, + "IslandFreebie": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "IslandFreebie", + "SuccessInterval": 0, + "FailureInterval": 120, + "ServerUpdate": "00:00" + }, + "IslandFreebie": { + "Share": true + }, + "Storage": { + "Storage": {} + } + }, + "IslandCollect": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "IslandCollect", + "SuccessInterval": 0, + "FailureInterval": 120, + "ServerUpdate": "03:00, 18:00" + }, + "Storage": { + "Storage": {} + } + }, + "IslandSeasonTask": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "IslandSeasonTask", + "SuccessInterval": 0, + "FailureInterval": 120, + "ServerUpdate": "00:00" + }, + "IslandSeasonTask": { + "TaskTarget": "{}" + }, + "Storage": { + "Storage": {} + } + }, + "IslandBusiness": { + "Scheduler": { + "Enable": false, + "NextRun": "2020-01-01 00:00:00", + "Command": "IslandBusiness", + "SuccessInterval": 0, + "FailureInterval": 120, + "ServerUpdate": "00:00" + }, + "IslandRestaurant": { + "KoiGrade": "bronze", + "KoiWaitress": "any", + "KoiMenu": "{}", + "BearGrade": "bronze", + "BearWaitress": "any", + "BearMenu": "{}", + "EateryGrade": "bronze", + "EateryWaitress": "any", + "EateryMenu": "{}", + "GrillGrade": "bronze", + "GrillWaitress": "any", + "GrillMenu": "{}", + "CafeGrade": "bronze", + "CafeWaitress": "any", + "CafeMenu": "{}" + }, + "Storage": { + "Storage": {} + } + }, "Daemon": { "Daemon": { "EnterMap": true @@ -1932,6 +2043,19 @@ "Storage": {} } }, + "IslandProductionPlanner": { + "IslandProductionPlanner": { + "RescanIslandTechnology": false, + "DailyProfitLowerLimit": 50000, + "DailyBufferSafetyMargin": 0, + "FieldsEfficiency": 0, + "OrchardEfficiency": 0, + "NurseryEfficiency": 0 + }, + "Storage": { + "Storage": {} + } + }, "Benchmark": { "Benchmark": { "DeviceType": "emulator", diff --git a/dev_tools/island_extractor.py b/dev_tools/island_extractor.py new file mode 100644 index 0000000000..b9c4d863e2 --- /dev/null +++ b/dev_tools/island_extractor.py @@ -0,0 +1,1051 @@ +import re + +from dev_tools.slpp import slpp +from dev_tools.utils import LuaLoader + + +class IslandItem: + def __init__(self, item): + """ + In the file 'sharecfg/island_item_data_template.lua': + id: serial of this item + name: name in server, default to CN + pt_num: pt value of this item + manage_influence: restaurant influence + order_price: price in order system + """ + self.id = item['id'] + # self.name = item['name'] + self.pt_num = item['pt_num'] + self.manage_influence = item['manage_influence'] + self.order_price = item['order_price'] + + def encode(self): + data = { + # 'id': self.id, + 'name': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'pt_num': self.pt_num, + 'manage_influence': self.manage_influence, + 'order_price': self.order_price, + } + return data + + +class IslandItemExtractor: + def __init__(self): + self.item = {} + + data = LOADER.load('sharecfg/island_item_data_template.lua', keyword='pg.base.island_item_data_template') + for index, item in data.items(): + if not isinstance(index, int) or 1 < index < 1000 or index > 100000: + continue + + self.item[item['id']] = IslandItem(item).encode() + + for index, name in self.extract_item_name('zh-CN').items(): + self.item[index]['name']['cn'] = name + for index, name in self.extract_item_name('en-US').items(): + self.item[index]['name']['en'] = name + for index, name in self.extract_item_name('ja-JP').items(): + self.item[index]['name']['jp'] = name + # for index, name in self.extract_item_name('zh-TW').items(): + # self.item[index]['name']['tw'] = name + + def extract_item_name(self, server): + LOADER.server = server + data = LOADER.load('sharecfg/island_item_data_template.lua', keyword='pg.base.island_item_data_template') + out = {} + for index, item in data.items(): + if not isinstance(index, int) or 1 < index < 1000 or index > 100000: + continue + out[item['id']] = item['name'] + + return out + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_ITEM = {') + lines.append(" 0: {'name': {'cn': '岛屿开发PT', 'en': 'Island Development Points', 'jp': '離島開発Pt'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0},") + for index, item in self.item.items(): + lines.append(f' {index}: {item},') + lines.append('}') + return lines + + def write(self, file): + print(f'writing {file}') + with open(file, 'w', encoding='utf-8') as f: + for text in self.encode(): + f.write(text + '\n') + + +def unpack_ingredient_dic(dic): + try: + result = {} + for _, entry in dic.items(): + # print(entry) + result[entry[0]] = entry[1] + return result + except TypeError: + print(dic) + raise + + +class IslandRecipe: + def __init__(self, recipe): + """ + In the file 'sharecfg/island_formula.lua': + id: serial of this recipe + name: name in server, default to CN + workload: using time with unit 0.1 second. + commission_cost: a nested dict of ingredients, each being a pair of item id and count + production_limit: consecutive commission upper bound for one commission handle + commission_product: a nested dict of products, each (only one) being a pair of item id and count + second_product_display: a nested dict of products, each being a pair of item id and count. + """ + self.id = recipe['id'] + # self.name = recipe['name'] + self.workload = recipe['workload'] + self.commission_cost = recipe['commission_cost'] + self.production_limit = recipe['production_limit'] + self.commission_product = recipe['commission_product'] + self.second_product_display = recipe['second_product_display'] + + def encode(self): + data = { + self.id: { + 'name': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'workload': self.workload, + 'commission_cost': unpack_ingredient_dic(self.commission_cost), + 'production_limit': self.production_limit, + 'commission_product': unpack_ingredient_dic(self.commission_product), + 'second_product_display': unpack_ingredient_dic(self.second_product_display), + } + } + return data + + +class IslandRecipeExtractor: + def __init__(self): + self.recipe = {} + data = LOADER.load('sharecfg/island_formula.lua') + for index, item in data.items(): + if not isinstance(index, int) or not (index // 10000 < 700 or index // 10000 >= 990): + continue + if item['attribute'] in [1, 2, 3, 4, 6]: + self.recipe.update(IslandRecipe(item).encode()) + + # print(item['id'], + # item['name'], + # item['workload'] // 10, + # item['commission_cost'], + # item['production_limit'], + # item['commission_product'], + # item['second_product_display']) + for index, name in self.extract_item_name('zh-CN').items(): + if index in self.recipe: + self.recipe[index]['name']['cn'] = name + for index, name in self.extract_item_name('en-US').items(): + if index in self.recipe: + self.recipe[index]['name']['en'] = name + for index, name in self.extract_item_name('ja-JP').items(): + if index in self.recipe: + self.recipe[index]['name']['jp'] = name + # for index, name in self.extract_item_name('zh-TW').items(): + # if index in self.recipe: + # self.recipe[index]['name']['tw'] = name + + def extract_item_name(self, server): + LOADER.server = server + data = LOADER.load('sharecfg/island_formula.lua') + out = {} + for index, item in data.items(): + if not isinstance(index, int) or not (index // 10000 < 700 or index // 10000 >= 990): + continue + if item['attribute'] in [1, 2, 3, 4, 6]: + out[item['id']] = item['name'] + + return out + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_RECIPE = {') + for index, recipe in self.recipe.items(): + lines.append(f' {index}: {recipe},') + lines.append('}') + return lines + + def write(self, file): + print(f'writing {file}') + with open(file, '', encoding='utf-8') as f: + for text in self.encode(): + f.write(text + '\n') + + +def unpack_activity_formula(dic): + try: + result = [] + for _, entry in dic.items(): + result += [item for _, item in entry[1].items()] + return result + except TypeError: + print(dic) + + +class IslandProduction: + def __init__(self, slot): + """ + In the file 'sharecfg/island_production_slot.lua': + attribute: 1 = agriculture, 2 = mineral, 3 = animal, 4 = restaurant, 6 = industry + place: slot position + exclusion_slot: id of slots that are exclusive to this slot, not necessary for our implementation + formula: applicable recipes + activity_formula: activity id and activity recipes + """ + self.id = slot['id'] + self.attribute = slot['attribute'] + self.place = slot['place'] + self.formula = [item for _, item in slot['formula'].items()] + self.activity_formula = unpack_activity_formula(slot['activity_formula']) + + def encode(self): + data = { + self.id: { + 'attribute': self.attribute, + 'place': self.place, + 'formula': self.formula, + 'activity_formula': self.activity_formula, + } + } + return data + + +class IslandProductionExtractor: + def __init__(self): + self.slot = {} + data = LOADER.load('sharecfg/island_production_slot.lua') + for index, item in data.items(): + if not isinstance(index, int) or index < 9000 or index > 10000: + continue + # print(item['attribute'], item['place'], item['formula'], item['activity_formula']) + self.slot.update(IslandProduction(item).encode()) + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_SLOT = {') + for index, slot in self.slot.items(): + lines.append(f' {index}: {slot},') + lines.append('}') + return lines + + def write(self, file): + print(f'writing {file}') + with open(file, 'w', encoding='utf-8') as f: + for text in self.encode(): + f.write(text + '\n') + + +def island_time_to_sql_time(island_time): + """ + island_time is like {0: {0: 2026, 1: 2, 2: 5}, 1: {0: 12, 1: 0, 2: 0}} + """ + year = island_time[0][0] + month = island_time[0][1] + day = island_time[0][2] + hour = island_time[1][0] + minute = island_time[1][1] + second = island_time[1][2] + return f'{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}' + + +class Activity: + def __init__(self, activity): + """ + In the file 'sharecfg/activity_template.lua': + id: serial of this activity + type: + 5001: Specialties, Pearl Trade + 5002: Order + 5003: Wild Gather + 5004: Recipe + 800: + config_data: config data of this activity + """ + self.id = activity['id'] + self.type = activity['type'] + self.config_data = [config for _, config in activity['config_data'].items()] + + def encode(self): + data = { + 'type': self.type, + 'start_time': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'end_time': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'config_data': self.config_data, + } + return data + + +class IslandActivityExtractor: + def __init__(self): + self.activity = {} + data = LOADER.load('sharecfg/island_activity_template.lua') + for index, item in data.items(): + if not isinstance(index, int) or index < 990000: + continue + self.activity[index] = None + data = LOADER.load('sharecfg/activity_template.lua') + for index, item in data.items(): + if not isinstance(index, int) or not index in self.activity: + continue + self.activity[index] = Activity(item).encode() + for index, item in self.extract_item_name('zh-CN').items(): + if item['time'] == 'always': + self.activity[index]['start_time']['cn'] = None + self.activity[index]['end_time']['cn'] = None + elif isinstance(item['time'], dict) and item['time'][0] == 'timer': + try: + self.activity[index]['start_time']['cn'] = island_time_to_sql_time(item['time'][1]) + self.activity[index]['end_time']['cn'] = island_time_to_sql_time(item['time'][2]) + except KeyError as e: + print(self.activity[index]) + raise e + for index, item in self.extract_item_name('en-US').items(): + if item['time'] == 'always': + self.activity[index]['start_time']['en'] = None + self.activity[index]['end_time']['en'] = None + elif isinstance(item['time'], dict) and item['time'][0] == 'timer': + self.activity[index]['start_time']['en'] = island_time_to_sql_time(item['time'][1]) + self.activity[index]['end_time']['en'] = island_time_to_sql_time(item['time'][2]) + for index, item in self.extract_item_name('ja-JP').items(): + if item['time'] == 'always': + self.activity[index]['start_time']['jp'] = None + self.activity[index]['end_time']['jp'] = None + elif isinstance(item['time'], dict) and item['time'][0] == 'timer': + self.activity[index]['start_time']['jp'] = island_time_to_sql_time(item['time'][1]) + self.activity[index]['end_time']['jp'] = island_time_to_sql_time(item['time'][2]) + # for index, item in self.extract_item_name('zh-TW').items(): + # if item['time'] == 'always': + # self.activity[index]['start_time']['tw'] = None + # self.activity[index]['end_time']['tw'] = None + # elif isinstance(item['time'], dict) and item['time'][0] == 'timer': + # self.activity[index]['start_time']['tw'] = island_time_to_sql_time(item['time'][1]) + # self.activity[index]['end_time']['tw'] = island_time_to_sql_time(item['time'][2]) + + def extract_item_name(self, server): + LOADER.server = server + data = LOADER.load('sharecfg/activity_template.lua') + out = {} + for index, item in data.items(): + if not isinstance(index, int) or not index in self.activity: + continue + out[item['id']] = item + return out + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_ACTIVITY = {') + for index, activity in self.activity.items(): + lines.append(f' {index}: {activity},') + lines.append('}') + return lines + + def write(self, file): + print(f'writing {file}') + with open(file, 'w', encoding='utf-8') as f: + for text in self.encode(): + f.write(text + '\n') + + +class IslandWildGatherExtractor: + def __init__(self): + item_id_to_count = { + 2606: 1, + 4001: 4, + 4002: 8, + 4003: 12, + 4004: 3, + 4015: 4, + 4016: 8, + 4017: 12, + 4018: 4, + 4029: 8, + 4030: 8, + 4031: 4, + 4032: 8, + } + self.gather = {} + data = LOADER.load('sharecfg/island_wild_gather.lua') + for index, item in data.items(): + if not isinstance(index, int): + continue + item_id = int(item['icon'].split('_')[-1]) + self.gather[index] = { + 'activity_id': item['activity_id'], + 'product': {item_id: item_id_to_count[item_id]}, + } + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_WILD_GATHER = {') + for index, gather in self.gather.items(): + lines.append(f' {index}: {gather},') + lines.append('}') + return lines + + + +class IslandProductionMiningExtractor: + def __init__(self): + self.mining = {} + for index in range(40101, 40110): + self.mining[index] = {2700: 8} + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_PRODUCTION_MINING = {') + for index, mining in self.mining.items(): + lines.append(f' {index}: {mining},') + lines.append('}') + return lines + + +class IslandProductionLoggingExtractor: + def __init__(self): + self.logging = {} + for index in range(40201, 40210): + self.logging[index] = {2800: 8} + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_PRODUCTION_LOGGING = {') + for index, logging in self.logging.items(): + lines.append(f' {index}: {logging},') + lines.append('}') + return lines + + +class IslandSeasonExtractor: + def __init__(self, activity_dict=None): + self.season = {} + data = LOADER.load('sharecfg/island_season.lua') + for index, item in data.items(): + if not isinstance(index, int): + continue + self.season[item['id']] = { + 'start_time': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'end_time': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'task_list': [task for _, task in item['task_list'].items()], + } + for index, item in self.extract_item_name('zh-CN').items(): + self.season[index]['start_time']['cn'] = island_time_to_sql_time(item['time'][0]) + self.season[index]['end_time']['cn'] = island_time_to_sql_time(item['time'][1]) + for index, item in self.extract_item_name('en-US').items(): + self.season[index]['start_time']['en'] = island_time_to_sql_time(item['time'][0]) + self.season[index]['end_time']['en'] = island_time_to_sql_time(item['time'][1]) + for index, item in self.extract_item_name('ja-JP').items(): + self.season[index]['start_time']['jp'] = island_time_to_sql_time(item['time'][0]) + self.season[index]['end_time']['jp'] = island_time_to_sql_time(item['time'][1]) + # for index, item in self.extract_item_name('zh-TW').items(): + # self.season[index]['start_time']['tw'] = island_time_to_sql_time(item['time'][0]) + # self.season[index]['end_time']['tw'] = island_time_to_sql_time(item['time'][1]) + if activity_dict is None: + print('activity_dict is None, skipping season-activity matching') + return + for index, season in self.season.items(): + for activity_id, activity in activity_dict.items(): + if season['start_time']['cn'] is not None and season['end_time']['cn'] is not None and activity['start_time']['cn'] is not None and activity['end_time']['cn'] is not None: + if season['start_time']['cn'] == activity['start_time']['cn'] and season['end_time']['cn'] == activity['end_time']['cn']: + season.setdefault('activity', []).append(activity_id) + + def extract_item_name(self, server): + LOADER.server = server + data = LOADER.load('sharecfg/island_season.lua') + out = {} + for index, item in data.items(): + if not isinstance(index, int): + continue + out[item['id']] = item + return out + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_SEASON = {') + for index, season in self.season.items(): + lines.append(f' {index}: {season},') + lines.append('}') + return lines + + +class IslandTaskExtractor: + def __init__(self): + self.task = {} + target_id_to_task_id = {} + data = LOADER.load('sharecfg/island_task.lua') + for index, item in data.items(): + if not isinstance(index, int): + continue + self.task[item['id']] = { + 'name': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'target_id': item['target_id'][0], + 'target': {}, + 'start_time': { + 'cn': None, + 'en': None, + 'jp': None, + # 'tw': None, + }, + 'end_time': { + 'cn': None, + 'en': None, + 'jp': None, + # 'tw': None, + }, + } + target_id_to_task_id[item['target_id'][0]] = item['id'] + for index, item in self.extract_item('zh-CN').items(): + self.task[index]['name']['cn'] = item['name'] + time_dict = item['unlock_time'] + if time_dict == 'stop': + self.task[index]['start_time']['cn'] = None + self.task[index]['end_time']['cn'] = None + elif time_dict == 'always': + self.task[index]['start_time']['cn'] = 'always' + self.task[index]['end_time']['cn'] = 'always' + else: + self.task[index]['start_time']['cn'] = island_time_to_sql_time(time_dict[0]) + self.task[index]['end_time']['cn'] = island_time_to_sql_time(time_dict[1]) + for index, item in self.extract_item('en-US').items(): + self.task[index]['name']['en'] = item['name'] + time_dict = item['unlock_time'] + if time_dict == 'stop': + self.task[index]['start_time']['en'] = None + self.task[index]['end_time']['en'] = None + elif time_dict == 'always': + self.task[index]['start_time']['en'] = 'always' + self.task[index]['end_time']['en'] = 'always' + else: + self.task[index]['start_time']['en'] = island_time_to_sql_time(time_dict[0]) + self.task[index]['end_time']['en'] = island_time_to_sql_time(time_dict[1]) + for index, item in self.extract_item('ja-JP').items(): + self.task[index]['name']['jp'] = item['name'] + time_dict = item['unlock_time'] + if time_dict == 'stop': + self.task[index]['start_time']['jp'] = None + self.task[index]['end_time']['jp'] = None + elif time_dict == 'always': + self.task[index]['start_time']['jp'] = 'always' + self.task[index]['end_time']['jp'] = 'always' + else: + self.task[index]['start_time']['jp'] = island_time_to_sql_time(time_dict[0]) + self.task[index]['end_time']['jp'] = island_time_to_sql_time(time_dict[1]) + # for index, item in self.extract_item('zh-TW').items(): + # self.task[index]['name']['tw'] = item['name'] + # time_dict = item['unlock_time'] + # if time_dict == 'stop': + # self.task[index]['start_time']['tw'] = None + # self.task[index]['end_time']['tw'] = None + # elif time_dict == 'always': + # self.task[index]['start_time']['tw'] = 'always' + # self.task[index]['end_time']['tw'] = 'always' + # else: + # self.task[index]['start_time']['tw'] = island_time_to_sql_time(time_dict[0]) + # self.task[index]['end_time']['tw'] = island_time_to_sql_time(time_dict[1]) + + data = LOADER.load('sharecfg/island_task_target.lua') + for index, item in data.items(): + if not isinstance(index, int) or not item['id'] in target_id_to_task_id: + continue + task_id = target_id_to_task_id[item['id']] + if isinstance(item['target_param'], dict): + self.task[task_id]['target'] = {item['target_param'][0]: item['target_num']} + + def extract_item(self, server): + LOADER.server = server + data = LOADER.load('sharecfg/island_task.lua') + out = {} + for index, item in data.items(): + if not isinstance(index, int) or not item['id'] in self.task.keys(): + continue + out[item['id']] = item + + return out + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_TASK = {') + for index, task in self.task.items(): + lines.append(f' {index}: {task},') + lines.append('}') + return lines + + +class IslandSeasonRequest: + def __init__(self, item): + self.activity_id = item['activity_id'] + self.next_order = item['next_order'] + self.season_pt_num = item['season_pt_num'] + self.request = {request[0]: request[1] for _, request in item['request'].items()} if isinstance(item['request'], dict) else {} + self.award = {item['award'][0]: item['award'][1]} if isinstance(item['award'], dict) else {} + + def encode(self): + data = { + 'activity_id': self.activity_id, + 'next_order': self.next_order, + 'season_pt_num': self.season_pt_num, + 'request': self.request, + 'award': self.award, + } + return data + + +class IslandSeasonRequestExtractor: + def __init__(self): + self.request = {} + data = LOADER.load('sharecfg/island_order.lua') + for index, item in data.items(): + if not isinstance(index, int): + continue + self.request[index] = IslandSeasonRequest(item).encode() + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_SEASON_ORDER = {') + for index, request in self.request.items(): + lines.append(f' {index}: {request},') + lines.append('}') + return lines + + +class IslandShop: + # item with top resource will be recorded + def __init__(self, item): + self.tag_type = item['tag_type'] + self.order = item['order'] + if self.tag_type == 3: + self.parent_id = item['second_shop'] + elif self.tag_type == 2: + self.parent_id = item['first_shop'] + else: + self.parent_id = None + self.currency = [coin[2] for _, coin in item['top_resource'].items()] if isinstance(item['top_resource'], dict) else [] + self.goods = [shop_recipe_id for _, shop_recipe_id in item['goods_id'].items()] if isinstance(item['goods_id'], dict) else [] + + def encode(self): + data = { + 'name': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'tag_type': self.tag_type, + 'order': self.order, + 'parent_id': self.parent_id, + 'currency': self.currency, + 'goods': self.goods, + } + return data + + +class IslandShopExtractor: + def __init__(self): + self.shop = {} + data = LOADER.load('sharecfg/island_shop_template.lua') + for index, item in data.items(): + self.shop[index] = IslandShop(item).encode() + for index, item in self.extract_item('zh-CN').items(): + self.shop[index]['name']['cn'] = item['tag_icon'][0] + for index, item in self.extract_item('en-US').items(): + self.shop[index]['name']['en'] = item['tag_icon'][0] + for index, item in self.extract_item('ja-JP').items(): + self.shop[index]['name']['jp'] = item['tag_icon'][0] + # for index, item in self.extract_item('zh-TW').items(): + # self.shop[index]['name']['tw'] = item['tag_icon'][0] + + # Fix bug of lua data to make sure the order of shops is correct + self.shop[10020]['order'] = 1 + self.shop[10021]['order'] = 1 + self.shop[10112]['order'] = 2 + self.shop[10113]['order'] = 3 + self.isolated_shop = {index: shop for index, shop in self.shop.items() if 10019 <= index < 10130} + + def extract_item(self, server): + LOADER.server = server + data = LOADER.load('sharecfg/island_shop_template.lua') + out = {} + for index, item in data.items(): + out[index] = item + + return out + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_SHOP = {') + for index, shop in sorted(self.isolated_shop.items()): + lines.append(f' {index}: {shop},') + lines.append('}') + return lines + + +class IslandShopItemExtractor: + def __init__(self): + self.item = {} + self.item_to_recipe_id = {} + data = LOADER.load('sharecfg/island_shop_goods.lua', keyword='pg.base.island_shop_goods') + for index, item in data.items(): + if not isinstance(index, int) or index < 100000 or index >= 412000: + continue + self.item[index] = { + 'name': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'resource_consume': {item['resource_consume'][1]: item['resource_consume'][2]}, + 'items': { + itm[1]: itm[2] for _, itm in item['items'].items() + }, + 'start_time': { + 'cn': None, + 'en': None, + 'jp': None, + # 'tw': None, + }, + 'end_time': { + 'cn': None, + 'en': None, + 'jp': None, + # 'tw': None, + } + } + for _, itm in item['items'].items(): + self.item_to_recipe_id[itm[1]] = index + + for index, item in self.extract_item('zh-CN').items(): + self.item[index]['name']['cn'] = item['goods_name'] + time_dict = item['time'] + if time_dict == 'always': + self.item[index]['start_time']['cn'] = 'always' + self.item[index]['end_time']['cn'] = 'always' + else: + self.item[index]['start_time']['cn'] = island_time_to_sql_time(time_dict[0]) + self.item[index]['end_time']['cn'] = island_time_to_sql_time(time_dict[1]) + for index, item in self.extract_item('en-US').items(): + self.item[index]['name']['en'] = item['goods_name'] + time_dict = item['time'] + if time_dict == 'always': + self.item[index]['start_time']['en'] = 'always' + self.item[index]['end_time']['en'] = 'always' + else: + self.item[index]['start_time']['en'] = island_time_to_sql_time(time_dict[0]) + self.item[index]['end_time']['en'] = island_time_to_sql_time(time_dict[1]) + for index, item in self.extract_item('ja-JP').items(): + self.item[index]['name']['jp'] = item['goods_name'] + time_dict = item['time'] + if time_dict == 'always': + self.item[index]['start_time']['jp'] = 'always' + self.item[index]['end_time']['jp'] = 'always' + else: + self.item[index]['start_time']['jp'] = island_time_to_sql_time(time_dict[0]) + self.item[index]['end_time']['jp'] = island_time_to_sql_time(time_dict[1]) + # for index, item in self.extract_item('zh-TW').items(): + # self.item[index]['name']['tw'] = item['goods_name'] + # time_dict = item['time'] + # if time_dict == 'always': + # self.item[index]['start_time']['tw'] = 'always' + # self.item[index]['end_time']['tw'] = 'always' + # else: + # self.item[index]['start_time']['tw'] = island_time_to_sql_time(time_dict[0]) + # self.item[index]['end_time']['tw'] = island_time_to + + def extract_item(self, server): + LOADER.server = server + data = LOADER.load('sharecfg/island_shop_goods.lua', keyword='pg.base.island_shop_goods') + out = {} + for index, item in data.items(): + if not isinstance(index, int) or index < 100000 or index >= 412000: + continue + out[index] = item + + return out + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_SHOP_RECIPE = {') + for index, item in self.item.items(): + lines.append(f' {index}: {item},') + lines.append('}') + lines.append('') + lines.append('DIC_ISLAND_SHOP_ITEM_TO_RECIPE = {') + for item_id, recipe_id in self.item_to_recipe_id.items(): + lines.append(f' {item_id}: {recipe_id},') + lines.append('}') + return lines + + + +class IslandExchangeRecipeExtractor: + def __init__(self): + self.item = {} + data = LOADER.load('sharecfg/island_exchange_template.lua') + for index, item in data.items(): + if not isinstance(index, int): + continue + self.item[index] = { + 'resource_consume': {item['origin_item']: 1}, + 'items': { + item['target_item']: item['target_num'] + }, + } + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_EXCHANGE_RECIPE = {') + for index, item in self.item.items(): + lines.append(f' {index}: {item},') + lines.append('}') + return lines + + +class IslandProductionCommission: + def __init__(self): + self.commission = {} + data = LOADER.load('sharecfg/island_production_commission.lua') + for index, item in data.items(): + if not isinstance(index, int): + continue + self.commission[index] = item['slot'] + + + +class IslandProductionPlaceExtractor(IslandProductionCommission): + def __init__(self): + super().__init__() + self.place = {} + data = LOADER.load('sharecfg/island_production_place.lua') + for index, item in data.items(): + if not isinstance(index, int): + continue + self.place[index] = { + 'name': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'slot': [self.commission[slot_id] for slot_id in item['commission_slot'].values()] + } + for index, name in self.extract_place_name('zh-CN').items(): + self.place[index]['name']['cn'] = name.strip() + for index, name in self.extract_place_name('en-US').items(): + self.place[index]['name']['en'] = name + for index, name in self.extract_place_name('ja-JP').items(): + self.place[index]['name']['jp'] = name + # for index, name in self.extract_place_name('zh-TW').items(): + # self.item[index]['name']['tw'] = name + + def extract_place_name(self, server): + LOADER.server = server + data = LOADER.load('sharecfg/island_production_place.lua') + out = {} + for index, item in data.items(): + if not isinstance(index, int): + continue + out[item['id']] = item['name'] + + return out + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_PRODUCTION_PLACE = {') + for index, place in self.place.items(): + lines.append(f' {index}: {place},') + lines.append('}') + return lines + + def write(self, file): + print(f'writing {file}') + with open(file, 'w', encoding='utf-8') as f: + for text in self.encode(): + f.write(text + '\n') + + +class IslandRestaurantExtractor: + def __init__(self): + self.restaurant = {} + data = LOADER.load('sharecfg/island_manage_restaurant.lua') + for index, item in data.items(): + if not isinstance(index, int): + continue + self.restaurant.update({ + item['id']: { + recipe[0]: recipe[1] for _, recipe in item['item_id'].items() + } + }) + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE = {') + for index, restaurant in self.restaurant.items(): + lines.append(f' {index}: {restaurant},') + lines.append('}') + return lines + + def write(self, file): + print(f'writing {file}') + with open(file, 'w', encoding='utf-8') as f: + for text in self.encode(): + f.write(text + '\n') + + +class IslandTechnology: + def __init__(self, item): + self.id = item['id'] + self.tech_belong = item['tech_belong'] + self.axis_x = item['axis'][0] + self.axis_y = item['axis'][1] + self.island_level = item['island_level'] + + def encode(self): + data = { + 'name': { + 'cn': '', + 'en': '', + 'jp': '', + # 'tw': '', + }, + 'tech_belong': self.tech_belong, + 'axis': (self.axis_x, self.axis_y), + 'island_level': self.island_level, + } + return data + +class IslandTechnologyExtractor: + def __init__(self): + self.item = {} + + data = LOADER.load('sharecfg/island_technology_template.lua', keyword='pg.base.island_technology_template') + for index, item in data.items(): + if not isinstance(index, int) or item['tech_belong'] == 1: + continue + + self.item[item['id']] = IslandTechnology(item).encode() + + for index, name in self.extract_item_name('zh-CN').items(): + self.item[index]['name']['cn'] = name + for index, name in self.extract_item_name('en-US').items(): + self.item[index]['name']['en'] = name + for index, name in self.extract_item_name('ja-JP').items(): + self.item[index]['name']['jp'] = name + # for index, name in self.extract_item_name('zh-TW').items(): + # self.item[index]['name']['tw'] = name + + # sort by id + self.item = dict(sorted(self.item.items(), key=lambda x: x[0])) + + def extract_item_name(self, server): + LOADER.server = server + data = LOADER.load('sharecfg/island_technology_template.lua', keyword='pg.base.island_technology_template') + out = {} + for index, item in data.items(): + if not isinstance(index, int) or item['tech_belong'] == 1: + continue + out[item['id']] = item['tech_name'] + + return out + + def encode(self): + lines = [] + lines.append('DIC_ISLAND_TECHNOLOGY = {') + for index, item in self.item.items(): + lines.append(f' {index}: {item},') + lines.append('}') + return lines + + def write(self, file): + print(f'writing {file}') + with open(file, 'w', encoding='utf-8') as f: + for text in self.encode(): + f.write(text + '\n') + + +if __name__ == '__main__': + FILE = '../AzurLaneLuaScripts' + LOADER = LuaLoader(FILE, server='CN') + save = './module/island/data.py' + + lines = [] + lines.append('# This file was automatically generated by dev_tools/island_extractor.py') + lines.append("# Don't modify it manually.") + lines.append('') + + lines += IslandItemExtractor().encode() + lines.append('') + lines += IslandRecipeExtractor().encode() + lines.append('') + lines += IslandProductionExtractor().encode() + lines.append('') + activity_extractor = IslandActivityExtractor() + lines += activity_extractor.encode() + lines.append('') + lines += IslandWildGatherExtractor().encode() + lines.append('') + lines += IslandProductionMiningExtractor().encode() + lines.append('') + lines += IslandProductionLoggingExtractor().encode() + lines.append('') + lines += IslandSeasonExtractor(activity_extractor.activity).encode() + lines.append('') + lines += IslandSeasonRequestExtractor().encode() + lines.append('') + lines += IslandShopExtractor().encode() + lines.append('') + lines += IslandShopItemExtractor().encode() + lines.append('') + lines += IslandExchangeRecipeExtractor().encode() + lines.append('') + lines += IslandTaskExtractor().encode() + lines.append('') + lines += IslandRestaurantExtractor().encode() + lines.append('') + lines += IslandTechnologyExtractor().encode() + lines.append('') + lines += IslandProductionPlaceExtractor().encode() + with open(save, 'w', encoding='utf-8') as f: + for text in lines: + f.write(text + '\n') diff --git a/module/config/argument/args.json b/module/config/argument/args.json index a269bafb61..0eb11cacee 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -9206,6 +9206,449 @@ } } }, + "IslandProduction": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": false, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "IslandProduction", + "display": "hide" + }, + "SuccessInterval": { + "type": "input", + "value": 0, + "display": "hide" + }, + "FailureInterval": { + "type": "input", + "value": 120, + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "00:00", + "display": "hide" + } + }, + "IslandProduction": { + "HardFloorItems": { + "type": "textarea", + "value": "{}" + }, + "ReserveItems": { + "type": "textarea", + "value": "{}" + }, + "RequestBufferItems": { + "type": "textarea", + "value": "{}" + }, + "DailyBufferItems": { + "type": "textarea", + "value": "{}" + }, + "IdleAccumulatingItems": { + "type": "textarea", + "value": "{}" + } + }, + "Storage": { + "Storage": { + "type": "storage", + "value": {}, + "valuetype": "ignore", + "display": "disabled" + } + } + }, + "IslandOrder": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": false, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "IslandOrder", + "display": "hide" + }, + "SuccessInterval": { + "type": "input", + "value": 0, + "display": "hide" + }, + "FailureInterval": { + "type": "input", + "value": 120, + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "00:00", + "display": "hide" + } + }, + "IslandOrder": { + "StuckSeasonOrderId": { + "type": "input", + "value": 0 + } + }, + "Storage": { + "Storage": { + "type": "storage", + "value": {}, + "valuetype": "ignore", + "display": "disabled" + } + } + }, + "IslandFreebie": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": false, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "IslandFreebie", + "display": "hide" + }, + "SuccessInterval": { + "type": "input", + "value": 0, + "display": "hide" + }, + "FailureInterval": { + "type": "input", + "value": 120, + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "00:00", + "display": "hide" + } + }, + "IslandFreebie": { + "Share": { + "type": "checkbox", + "value": true + } + }, + "Storage": { + "Storage": { + "type": "storage", + "value": {}, + "valuetype": "ignore", + "display": "disabled" + } + } + }, + "IslandCollect": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": false, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "IslandCollect", + "display": "hide" + }, + "SuccessInterval": { + "type": "input", + "value": 0, + "display": "hide" + }, + "FailureInterval": { + "type": "input", + "value": 120, + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "03:00, 18:00", + "display": "hide" + } + }, + "Storage": { + "Storage": { + "type": "storage", + "value": {}, + "valuetype": "ignore", + "display": "disabled" + } + } + }, + "IslandSeasonTask": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": false, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "IslandSeasonTask", + "display": "hide" + }, + "SuccessInterval": { + "type": "input", + "value": 0, + "display": "hide" + }, + "FailureInterval": { + "type": "input", + "value": 120, + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "00:00", + "display": "hide" + } + }, + "IslandSeasonTask": { + "TaskTarget": { + "type": "textarea", + "value": "{}" + } + }, + "Storage": { + "Storage": { + "type": "storage", + "value": {}, + "valuetype": "ignore", + "display": "disabled" + } + } + }, + "IslandBusiness": { + "Scheduler": { + "Enable": { + "type": "checkbox", + "value": false, + "option": [ + true, + false + ] + }, + "NextRun": { + "type": "datetime", + "value": "2020-01-01 00:00:00", + "validate": "datetime" + }, + "Command": { + "type": "input", + "value": "IslandBusiness", + "display": "hide" + }, + "SuccessInterval": { + "type": "input", + "value": 0, + "display": "hide" + }, + "FailureInterval": { + "type": "input", + "value": 120, + "display": "hide" + }, + "ServerUpdate": { + "type": "input", + "value": "00:00", + "display": "hide" + } + }, + "IslandRestaurant": { + "KoiGrade": { + "type": "select", + "value": "bronze", + "option": [ + "bronze", + "silver", + "gold", + "diamond" + ] + }, + "KoiWaitress": { + "type": "select", + "value": "any", + "option": [ + "none", + "any", + "Chao_Ho", + "any+any", + "Chao_Ho+any" + ] + }, + "KoiMenu": { + "type": "textarea", + "value": "{}" + }, + "BearGrade": { + "type": "select", + "value": "bronze", + "option": [ + "bronze", + "silver", + "gold", + "diamond" + ] + }, + "BearWaitress": { + "type": "select", + "value": "any", + "option": [ + "none", + "any", + "Cheshire", + "any+any", + "Cheshire+any" + ] + }, + "BearMenu": { + "type": "textarea", + "value": "{}" + }, + "EateryGrade": { + "type": "select", + "value": "bronze", + "option": [ + "bronze", + "silver", + "gold", + "diamond" + ] + }, + "EateryWaitress": { + "type": "select", + "value": "any", + "option": [ + "none", + "any", + "Helena", + "Prinz_Eugen", + "any+any", + "Helena+any", + "Prinz_Eugen+any", + "Helena+Prinz_Eugen" + ] + }, + "EateryMenu": { + "type": "textarea", + "value": "{}" + }, + "GrillGrade": { + "type": "select", + "value": "bronze", + "option": [ + "bronze", + "silver", + "gold", + "diamond" + ] + }, + "GrillWaitress": { + "type": "select", + "value": "any", + "option": [ + "none", + "any", + "August_von_Parseval", + "Prinz_Eugen", + "any+any", + "August_von_Parseval+any", + "Prinz_Eugen+any", + "August_von_Parseval+Prinz_Eugen" + ] + }, + "GrillMenu": { + "type": "textarea", + "value": "{}" + }, + "CafeGrade": { + "type": "select", + "value": "bronze", + "option": [ + "bronze", + "silver", + "gold", + "diamond" + ] + }, + "CafeWaitress": { + "type": "select", + "value": "any", + "option": [ + "none", + "any", + "Cheshire", + "any+any", + "Cheshire+any" + ] + }, + "CafeMenu": { + "type": "textarea", + "value": "{}" + } + }, + "Storage": { + "Storage": { + "type": "storage", + "value": {}, + "valuetype": "ignore", + "display": "disabled" + } + } + }, "Daemon": { "Daemon": { "EnterMap": { @@ -9264,6 +9707,57 @@ } } }, + "IslandProductionPlanner": { + "IslandProductionPlanner": { + "RescanIslandTechnology": { + "type": "checkbox", + "value": false + }, + "DailyProfitLowerLimit": { + "type": "input", + "value": 50000 + }, + "DailyBufferSafetyMargin": { + "type": "input", + "value": 0 + }, + "FieldsEfficiency": { + "type": "select", + "value": 0, + "option": [ + 0, + 0.04, + 0.12 + ] + }, + "OrchardEfficiency": { + "type": "select", + "value": 0, + "option": [ + 0, + 0.04, + 0.12 + ] + }, + "NurseryEfficiency": { + "type": "select", + "value": 0, + "option": [ + 0, + 0.04, + 0.12 + ] + } + }, + "Storage": { + "Storage": { + "type": "storage", + "value": {}, + "valuetype": "ignore", + "display": "disabled" + } + } + }, "Benchmark": { "Benchmark": { "DeviceType": { diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 72e3302d52..98eb7c8a0a 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -734,6 +734,90 @@ OpsiHazard1Leveling: value: 0 option: [ 0, 44, 22 ] +# =================== Island ==================== + +IslandProduction: + HardFloorItems: + type: textarea + value: |- + {} + ReserveItems: + type: textarea + value: |- + {} + RequestBufferItems: + type: textarea + value: |- + {} + DailyBufferItems: + type: textarea + value: |- + {} + IdleAccumulatingItems: + type: textarea + value: |- + {} +IslandOrder: + StuckSeasonOrderId: 0 +IslandFreebie: + Share: true +IslandRestaurant: + KoiGrade: + value: bronze + option: [ bronze, silver, gold, diamond ] + KoiWaitress: + value: any + option: [ none, any, Chao_Ho, any+any, Chao_Ho+any ] + KoiMenu: + type: textarea + value: |- + {} + BearGrade: + value: bronze + option: [ bronze, silver, gold, diamond ] + BearWaitress: + value: any + option: [ none, any, Cheshire, any+any, Cheshire+any ] + BearMenu: + type: textarea + value: |- + {} + EateryGrade: + value: bronze + option: [ bronze, silver, gold, diamond ] + EateryWaitress: + value: any + option: [ none, any, Helena, Prinz_Eugen, any+any, Helena+any, Prinz_Eugen+any, Helena+Prinz_Eugen ] + EateryMenu: + type: textarea + value: |- + {} + GrillGrade: + value: bronze + option: [ bronze, silver, gold, diamond ] + GrillWaitress: + value: any + option: [ none, any, August_von_Parseval, Prinz_Eugen, any+any, August_von_Parseval+any, Prinz_Eugen+any, August_von_Parseval+Prinz_Eugen ] + GrillMenu: + type: textarea + value: |- + {} + CafeGrade: + value: bronze + option: [ bronze, silver, gold, diamond ] + CafeWaitress: + value: any + option: [ none, any, Cheshire, any+any, Cheshire+any ] + CafeMenu: + type: textarea + value: |- + {} +IslandSeasonTask: + TaskTarget: + type: textarea + value: |- + {} + # ==================== Tools ==================== Daemon: @@ -746,6 +830,19 @@ EventStory: type: checkbox value: false option: [ true, false ] +IslandProductionPlanner: + RescanIslandTechnology: false + DailyProfitLowerLimit: 50000 + DailyBufferSafetyMargin: 0 + FieldsEfficiency: + value: 0 + option: [ 0, 0.04, 0.12 ] + OrchardEfficiency: + value: 0 + option: [ 0, 0.04, 0.12 ] + NurseryEfficiency: + value: 0 + option: [ 0, 0.04, 0.12 ] Benchmark: DeviceType: value: emulator diff --git a/module/config/argument/menu.json b/module/config/argument/menu.json index abdfe9c9b7..84b66d5e7e 100644 --- a/module/config/argument/menu.json +++ b/module/config/argument/menu.json @@ -96,6 +96,18 @@ "OpsiCrossMonth" ] }, + "Island": { + "menu": "collapse", + "page": "setting", + "tasks": [ + "IslandProduction", + "IslandOrder", + "IslandFreebie", + "IslandCollect", + "IslandSeasonTask", + "IslandBusiness" + ] + }, "Tool": { "menu": "collapse", "page": "tool", @@ -103,6 +115,7 @@ "Daemon", "OpsiDaemon", "EventStory", + "IslandProductionPlanner", "Benchmark", "AzurLaneUncensored", "GameManager" diff --git a/module/config/argument/override.yaml b/module/config/argument/override.yaml index d1a33f3ab8..57a6718288 100644 --- a/module/config/argument/override.yaml +++ b/module/config/argument/override.yaml @@ -432,6 +432,12 @@ OpsiHazard1Leveling: OpsiFleet: Submarine: false +# =================== Island =================== + +IslandCollect: + Scheduler: + ServerUpdate: 03:00, 18:00 + # ==================== Tool ==================== EventStory: diff --git a/module/config/argument/task.yaml b/module/config/argument/task.yaml index c6f20bb00d..a7d9da5533 100644 --- a/module/config/argument/task.yaml +++ b/module/config/argument/task.yaml @@ -325,6 +325,29 @@ Opsi: OpsiCrossMonth: - Scheduler +# =================== Island =================== +Island: + menu: 'collapse' + page: 'setting' + tasks: + IslandProduction: + - Scheduler + - IslandProduction + IslandOrder: + - Scheduler + - IslandOrder + IslandFreebie: + - Scheduler + - IslandFreebie + IslandCollect: + - Scheduler + IslandSeasonTask: + - Scheduler + - IslandSeasonTask + IslandBusiness: + - Scheduler + - IslandRestaurant + # ==================== Tool ==================== Tool: @@ -337,6 +360,8 @@ Tool: - OpsiDaemon EventStory: - EventStory + IslandProductionPlanner: + - IslandProductionPlanner Benchmark: - Benchmark AzurLaneUncensored: diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 4f7289b7f8..5324c13b4a 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -436,6 +436,39 @@ class GeneratedConfig: # Group `OpsiHazard1Leveling` OpsiHazard1Leveling_TargetZone = 0 # 0, 44, 22 + # Group `IslandProduction` + IslandProduction_HardFloorItems = '{}' + IslandProduction_ReserveItems = '{}' + IslandProduction_RequestBufferItems = '{}' + IslandProduction_DailyBufferItems = '{}' + IslandProduction_IdleAccumulatingItems = '{}' + + # Group `IslandOrder` + IslandOrder_StuckSeasonOrderId = 0 + + # Group `IslandFreebie` + IslandFreebie_Share = True + + # Group `IslandRestaurant` + IslandRestaurant_KoiGrade = 'bronze' # bronze, silver, gold, diamond + IslandRestaurant_KoiWaitress = 'any' # none, any, Chao_Ho, any+any, Chao_Ho+any + IslandRestaurant_KoiMenu = '{}' + IslandRestaurant_BearGrade = 'bronze' # bronze, silver, gold, diamond + IslandRestaurant_BearWaitress = 'any' # none, any, Cheshire, any+any, Cheshire+any + IslandRestaurant_BearMenu = '{}' + IslandRestaurant_EateryGrade = 'bronze' # bronze, silver, gold, diamond + IslandRestaurant_EateryWaitress = 'any' # none, any, Helena, Prinz_Eugen, any+any, Helena+any, Prinz_Eugen+any, Helena+Prinz_Eugen + IslandRestaurant_EateryMenu = '{}' + IslandRestaurant_GrillGrade = 'bronze' # bronze, silver, gold, diamond + IslandRestaurant_GrillWaitress = 'any' # none, any, August_von_Parseval, Prinz_Eugen, any+any, August_von_Parseval+any, Prinz_Eugen+any, August_von_Parseval+Prinz_Eugen + IslandRestaurant_GrillMenu = '{}' + IslandRestaurant_CafeGrade = 'bronze' # bronze, silver, gold, diamond + IslandRestaurant_CafeWaitress = 'any' # none, any, Cheshire, any+any, Cheshire+any + IslandRestaurant_CafeMenu = '{}' + + # Group `IslandSeasonTask` + IslandSeasonTask_TaskTarget = '{}' + # Group `Daemon` Daemon_EnterMap = True @@ -446,6 +479,14 @@ class GeneratedConfig: # Group `EventStory` EventStory_SkipBattle = False # True, False + # Group `IslandProductionPlanner` + IslandProductionPlanner_RescanIslandTechnology = False + IslandProductionPlanner_DailyProfitLowerLimit = 50000 + IslandProductionPlanner_DailyBufferSafetyMargin = 0 + IslandProductionPlanner_FieldsEfficiency = 0 # 0, 0.04, 0.12 + IslandProductionPlanner_OrchardEfficiency = 0 # 0, 0.04, 0.12 + IslandProductionPlanner_NurseryEfficiency = 0 # 0, 0.04, 0.12 + # Group `Benchmark` Benchmark_DeviceType = 'emulator' # emulator, plone_cloud_with_adb, phone_cloud_without_adb, android_phone, android_phone_vmos Benchmark_TestScene = 'screenshot_click' # screenshot_click, screenshot, click diff --git a/module/config/config_manual.py b/module/config/config_manual.py index d8045f7fc1..66ab37cf3e 100644 --- a/module/config/config_manual.py +++ b/module/config/config_manual.py @@ -22,6 +22,10 @@ def SERVER(self): > OpsiAshBeacon > OpsiDaily > OpsiShop > OpsiVoucher > OpsiAbyssal > OpsiStronghold > OpsiObscure > OpsiArchive + > IslandFreebie > IslandCollect + > IslandBusiness > IslandSeasonTask + > IslandOrder + > IslandProduction > Daily > Hard > OpsiAshBeacon > OpsiAshAssist > OpsiMonthBoss > Sos > EventSp > EventA > EventB > EventC > EventD > RaidDaily > CoalitionSp > WarArchives > MaritimeEscort diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index afb3ebd5f8..2f32e75c07 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -28,6 +28,10 @@ "name": "OpSi", "help": "" }, + "Island": { + "name": "Island Planner", + "help": "" + }, "Tool": { "name": "Tools", "help": "" @@ -254,6 +258,30 @@ "name": "Cross Month Daily", "help": " ALAS will enter OpSi 10min before OpSi reset, wait until OpSi reset but not exit OpSi. Then do the daily, obscure, abyssal and meowfficer farming to get extra gold plates. When running dailies, settings in task \"OpSiDaily\" are used, the rest function are the same.\n IMPORTANT: Please do not touch the game while ALAS is waiting for OpSi reset." }, + "IslandProduction": { + "name": "Island Production", + "help": "" + }, + "IslandOrder": { + "name": "Island Request", + "help": "" + }, + "IslandFreebie": { + "name": "Daily Supplies", + "help": "" + }, + "IslandCollect": { + "name": "Daily Gathering", + "help": "Use management panel to gather season specialities and coal/wood.\nWill use multiple characters with least levels until stamina requirement is fulfilled." + }, + "IslandSeasonTask": { + "name": "Island Season Task", + "help": "" + }, + "IslandBusiness": { + "name": "Restaurant Management", + "help": "" + }, "Daemon": { "name": "Normal Semi-auto", "help": "" @@ -266,6 +294,10 @@ "name": "Event Story", "help": "" }, + "IslandProductionPlanner": { + "name": "Island Production Planner", + "help": "Planner for optimizing island production plan to earn pt subject to given daily profit.\nWill export bootstrap inventory stock quantities and accumulating item quantities to config after running, which can be used for production planning in the future without scanning technology again.\nNote: The production plan is optimized based on the technology status at the time of scanning, if there are changes in technology status, it is recommended to run the planner again to get an updated production plan." + }, "Benchmark": { "name": "Performance Test", "help": "" @@ -2555,6 +2587,179 @@ "22": "22 | NA Ocean SW Sector B" } }, + "IslandProduction": { + "_info": { + "name": "Island Production", + "help": "" + }, + "HardFloorItems": { + "name": "Hard Floor Items", + "help": "Stock protected from orders, transport, and production recipes\nBusiness modules may consume this stock\nUse this for dishes or materials reserved for business module operation\nPlanner-exported business items are kept at least at their business capacity here\nExample format:\n(item name on the current server): (quantity)" + }, + "ReserveItems": { + "name": "Reserve Items", + "help": "Stock reserved from external consumers such as island requests and transport\nProduction recipes and business modules may consume reserved stock, and recipes will replenish it\nExample format:\n(item name on the current server): (quantity)" + }, + "RequestBufferItems": { + "name": "Request Buffer Items", + "help": "User-defined consumable buffer for island request items\nRecipe production targets hard floor plus reserve plus the larger of request buffer and daily buffer\nUse this for request items that the planner does not otherwise keep in stock" + }, + "DailyBufferItems": { + "name": "Daily Buffer Items", + "help": "Planner-generated working buffer for expected daily consumption\nRecipe production targets hard floor plus reserve plus the larger of request buffer and daily buffer\nThis can be automatically generated by Island Production Planner in Tools." + }, + "IdleAccumulatingItems": { + "name": "Idle Accumulating Items", + "help": "Items that may accumulate when production is idle and ingredients are sufficient\nThe value is the expected daily accumulation rate used for idle recipe priority\nThis can be automatically generated by Island Production Planner in Tools." + } + }, + "IslandOrder": { + "_info": { + "name": "Island Request", + "help": "For normal requests, will submit only if current stock minus hard floor and reserved quantity is sufficient.\nFor urgent requests, will submit as long as current stock is sufficient" + }, + "StuckSeasonOrderId": { + "name": "Unfulfilled Season Request ID", + "help": "0 means no stuck request\nUsually do not need to modify it" + } + }, + "IslandFreebie": { + "_info": { + "name": "Daily Supplies", + "help": "" + }, + "Share": { + "name": "Share Supplies with Friends", + "help": "" + } + }, + "IslandRestaurant": { + "_info": { + "name": "Restaurant Management", + "help": "" + }, + "KoiGrade": { + "name": "Golden Koi Restaurant Grade", + "help": "", + "bronze": "Bronze", + "silver": "Silver", + "gold": "Gold", + "diamond": "Diamond" + }, + "KoiWaitress": { + "name": "Golden Koi Restaurant Waitress", + "help": "None means not running this restaurant and Any means not restricting the choice of waitress, in which case the character with the highest manage level will be chosen\nIf a specific character is chosen but not available, the restaurant will fallback to Any instead of skipping the restaurant", + "none": "None", + "any": "Any", + "Chao_Ho": "Chao Ho", + "any+any": "Any + Any", + "Chao_Ho+any": "Chao Ho + Any" + }, + "KoiMenu": { + "name": "Golden Koi Restaurant Menu", + "help": "Will only sell from these dishes.\nAutomatically generated by Island Production Planner in Tools." + }, + "BearGrade": { + "name": "Polar Bear Teahouse Grade", + "help": "", + "bronze": "Bronze", + "silver": "Silver", + "gold": "Gold", + "diamond": "Diamond" + }, + "BearWaitress": { + "name": "Polar Bear Teahouse Waitress", + "help": "None means not running this restaurant and Any means not restricting the choice of waitress, in which case the character with the highest manage level will be chosen\nIf a specific character is chosen but not available, the restaurant will fallback to Any instead of skipping the restaurant", + "none": "None", + "any": "Any", + "Cheshire": "HMS Cheshire", + "any+any": "Any + Any", + "Cheshire+any": "HMS Cheshire + Any" + }, + "BearMenu": { + "name": "Polar Bear Teahouse Menu", + "help": "Will only sell from these dishes.\nAutomatically generated by Island Production Planner in Tools." + }, + "EateryGrade": { + "name": "Manjuu Eatery Grade", + "help": "", + "bronze": "Bronze", + "silver": "Silver", + "gold": "Gold", + "diamond": "Diamond" + }, + "EateryWaitress": { + "name": "Manjuu Eatery Waitress", + "help": "None means not running this restaurant and Any means not restricting the choice of waitress, in which case the character with the highest manage level will be chosen\nIf a specific character is chosen but not available, the restaurant will fallback to Any instead of skipping the restaurant", + "none": "None", + "any": "Any", + "Helena": "USS Helena", + "Prinz_Eugen": "KMS Prinz Eugen", + "any+any": "Any + Any", + "Helena+any": "USS Helena + Any", + "Prinz_Eugen+any": "KMS Prinz Eugen + Any", + "Helena+Prinz_Eugen": "USS Helena + KMS Prinz Eugen" + }, + "EateryMenu": { + "name": "Manjuu Eatery Menu", + "help": "Will only sell from these dishes.\nAutomatically generated by Island Production Planner in Tools." + }, + "GrillGrade": { + "name": "Fin-'n'-Feather Grill Grade", + "help": "", + "bronze": "Bronze", + "silver": "Silver", + "gold": "Gold", + "diamond": "Diamond" + }, + "GrillWaitress": { + "name": "Fin-'n'-Feather Grill Waitress", + "help": "", + "none": "None", + "any": "Any", + "August_von_Parseval": "KMS August von Parseval", + "Prinz_Eugen": "KMS Prinz Eugen", + "any+any": "Any + Any", + "August_von_Parseval+any": "KMS August von Parseval + Any", + "Prinz_Eugen+any": "KMS Prinz Eugen + Any", + "August_von_Parseval+Prinz_Eugen": "KMS August von Parseval + KMS Prinz Eugen" + }, + "GrillMenu": { + "name": "Fin-'n'-Feather Grill Menu", + "help": "Will only sell from these dishes.\nAutomatically generated by Island Production Planner in Tools." + }, + "CafeGrade": { + "name": "Café Manjuu Grade", + "help": "", + "bronze": "Bronze", + "silver": "Silver", + "gold": "Gold", + "diamond": "Diamond" + }, + "CafeWaitress": { + "name": "Café Manjuu Waitress", + "help": "", + "none": "None", + "any": "Any", + "Cheshire": "HMS Cheshire", + "any+any": "Any + Any", + "Cheshire+any": "HMS Cheshire + Any" + }, + "CafeMenu": { + "name": "Café Manjuu Menu", + "help": "Will only sell from these dishes.\nAutomatically generated by Island Production Planner in Tools." + } + }, + "IslandSeasonTask": { + "_info": { + "name": "Island Season Task", + "help": "" + }, + "TaskTarget": { + "name": "Task Preserved Items", + "help": "" + } + }, "Daemon": { "_info": { "name": "Semi-auto Clicking", @@ -2591,6 +2796,45 @@ "False": "False" } }, + "IslandProductionPlanner": { + "_info": { + "name": "Island Production Planner", + "help": "Will use linear programming to optimize the production plan for the island based on the parameters you set and generate the config for Island Production module.\nPlease set Island Restaurant config and Island Production Preserved Items first.\nClear Task status to rerun Island Technology Scanner." + }, + "RescanIslandTechnology": { + "name": "Rescan Island Technology this time", + "help": "Alas will rescan the island technology and update the config for Island Production module with the new technology levels\nThis is useful when you have just upgraded your island technology and want to update the production plan accordingly." + }, + "DailyProfitLowerLimit": { + "name": "Daily Profit Lower Limit", + "help": "Negative value means accepting a daily loss, 0 means breaking even, and positive value means profit." + }, + "DailyBufferSafetyMargin": { + "name": "Daily Buffer Safety Margin", + "help": "Multiplier margin applied to planner-generated daily buffer. 0.2 means exporting 120% of the calculated daily demand." + }, + "FieldsEfficiency": { + "name": "Faircrop Fields Efficiency", + "help": "See character collecting reward for your value", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + }, + "OrchardEfficiency": { + "name": "Sweetscent Orchard Efficiency", + "help": "See character collecting reward for your value", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + }, + "NurseryEfficiency": { + "name": "Newsprout Nursery Efficiency", + "help": "See character collecting reward for your value", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + } + }, "Benchmark": { "_info": { "name": "Performance Test", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 624e502fd3..981cb35f9f 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -28,6 +28,10 @@ "name": "セイレーン作戦", "help": "" }, + "Island": { + "name": "離島開発", + "help": "" + }, "Tool": { "name": "ツール", "help": "" @@ -254,6 +258,30 @@ "name": "Cross Month Daily", "help": " ALAS will enter OpSi 10min before OpSi reset, wait until OpSi reset but not exit OpSi. Then do the daily, obscure, abyssal and meowfficer farming to get extra gold plates. When running dailies, settings in task \"OpSiDaily\" are used, the rest function are the same.\n IMPORTANT: Please do not touch the game while ALAS is waiting for OpSi reset." }, + "IslandProduction": { + "name": "離島生産", + "help": "" + }, + "IslandOrder": { + "name": "離島依頼", + "help": "" + }, + "IslandFreebie": { + "name": "デイリー補給", + "help": "" + }, + "IslandCollect": { + "name": "デイリー採集", + "help": "管理パネルを使用して、季節の特産品や石炭/木材を収集します。\nスタミナ要件が満たされるまで、レベルの最も低い複数のキャラクターを使用します。" + }, + "IslandSeasonTask": { + "name": "開発計画", + "help": "" + }, + "IslandBusiness": { + "name": "店舗経営", + "help": "" + }, "Daemon": { "name": "半自動クリック", "help": "" @@ -266,6 +294,10 @@ "name": "Event Story", "help": "" }, + "IslandProductionPlanner": { + "name": "離島生産ソルバー", + "help": "線形計画法を使用して、指定した日次利益を満たしつつ ポイントを獲得します。\n実行後、ブートストラップの在庫数量と累積アイテム数量をコンフィグに出力します。これは、再度技術をスキャンしなくても将来の生産計画に使用できます。\n注意: 生産計画はスキャン時点の技術状況に基づいて最適化されるため、技術状況に変更がある場合は、更新された生産計画を得るために再度プランナーを実行することを推奨します。" + }, "Benchmark": { "name": "機能テスト", "help": "" @@ -2555,6 +2587,179 @@ "22": "22" } }, + "IslandProduction": { + "_info": { + "name": "離島生産", + "help": "" + }, + "HardFloorItems": { + "name": "ハード下限品目", + "help": "依頼、輸送、生産レシピから保護される在庫です。\n事業モジュールはこの在庫を消費できます。\n事業モジュール用に確保したい料理や素材に使用します。\nプランナーが出力する事業用アイテムはここで少なくとも対応する事業容量分を保持します。\n形式例:\n(現在のサーバーでの品目名): (数量)" + }, + "ReserveItems": { + "name": "予約品目", + "help": "離島依頼や輸送などの外部消費から予約する在庫です。\n生産レシピと事業モジュールは予約在庫を消費でき、レシピはそれを補充します。\n形式例:\n(現在のサーバーでの品目名): (数量)" + }, + "RequestBufferItems": { + "name": "依頼バッファ品目", + "help": "離島依頼用にユーザーが指定する消費可能なバッファです。\nレシピ生産はハード下限と予約数量に、依頼バッファと日次バッファの大きい方を加えた数量を目標にします。\nプランナーが在庫を維持しない依頼品目に使用します。" + }, + "DailyBufferItems": { + "name": "日次バッファ品目", + "help": "想定日次消費からプランナーが生成する作業用バッファです。\nレシピ生産はハード下限と予約数量に、依頼バッファと日次バッファの大きい方を加えた数量を目標にします。\nこれはツール内の島生産プランナーで自動生成できます。" + }, + "IdleAccumulatingItems": { + "name": "アイドル累積品目", + "help": "生産が空いていて素材が十分なときに累積してよい品目です。\n値はアイドル時のレシピ優先度に使う想定日次累積速度です。\nこれはツール内の島生産プランナーで自動生成できます。" + } + }, + "IslandOrder": { + "_info": { + "name": "離島依頼", + "help": "通常の依頼の場合、現在の在庫からハード下限と予約数量を引いた数量が十分である場合にのみ提出されます。\n緊急の依頼の場合、現在の在庫が十分である限り提出されます。" + }, + "StuckSeasonOrderId": { + "name": "未完了シーズン依頼ID", + "help": "0は未完了の依頼がないことを意味します\n通常は変更する必要はありません" + } + }, + "IslandFreebie": { + "_info": { + "name": "デイリー補給", + "help": "" + }, + "Share": { + "name": "補給を友達とシェアする", + "help": "" + } + }, + "IslandRestaurant": { + "_info": { + "name": "グルメ運営", + "help": "" + }, + "KoiGrade": { + "name": "有魚飯店のランク", + "help": "", + "bronze": "ブロンズ", + "silver": "シルバー", + "gold": "ゴールド", + "diamond": "ダイヤモンド" + }, + "KoiWaitress": { + "name": "有魚飯店のウェイトレス", + "help": "任意の場合、最も管理レベルが高い艦船が選択される", + "none": "なし", + "any": "任意", + "Chao_Ho": "肇和", + "any+any": "任意 + 任意", + "Chao_Ho+any": "肇和 + 任意" + }, + "KoiMenu": { + "name": "有魚飯店のメニュー", + "help": "メニュー以外のものは販売されません。\nツールの離島生産ソルバーで自動生成されます。" + }, + "BearGrade": { + "name": "白クマ茶房のランク", + "help": "", + "bronze": "ブロンズ", + "silver": "シルバー", + "gold": "ゴールド", + "diamond": "ダイヤモンド" + }, + "BearWaitress": { + "name": "白クマ茶房のウェイトレス", + "help": "任意の場合、最も管理レベルが高い艦船が選択される", + "none": "なし", + "any": "任意", + "Cheshire": "チェシャー", + "any+any": "任意 + 任意", + "Cheshire+any": "チェシャー + 任意" + }, + "BearMenu": { + "name": "白クマ茶房のメニュー", + "help": "メニュー以外のものは販売されません。\nツールの離島生産ソルバーで自動生成されます。" + }, + "EateryGrade": { + "name": "饅頭軽食のランク", + "help": "", + "bronze": "ブロンズ", + "silver": "シルバー", + "gold": "ゴールド", + "diamond": "ダイヤモンド" + }, + "EateryWaitress": { + "name": "饅頭軽食のウェイトレス", + "help": "任意の場合、最も管理レベルが高い艦船が選択される", + "none": "なし", + "any": "任意", + "Helena": "ヘレナ", + "Prinz_Eugen": "プリンツ・オイゲン", + "any+any": "任意 + 任意", + "Helena+any": "ヘレナ + 任意", + "Prinz_Eugen+any": "プリンツ・オイゲン + 任意", + "Helena+Prinz_Eugen": "ヘレナ + プリンツ・オイゲン" + }, + "EateryMenu": { + "name": "饅頭軽食のメニュー", + "help": "メニュー以外のものは販売されません。\nツールの離島生産ソルバーで自動生成されます。" + }, + "GrillGrade": { + "name": "烏魚焼肉のランク", + "help": "", + "bronze": "ブロンズ", + "silver": "シルバー", + "gold": "ゴールド", + "diamond": "ダイヤモンド" + }, + "GrillWaitress": { + "name": "烏魚焼肉のウェイトレス", + "help": "任意の場合、最も管理レベルが高い艦船が選択される", + "none": "なし", + "any": "任意", + "August_von_Parseval": "アウグスト·フォン·パーセヴァル", + "Prinz_Eugen": "プリンツ・オイゲン", + "any+any": "任意 + 任意", + "August_von_Parseval+any": "アウグスト·フォン·パーセヴァル + 任意", + "Prinz_Eugen+any": "プリンツ・オイゲン + 任意", + "August_von_Parseval+Prinz_Eugen": "アウグスト·フォン·パーセヴァル + プリンツ・オイゲン" + }, + "GrillMenu": { + "name": "烏魚焼肉のメニュー", + "help": "メニュー以外のものは販売されません。\nツールの離島生産ソルバーで自動生成されます。" + }, + "CafeGrade": { + "name": "饅頭カフェのランク", + "help": "", + "bronze": "ブロンズ", + "silver": "シルバー", + "gold": "ゴールド", + "diamond": "ダイヤモンド" + }, + "CafeWaitress": { + "name": "饅頭カフェのウェイトレス", + "help": "任意の場合、最も管理レベルが高い艦船が選択される", + "none": "なし", + "any": "任意", + "Cheshire": "チェシャー", + "any+any": "任意 + 任意", + "Cheshire+any": "チェシャー + 任意" + }, + "CafeMenu": { + "name": "饅頭カフェのメニュー", + "help": "メニュー以外のものは販売されません。\nツールの離島生産ソルバーで自動生成されます。" + } + }, + "IslandSeasonTask": { + "_info": { + "name": "開発計画", + "help": "" + }, + "TaskTarget": { + "name": "要望品目", + "help": "" + } + }, "Daemon": { "_info": { "name": "Daemon._info.name", @@ -2591,6 +2796,45 @@ "False": "False" } }, + "IslandProductionPlanner": { + "_info": { + "name": "離島生産ソルバー", + "help": "設定したパラメータに基づいて線形計画法で離島の生産計画を最適化し、離島生産モジュール用の設定を生成します。\nグルメ運営と離島生産の要望品目のコンフィグ設定を先に行ってください。\n任務状態を初期化すれば、離島技術を再度スキャンできます。" + }, + "RescanIslandTechnology": { + "name": "離島技術を再スキャン", + "help": "離島技術のスキャンをやり直し、離島生産プランナーの設定を更新します。" + }, + "DailyProfitLowerLimit": { + "name": "日次利益下限", + "help": "負の値は日次損失を許容、0は収支トントン、正の値は利益を意味します。" + }, + "DailyBufferSafetyMargin": { + "name": "日次バッファ安全マージン", + "help": "生産プランナーが生成する日次バッファに適用する倍率マージンです。0.2 は計算された日次需要の 120% を出力します。" + }, + "FieldsEfficiency": { + "name": "豊穣の畑の効率", + "help": "キャラの収集報酬を参照してください", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + }, + "OrchardEfficiency": { + "name": "薫る果樹園の効率", + "help": "キャラの収集報酬を参照してください", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + }, + "NurseryEfficiency": { + "name": "青々苗場の効率", + "help": "キャラの収集報酬を参照してください", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + } + }, "Benchmark": { "_info": { "name": "Benchmark._info.name", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 6566771ae3..36dcb10fcf 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -28,6 +28,10 @@ "name": "大世界", "help": "" }, + "Island": { + "name": "岛屿计划", + "help": "" + }, "Tool": { "name": "工具", "help": "" @@ -254,6 +258,30 @@ "name": "跨月每日", "help": " Alas将在大世界跨月重置之前10分钟进入大世界,等待大世界重置但不退出大世界,然后完成新一天的大世界每日、隐秘海域、深渊海域和短猫相接,以获得额外的金菜。运行大世界每日时,按\"大世界每日\"任务设置运行,其余同理。\n 重要:Alas等待跨月期间,请不要操作游戏。" }, + "IslandProduction": { + "name": "岛屿生产", + "help": "" + }, + "IslandOrder": { + "name": "岛屿订单", + "help": "" + }, + "IslandFreebie": { + "name": "每日补给", + "help": "" + }, + "IslandCollect": { + "name": "每日采集", + "help": "使用管理面板采集季节特产和煤炭/自然之木。\n将使用多个等级最低的角色直到满足体力要求。" + }, + "IslandSeasonTask": { + "name": "开发计划", + "help": "" + }, + "IslandBusiness": { + "name": "店铺经营", + "help": "" + }, "Daemon": { "name": "半自动点击", "help": "" @@ -266,6 +294,10 @@ "name": "活动剧情", "help": "" }, + "IslandProductionPlanner": { + "name": "岛屿生产规划器", + "help": "将根据用户设置的参数使用线性规划优化岛屿的生产计划,并生成岛屿生产模块的配置" + }, "Benchmark": { "name": "性能测试", "help": "" @@ -2555,6 +2587,179 @@ "22": "22 | NA海域西南B" } }, + "IslandProduction": { + "_info": { + "name": "岛屿生产", + "help": "" + }, + "HardFloorItems": { + "name": "硬性保底物品", + "help": "不会被订单、运输和生产配方消耗的保底库存\n经营模块可以消耗这部分库存\n用于为经营模块保留的菜品或材料\n生产规划器导出的经营物品至少会在这里保持为对应经营容量\n格式示例:\n(物品在当前服务器的名称): (数量)" + }, + "ReserveItems": { + "name": "保留物品", + "help": "不会被岛屿订单和运输等外部消耗拿走的保留库存\n生产配方和经营模块可以消耗保留库存,配方会将其补回\n格式示例:\n(物品在当前服务器的名称): (数量)" + }, + "RequestBufferItems": { + "name": "订单缓冲物品", + "help": "用户自定义的岛屿订单可消耗缓冲\n配方生产目标为硬性保底加保留数量,再加订单缓冲和每日缓冲中的较大值\n用于生产规划器不会主动保留库存的订单物品" + }, + "DailyBufferItems": { + "name": "每日缓冲物品", + "help": "生产规划器根据预期每日消耗生成的工作缓冲\n配方生产目标为硬性保底加保留数量,再加订单缓冲和每日缓冲中的较大值\n可以通过工具中的岛屿生产规划器自动生成" + }, + "IdleAccumulatingItems": { + "name": "闲置累积物品", + "help": "生产闲置且原料充足时可以继续累积的物品\n数值为预期每日累积速度,用于闲置配方优先级\n可以通过工具中的岛屿生产规划器自动生成" + } + }, + "IslandOrder": { + "_info": { + "name": "岛屿订单", + "help": "对于普通订单,将仅在当前库存减去硬性保底和保留数量后数量足够时提交。\n对于紧急订单,只要当前库存足够就会提交。" + }, + "StuckSeasonOrderId": { + "name": "卡住的赛季订单ID", + "help": "0表示没有卡住的订单\n一般不用手动修改" + } + }, + "IslandFreebie": { + "_info": { + "name": "每日补给", + "help": "" + }, + "Share": { + "name": "将补给分享给好友", + "help": "" + } + }, + "IslandRestaurant": { + "_info": { + "name": "餐厅管理", + "help": "" + }, + "KoiGrade": { + "name": "有鱼餐馆等级", + "help": "", + "bronze": "青铜", + "silver": "白银", + "gold": "黄金", + "diamond": "钻石" + }, + "KoiWaitress": { + "name": "有鱼餐馆服务员", + "help": "无表示不运营此餐厅,任意表示不限制服务员的选择,此时将选择管理等级最高的角色\n如果选择的特定角色不可用,餐厅将回退到任意而不是跳过此餐厅", + "none": "无", + "any": "任意", + "Chao_Ho": "肇和", + "any+any": "任意 + 任意", + "Chao_Ho+any": "肇和 + 任意" + }, + "KoiMenu": { + "name": "有鱼餐馆菜单", + "help": "只销售菜单里指定的菜品。\n通过工具中的岛屿生产规划器自动生成" + }, + "BearGrade": { + "name": "白熊饮品等级", + "help": "", + "bronze": "青铜", + "silver": "白银", + "gold": "黄金", + "diamond": "钻石" + }, + "BearWaitress": { + "name": "白熊饮品服务员", + "help": "无表示不运营此餐厅,任意表示不限制服务员的选择,此时将选择管理等级最高的角色\n如果选择的特定角色不可用,餐厅将回退到任意而不是跳过此餐厅", + "none": "无", + "any": "任意", + "Cheshire": "柴郡", + "any+any": "任意 + 任意", + "Cheshire+any": "柴郡 + 任意" + }, + "BearMenu": { + "name": "白熊饮品菜单", + "help": "只销售菜单里指定的菜品。\n通过工具中的岛屿生产规划器自动生成" + }, + "EateryGrade": { + "name": "啾啾简餐等级", + "help": "", + "bronze": "青铜", + "silver": "白银", + "gold": "黄金", + "diamond": "钻石" + }, + "EateryWaitress": { + "name": "啾啾简餐服务员", + "help": "无表示不运营此餐厅,任意表示不限制服务员的选择,此时将选择管理等级最高的角色\n如果选择的特定角色不可用,餐厅将回退到任意而不是跳过此餐厅", + "none": "无", + "any": "任意", + "Helena": "海伦娜", + "Prinz_Eugen": "欧根亲王", + "any+any": "任意 + 任意", + "Helena+any": "海伦娜 + 任意", + "Prinz_Eugen+any": "欧根亲王 + 任意", + "Helena+Prinz_Eugen": "海伦娜 + 欧根亲王" + }, + "EateryMenu": { + "name": "啾啾简餐菜单", + "help": "只销售菜单里指定的菜品。\n通过工具中的岛屿生产规划器自动生成" + }, + "GrillGrade": { + "name": "乌鱼烤肉等级", + "help": "", + "bronze": "青铜", + "silver": "白银", + "gold": "黄金", + "diamond": "钻石" + }, + "GrillWaitress": { + "name": "乌鱼烤肉服务员", + "help": "无表示不运营此餐厅,任意表示不限制服务员的选择,此时将选择管理等级最高的角色\n如果选择的特定角色不可用,餐厅将回退到任意而不是跳过此餐厅", + "none": "无", + "any": "任意", + "August_von_Parseval": "奥古斯特·冯·帕塞瓦尔", + "Prinz_Eugen": "欧根亲王", + "any+any": "任意 + 任意", + "August_von_Parseval+any": "奥古斯特·冯·帕塞瓦尔 + 任意", + "Prinz_Eugen+any": "欧根亲王 + 任意", + "August_von_Parseval+Prinz_Eugen": "奥古斯特·冯·帕塞瓦尔 + 欧根亲王" + }, + "GrillMenu": { + "name": "乌鱼烤肉菜单", + "help": "只销售菜单里指定的菜品。\n通过工具中的岛屿生产规划器自动生成" + }, + "CafeGrade": { + "name": "啾咖啡等级", + "help": "", + "bronze": "青铜", + "silver": "白银", + "gold": "黄金", + "diamond": "钻石" + }, + "CafeWaitress": { + "name": "啾咖啡服务员", + "help": "无表示不运营此餐厅,任意表示不限制服务员的选择,此时将选择管理等级最高的角色\n如果选择的特定角色不可用,餐厅将回退到任意而不是跳过此餐厅", + "none": "无", + "any": "任意", + "Cheshire": "柴郡", + "any+any": "任意 + 任意", + "Cheshire+any": "柴郡 + 任意" + }, + "CafeMenu": { + "name": "啾咖啡菜单", + "help": "只销售菜单里指定的菜品。\n通过工具中的岛屿生产规划器自动生成" + } + }, + "IslandSeasonTask": { + "_info": { + "name": "开发计划", + "help": "" + }, + "TaskTarget": { + "name": "需求物品", + "help": "" + } + }, "Daemon": { "_info": { "name": "半自动点击", @@ -2591,6 +2796,45 @@ "False": "False" } }, + "IslandProductionPlanner": { + "_info": { + "name": "岛屿生产规划器", + "help": "将使用线性规划根据用户设置的参数优化岛屿生产计划,并为岛屿生产模块生成配置。\n需要先配置好岛屿餐厅管理的参数和岛屿生产的追加需求物品。\n清除任务状态以重新扫描岛屿科技树。" + }, + "RescanIslandTechnology": { + "name": "本次重新扫描岛屿科技", + "help": "如果你的岛屿科技树有变动,请勾选此项以重新扫描岛屿科技树并生成新的生产计划\n如果不勾选,Alas将使用上次扫描的结果,可能会导致生产计划不合理" + }, + "DailyProfitLowerLimit": { + "name": "每日利润下限", + "help": "负值表示接受每日亏损,0表示收支平衡,正值表示利润" + }, + "DailyBufferSafetyMargin": { + "name": "每日缓冲安全余量", + "help": "应用于生产规划器生成的每日缓冲的倍率余量。0.2 表示导出计算每日需求的 120%。" + }, + "FieldsEfficiency": { + "name": "丰壤农田效率", + "help": "参见角色收集奖励中的数值", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + }, + "OrchardEfficiency": { + "name": "坠香果园效率", + "help": "参见角色收集奖励中的数值", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + }, + "NurseryEfficiency": { + "name": "青芽苗圃效率", + "help": "参见角色收集奖励中的数值", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + } + }, "Benchmark": { "_info": { "name": "性能测试", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index d36f89f7f3..abd3e5396a 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -28,6 +28,10 @@ "name": "大世界", "help": "" }, + "Island": { + "name": "離島計劃", + "help": "Menu.Island.help" + }, "Tool": { "name": "工具", "help": "" @@ -254,6 +258,30 @@ "name": "跨月每日", "help": " Alas將在大世界跨月重置之前10分鐘進入大世界,等待大世界重置但不退出大世界,然後完成新一天的大世界每日、隱秘海域、深淵海域和短貓相接,以獲得額外的金菜。運行大世界每日時,按\"大世界每日\"任務設定運行,其餘同理。\n 重要:Alas等待跨月期間,請不要操作遊戲。" }, + "IslandProduction": { + "name": "離島生產", + "help": "" + }, + "IslandOrder": { + "name": "離島訂單", + "help": "" + }, + "IslandFreebie": { + "name": "每日補給", + "help": "" + }, + "IslandCollect": { + "name": "每日採集", + "help": "使用管理面板採集季節特產和煤炭/自然之木。\n將使用多個等級最低的角色直到滿足體力要求。" + }, + "IslandSeasonTask": { + "name": "開發計劃", + "help": "" + }, + "IslandBusiness": { + "name": "店鋪經營", + "help": "" + }, "Daemon": { "name": "半自動點擊", "help": "" @@ -266,6 +294,10 @@ "name": "活動劇情", "help": "" }, + "IslandProductionPlanner": { + "name": "離島生產規劃器", + "help": "將根據用戶設定的參數使用線性規劃優化離島的生產計劃,並生成離島生產模組的配置" + }, "Benchmark": { "name": "性能測試", "help": "" @@ -2555,6 +2587,179 @@ "22": "22 | NA海域西南B" } }, + "IslandProduction": { + "_info": { + "name": "離島生產", + "help": "" + }, + "HardFloorItems": { + "name": "硬性保底物品", + "help": "不會被訂單、運輸和生產配方消耗的保底庫存\n經營模組可以消耗這部分庫存\n用於為經營模組保留的菜品或材料\n生產規劃器匯出的經營物品至少會在這裡保持為對應經營容量\n格式示例:\n(物品在當前伺服器的名稱): (數量)" + }, + "ReserveItems": { + "name": "保留物品", + "help": "不會被離島訂單和運輸等外部消耗拿走的保留庫存\n生產配方和經營模組可以消耗保留庫存,配方會將其補回\n格式示例:\n(物品在當前伺服器的名稱): (數量)" + }, + "RequestBufferItems": { + "name": "訂單緩衝物品", + "help": "用戶自訂的離島訂單可消耗緩衝\n配方生產目標為硬性保底加保留數量,再加訂單緩衝和每日緩衝中的較大值\n用於生產規劃器不會主動保留庫存的訂單物品" + }, + "DailyBufferItems": { + "name": "每日緩衝物品", + "help": "生產規劃器根據預期每日消耗生成的工作緩衝\n配方生產目標為硬性保底加保留數量,再加訂單緩衝和每日緩衝中的較大值\n可以透過工具中的離島生產規劃器自動生成" + }, + "IdleAccumulatingItems": { + "name": "閒置累積物品", + "help": "生產閒置且原料充足時可以繼續累積的物品\n數值為預期每日累積速度,用於閒置配方優先級\n可以透過工具中的離島生產規劃器自動生成" + } + }, + "IslandOrder": { + "_info": { + "name": "離島訂單", + "help": "對於普通訂單,將僅在當前庫存減去硬性保底和保留數量後數量足夠時提交。\n對於緊急訂單,只要當前庫存足夠就會提交。" + }, + "StuckSeasonOrderId": { + "name": "卡住的季節訂單ID", + "help": "0表示沒有卡住的訂單\n一般不需手動修改" + } + }, + "IslandFreebie": { + "_info": { + "name": "每日補給", + "help": "" + }, + "Share": { + "name": "將補給分享給好友", + "help": "" + } + }, + "IslandRestaurant": { + "_info": { + "name": "餐廳管理", + "help": "" + }, + "KoiGrade": { + "name": "有魚餐館等級", + "help": "", + "bronze": "青銅", + "silver": "白銀", + "gold": "黃金", + "diamond": "鑽石" + }, + "KoiWaitress": { + "name": "有魚餐館服務員", + "help": "無表示不運營此餐廳,任意表示不限制服務員的選擇,此時將選擇管理等級最高的角色\n如果選擇的特定角色不可用,餐廳將回退到任意而不是跳過此餐廳", + "none": "無", + "any": "任意", + "Chao_Ho": "肇和", + "any+any": "任意 + 任意", + "Chao_Ho+any": "肇和 + 任意" + }, + "KoiMenu": { + "name": "有魚餐館菜單", + "help": "只銷售菜單裡指定的菜品。\n通過工具中的離島生產規劃器自動生成" + }, + "BearGrade": { + "name": "白熊飲品等級", + "help": "", + "bronze": "青銅", + "silver": "白銀", + "gold": "黃金", + "diamond": "鑽石" + }, + "BearWaitress": { + "name": "白熊飲品服務員", + "help": "無表示不運營此餐廳,任意表示不限制服務員的選擇,此時將選擇管理等級最高的角色\n如果選擇的特定角色不可用,餐廳將回退到任意而不是跳過此餐廳", + "none": "無", + "any": "任意", + "Cheshire": "柴郡", + "any+any": "任意 + 任意", + "Cheshire+any": "柴郡 + 任意" + }, + "BearMenu": { + "name": "白熊飲品菜單", + "help": "只銷售菜單裡指定的饮品。\n通過工具中的離島生產規劃器自動生成" + }, + "EateryGrade": { + "name": "啾啾簡餐等級", + "help": "", + "bronze": "青銅", + "silver": "白銀", + "gold": "黃金", + "diamond": "鑽石" + }, + "EateryWaitress": { + "name": "啾啾簡餐服務員", + "help": "無表示不運營此餐廳,任意表示不限制服務員的選擇,此時將選擇管理等級最高的角色\n如果選擇的特定角色不可用,餐廳將回退到任意而不是跳過此餐廳", + "none": "無", + "any": "任意", + "Helena": "海倫娜", + "Prinz_Eugen": "歐根親王", + "any+any": "任意 + 任意", + "Helena+any": "海倫娜 + 任意", + "Prinz_Eugen+any": "歐根親王 + 任意", + "Helena+Prinz_Eugen": "海倫娜 + 歐根親王" + }, + "EateryMenu": { + "name": "啾啾簡餐菜單", + "help": "只銷售菜單裡指定的菜品。\n通過工具中的離島生產規劃器自動生成" + }, + "GrillGrade": { + "name": "烏魚烤肉等級", + "help": "", + "bronze": "青銅", + "silver": "白銀", + "gold": "黃金", + "diamond": "鑽石" + }, + "GrillWaitress": { + "name": "烏魚烤肉服務員", + "help": "無表示不運營此餐廳,任意表示不限制服務員的選擇,此時將選擇管理等級最高的角色\n如果選擇的特定角色不可用,餐廳將回退到任意而不是跳過此餐廳", + "none": "無", + "any": "任意", + "August_von_Parseval": "奧古斯特·馮·帕塞瓦爾", + "Prinz_Eugen": "歐根親王", + "any+any": "任意 + 任意", + "August_von_Parseval+any": "奧古斯特·馮·帕塞瓦爾 + 任意", + "Prinz_Eugen+any": "歐根親王 + 任意", + "August_von_Parseval+Prinz_Eugen": "奧古斯特·馮·帕塞瓦爾 + 歐根親王" + }, + "GrillMenu": { + "name": "烏魚烤肉菜單", + "help": "只銷售菜單裡指定的菜品。\n通過工具中的離島生產規劃器自動生成" + }, + "CafeGrade": { + "name": "啾咖啡等級", + "help": "", + "bronze": "青銅", + "silver": "白銀", + "gold": "黃金", + "diamond": "鑽石" + }, + "CafeWaitress": { + "name": "啾咖啡服務員", + "help": "無表示不運營此餐廳,任意表示不限制服務員的選擇,此時將選擇管理等級最高的角色\n如果選擇的特定角色不可用,餐廳將回退到任意而不是跳過此餐廳", + "none": "無", + "any": "任意", + "Cheshire": "柴郡", + "any+any": "任意 + 任意", + "Cheshire+any": "柴郡 + 任意" + }, + "CafeMenu": { + "name": "啾咖啡菜單", + "help": "只銷售菜單裡指定的菜品。\n通過工具中的離島生產規劃器自動生成" + } + }, + "IslandSeasonTask": { + "_info": { + "name": "開發計劃", + "help": "" + }, + "TaskTarget": { + "name": "需求物品", + "help": "" + } + }, "Daemon": { "_info": { "name": "半自動點擊", @@ -2591,6 +2796,45 @@ "False": "False" } }, + "IslandProductionPlanner": { + "_info": { + "name": "離島生產規劃器", + "help": "將使用線性規劃根據用戶設定的參數優化離島生產計畫,並為離島生產模組生成配置。\n需要先配置離島餐廳管理的參數及離島生產的追加需求物品。\n清除任務狀態以重新掃描離島科技樹。" + }, + "RescanIslandTechnology": { + "name": "重新掃描離島科技樹", + "help": "如果你的離島科技樹有變動,請勾選此項以重新掃描離島科技樹並生成新的生產計畫\n如果不勾選,Alas將使用上次掃描的結果,可能會導致生產計畫不合理" + }, + "DailyProfitLowerLimit": { + "name": "每日利潤下限", + "help": "負值表示接受每日虧損,0表示收支平衡,正值表示利潤" + }, + "DailyBufferSafetyMargin": { + "name": "每日緩衝安全餘量", + "help": "套用於生產規劃器生成的每日緩衝的倍率餘量。0.2 表示匯出計算每日需求的 120%。" + }, + "FieldsEfficiency": { + "name": "豐壤農田效率", + "help": "參見角色收集獎勵中的數值", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + }, + "OrchardEfficiency": { + "name": "墜香果園效率", + "help": "參見角色收集獎勵中的數值", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + }, + "NurseryEfficiency": { + "name": "青芽苗圃效率", + "help": "參見角色收集獎勵中的數值", + "0": "0", + "0.04": "0.04", + "0.12": "0.12" + } + }, "Benchmark": { "_info": { "name": "性能測試", diff --git a/module/device/control.py b/module/device/control.py index 1224c45475..7e055ba007 100644 --- a/module/device/control.py +++ b/module/device/control.py @@ -148,7 +148,7 @@ def swipe_vector(self, vector, box=(123, 159, 1175, 628), random_range=(0, 0, 0, self.swipe(p1, p2, duration=duration, name=name, distance_check=distance_check) def drag(self, p1, p2, segments=1, shake=(0, 15), point_random=(-10, -10, 10, 10), shake_random=(-5, -5, 5, 5), - swipe_duration=0.25, shake_duration=0.1, name='DRAG'): + swipe_duration=0.25, shake_duration=0.1, hold_duration=0.0, name='DRAG'): self.handle_control_check(name) p1, p2 = ensure_int(p1, p2) logger.info( @@ -156,19 +156,22 @@ def drag(self, p1, p2, segments=1, shake=(0, 15), point_random=(-10, -10, 10, 10 ) method = self.config.Emulator_ControlMethod if method == 'minitouch': - self.drag_minitouch(p1, p2, point_random=point_random) + self.drag_minitouch(p1, p2, point_random=point_random, hold_duration=hold_duration) elif method == 'uiautomator2': self.drag_uiautomator2( p1, p2, segments=segments, shake=shake, point_random=point_random, shake_random=shake_random, - swipe_duration=swipe_duration, shake_duration=shake_duration) + swipe_duration=swipe_duration, shake_duration=shake_duration, hold_duration=hold_duration) elif method == 'scrcpy': - self.drag_scrcpy(p1, p2, point_random=point_random) + self.drag_scrcpy(p1, p2, point_random=point_random, hold_duration=hold_duration) elif method == 'MaaTouch': - self.drag_maatouch(p1, p2, point_random=point_random) + self.drag_maatouch(p1, p2, point_random=point_random, hold_duration=hold_duration) elif method == 'nemu_ipc': - self.drag_nemu_ipc(p1, p2, point_random=point_random) + self.drag_nemu_ipc(p1, p2, point_random=point_random, hold_duration=hold_duration) else: logger.warning(f'Control method {method} does not support drag well, ' f'falling back to ADB swipe may cause unexpected behaviour') self.swipe_adb(p1, p2, duration=ensure_time(swipe_duration * 2)) + hold_duration = ensure_time(hold_duration) + if hold_duration > 0: + self.sleep(hold_duration) self.click(Button(area=(), color=(), button=area_offset(point_random, p2), name=name), False) diff --git a/module/device/method/maatouch.py b/module/device/method/maatouch.py index 146cf7fa22..d28e93d92f 100644 --- a/module/device/method/maatouch.py +++ b/module/device/method/maatouch.py @@ -367,7 +367,7 @@ def swipe_maatouch(self, p1, p2): builder.send_sync() @retry - def drag_maatouch(self, p1, p2, point_random=(-10, -10, 10, 10)): + def drag_maatouch(self, p1, p2, point_random=(-10, -10, 10, 10), hold_duration=0.0): p1 = np.array(p1) - random_rectangle_point(point_random) p2 = np.array(p2) - random_rectangle_point(point_random) points = insert_swipe(p0=p1, p3=p2, speed=20) @@ -384,6 +384,12 @@ def drag_maatouch(self, p1, p2, point_random=(-10, -10, 10, 10)): builder.move(*p2).commit().wait(140) builder.send_sync() + hold_duration = ensure_time(hold_duration) - 0.28 + hold_ms = int(max(0.0, hold_duration) * 1000) + if hold_ms > 0: + builder.move(*p2).commit().wait(hold_ms) + builder.send_sync() + builder.up().commit() builder.send_sync() diff --git a/module/device/method/minitouch.py b/module/device/method/minitouch.py index d97c2ca884..8f96fe9d9b 100644 --- a/module/device/method/minitouch.py +++ b/module/device/method/minitouch.py @@ -694,7 +694,7 @@ def swipe_minitouch(self, p1, p2): builder.send() @retry - def drag_minitouch(self, p1, p2, point_random=(-10, -10, 10, 10)): + def drag_minitouch(self, p1, p2, point_random=(-10, -10, 10, 10), hold_duration=0.0): p1 = np.array(p1) - random_rectangle_point(point_random) p2 = np.array(p2) - random_rectangle_point(point_random) points = insert_swipe(p0=p1, p3=p2, speed=20) @@ -711,5 +711,11 @@ def drag_minitouch(self, p1, p2, point_random=(-10, -10, 10, 10)): builder.move(*p2).commit().wait(140) builder.send() + hold_duration = ensure_time(hold_duration) - 0.28 + hold_ms = int(max(0.0, hold_duration) * 1000) + if hold_ms > 0: + builder.move(*p2).commit().wait(hold_ms) + builder.send() + builder.up().commit() builder.send() diff --git a/module/device/method/nemu_ipc.py b/module/device/method/nemu_ipc.py index 556255f328..4f21b59346 100644 --- a/module/device/method/nemu_ipc.py +++ b/module/device/method/nemu_ipc.py @@ -622,7 +622,7 @@ def swipe_nemu_ipc(self, p1, p2): self.nemu_ipc.up() self.sleep(0.050) - def drag_nemu_ipc(self, p1, p2, point_random=(-10, -10, 10, 10)): + def drag_nemu_ipc(self, p1, p2, point_random=(-10, -10, 10, 10), hold_duration=0.0): p1 = np.array(p1) - random_rectangle_point(point_random) p2 = np.array(p2) - random_rectangle_point(point_random) points = insert_swipe(p0=p1, p3=p2, speed=20) @@ -636,5 +636,10 @@ def drag_nemu_ipc(self, p1, p2, point_random=(-10, -10, 10, 10)): self.nemu_ipc.down(*p2) self.sleep(0.140) + hold_duration = ensure_time(hold_duration) - 0.28 + if hold_duration > 0: + self.nemu_ipc.down(*p2) + self.sleep(hold_duration) + self.nemu_ipc.up() self.sleep(0.050) diff --git a/module/device/method/scrcpy/scrcpy.py b/module/device/method/scrcpy/scrcpy.py index ef0192b3a8..10ea398dd1 100644 --- a/module/device/method/scrcpy/scrcpy.py +++ b/module/device/method/scrcpy/scrcpy.py @@ -6,7 +6,7 @@ from adbutils.errors import AdbError, AdbTimeout import module.device.method.scrcpy.const as const -from module.base.utils import random_rectangle_point +from module.base.utils import ensure_time, random_rectangle_point from module.device.method.minitouch import insert_swipe from module.device.method.scrcpy.core import ScrcpyCore, ScrcpyError from module.device.method.uiautomator_2 import Uiautomator2 @@ -143,7 +143,7 @@ def swipe_scrcpy(self, p1, p2): self.sleep(0.05) @retry - def drag_scrcpy(self, p1, p2, point_random=(-10, -10, 10, 10)): + def drag_scrcpy(self, p1, p2, point_random=(-10, -10, 10, 10), hold_duration=0.0): self.scrcpy_ensure_running() with self._scrcpy_control_socket_lock: @@ -162,5 +162,17 @@ def drag_scrcpy(self, p1, p2, point_random=(-10, -10, 10, 10)): self._scrcpy_control.touch(*p2, const.ACTION_MOVE) self.sleep(0.002) + hold_duration = ensure_time(hold_duration) - 0.28 + if hold_duration > 0: + step = 0.002 + repeats = int(hold_duration // step) + for _ in range(repeats): + self._scrcpy_control.touch(*p2, const.ACTION_MOVE) + self.sleep(step) + remainder = hold_duration - (repeats * step) + if remainder > 0: + self._scrcpy_control.touch(*p2, const.ACTION_MOVE) + self.sleep(remainder) + self._scrcpy_control.touch(*p2, const.ACTION_UP) self.sleep(0.05) diff --git a/module/device/method/uiautomator_2.py b/module/device/method/uiautomator_2.py index fb5274356d..b54fbd6546 100644 --- a/module/device/method/uiautomator_2.py +++ b/module/device/method/uiautomator_2.py @@ -187,7 +187,8 @@ def _drag_along(self, path): self.sleep(second) def drag_uiautomator2(self, p1, p2, segments=1, shake=(0, 15), point_random=(-10, -10, 10, 10), - shake_random=(-5, -5, 5, 5), swipe_duration=0.25, shake_duration=0.1): + shake_random=(-5, -5, 5, 5), swipe_duration=0.25, shake_duration=0.1, + hold_duration=0.0): """Drag and shake, like: /\ +-----------+ + + @@ -204,6 +205,7 @@ def drag_uiautomator2(self, p1, p2, segments=1, shake=(0, 15), point_random=(-10 shake_random: Add random to shake array. swipe_duration: Duration between way points. shake_duration: Duration between shake points. + hold_duration: Hold time before release. """ p1 = np.array(p1) - random_rectangle_point(point_random) p2 = np.array(p2) - random_rectangle_point(point_random) @@ -213,6 +215,13 @@ def drag_uiautomator2(self, p1, p2, segments=1, shake=(0, 15), point_random=(-10 (*p2 - shake - random_rectangle_point(shake_random), shake_duration), (*p2, shake_duration) ] + internal_hold = ensure_time(shake_duration) * 3 + hold_duration = ensure_time(hold_duration) - internal_hold + if hold_duration > 0: + path += [ + (*p2, hold_duration), + (*p2, 0), + ] path = [(int(x), int(y), d) for x, y, d in path] self._drag_along(path) diff --git a/module/island/assets.py b/module/island/assets.py new file mode 100644 index 0000000000..217c5d3740 --- /dev/null +++ b/module/island/assets.py @@ -0,0 +1,49 @@ +from module.base.button import Button +from module.base.template import Template + +# This file was automatically generated by dev_tools/button_extract.py. +# Don't modify it manually. + +ISLAND_BUSINESS_EVENT_POPUP = Button(area={'cn': (593, 388, 604, 400), 'en': (593, 388, 604, 400), 'jp': (593, 388, 604, 400), 'tw': (593, 388, 604, 400)}, color={'cn': (92, 152, 110), 'en': (92, 152, 110), 'jp': (92, 152, 110), 'tw': (92, 152, 110)}, button={'cn': (593, 388, 604, 400), 'en': (593, 388, 604, 400), 'jp': (593, 388, 604, 400), 'tw': (593, 388, 604, 400)}, file={'cn': './assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP.png', 'en': './assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP.png', 'jp': './assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP.png', 'tw': './assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP.png'}) +ISLAND_BUSINESS_EVENT_POPUP_CANCEL = Button(area={'cn': (492, 634, 536, 657), 'en': (492, 634, 536, 657), 'jp': (492, 634, 536, 657), 'tw': (492, 634, 536, 657)}, color={'cn': (97, 97, 97), 'en': (97, 97, 97), 'jp': (97, 97, 97), 'tw': (97, 97, 97)}, button={'cn': (492, 634, 536, 657), 'en': (492, 634, 536, 657), 'jp': (492, 634, 536, 657), 'tw': (492, 634, 536, 657)}, file={'cn': './assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png', 'en': './assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png', 'jp': './assets/jp/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png', 'tw': './assets/cn/island/ISLAND_BUSINESS_EVENT_POPUP_CANCEL.png'}) +ISLAND_CLICK_SAFE_AREA = Button(area={'cn': (596, 687, 680, 720), 'en': (596, 687, 680, 720), 'jp': (596, 687, 680, 720), 'tw': (596, 687, 680, 720)}, color={'cn': (255, 255, 255), 'en': (255, 255, 255), 'jp': (255, 255, 255), 'tw': (255, 255, 255)}, button={'cn': (596, 687, 680, 720), 'en': (596, 687, 680, 720), 'jp': (596, 687, 680, 720), 'tw': (596, 687, 680, 720)}, file={'cn': './assets/cn/island/ISLAND_CLICK_SAFE_AREA.png', 'en': './assets/cn/island/ISLAND_CLICK_SAFE_AREA.png', 'jp': './assets/cn/island/ISLAND_CLICK_SAFE_AREA.png', 'tw': './assets/cn/island/ISLAND_CLICK_SAFE_AREA.png'}) +ISLAND_COLLECT_LOCATION_1 = Button(area={'cn': (431, 216, 608, 466), 'en': (431, 216, 608, 466), 'jp': (431, 216, 608, 466), 'tw': (431, 216, 608, 466)}, color={'cn': (156, 186, 138), 'en': (156, 186, 138), 'jp': (156, 186, 138), 'tw': (156, 186, 138)}, button={'cn': (431, 216, 608, 466), 'en': (431, 216, 608, 466), 'jp': (431, 216, 608, 466), 'tw': (431, 216, 608, 466)}, file={'cn': './assets/cn/island/ISLAND_COLLECT_LOCATION_1.png', 'en': './assets/cn/island/ISLAND_COLLECT_LOCATION_1.png', 'jp': './assets/cn/island/ISLAND_COLLECT_LOCATION_1.png', 'tw': './assets/cn/island/ISLAND_COLLECT_LOCATION_1.png'}) +ISLAND_COLLECT_LOCATION_2 = Button(area={'cn': (672, 216, 849, 466), 'en': (672, 216, 849, 466), 'jp': (672, 216, 849, 466), 'tw': (672, 216, 849, 466)}, color={'cn': (174, 185, 184), 'en': (174, 185, 184), 'jp': (174, 185, 184), 'tw': (174, 185, 184)}, button={'cn': (672, 216, 849, 466), 'en': (672, 216, 849, 466), 'jp': (672, 216, 849, 466), 'tw': (672, 216, 849, 466)}, file={'cn': './assets/cn/island/ISLAND_COLLECT_LOCATION_2.png', 'en': './assets/cn/island/ISLAND_COLLECT_LOCATION_2.png', 'jp': './assets/cn/island/ISLAND_COLLECT_LOCATION_2.png', 'tw': './assets/cn/island/ISLAND_COLLECT_LOCATION_2.png'}) +ISLAND_COLLECT_SELECT_CANCEL = Button(area={'cn': (474, 543, 530, 571), 'en': (474, 543, 530, 571), 'jp': (474, 543, 530, 571), 'tw': (474, 543, 530, 571)}, color={'cn': (200, 200, 199), 'en': (200, 200, 199), 'jp': (200, 200, 199), 'tw': (200, 200, 199)}, button={'cn': (474, 543, 530, 571), 'en': (474, 543, 530, 571), 'jp': (474, 543, 530, 571), 'tw': (474, 543, 530, 571)}, file={'cn': './assets/cn/island/ISLAND_COLLECT_SELECT_CANCEL.png', 'en': './assets/cn/island/ISLAND_COLLECT_SELECT_CANCEL.png', 'jp': './assets/jp/island/ISLAND_COLLECT_SELECT_CANCEL.png', 'tw': './assets/cn/island/ISLAND_COLLECT_SELECT_CANCEL.png'}) +ISLAND_COLLECT_SELECT_CONFIRM = Button(area={'cn': (749, 542, 812, 573), 'en': (749, 542, 812, 573), 'jp': (749, 542, 812, 573), 'tw': (749, 542, 812, 573)}, color={'cn': (104, 205, 255), 'en': (104, 205, 255), 'jp': (104, 205, 255), 'tw': (104, 205, 255)}, button={'cn': (749, 542, 812, 573), 'en': (749, 542, 812, 573), 'jp': (749, 542, 812, 573), 'tw': (749, 542, 812, 573)}, file={'cn': './assets/cn/island/ISLAND_COLLECT_SELECT_CONFIRM.png', 'en': './assets/cn/island/ISLAND_COLLECT_SELECT_CONFIRM.png', 'jp': './assets/jp/island/ISLAND_COLLECT_SELECT_CONFIRM.png', 'tw': './assets/cn/island/ISLAND_COLLECT_SELECT_CONFIRM.png'}) +ISLAND_COLLECT_SELECT_ENTER = Button(area={'cn': (892, 473, 1035, 502), 'en': (892, 473, 1035, 502), 'jp': (892, 473, 1035, 502), 'tw': (892, 473, 1035, 502)}, color={'cn': (108, 206, 254), 'en': (108, 206, 254), 'jp': (108, 206, 254), 'tw': (108, 206, 254)}, button={'cn': (892, 473, 1035, 502), 'en': (892, 473, 1035, 502), 'jp': (892, 473, 1035, 502), 'tw': (892, 473, 1035, 502)}, file={'cn': './assets/cn/island/ISLAND_COLLECT_SELECT_ENTER.png', 'en': './assets/cn/island/ISLAND_COLLECT_SELECT_ENTER.png', 'jp': './assets/jp/island/ISLAND_COLLECT_SELECT_ENTER.png', 'tw': './assets/cn/island/ISLAND_COLLECT_SELECT_ENTER.png'}) +ISLAND_COLLECT_START = Button(area={'cn': (1015, 473, 1072, 504), 'en': (1015, 473, 1072, 504), 'jp': (1015, 473, 1072, 504), 'tw': (1015, 473, 1072, 504)}, color={'cn': (93, 201, 255), 'en': (93, 201, 255), 'jp': (93, 201, 255), 'tw': (93, 201, 255)}, button={'cn': (1015, 473, 1072, 504), 'en': (1015, 473, 1072, 504), 'jp': (1015, 473, 1072, 504), 'tw': (1015, 473, 1072, 504)}, file={'cn': './assets/cn/island/ISLAND_COLLECT_START.png', 'en': './assets/cn/island/ISLAND_COLLECT_START.png', 'jp': './assets/jp/island/ISLAND_COLLECT_START.png', 'tw': './assets/cn/island/ISLAND_COLLECT_START.png'}) +ISLAND_COLLECT_START_UNAVAILABLE = Button(area={'cn': (1016, 474, 1071, 503), 'en': (1016, 474, 1071, 503), 'jp': (1016, 474, 1071, 503), 'tw': (1016, 474, 1071, 503)}, color={'cn': (201, 201, 200), 'en': (201, 201, 200), 'jp': (201, 201, 200), 'tw': (201, 201, 200)}, button={'cn': (1016, 474, 1071, 503), 'en': (1016, 474, 1071, 503), 'jp': (1016, 474, 1071, 503), 'tw': (1016, 474, 1071, 503)}, file={'cn': './assets/cn/island/ISLAND_COLLECT_START_UNAVAILABLE.png', 'en': './assets/cn/island/ISLAND_COLLECT_START_UNAVAILABLE.png', 'jp': './assets/jp/island/ISLAND_COLLECT_START_UNAVAILABLE.png', 'tw': './assets/cn/island/ISLAND_COLLECT_START_UNAVAILABLE.png'}) +ISLAND_FREEBIE_AVAILABLE = Button(area={'cn': (0, 600, 120, 720), 'en': (0, 600, 120, 720), 'jp': (0, 600, 120, 720), 'tw': (0, 600, 120, 720)}, color={'cn': (83, 83, 75), 'en': (83, 83, 75), 'jp': (83, 83, 75), 'tw': (83, 83, 75)}, button={'cn': (37, 631, 87, 681), 'en': (37, 631, 87, 681), 'jp': (37, 631, 87, 681), 'tw': (37, 631, 87, 681)}, file={'cn': './assets/cn/island/ISLAND_FREEBIE_AVAILABLE.gif', 'en': './assets/cn/island/ISLAND_FREEBIE_AVAILABLE.gif', 'jp': './assets/cn/island/ISLAND_FREEBIE_AVAILABLE.gif', 'tw': './assets/cn/island/ISLAND_FREEBIE_AVAILABLE.gif'}) +ISLAND_FREEBIE_CLAIM = Button(area={'cn': (890, 381, 984, 405), 'en': (890, 381, 984, 405), 'jp': (890, 381, 984, 405), 'tw': (890, 381, 984, 405)}, color={'cn': (194, 195, 196), 'en': (194, 195, 196), 'jp': (194, 195, 196), 'tw': (194, 195, 196)}, button={'cn': (890, 381, 984, 405), 'en': (890, 381, 984, 405), 'jp': (890, 381, 984, 405), 'tw': (890, 381, 984, 405)}, file={'cn': './assets/cn/island/ISLAND_FREEBIE_CLAIM.png', 'en': './assets/cn/island/ISLAND_FREEBIE_CLAIM.png', 'jp': './assets/jp/island/ISLAND_FREEBIE_CLAIM.png', 'tw': './assets/cn/island/ISLAND_FREEBIE_CLAIM.png'}) +ISLAND_FREEBIE_COOLDOWN = Button(area={'cn': (870, 380, 1003, 406), 'en': (870, 380, 1003, 406), 'jp': (870, 380, 1003, 406), 'tw': (870, 380, 1003, 406)}, color={'cn': (200, 202, 201), 'en': (200, 202, 201), 'jp': (200, 202, 201), 'tw': (200, 202, 201)}, button={'cn': (870, 380, 1003, 406), 'en': (870, 380, 1003, 406), 'jp': (870, 380, 1003, 406), 'tw': (870, 380, 1003, 406)}, file={'cn': './assets/cn/island/ISLAND_FREEBIE_COOLDOWN.png', 'en': './assets/cn/island/ISLAND_FREEBIE_COOLDOWN.png', 'jp': './assets/jp/island/ISLAND_FREEBIE_COOLDOWN.png', 'tw': './assets/cn/island/ISLAND_FREEBIE_COOLDOWN.png'}) +ISLAND_FREEBIE_RECEIVE = Button(area={'cn': (859, 380, 1012, 406), 'en': (859, 380, 1012, 406), 'jp': (859, 380, 1012, 406), 'tw': (859, 380, 1012, 406)}, color={'cn': (205, 207, 206), 'en': (205, 207, 206), 'jp': (205, 207, 206), 'tw': (205, 207, 206)}, button={'cn': (859, 380, 1012, 406), 'en': (859, 380, 1012, 406), 'jp': (859, 380, 1012, 406), 'tw': (859, 380, 1012, 406)}, file={'cn': './assets/cn/island/ISLAND_FREEBIE_RECEIVE.png', 'en': './assets/cn/island/ISLAND_FREEBIE_RECEIVE.png', 'jp': './assets/jp/island/ISLAND_FREEBIE_RECEIVE.png', 'tw': './assets/cn/island/ISLAND_FREEBIE_RECEIVE.png'}) +ISLAND_FREEBIE_SHARE = Button(area={'cn': (849, 380, 1024, 406), 'en': (849, 380, 1024, 406), 'jp': (849, 380, 1024, 406), 'tw': (849, 380, 1024, 406)}, color={'cn': (210, 214, 214), 'en': (210, 214, 214), 'jp': (210, 214, 214), 'tw': (210, 214, 214)}, button={'cn': (849, 380, 1024, 406), 'en': (849, 380, 1024, 406), 'jp': (849, 380, 1024, 406), 'tw': (849, 380, 1024, 406)}, file={'cn': './assets/cn/island/ISLAND_FREEBIE_SHARE.png', 'en': './assets/cn/island/ISLAND_FREEBIE_SHARE.png', 'jp': './assets/jp/island/ISLAND_FREEBIE_SHARE.png', 'tw': './assets/cn/island/ISLAND_FREEBIE_SHARE.png'}) +ISLAND_FREEBIE_SHARE_ALL = Button(area={'cn': (724, 587, 814, 613), 'en': (724, 587, 814, 613), 'jp': (724, 587, 814, 613), 'tw': (724, 587, 814, 613)}, color={'cn': (73, 104, 128), 'en': (73, 104, 128), 'jp': (73, 104, 128), 'tw': (73, 104, 128)}, button={'cn': (724, 587, 814, 613), 'en': (724, 587, 814, 613), 'jp': (724, 587, 814, 613), 'tw': (724, 587, 814, 613)}, file={'cn': './assets/cn/island/ISLAND_FREEBIE_SHARE_ALL.png', 'en': './assets/cn/island/ISLAND_FREEBIE_SHARE_ALL.png', 'jp': './assets/jp/island/ISLAND_FREEBIE_SHARE_ALL.png', 'tw': './assets/cn/island/ISLAND_FREEBIE_SHARE_ALL.png'}) +ISLAND_FREEBIE_SHARE_BACK = Button(area={'cn': (844, 101, 878, 135), 'en': (844, 101, 878, 135), 'jp': (844, 101, 878, 135), 'tw': (844, 101, 878, 135)}, color={'cn': (170, 213, 232), 'en': (170, 213, 232), 'jp': (170, 213, 232), 'tw': (170, 213, 232)}, button={'cn': (844, 101, 878, 135), 'en': (844, 101, 878, 135), 'jp': (844, 101, 878, 135), 'tw': (844, 101, 878, 135)}, file={'cn': './assets/cn/island/ISLAND_FREEBIE_SHARE_BACK.png', 'en': './assets/cn/island/ISLAND_FREEBIE_SHARE_BACK.png', 'jp': './assets/cn/island/ISLAND_FREEBIE_SHARE_BACK.png', 'tw': './assets/cn/island/ISLAND_FREEBIE_SHARE_BACK.png'}) +ISLAND_FREEBIE_UNAVAILABLE = Button(area={'cn': (36, 631, 88, 683), 'en': (36, 631, 88, 683), 'jp': (36, 631, 88, 683), 'tw': (36, 631, 88, 683)}, color={'cn': (122, 122, 122), 'en': (122, 122, 122), 'jp': (122, 122, 122), 'tw': (122, 122, 122)}, button={'cn': (36, 631, 88, 683), 'en': (36, 631, 88, 683), 'jp': (36, 631, 88, 683), 'tw': (36, 631, 88, 683)}, file={'cn': './assets/cn/island/ISLAND_FREEBIE_UNAVAILABLE.png', 'en': './assets/cn/island/ISLAND_FREEBIE_UNAVAILABLE.png', 'jp': './assets/cn/island/ISLAND_FREEBIE_UNAVAILABLE.png', 'tw': './assets/cn/island/ISLAND_FREEBIE_UNAVAILABLE.png'}) +ISLAND_LEVEL_UP = Button(area={'cn': (616, 339, 662, 377), 'en': (616, 339, 662, 377), 'jp': (616, 339, 662, 377), 'tw': (616, 339, 662, 377)}, color={'cn': (186, 213, 226), 'en': (186, 213, 226), 'jp': (186, 213, 226), 'tw': (186, 213, 226)}, button={'cn': (616, 339, 662, 377), 'en': (616, 339, 662, 377), 'jp': (616, 339, 662, 377), 'tw': (616, 339, 662, 377)}, file={'cn': './assets/cn/island/ISLAND_LEVEL_UP.png', 'en': './assets/cn/island/ISLAND_LEVEL_UP.png', 'jp': './assets/cn/island/ISLAND_LEVEL_UP.png', 'tw': './assets/cn/island/ISLAND_LEVEL_UP.png'}) +ISLAND_ORDER_ACCEPT = Button(area={'cn': (1115, 639, 1169, 665), 'en': (1115, 639, 1169, 665), 'jp': (1115, 639, 1169, 665), 'tw': (1115, 639, 1169, 665)}, color={'cn': (103, 204, 255), 'en': (103, 204, 255), 'jp': (103, 204, 255), 'tw': (103, 204, 255)}, button={'cn': (1115, 639, 1169, 665), 'en': (1115, 639, 1169, 665), 'jp': (1115, 639, 1169, 665), 'tw': (1115, 639, 1169, 665)}, file={'cn': './assets/cn/island/ISLAND_ORDER_ACCEPT.png', 'en': './assets/cn/island/ISLAND_ORDER_ACCEPT.png', 'jp': './assets/jp/island/ISLAND_ORDER_ACCEPT.png', 'tw': './assets/cn/island/ISLAND_ORDER_ACCEPT.png'}) +ISLAND_ORDER_ACCEPT_URGENT = Button(area={'cn': (1022, 638, 1076, 667), 'en': (1022, 638, 1076, 667), 'jp': (1022, 638, 1076, 667), 'tw': (1022, 638, 1076, 667)}, color={'cn': (98, 202, 255), 'en': (98, 202, 255), 'jp': (98, 202, 255), 'tw': (98, 202, 255)}, button={'cn': (1022, 638, 1076, 667), 'en': (1022, 638, 1076, 667), 'jp': (1022, 638, 1076, 667), 'tw': (1022, 638, 1076, 667)}, file={'cn': './assets/cn/island/ISLAND_ORDER_ACCEPT_URGENT.png', 'en': './assets/cn/island/ISLAND_ORDER_ACCEPT_URGENT.png', 'jp': './assets/jp/island/ISLAND_ORDER_ACCEPT_URGENT.png', 'tw': './assets/cn/island/ISLAND_ORDER_ACCEPT_URGENT.png'}) +ISLAND_ORDER_BACKGROUND = Button(area={'cn': (988, 394, 1108, 516), 'en': (988, 394, 1108, 516), 'jp': (988, 394, 1108, 516), 'tw': (988, 394, 1108, 516)}, color={'cn': (138, 171, 184), 'en': (138, 171, 184), 'jp': (138, 171, 184), 'tw': (138, 171, 184)}, button={'cn': (988, 394, 1108, 516), 'en': (988, 394, 1108, 516), 'jp': (988, 394, 1108, 516), 'tw': (988, 394, 1108, 516)}, file={'cn': './assets/cn/island/ISLAND_ORDER_BACKGROUND.png', 'en': './assets/cn/island/ISLAND_ORDER_BACKGROUND.png', 'jp': './assets/cn/island/ISLAND_ORDER_BACKGROUND.png', 'tw': './assets/cn/island/ISLAND_ORDER_BACKGROUND.png'}) +ISLAND_ORDER_COOLDOWN_REMAIN_TIME = Button(area={'cn': (989, 427, 1101, 473), 'en': (989, 427, 1101, 473), 'jp': (989, 427, 1101, 473), 'tw': (989, 427, 1101, 473)}, color={'cn': (216, 216, 217), 'en': (216, 216, 217), 'jp': (216, 216, 217), 'tw': (216, 216, 217)}, button={'cn': (989, 427, 1101, 473), 'en': (989, 427, 1101, 473), 'jp': (989, 427, 1101, 473), 'tw': (989, 427, 1101, 473)}, file={'cn': './assets/cn/island/ISLAND_ORDER_COOLDOWN_REMAIN_TIME.png', 'en': './assets/cn/island/ISLAND_ORDER_COOLDOWN_REMAIN_TIME.png', 'jp': './assets/cn/island/ISLAND_ORDER_COOLDOWN_REMAIN_TIME.png', 'tw': './assets/cn/island/ISLAND_ORDER_COOLDOWN_REMAIN_TIME.png'}) +ISLAND_ORDER_COOLDOWN_SPEED_UP = Button(area={'cn': (1023, 638, 1078, 667), 'en': (1023, 638, 1078, 667), 'jp': (1023, 638, 1078, 667), 'tw': (1023, 638, 1078, 667)}, color={'cn': (110, 206, 255), 'en': (110, 206, 255), 'jp': (110, 206, 255), 'tw': (110, 206, 255)}, button={'cn': (1023, 638, 1078, 667), 'en': (1023, 638, 1078, 667), 'jp': (1023, 638, 1078, 667), 'tw': (1023, 638, 1078, 667)}, file={'cn': './assets/cn/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png', 'en': './assets/cn/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png', 'jp': './assets/jp/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png', 'tw': './assets/cn/island/ISLAND_ORDER_COOLDOWN_SPEED_UP.png'}) +ISLAND_ORDER_LEVEL_UP = Button(area={'cn': (610, 288, 677, 350), 'en': (610, 288, 677, 350), 'jp': (610, 288, 677, 350), 'tw': (610, 288, 677, 350)}, color={'cn': (245, 123, 66), 'en': (245, 123, 66), 'jp': (245, 123, 66), 'tw': (245, 123, 66)}, button={'cn': (610, 288, 677, 350), 'en': (610, 288, 677, 350), 'jp': (610, 288, 677, 350), 'tw': (610, 288, 677, 350)}, file={'cn': './assets/cn/island/ISLAND_ORDER_LEVEL_UP.png', 'en': './assets/cn/island/ISLAND_ORDER_LEVEL_UP.png', 'jp': './assets/cn/island/ISLAND_ORDER_LEVEL_UP.png', 'tw': './assets/cn/island/ISLAND_ORDER_LEVEL_UP.png'}) +ISLAND_ORDER_REJECT = Button(area={'cn': (929, 639, 982, 666), 'en': (929, 639, 982, 666), 'jp': (929, 639, 982, 666), 'tw': (929, 639, 982, 666)}, color={'cn': (206, 206, 205), 'en': (206, 206, 205), 'jp': (206, 206, 205), 'tw': (206, 206, 205)}, button={'cn': (929, 639, 982, 666), 'en': (929, 639, 982, 666), 'jp': (929, 639, 982, 666), 'tw': (929, 639, 982, 666)}, file={'cn': './assets/cn/island/ISLAND_ORDER_REJECT.png', 'en': './assets/cn/island/ISLAND_ORDER_REJECT.png', 'jp': './assets/jp/island/ISLAND_ORDER_REJECT.png', 'tw': './assets/cn/island/ISLAND_ORDER_REJECT.png'}) +ISLAND_ORDER_REQUIREMENTS_CHECK = Button(area={'cn': (1185, 185, 1221, 218), 'en': (1185, 185, 1221, 218), 'jp': (1185, 185, 1221, 218), 'tw': (1185, 185, 1221, 218)}, color={'cn': (210, 210, 210), 'en': (210, 210, 210), 'jp': (210, 210, 210), 'tw': (210, 210, 210)}, button={'cn': (1185, 185, 1221, 218), 'en': (1185, 185, 1221, 218), 'jp': (1185, 185, 1221, 218), 'tw': (1185, 185, 1221, 218)}, file={'cn': './assets/cn/island/ISLAND_ORDER_REQUIREMENTS_CHECK.png', 'en': './assets/cn/island/ISLAND_ORDER_REQUIREMENTS_CHECK.png', 'jp': './assets/cn/island/ISLAND_ORDER_REQUIREMENTS_CHECK.png', 'tw': './assets/cn/island/ISLAND_ORDER_REQUIREMENTS_CHECK.png'}) +ISLAND_PRODUCTION_RECEIVE = Button(area={'cn': (591, 549, 630, 570), 'en': (591, 549, 630, 570), 'jp': (591, 549, 630, 570), 'tw': (591, 549, 630, 570)}, color={'cn': (83, 197, 255), 'en': (83, 197, 255), 'jp': (83, 197, 255), 'tw': (83, 197, 255)}, button={'cn': (591, 549, 630, 570), 'en': (591, 549, 630, 570), 'jp': (591, 549, 630, 570), 'tw': (591, 549, 630, 570)}, file={'cn': './assets/cn/island/ISLAND_PRODUCTION_RECEIVE.png', 'en': './assets/cn/island/ISLAND_PRODUCTION_RECEIVE.png', 'jp': './assets/jp/island/ISLAND_PRODUCTION_RECEIVE.png', 'tw': './assets/cn/island/ISLAND_PRODUCTION_RECEIVE.png'}) +ISLAND_PRODUCTION_RERUN = Button(area={'cn': (596, 540, 684, 565), 'en': (596, 540, 684, 565), 'jp': (596, 540, 684, 565), 'tw': (596, 540, 684, 565)}, color={'cn': (247, 211, 142), 'en': (247, 211, 142), 'jp': (247, 211, 142), 'tw': (247, 211, 142)}, button={'cn': (596, 540, 684, 565), 'en': (596, 540, 684, 565), 'jp': (596, 540, 684, 565), 'tw': (596, 540, 684, 565)}, file={'cn': './assets/cn/island/ISLAND_PRODUCTION_RERUN.png', 'en': './assets/cn/island/ISLAND_PRODUCTION_RERUN.png', 'jp': './assets/jp/island/ISLAND_PRODUCTION_RERUN.png', 'tw': './assets/cn/island/ISLAND_PRODUCTION_RERUN.png'}) +ISLAND_PRODUCTION_SELECT_CHARACTER = Button(area={'cn': (579, 315, 607, 348), 'en': (579, 315, 607, 348), 'jp': (579, 315, 607, 348), 'tw': (579, 315, 607, 348)}, color={'cn': (246, 246, 245), 'en': (246, 246, 245), 'jp': (246, 246, 245), 'tw': (246, 246, 245)}, button={'cn': (579, 315, 607, 348), 'en': (579, 315, 607, 348), 'jp': (579, 315, 607, 348), 'tw': (579, 315, 607, 348)}, file={'cn': './assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png', 'en': './assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png', 'jp': './assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png', 'tw': './assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png'}) +ISLAND_SEASON_TASK_RECEIVE_ALL = Button(area={'cn': (1059, 618, 1093, 646), 'en': (1059, 618, 1093, 646), 'jp': (1059, 618, 1093, 646), 'tw': (1059, 618, 1093, 646)}, color={'cn': (247, 220, 119), 'en': (247, 220, 119), 'jp': (247, 220, 119), 'tw': (247, 220, 119)}, button={'cn': (1059, 618, 1093, 646), 'en': (1059, 618, 1093, 646), 'jp': (1059, 618, 1093, 646), 'tw': (1059, 618, 1093, 646)}, file={'cn': './assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png', 'en': './assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png', 'jp': './assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png', 'tw': './assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png'}) +ISLAND_SEASON_TASK_SCROLL_AREA = Button(area={'cn': (1236, 171, 1237, 604), 'en': (1236, 171, 1237, 604), 'jp': (1236, 171, 1237, 604), 'tw': (1236, 171, 1237, 604)}, color={'cn': (191, 195, 192), 'en': (191, 195, 192), 'jp': (191, 195, 192), 'tw': (191, 195, 192)}, button={'cn': (1236, 171, 1237, 604), 'en': (1236, 171, 1237, 604), 'jp': (1236, 171, 1237, 604), 'tw': (1236, 171, 1237, 604)}, file={'cn': './assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png', 'en': './assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png', 'jp': './assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png', 'tw': './assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png'}) +TEMPLATE_ISLAND_BUSINESS_BEAR = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png', 'jp': './assets/jp/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_BEAR.png'}) +TEMPLATE_ISLAND_BUSINESS_CAFE = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png', 'jp': './assets/jp/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_CAFE.png'}) +TEMPLATE_ISLAND_BUSINESS_EATERY = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png', 'jp': './assets/jp/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_EATERY.png'}) +TEMPLATE_ISLAND_BUSINESS_GRILL = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png', 'jp': './assets/jp/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_GRILL.png'}) +TEMPLATE_ISLAND_BUSINESS_KOI = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_KOI.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_KOI.png', 'jp': './assets/jp/island/TEMPLATE_ISLAND_BUSINESS_KOI.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_KOI.png'}) +TEMPLATE_ISLAND_BUSINESS_RESTING = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png', 'jp': './assets/jp/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RESTING.png'}) +TEMPLATE_ISLAND_BUSINESS_RUNNING = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png', 'jp': './assets/jp/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_BUSINESS_RUNNING.png'}) +TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png', 'jp': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png'}) +TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png', 'jp': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png'}) +TEMPLATE_ISLAND_SEASON_REWARD = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png', 'jp': './assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png'}) +TEMPLATE_ISLAND_SEASON_TASK_OBTAINED = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png', 'jp': './assets/jp/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png'}) diff --git a/module/island/business.py b/module/island/business.py new file mode 100644 index 0000000000..c6ee2112c2 --- /dev/null +++ b/module/island/business.py @@ -0,0 +1,182 @@ +from datetime import datetime, timedelta + +from module.base.button import ButtonGrid +from module.base.decorator import cached_property, del_cached_property +from module.config.utils import get_server_next_update +from module.island.assets import * +from module.island_handler.restaurant import IslandRestaurant, WaitressOccupied +from module.logger import logger +from module.ocr.ocr import Duration +from module.ui.page import page_island_manage + + +BUSINESS_DETECT_AREA = (210, 72, 1203, 685) +BUSINESS_ENTRANCE_AREA = (794, 84, 950, 120) + + +class IslandBusiness(IslandRestaurant): + @cached_property + def skip_restaurant(self): + open = { + 601: self.has_waitress("IslandBusiness.IslandRestaurant.KoiWaitress", 'none'), + 602: self.has_waitress("IslandBusiness.IslandRestaurant.BearWaitress", 'none'), + 603: self.has_waitress("IslandBusiness.IslandRestaurant.EateryWaitress", 'none'), + 604: self.has_waitress("IslandBusiness.IslandRestaurant.GrillWaitress", 'none'), + 901: self.has_waitress("IslandBusiness.IslandRestaurant.CafeWaitress", 'none') + } + return open + + @property + def business_grid(self): + return ButtonGrid( + origin=(210, 88), + delta=(0, 177), + button_shape=(993, 161), + grid_shape=(1, 3), + name="BUSINESS_GRID" + ) + + @property + def business_grid_shifted(self): + return ButtonGrid( + origin=(210, 168), + delta=(0, 177), + button_shape=(993, 161), + grid_shape=(1, 3), + name="BUSINESS_GRID_SHIFTED" + ) + + def handle_restaurant_popup(self): + for _ in self.loop(timeout=5): + if self.appear(ISLAND_BUSINESS_EVENT_POPUP, offset=(50, 50), interval=1): + self.device.click(ISLAND_CLICK_SAFE_AREA) + continue + if self.appear_then_click(ISLAND_BUSINESS_EVENT_POPUP_CANCEL, offset=(20, 20)): + continue + # End + if self.match_template_color(page_island_manage.check_button, offset=(20, 20)): + return True + + def restaurant_swipe_to_top(self): + self.device.swipe_vector((0, 500), box=BUSINESS_DETECT_AREA, padding=-20) + for _ in self.loop(timeout=0.8): + pass + self.shifted = False + self.index = 0 + + def restaurant_swipe_to_bottom(self): + self.device.swipe_vector((0, -500), box=BUSINESS_DETECT_AREA, padding=-20) + for _ in self.loop(timeout=0.8): + pass + self.shifted = True + self.index = 1 + + def get_restaurant_id(self, button): + template_to_id = { + TEMPLATE_ISLAND_BUSINESS_KOI: 601, + TEMPLATE_ISLAND_BUSINESS_BEAR: 602, + TEMPLATE_ISLAND_BUSINESS_EATERY: 603, + TEMPLATE_ISLAND_BUSINESS_GRILL: 604, + TEMPLATE_ISLAND_BUSINESS_CAFE: 901 + } + image = self.image_crop(button, copy=True) + for template, id in template_to_id.items(): + if template.match(image): + return id + return None + + def is_restaurant_running(self, button): + return TEMPLATE_ISLAND_BUSINESS_RUNNING.match(self.image_crop(button, copy=True)) + + def is_restaurant_resting(self, button): + return TEMPLATE_ISLAND_BUSINESS_RESTING.match(self.image_crop(button, copy=True)) + + def get_remain_time(self, button): + time_button = button.crop((851, 11, 913, 38)) + ocr = Duration(time_button, name="RESTAURANT_REMAIN_TIME") + remain_time = ocr.ocr(self.device.image) + return remain_time + + index = None + shifted = None + + def next_restaurant(self): + self.index += 1 + if self.index >= 3 and not self.shifted: + self.restaurant_swipe_to_bottom() + elif self.index >= 3 and self.shifted: + logger.info("No more restaurants") + + def run(self): + self.ui_ensure(page_island_manage) + self.island_manage_side_navbar_ensure(upper=2) + self.handle_restaurant_popup() + self.restaurant_swipe_to_top() + unchecked_restaurants = [601, 602, 603, 604, 901] + next_run_time = { + 601: get_server_next_update('00:00') if not self.skip_restaurant[601] else datetime.now() + timedelta(days=3), + 602: get_server_next_update('00:00') if not self.skip_restaurant[602] else datetime.now() + timedelta(days=3), + 603: get_server_next_update('00:00') if not self.skip_restaurant[603] else datetime.now() + timedelta(days=3), + 604: get_server_next_update('00:00') if not self.skip_restaurant[604] else datetime.now() + timedelta(days=3), + 901: get_server_next_update('00:00') if not self.skip_restaurant[901] else datetime.now() + timedelta(days=3) + } + while unchecked_restaurants: + if self.shifted: + button = self.business_grid_shifted.buttons[self.index] + else: + button = self.business_grid.buttons[self.index] + restaurant_id = self.get_restaurant_id(button) + if restaurant_id not in unchecked_restaurants: + self.next_restaurant() + continue + if restaurant_id is None: + logger.warning("Unrecognized restaurant, should check assets") + self.next_restaurant() + continue + entrance_button = button.crop(BUSINESS_ENTRANCE_AREA) + if self.skip_restaurant[restaurant_id]: + logger.info(f"Skip restaurant {restaurant_id}") + unchecked_restaurants.remove(restaurant_id) + self.next_restaurant() + continue + if self.is_restaurant_running(entrance_button): + remain_time = self.get_remain_time(button) + next_run_time[restaurant_id] = datetime.now() + remain_time if remain_time else "Unknown" + logger.info(f"Restaurant {restaurant_id} is running") + unchecked_restaurants.remove(restaurant_id) + self.next_restaurant() + continue + if self.is_restaurant_resting(entrance_button): + logger.info(f"Restaurant {restaurant_id} is resting") + unchecked_restaurants.remove(restaurant_id) + self.next_restaurant() + continue + logger.info(f"Restaurant {restaurant_id} is ready") + for _ in self.loop(): + if self.appear(page_island_manage.check_button, offset=(20, 20), interval=1): + self.device.click(entrance_button) + continue + if self.is_in_island_restaurant(): + break + self.working_restaurant_id = restaurant_id + try: + success = super().run() + except WaitressOccupied: + next_run_time[restaurant_id] = datetime.now() + timedelta(hours=8) + unchecked_restaurants.remove(restaurant_id) + success = False + self.ui_back(page_island_manage.check_button) + for _ in self.loop(timeout=0.8, skip_first=False): + if self.appear(page_island_manage.check_button, offset=(0, 20)): + break + del_cached_property(super(), 'restaurant_has_event') + del_cached_property(super(), '_restaurant_offset_x') + del_cached_property(super(), 'restaurant_grid') + del_cached_property(super(), 'event_buff') + if restaurant_id in unchecked_restaurants: + unchecked_restaurants.remove(restaurant_id) + if success: + next_run_time[restaurant_id] = datetime.now() + timedelta(hours=8) + # Since dealt restaurants will be moved to the bottom, + # we can directly check the next one without swiping + self.config.task_delay(target=min(next_run_time.values())) diff --git a/module/island/collect.py b/module/island/collect.py new file mode 100644 index 0000000000..ca7a7c1508 --- /dev/null +++ b/module/island/collect.py @@ -0,0 +1,139 @@ +from module.base.button import ButtonGrid +from module.base.timer import Timer +from module.exception import GameStuckError +from module.island.assets import * +from module.island_handler.dock import IslandDock +from module.island_handler.dock_scanner import CharacterScanner +from module.logger import logger +from module.ui.page import page_island_manage, page_island_phone + +ISLAND_COLLECT_WORKSLOT_GRID = ButtonGrid( + origin=(832, 227), delta=(95, 0), button_shape=(74, 74), grid_shape=(3, 1) +) + + +class IslandCollect(IslandDock): + def collect_location_unselected_buttons(self): + buttons = [] + if not self.is_button_selected(ISLAND_COLLECT_LOCATION_1): + buttons.append(ISLAND_COLLECT_LOCATION_1) + if not self.is_button_selected(ISLAND_COLLECT_LOCATION_2): + buttons.append(ISLAND_COLLECT_LOCATION_2) + return buttons + + def collect_available(self): + for _ in self.loop(timeout=10): + if self.appear_then_click(ISLAND_COLLECT_SELECT_ENTER, offset=(20, 20), interval=3): + continue + # End + if self.appear(ISLAND_COLLECT_SELECT_CONFIRM, offset=(20, 20)): + break + else: + logger.warning('Cannot find collect enter button, collect may not be available') + return False + + available = None + for _ in self.loop(skip_first=False, timeout=20): + # End + if self.handle_info_bar(): + logger.warning('Info bar appears, nothing more to collect') + available = False + break + if self.match_template_color(ISLAND_COLLECT_START_UNAVAILABLE, offset=(20, 20)): + logger.info('has something to collect, continue') + available = True + break + + if self.appear(ISLAND_COLLECT_SELECT_CONFIRM, offset=(20, 20)): + buttons = self.collect_location_unselected_buttons() + if not buttons: + self.device.click(ISLAND_COLLECT_SELECT_CONFIRM) + continue + for button in buttons: + self.device.click(button) + continue + else: + logger.warning('Cannot determine collect availability, possibly due to network issues') + available = False + + if available: + return True + # Back to island manage page + for _ in self.loop(timeout=10): + if self.appear_then_click(ISLAND_COLLECT_SELECT_CANCEL, offset=(20, 20), interval=3): + continue + if self.ui_page_appear(page_island_manage, offset=(20, 20)): + break + else: + logger.warning('Cannot return to island manage page, something may be wrong') + raise GameStuckError('Cannot return to island manage page, something may be wrong') + return False + + def collect_execute(self): + for workslot, button in enumerate(ISLAND_COLLECT_WORKSLOT_GRID.buttons): + click_timer = Timer(1, count=3) + for _ in self.loop(timeout=10): + # End + if self.is_in_island_dock(): + break + if click_timer.reached(): + self.device.click(button) + click_timer.reset() + else: + logger.warning(f'Failed to click workslot button for workslot {workslot}') + return False + self.island_dock_sort_method_dsc_set(enable=False) + dock_grid = super().dock_grid + scanner = CharacterScanner(dock_grid, emotion=(80, 150), status='free') + scanner.disable('emotion_limit') + candidates = scanner.scan(self.device.image) + if not candidates: + logger.warning(f'No candidate found for workslot {workslot}, canceling collect') + self.island_dock_quit() + return False + button = candidates[0].button + self.island_dock_select_one(button) + self.island_dock_select_confirm(check_button=[ISLAND_COLLECT_START, ISLAND_COLLECT_START_UNAVAILABLE]) + if self.match_template_color(ISLAND_COLLECT_START, offset=(20, 20)): + break + if not self.match_template_color(ISLAND_COLLECT_START, offset=(20, 20)): + logger.warning('Failed to start collect, something may be wrong') + return False + confirm_timer = Timer(3, count=5).start() + for _ in self.loop(timeout=10): + if self.handle_island_additional(): + confirm_timer.reset() + continue + if self.match_template_color(ISLAND_COLLECT_START, offset=(20, 20)): + self.device.click(ISLAND_COLLECT_START) + confirm_timer.reset() + continue + + # End + if self.appear(ISLAND_COLLECT_SELECT_ENTER, offset=(20, 20)): + if confirm_timer.reached(): + return True + else: + logger.warning('Failed to start collect, something may be wrong') + return False + + def run(self): + self.ui_ensure(page_island_manage) + self.island_manage_side_navbar_ensure(upper=3) + + if self.collect_available(): + success = self.collect_execute() + if success: + logger.info('Collect successfully') + self.config.task_delay(server_update=True) + else: + logger.warning('Failed to collect, will retry later') + self.config.task_delay(success=False) + else: + logger.info('Collect not available, possibly due to cooldown') + for _ in self.loop(): + if self.appear_then_click(ISLAND_COLLECT_SELECT_CANCEL, offset=(20, 20), interval=3): + continue + if self.appear(ISLAND_COLLECT_SELECT_ENTER, offset=(20, 20)): + break + self.config.task_delay(server_update=True) diff --git a/module/island/data.py b/module/island/data.py new file mode 100644 index 0000000000..381b183a84 --- /dev/null +++ b/module/island/data.py @@ -0,0 +1,1352 @@ +# This file was automatically generated by dev_tools/island_extractor.py +# Don't modify it manually. + +DIC_ISLAND_ITEM = { + 0: {'name': {'cn': '岛屿开发PT', 'en': 'Island Development Points', 'jp': '離島開発Pt'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1: {'name': {'cn': '开发资金', 'en': 'Development Funds', 'jp': '開発資金'}, 'pt_num': 0, 'manage_influence': 0, 'order_price': 1}, + 1000: {'name': {'cn': '小麦种子', 'en': 'Wheat Seeds', 'jp': '小麦の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1001: {'name': {'cn': '玉米种子', 'en': 'Corn Seeds', 'jp': 'とうもろこしの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1002: {'name': {'cn': '旱稻种子', 'en': 'Upland Rice Seeds', 'jp': '陸稲の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1003: {'name': {'cn': '白菜种子', 'en': 'Napa Cabbage Seeds', 'jp': '白菜の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1004: {'name': {'cn': '胡萝卜种子', 'en': 'Carrot Seeds', 'jp': 'ニンジンの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1005: {'name': {'cn': '土豆种子', 'en': 'Potato Seeds', 'jp': 'じゃがいもの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1006: {'name': {'cn': '大豆种子', 'en': 'Soy Bean Seeds', 'jp': '大豆の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1007: {'name': {'cn': '洋葱种子', 'en': 'Onion Seeds', 'jp': '玉ねぎの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1008: {'name': {'cn': '牧草种子', 'en': 'Grass Seeds', 'jp': '牧草の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1009: {'name': {'cn': '咖啡树种', 'en': 'Coffee Tree Seeds', 'jp': 'コーヒーの木の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1010: {'name': {'cn': '亚麻种子', 'en': 'Flax Seeds', 'jp': '亜麻の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1011: {'name': {'cn': '草莓种子', 'en': 'Strawberry Seeds', 'jp': 'いちごの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1012: {'name': {'cn': '棉花种子', 'en': 'Cotton Seeds', 'jp': '綿の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1014: {'name': {'cn': '茶树种子', 'en': 'Tea Tree Seeds', 'jp': '茶の木の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1015: {'name': {'cn': '薰衣草种子', 'en': 'Lavender Seeds', 'jp': 'ラベンダーの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1016: {'name': {'cn': '苹果树种', 'en': 'Apple Tree Seeds', 'jp': 'りんごの木の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1017: {'name': {'cn': '柑橘树种', 'en': 'Citrus Fruit Tree Seeds', 'jp': '柑橘類の木の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1018: {'name': {'cn': '香蕉树种', 'en': 'Banana Tree Seed', 'jp': 'バナナの木の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1019: {'name': {'cn': '芒果树种', 'en': 'Mango Tree Seeds', 'jp': 'マンゴーの木の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1020: {'name': {'cn': '柠檬树种', 'en': 'Lemon Tree Seed', 'jp': 'レモンの木の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1021: {'name': {'cn': '牛油果树种', 'en': 'Avocado Tree Seeds', 'jp': 'アボカドの木の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1022: {'name': {'cn': '橡胶树种', 'en': 'Rubber Tree Seeds', 'jp': 'ゴムの木の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1101: {'name': {'cn': '贝苗', 'en': 'Shellfish Spat', 'jp': '稚貝'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1102: {'name': {'cn': '鲶鱼苗', 'en': 'Catfish Fry', 'jp': 'ナマズの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1103: {'name': {'cn': '鲤鱼苗', 'en': 'Koi Carp Fry', 'jp': 'コイの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1104: {'name': {'cn': '鲫鱼苗', 'en': 'Common Carp Fry', 'jp': 'フナの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1105: {'name': {'cn': '小河虾苗', 'en': 'Freshwater Shrimp Fry', 'jp': '稚エビ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1106: {'name': {'cn': '小龙虾苗', 'en': 'Crayfish Fry', 'jp': '稚ザリ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1107: {'name': {'cn': '鲈鱼苗', 'en': 'Sea Bass Fry', 'jp': 'スズキの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1108: {'name': {'cn': '蟹苗', 'en': 'Juvenile Crab', 'jp': '稚ガニ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1201: {'name': {'cn': '鱿鱼苗', 'en': 'Squid Fry', 'jp': '稚イカ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1202: {'name': {'cn': '马鲛鱼苗', 'en': 'Mackerel Fry', 'jp': 'サワラの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1203: {'name': {'cn': '金枪鱼苗', 'en': 'Tuna Fry', 'jp': 'マグロの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1204: {'name': {'cn': '三文鱼苗', 'en': 'Salmon Fry', 'jp': 'サーモンの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1205: {'name': {'cn': '红鲷鱼苗', 'en': 'Red Sea Bream Fry', 'jp': 'マダイの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1206: {'name': {'cn': '黑鲷鱼苗', 'en': 'Black Porgy Fry', 'jp': 'クロダイの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1207: {'name': {'cn': '黄鳍金枪鱼苗', 'en': 'Yellowfin Tuna Fry', 'jp': 'キハダの稚魚'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1208: {'name': {'cn': '海参苗', 'en': 'Sea Cucumber Fry', 'jp': '稚ナマコ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 1500: {'name': {'cn': '蚯蚓', 'en': 'Earthworm', 'jp': 'ミミズ'}, 'pt_num': 0, 'manage_influence': 0, 'order_price': 0}, + 1501: {'name': {'cn': '玉米粒', 'en': 'Corn', 'jp': 'コーン'}, 'pt_num': 0, 'manage_influence': 0, 'order_price': 0}, + 1502: {'name': {'cn': '虾仁', 'en': 'Shelled Shrimp', 'jp': 'むきエビ'}, 'pt_num': 0, 'manage_influence': 0, 'order_price': 0}, + 1503: {'name': {'cn': '章鱼须', 'en': 'Octopus Arm', 'jp': 'タコ足'}, 'pt_num': 0, 'manage_influence': 0, 'order_price': 0}, + 2000: {'name': {'cn': '小麦', 'en': 'Wheat', 'jp': '小麦'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 8}, + 2001: {'name': {'cn': '玉米', 'en': 'Corn', 'jp': 'とうもろこし'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 8}, + 2002: {'name': {'cn': '大米', 'en': 'Rice', 'jp': '米'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 17}, + 2003: {'name': {'cn': '白菜', 'en': 'Napa Cabbage', 'jp': '白菜'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 14}, + 2004: {'name': {'cn': '胡萝卜', 'en': 'Carrot', 'jp': 'ニンジン'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 34}, + 2005: {'name': {'cn': '土豆', 'en': 'Potato', 'jp': 'じゃがいも'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 33}, + 2006: {'name': {'cn': '大豆', 'en': 'Soy Beans', 'jp': '大豆'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 14}, + 2007: {'name': {'cn': '洋葱', 'en': 'Onion', 'jp': '玉ねぎ'}, 'pt_num': 24, 'manage_influence': 0, 'order_price': 244}, + 2008: {'name': {'cn': '牧草', 'en': 'Grass', 'jp': '牧草'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 11}, + 2009: {'name': {'cn': '咖啡豆', 'en': 'Coffee Beans', 'jp': 'コーヒー豆'}, 'pt_num': 6, 'manage_influence': 0, 'order_price': 68}, + 2010: {'name': {'cn': '亚麻', 'en': 'Flax', 'jp': '亜麻'}, 'pt_num': 6, 'manage_influence': 0, 'order_price': 72}, + 2011: {'name': {'cn': '草莓', 'en': 'Strawberries', 'jp': 'いちご'}, 'pt_num': 5, 'manage_influence': 0, 'order_price': 54}, + 2012: {'name': {'cn': '棉花', 'en': 'Cotton', 'jp': '綿'}, 'pt_num': 6, 'manage_influence': 0, 'order_price': 92}, + 2014: {'name': {'cn': '茶叶', 'en': 'Tea Leaves', 'jp': '茶葉'}, 'pt_num': 14, 'manage_influence': 0, 'order_price': 118}, + 2015: {'name': {'cn': '薰衣草', 'en': 'Lavender', 'jp': 'ラベンダー'}, 'pt_num': 35, 'manage_influence': 0, 'order_price': 294}, + 2016: {'name': {'cn': '苹果', 'en': 'Apple', 'jp': 'りんご'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 50}, + 2017: {'name': {'cn': '柑橘', 'en': 'Citrus Fruit', 'jp': '柑橘フルーツ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 65}, + 2018: {'name': {'cn': '香蕉', 'en': 'Banana', 'jp': 'バナナ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 80}, + 2019: {'name': {'cn': '芒果', 'en': 'Mango', 'jp': 'マンゴー'}, 'pt_num': 18, 'manage_influence': 0, 'order_price': 180}, + 2020: {'name': {'cn': '柠檬', 'en': 'Lemon', 'jp': 'レモン'}, 'pt_num': 3, 'manage_influence': 0, 'order_price': 32}, + 2021: {'name': {'cn': '牛油果', 'en': 'Avocado', 'jp': 'アボカド'}, 'pt_num': 45, 'manage_influence': 0, 'order_price': 375}, + 2022: {'name': {'cn': '橡胶', 'en': 'Rubber', 'jp': 'ゴム'}, 'pt_num': 30, 'manage_influence': 0, 'order_price': 250}, + 2521: {'name': {'cn': '淡水鱼肉', 'en': 'Freshwater Fish Meat', 'jp': '淡水魚の肉'}, 'pt_num': 18, 'manage_influence': 0, 'order_price': 180}, + 2522: {'name': {'cn': '海水鱼肉', 'en': 'Saltwater Fish Meat', 'jp': '海水魚の肉'}, 'pt_num': 48, 'manage_influence': 0, 'order_price': 400}, + 2600: {'name': {'cn': '鲜肉', 'en': 'Fresh Meat', 'jp': '新鮮な肉'}, 'pt_num': 2, 'manage_influence': 0, 'order_price': 200}, + 2601: {'name': {'cn': '鸡蛋', 'en': 'Eggs', 'jp': '卵'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 55}, + 2602: {'name': {'cn': '禽肉', 'en': 'Poultry', 'jp': '鶏肉'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 20}, + 2603: {'name': {'cn': '牛奶', 'en': 'Milk', 'jp': '牛乳'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 136}, + 2604: {'name': {'cn': '皮料', 'en': 'Pelt', 'jp': '皮素材'}, 'pt_num': 10, 'manage_influence': 0, 'order_price': 95}, + 2605: {'name': {'cn': '羊毛', 'en': 'Wool', 'jp': '羊毛'}, 'pt_num': 72, 'manage_influence': 0, 'order_price': 600}, + 2606: {'name': {'cn': '新鲜蜂蜜', 'en': 'Fresh Honey', 'jp': '新鮮なはちみつ'}, 'pt_num': 2, 'manage_influence': 0, 'order_price': 200}, + 2700: {'name': {'cn': '煤炭', 'en': 'Coal', 'jp': '石炭'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 12}, + 2701: {'name': {'cn': '铜矿', 'en': 'Copper Ore', 'jp': '銅鉱石'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 100}, + 2702: {'name': {'cn': '铝矿', 'en': 'Bauxite Ore', 'jp': 'アルミ鉱石'}, 'pt_num': 30, 'manage_influence': 0, 'order_price': 300}, + 2703: {'name': {'cn': '铁矿', 'en': 'Iron Ore', 'jp': '鉄鉱石'}, 'pt_num': 18, 'manage_influence': 0, 'order_price': 180}, + 2704: {'name': {'cn': '硫矿', 'en': 'Sulfur', 'jp': '硫黄鉱石'}, 'pt_num': 80, 'manage_influence': 0, 'order_price': 700}, + 2705: {'name': {'cn': '银矿', 'en': 'Silver Ore', 'jp': '銀鉱石'}, 'pt_num': 240, 'manage_influence': 0, 'order_price': 1600}, + 2800: {'name': {'cn': '自然之木', 'en': 'Raw Timber', 'jp': '大自然の原木'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 12}, + 2801: {'name': {'cn': '实用之木', 'en': 'Workable Wood', 'jp': '実用の木材'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 125}, + 2802: {'name': {'cn': '精选之木', 'en': 'Premium Wood', 'jp': 'プレミアム木材'}, 'pt_num': 36, 'manage_influence': 0, 'order_price': 360}, + 2803: {'name': {'cn': '典雅之木', 'en': 'Elegant Wood', 'jp': 'エレガント木材'}, 'pt_num': 180, 'manage_influence': 0, 'order_price': 1200}, + 3000: {'name': {'cn': '咯咯鸡饲料', 'en': 'Clucky Clucky Bird Feed', 'jp': 'コッコートリの餌'}, 'pt_num': 3, 'manage_influence': 0, 'order_price': 25}, + 3001: {'name': {'cn': '哼哼猪饲料', 'en': 'Oinky Oinky Pig Feed', 'jp': 'ブーブーブタの餌'}, 'pt_num': 3, 'manage_influence': 0, 'order_price': 25}, + 3002: {'name': {'cn': '哞哞牛饲料', 'en': 'Moo Moo Cow Feed', 'jp': 'モーモーウシの餌'}, 'pt_num': 4, 'manage_influence': 0, 'order_price': 40}, + 3003: {'name': {'cn': '咩咩羊饲料', 'en': 'Baa Baa Sheep Feed', 'jp': 'メェメーヒツジの餌'}, 'pt_num': 4, 'manage_influence': 0, 'order_price': 40}, + 3004: {'name': {'cn': '面粉', 'en': 'Flour', 'jp': '小麦粉'}, 'pt_num': 6, 'manage_influence': 0, 'order_price': 50}, + 3005: {'name': {'cn': '冰咖啡', 'en': 'Iced Coffee', 'jp': 'アイスコーヒー'}, 'pt_num': 15, 'manage_influence': 190, 'order_price': 95}, + 3006: {'name': {'cn': '芝士', 'en': 'Cheese', 'jp': 'チーズ'}, 'pt_num': 55, 'manage_influence': 150, 'order_price': 550}, + 3007: {'name': {'cn': '拿铁', 'en': 'Latte', 'jp': 'ラテ'}, 'pt_num': 25, 'manage_influence': 180, 'order_price': 250}, + 3008: {'name': {'cn': '柑橘咖啡', 'en': 'Citrus Coffee', 'jp': 'シトラスコーヒー'}, 'pt_num': 25, 'manage_influence': 180, 'order_price': 190}, + 3009: {'name': {'cn': '苹果派', 'en': 'Apple Pie', 'jp': 'アップルパイ'}, 'pt_num': 35, 'manage_influence': 190, 'order_price': 385}, + 3010: {'name': {'cn': '草莓奶绿', 'en': 'Strawberry Milkshake', 'jp': 'いちごミルクシェイク'}, 'pt_num': 60, 'manage_influence': 240, 'order_price': 260}, + 3011: {'name': {'cn': '豆腐', 'en': 'Tofu', 'jp': '豆腐'}, 'pt_num': 35, 'manage_influence': 170, 'order_price': 340}, + 3012: {'name': {'cn': '肉末烧豆腐', 'en': 'Tofu with Minced Meat', 'jp': '肉そぼろ豆腐'}, 'pt_num': 155, 'manage_influence': 180, 'order_price': 1300}, + 3013: {'name': {'cn': '蛋包饭', 'en': 'Omurice', 'jp': 'オムライス'}, 'pt_num': 35, 'manage_influence': 240, 'order_price': 355}, + 3014: {'name': {'cn': '白菜豆腐汤', 'en': 'Cabbage and Tofu Soup', 'jp': '白菜と豆腐のスープ'}, 'pt_num': 65, 'manage_influence': 180, 'order_price': 425}, + 3015: {'name': {'cn': '蔬菜沙拉', 'en': 'Vegetable Salad', 'jp': '野菜サラダ'}, 'pt_num': 10, 'manage_influence': 160, 'order_price': 105}, + 3017: {'name': {'cn': '苹果汁', 'en': 'Apple Juice', 'jp': 'りんごジュース'}, 'pt_num': 10, 'manage_influence': 200, 'order_price': 105}, + 3018: {'name': {'cn': '香蕉芒果汁', 'en': 'Banana and Mango Juice', 'jp': 'バナナマンゴージュース'}, 'pt_num': 25, 'manage_influence': 190, 'order_price': 215}, + 3019: {'name': {'cn': '蜂蜜柠檬水', 'en': 'Honey and Lemon Water', 'jp': 'はちみつレモン水'}, 'pt_num': 15, 'manage_influence': 240, 'order_price': 140}, + 3020: {'name': {'cn': '草莓蜜沁', 'en': 'Strawberry Lemon Drink', 'jp': 'いちごレモンドリンク'}, 'pt_num': 50, 'manage_influence': 180, 'order_price': 270}, + 3021: {'name': {'cn': '薰衣草茶', 'en': 'Lavender Tea', 'jp': 'ラベンダーティー'}, 'pt_num': 240, 'manage_influence': 160, 'order_price': 1590}, + 3022: {'name': {'cn': '草莓蜂蜜冰沙', 'en': 'Strawberry Honey Frappé', 'jp': 'いちごのハニーフラッペ'}, 'pt_num': 95, 'manage_influence': 220, 'order_price': 790}, + 3023: {'name': {'cn': '玉米杯', 'en': 'Corn Cup', 'jp': 'コーンカップ'}, 'pt_num': 7, 'manage_influence': 180, 'order_price': 45}, + 3024: {'name': {'cn': '香橙派', 'en': 'Orange Pie', 'jp': 'オレンジパイ'}, 'pt_num': 35, 'manage_influence': 185, 'order_price': 375}, + 3025: {'name': {'cn': '芒果糯米饭', 'en': 'Sticky Rice with Mango', 'jp': 'マンゴーともち米の蒸し飯'}, 'pt_num': 60, 'manage_influence': 160, 'order_price': 510}, + 3026: {'name': {'cn': '香蕉可丽饼', 'en': 'Banana Crêpe', 'jp': 'バナナクレープ'}, 'pt_num': 30, 'manage_influence': 170, 'order_price': 230}, + 3028: {'name': {'cn': '草莓夏洛特', 'en': 'Strawberry Charlotte', 'jp': 'いちごシャルロット'}, 'pt_num': 200, 'manage_influence': 190, 'order_price': 1350}, + 3029: {'name': {'cn': '炭烤肉串', 'en': 'Coal-Roasted Skewer', 'jp': '炭火串焼き'}, 'pt_num': 40, 'manage_influence': 210, 'order_price': 390}, + 3030: {'name': {'cn': '禽肉土豆拼盘', 'en': "Chicken and Potato Hors d'Oeuvre", 'jp': '鶏肉とポテトの盛り合わせ'}, 'pt_num': 36, 'manage_influence': 230, 'order_price': 370}, + 3032: {'name': {'cn': '爆炒禽肉', 'en': 'Stir-Fried Chicken', 'jp': '鶏肉炒め'}, 'pt_num': 70, 'manage_influence': 220, 'order_price': 580}, + 3033: {'name': {'cn': '胡萝卜厚蛋烧', 'en': 'Rolled Carrot Omelette', 'jp': 'ニンジン厚焼き玉子'}, 'pt_num': 16, 'manage_influence': 180, 'order_price': 170}, + 3034: {'name': {'cn': '汉堡肉饭', 'en': 'Steak Bowl', 'jp': 'ハンバーグ丼'}, 'pt_num': 100, 'manage_influence': 150, 'order_price': 845}, + 3035: {'name': {'cn': '布料', 'en': 'Cloth', 'jp': '布生地'}, 'pt_num': 34, 'manage_influence': 0, 'order_price': 340}, + 3036: {'name': {'cn': '皮革', 'en': 'Leather', 'jp': '革'}, 'pt_num': 60, 'manage_influence': 0, 'order_price': 600}, + 3037: {'name': {'cn': '绳索', 'en': 'Rope', 'jp': 'ロープ'}, 'pt_num': 72, 'manage_influence': 0, 'order_price': 600}, + 3038: {'name': {'cn': '手套', 'en': 'Gloves', 'jp': '手袋'}, 'pt_num': 105, 'manage_influence': 0, 'order_price': 890}, + 3039: {'name': {'cn': '香囊', 'en': 'Aroma Sachet', 'jp': '香り袋'}, 'pt_num': 130, 'manage_influence': 0, 'order_price': 1100}, + 3040: {'name': {'cn': '鞋靴', 'en': 'Shoes', 'jp': '靴'}, 'pt_num': 350, 'manage_influence': 0, 'order_price': 2380}, + 3041: {'name': {'cn': '绷带', 'en': 'Wound Dressings', 'jp': '包帯'}, 'pt_num': 350, 'manage_influence': 0, 'order_price': 2380}, + 3042: {'name': {'cn': '炭笔', 'en': 'Charcoal Brush', 'jp': '木炭筆'}, 'pt_num': 30, 'manage_influence': 0, 'order_price': 300}, + 3043: {'name': {'cn': '电缆', 'en': 'Cable', 'jp': 'ケーブル'}, 'pt_num': 92, 'manage_influence': 0, 'order_price': 770}, + 3044: {'name': {'cn': '铁钉', 'en': 'Nails', 'jp': '鉄釘'}, 'pt_num': 66, 'manage_influence': 0, 'order_price': 660}, + 3045: {'name': {'cn': '硫酸', 'en': 'Chemicals', 'jp': '化学品'}, 'pt_num': 84, 'manage_influence': 0, 'order_price': 840}, + 3046: {'name': {'cn': '火药', 'en': 'Gunpowder', 'jp': '火薬'}, 'pt_num': 150, 'manage_influence': 0, 'order_price': 1200}, + 3047: {'name': {'cn': '刀叉餐具', 'en': 'Utensils', 'jp': '食器'}, 'pt_num': 380, 'manage_influence': 0, 'order_price': 2560}, + 3048: {'name': {'cn': '纸张', 'en': 'Paper', 'jp': '紙'}, 'pt_num': 6, 'manage_influence': 0, 'order_price': 175}, + 3049: {'name': {'cn': '记事本', 'en': 'Notebook', 'jp': 'メモ帳'}, 'pt_num': 120, 'manage_influence': 0, 'order_price': 1230}, + 3050: {'name': {'cn': '桌椅', 'en': 'Chair and Desk', 'jp': '机と椅子'}, 'pt_num': 80, 'manage_influence': 0, 'order_price': 810}, + 3051: {'name': {'cn': '精选木桶', 'en': 'Choice Wooden Barrel', 'jp': 'セレクション樽'}, 'pt_num': 190, 'manage_influence': 0, 'order_price': 1610}, + 3052: {'name': {'cn': '文件柜', 'en': 'Filing Cabinet', 'jp': 'ファイルキャビネット'}, 'pt_num': 430, 'manage_influence': 0, 'order_price': 2880}, + 3053: {'name': {'cn': '墨盒', 'en': 'Ink Cartridge', 'jp': 'インクカートリッジ'}, 'pt_num': 55, 'manage_influence': 0, 'order_price': 570}, + 3054: {'name': {'cn': '钟表', 'en': 'Clock', 'jp': '時計'}, 'pt_num': 310, 'manage_influence': 0, 'order_price': 2590}, + 3055: {'name': {'cn': '蓄电池', 'en': 'Battery', 'jp': '蓄電池'}, 'pt_num': 210, 'manage_influence': 0, 'order_price': 1750}, + 3056: {'name': {'cn': '净水滤芯', 'en': 'Water Filter', 'jp': '浄水フィルター'}, 'pt_num': 360, 'manage_influence': 0, 'order_price': 2400}, + 3059: {'name': {'cn': '欧姆蛋', 'en': 'Omelette', 'jp': 'オムレツ'}, 'pt_num': 2, 'manage_influence': 210, 'order_price': 50}, + 3101: {'name': {'cn': '经典豆腐套餐', 'en': 'Classic Tofu Combo', 'jp': '定番豆腐セット'}, 'pt_num': 230, 'manage_influence': 210, 'order_price': 1735}, + 3102: {'name': {'cn': '绵玉定食', 'en': 'Hearty Meal', 'jp': 'ふんわり定食'}, 'pt_num': 100, 'manage_influence': 220, 'order_price': 695}, + 3103: {'name': {'cn': '花香果韵', 'en': 'Floral and Fruity', 'jp': '香りも楽しめるフルーツセット'}, 'pt_num': 250, 'manage_influence': 210, 'order_price': 1700}, + 3104: {'name': {'cn': '缤纷果乐园', 'en': 'Colorful Fruit Paradise', 'jp': 'カラフル果物パラダイス'}, 'pt_num': 120, 'manage_influence': 215, 'order_price': 1000}, + 3105: {'name': {'cn': '阳光蜜水', 'en': 'Sunny Honey', 'jp': '太陽のハニー'}, 'pt_num': 70, 'manage_influence': 260, 'order_price': 410}, + 3106: {'name': {'cn': '香甜组合', 'en': 'Succulently Sweet', 'jp': 'スイートテイスト'}, 'pt_num': 70, 'manage_influence': 250, 'order_price': 560}, + 3107: {'name': {'cn': '果园二重奏', 'en': 'Orchard Duo', 'jp': '果樹園二重奏'}, 'pt_num': 70, 'manage_influence': 240, 'order_price': 615}, + 3108: {'name': {'cn': '莓果香橙甜点组', 'en': 'Berry and Orange Dessert', 'jp': 'ベリーオレンジスイーツ'}, 'pt_num': 260, 'manage_influence': 210, 'order_price': 1730}, + 3109: {'name': {'cn': '烤肉狂欢', 'en': 'The Carne-val', 'jp': '肉カーニバル'}, 'pt_num': 90, 'manage_influence': 230, 'order_price': 760}, + 3110: {'name': {'cn': '能量双拼套餐', 'en': 'Double Energy Combo', 'jp': 'エナジーダブルコンボ'}, 'pt_num': 210, 'manage_influence': 210, 'order_price': 1430}, + 3111: {'name': {'cn': '晨光活力组合', 'en': 'Morning Light Energy Combo', 'jp': '朝光活力コンビ'}, 'pt_num': 36, 'manage_influence': 250, 'order_price': 300}, + 3112: {'name': {'cn': '醒神套餐', 'en': 'The Wake-Up Call', 'jp': 'お目覚めブレックファスト'}, 'pt_num': 80, 'manage_influence': 240, 'order_price': 650}, + 3113: {'name': {'cn': '果香双杯乐', 'en': 'Fruity & Fruitier', 'jp': 'フルーツツインズ'}, 'pt_num': 90, 'manage_influence': 260, 'order_price': 450}, + 3114: {'name': {'cn': '炸鱼薯条', 'en': 'Fish & Chips', 'jp': 'フィッシュ&チップス'}, 'pt_num': 30, 'manage_influence': 280, 'order_price': 300}, + 3115: {'name': {'cn': '柠檬虾', 'en': 'Lemon Shrimp', 'jp': 'レモンエビ'}, 'pt_num': 60, 'manage_influence': 220, 'order_price': 500}, + 3116: {'name': {'cn': '洋葱蒸鱼', 'en': 'Steamed Fish with Onions', 'jp': '魚の玉ねぎ蒸し'}, 'pt_num': 57, 'manage_influence': 180, 'order_price': 420}, + 3117: {'name': {'cn': '装饰画', 'en': 'Ornamental Painting', 'jp': '装飾画'}, 'pt_num': 82, 'manage_influence': 0, 'order_price': 820}, + 3118: {'name': {'cn': '海鲜饭', 'en': 'Paella', 'jp': 'パエリア'}, 'pt_num': 187, 'manage_influence': 200, 'order_price': 900}, + 3119: {'name': {'cn': '爆炒小龙虾', 'en': 'Crayfish Stir-Fry', 'jp': 'ザリガニ炒め'}, 'pt_num': 152, 'manage_influence': 240, 'order_price': 720}, + 3120: {'name': {'cn': '佛跳墙', 'en': "Buddha's Temptation", 'jp': '佛跳牆'}, 'pt_num': 381, 'manage_influence': 150, 'order_price': 2000}, + 4001: {'name': {'cn': '秋菊', 'en': 'Autumn Chrysanthemum', 'jp': 'アキギク'}, 'pt_num': 40, 'manage_influence': 0, 'order_price': 400}, + 4002: {'name': {'cn': '芦苇花', 'en': 'Reed Flowers', 'jp': '葦の花'}, 'pt_num': 2, 'manage_influence': 0, 'order_price': 200}, + 4003: {'name': {'cn': '花生', 'en': 'Peanuts', 'jp': '落花生'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 150}, + 4004: {'name': {'cn': '松茸', 'en': 'Matsutake', 'jp': '松茸'}, 'pt_num': 95, 'manage_influence': 0, 'order_price': 800}, + 4005: {'name': {'cn': '秋月梨', 'en': 'Yoizuki Pear', 'jp': '宵月梨'}, 'pt_num': 5, 'manage_influence': 0, 'order_price': 70}, + 4006: {'name': {'cn': '秋月梨树种', 'en': 'Yoizuki Pear Seeds', 'jp': '宵月梨の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 4007: {'name': {'cn': '柿子', 'en': 'Kaki Persimmon', 'jp': '柿'}, 'pt_num': 24, 'manage_influence': 0, 'order_price': 200}, + 4008: {'name': {'cn': '柿子树种', 'en': 'Kaki Persimmon Seeds', 'jp': '柿の種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 4009: {'name': {'cn': '柿子饼', 'en': 'Dried Persimmon', 'jp': '干し柿'}, 'pt_num': 25, 'manage_influence': 280, 'order_price': 210}, + 4010: {'name': {'cn': '松茸鸡汤', 'en': 'Matsutake and Chicken Soup', 'jp': '松茸と鶏のスープ'}, 'pt_num': 135, 'manage_influence': 280, 'order_price': 900}, + 4011: {'name': {'cn': '秋季花束', 'en': 'Autumn Bouquet', 'jp': '秋のブーケ'}, 'pt_num': 70, 'manage_influence': 280, 'order_price': 705}, + 4012: {'name': {'cn': '花生油', 'en': 'Peanut Oil', 'jp': '落花生油'}, 'pt_num': 100, 'manage_influence': 280, 'order_price': 1005}, + 4013: {'name': {'cn': '胡萝卜秋梨汁', 'en': 'Carrot and Pear Juice', 'jp': 'ニンジンと梨のジュース'}, 'pt_num': 20, 'manage_influence': 280, 'order_price': 200}, + 4014: {'name': {'cn': '菊花茶', 'en': 'Chrysanthemum Tea', 'jp': '菊花の茶'}, 'pt_num': 100, 'manage_influence': 280, 'order_price': 840}, + 4015: {'name': {'cn': '春笋', 'en': 'Spring Bamboo Shoots', 'jp': '春タケノコ'}, 'pt_num': 75, 'manage_influence': 0, 'order_price': 600}, + 4016: {'name': {'cn': '荠菜', 'en': "Shepherd's Purse", 'jp': 'ナズナ'}, 'pt_num': 2, 'manage_influence': 0, 'order_price': 225}, + 4017: {'name': {'cn': '迎春花', 'en': 'Winter Jasmine', 'jp': 'オウバイ'}, 'pt_num': 18, 'manage_influence': 0, 'order_price': 180}, + 4018: {'name': {'cn': '风信子', 'en': 'Hyacinth', 'jp': 'ヒヤシンス'}, 'pt_num': 54, 'manage_influence': 0, 'order_price': 540}, + 4019: {'name': {'cn': '芦笋', 'en': 'Asparagus', 'jp': 'アスパラガス'}, 'pt_num': 5, 'manage_influence': 0, 'order_price': 50}, + 4020: {'name': {'cn': '芦笋种子', 'en': 'Asparagus Seeds', 'jp': 'アスパラガスの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 4021: {'name': {'cn': '凤梨', 'en': 'Pineapple', 'jp': 'パイナップル'}, 'pt_num': 11, 'manage_influence': 0, 'order_price': 70}, + 4022: {'name': {'cn': '凤梨种子', 'en': 'Pineapple Seeds', 'jp': 'パイナップルの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 0}, + 4023: {'name': {'cn': '鲜榨菠萝汁', 'en': 'Fresh Pineapple Juice', 'jp': '搾りたてパイナップルジュース'}, 'pt_num': 18, 'manage_influence': 250, 'order_price': 200}, + 4024: {'name': {'cn': '迎春花茶', 'en': 'Winter Jasmine Tea', 'jp': 'オウバイ茶'}, 'pt_num': 112, 'manage_influence': 250, 'order_price': 800}, + 4025: {'name': {'cn': '凉拌双笋', 'en': 'Cold Mixed Bamboo Shoots and Asparagus', 'jp': 'タケノコとアスパラの冷菜'}, 'pt_num': 72, 'manage_influence': 250, 'order_price': 800}, + 4026: {'name': {'cn': '芦笋炒虾仁', 'en': 'Shrimp and Asparagus Stir-Fry', 'jp': 'エビのアスパラ炒め物'}, 'pt_num': 180, 'manage_influence': 250, 'order_price': 600}, + 4027: {'name': {'cn': '袋装荠菜干', 'en': "Dried Shepherd's Purse", 'jp': '乾燥ナズナ'}, 'pt_num': 72, 'manage_influence': 0, 'order_price': 720}, + 4028: {'name': {'cn': '春季花束', 'en': 'Spring Bouquet', 'jp': '春の花束'}, 'pt_num': 108, 'manage_influence': 0, 'order_price': 1080}, + 4029: {'name': {'cn': '茉莉花', 'en': 'Jasmine', 'jp': 'ジャスミン'}, 'pt_num': 32, 'manage_influence': 0, 'order_price': 315}, + 4030: {'name': {'cn': '向日葵', 'en': 'Sunflower', 'jp': 'ヒマワリ'}, 'pt_num': 27, 'manage_influence': 0, 'order_price': 270}, + 4031: {'name': {'cn': '西瓜', 'en': 'Watermelon', 'jp': 'スイカ'}, 'pt_num': 54, 'manage_influence': 0, 'order_price': 540}, + 4032: {'name': {'cn': '红米苋', 'en': 'Amaranth Greens', 'jp': 'ヒユナ'}, 'pt_num': 2, 'manage_influence': 0, 'order_price': 225}, + 4033: {'name': {'cn': '番茄', 'en': 'Tomato', 'jp': 'トマト'}, 'pt_num': 3, 'manage_influence': 0, 'order_price': 30}, + 4034: {'name': {'cn': '番茄种子', 'en': 'Tomato Seeds', 'jp': 'トマトの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 1}, + 4035: {'name': {'cn': '黄瓜', 'en': 'Cucumber', 'jp': 'キュウリ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 20}, + 4036: {'name': {'cn': '黄瓜种子', 'en': 'Cucumber Seeds', 'jp': 'キュウリの種'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 1}, + 4037: {'name': {'cn': '黄瓜汁', 'en': 'Cucumber Juice', 'jp': 'キュウリジュース'}, 'pt_num': 1, 'manage_influence': 250, 'order_price': 100}, + 4038: {'name': {'cn': '西瓜汁', 'en': 'Watermelon Juice', 'jp': 'スイカジュース'}, 'pt_num': 60, 'manage_influence': 250, 'order_price': 600}, + 4039: {'name': {'cn': '苋菜饭团', 'en': 'Amaranth Onigiri', 'jp': 'ヒユナ入りおにぎり'}, 'pt_num': 8, 'manage_influence': 250, 'order_price': 800}, + 4040: {'name': {'cn': '番茄炒蛋', 'en': 'Tomato and Egg Stir-Fry', 'jp': 'トマトと卵の炒め'}, 'pt_num': 20, 'manage_influence': 250, 'order_price': 200}, + 4041: {'name': {'cn': '茉莉精油', 'en': 'Jasmine Essential Oil', 'jp': 'ジャスミン精油'}, 'pt_num': 165, 'manage_influence': 0, 'order_price': 1100}, + 4042: {'name': {'cn': '夏季花束', 'en': 'Summery Bouquet', 'jp': '夏の花束'}, 'pt_num': 130, 'manage_influence': 0, 'order_price': 900}, + 5001: {'name': {'cn': '贝类', 'en': 'Shellfish', 'jp': '貝'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 42}, + 5002: {'name': {'cn': '鲶鱼', 'en': 'Catfish', 'jp': 'ナマズ'}, 'pt_num': 2, 'manage_influence': 0, 'order_price': 250}, + 5003: {'name': {'cn': '鲤鱼', 'en': 'Koi Carp', 'jp': 'コイ'}, 'pt_num': 16, 'manage_influence': 0, 'order_price': 165}, + 5004: {'name': {'cn': '鲫鱼', 'en': 'Common Carp', 'jp': 'フナ'}, 'pt_num': 11, 'manage_influence': 0, 'order_price': 110}, + 5005: {'name': {'cn': '小河虾', 'en': 'Freshwater Shrimp', 'jp': '川エビ'}, 'pt_num': 12, 'manage_influence': 0, 'order_price': 100}, + 5006: {'name': {'cn': '小龙虾', 'en': 'Crayfish', 'jp': 'ザリガニ'}, 'pt_num': 13, 'manage_influence': 0, 'order_price': 115}, + 5007: {'name': {'cn': '鲈鱼', 'en': 'Sea Bass', 'jp': 'スズキ'}, 'pt_num': 16, 'manage_influence': 0, 'order_price': 136}, + 5008: {'name': {'cn': '螃蟹', 'en': 'Crab', 'jp': 'カニ'}, 'pt_num': 58, 'manage_influence': 0, 'order_price': 390}, + 5101: {'name': {'cn': '鱿鱼', 'en': 'Squid', 'jp': 'イカ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 116}, + 5102: {'name': {'cn': '马鲛鱼', 'en': 'Mackerel', 'jp': 'サワラ'}, 'pt_num': 27, 'manage_influence': 0, 'order_price': 270}, + 5103: {'name': {'cn': '金枪鱼', 'en': 'Tuna', 'jp': 'マグロ'}, 'pt_num': 104, 'manage_influence': 0, 'order_price': 870}, + 5104: {'name': {'cn': '三文鱼', 'en': 'Salmon', 'jp': 'サーモン'}, 'pt_num': 45, 'manage_influence': 0, 'order_price': 380}, + 5105: {'name': {'cn': '红鲷鱼', 'en': 'Red Sea Bream', 'jp': 'マダイ'}, 'pt_num': 1, 'manage_influence': 0, 'order_price': 172}, + 5106: {'name': {'cn': '黑鲷鱼', 'en': 'Black Porgy', 'jp': 'クロダイ'}, 'pt_num': 24, 'manage_influence': 0, 'order_price': 245}, + 5107: {'name': {'cn': '黄鳍金枪鱼', 'en': 'Yellowfin Tuna', 'jp': 'キハダ'}, 'pt_num': 198, 'manage_influence': 0, 'order_price': 1320}, + 5108: {'name': {'cn': '海参', 'en': 'Sea Cucumber', 'jp': 'ナマコ'}, 'pt_num': 162, 'manage_influence': 0, 'order_price': 1080}, + 9900: {'name': {'cn': '珍珠', 'en': 'Pearl', 'jp': '真珠'}, 'pt_num': 0, 'manage_influence': 0, 'order_price': 0}, +} + +DIC_ISLAND_RECIPE = { + 101001: {'name': {'cn': '小麦', 'en': 'Wheat', 'jp': '小麦'}, 'workload': 24000, 'commission_cost': {1000: 9}, 'production_limit': 12, 'commission_product': {2000: 162}, 'second_product_display': {}}, + 101002: {'name': {'cn': '玉米', 'en': 'Corn', 'jp': 'とうもろこし'}, 'workload': 24000, 'commission_cost': {1001: 9}, 'production_limit': 12, 'commission_product': {2001: 162}, 'second_product_display': {}}, + 101003: {'name': {'cn': '牧草', 'en': 'Grass', 'jp': '牧草'}, 'workload': 24000, 'commission_cost': {1008: 9}, 'production_limit': 12, 'commission_product': {2008: 108}, 'second_product_display': {}}, + 101004: {'name': {'cn': '咖啡豆', 'en': 'Coffee Beans', 'jp': 'コーヒー豆'}, 'workload': 84000, 'commission_cost': {1009: 9}, 'production_limit': 5, 'commission_product': {2009: 81}, 'second_product_display': {}}, + 101005: {'name': {'cn': '大米', 'en': 'Rice', 'jp': '米'}, 'workload': 48000, 'commission_cost': {1002: 9}, 'production_limit': 6, 'commission_product': {2002: 162}, 'second_product_display': {}}, + 101006: {'name': {'cn': '白菜', 'en': 'Napa Cabbage', 'jp': '白菜'}, 'workload': 24000, 'commission_cost': {1003: 9}, 'production_limit': 12, 'commission_product': {2003: 81}, 'second_product_display': {}}, + 101007: {'name': {'cn': '土豆', 'en': 'Potato', 'jp': 'じゃがいも'}, 'workload': 108000, 'commission_cost': {1005: 9}, 'production_limit': 5, 'commission_product': {2005: 243}, 'second_product_display': {}}, + 101008: {'name': {'cn': '大豆', 'en': 'Soy Beans', 'jp': '大豆'}, 'workload': 42000, 'commission_cost': {1006: 9}, 'production_limit': 6, 'commission_product': {2006: 243}, 'second_product_display': {}}, + 101013: {'name': {'cn': '鸡蛋', 'en': 'Eggs', 'jp': '卵'}, 'workload': 36000, 'commission_cost': {3000: 2}, 'production_limit': 8, 'commission_product': {2601: 5}, 'second_product_display': {2602: 4}}, + 101015: {'name': {'cn': '鲜肉', 'en': 'Fresh Meat', 'jp': '新鮮な肉'}, 'workload': 72000, 'commission_cost': {3001: 2}, 'production_limit': 5, 'commission_product': {2600: 4}, 'second_product_display': {}}, + 101016: {'name': {'cn': '牛奶', 'en': 'Milk', 'jp': '牛乳'}, 'workload': 54000, 'commission_cost': {3002: 2}, 'production_limit': 5, 'commission_product': {2603: 4}, 'second_product_display': {2604: 4}}, + 101018: {'name': {'cn': '羊毛', 'en': 'Wool', 'jp': '羊毛'}, 'workload': 180000, 'commission_cost': {3003: 2}, 'production_limit': 5, 'commission_product': {2605: 4}, 'second_product_display': {}}, + 201001: {'name': {'cn': '贝类', 'en': 'Shellfish', 'jp': '貝'}, 'workload': 54000, 'commission_cost': {1101: 1}, 'production_limit': 5, 'commission_product': {5001: 10}, 'second_product_display': {}}, + 201002: {'name': {'cn': '鲶鱼', 'en': 'Catfish', 'jp': 'ナマズ'}, 'workload': 72000, 'commission_cost': {1102: 1}, 'production_limit': 5, 'commission_product': {5002: 2}, 'second_product_display': {}}, + 201003: {'name': {'cn': '鲤鱼', 'en': 'Koi Carp', 'jp': 'コイ'}, 'workload': 72000, 'commission_cost': {1103: 1}, 'production_limit': 5, 'commission_product': {5003: 4}, 'second_product_display': {}}, + 201004: {'name': {'cn': '鲫鱼', 'en': 'Common Carp', 'jp': 'フナ'}, 'workload': 72000, 'commission_cost': {1104: 1}, 'production_limit': 5, 'commission_product': {5004: 6}, 'second_product_display': {}}, + 201005: {'name': {'cn': '小河虾', 'en': 'Freshwater Shrimp', 'jp': '川エビ'}, 'workload': 36000, 'commission_cost': {1105: 1}, 'production_limit': 8, 'commission_product': {5005: 12}, 'second_product_display': {}}, + 201006: {'name': {'cn': '小龙虾', 'en': 'Crayfish', 'jp': 'ザリガニ'}, 'workload': 90000, 'commission_cost': {1106: 1}, 'production_limit': 5, 'commission_product': {5006: 8}, 'second_product_display': {}}, + 201007: {'name': {'cn': '鲈鱼', 'en': 'Sea Bass', 'jp': 'スズキ'}, 'workload': 72000, 'commission_cost': {1107: 1}, 'production_limit': 5, 'commission_product': {5007: 6}, 'second_product_display': {}}, + 201008: {'name': {'cn': '螃蟹', 'en': 'Crab', 'jp': 'カニ'}, 'workload': 144000, 'commission_cost': {1108: 1}, 'production_limit': 5, 'commission_product': {5008: 4}, 'second_product_display': {}}, + 201101: {'name': {'cn': '鱿鱼', 'en': 'Squid', 'jp': 'イカ'}, 'workload': 54000, 'commission_cost': {1201: 1}, 'production_limit': 5, 'commission_product': {5101: 4}, 'second_product_display': {}}, + 201102: {'name': {'cn': '马鲛鱼', 'en': 'Mackerel', 'jp': 'サワラ'}, 'workload': 144000, 'commission_cost': {1202: 1}, 'production_limit': 5, 'commission_product': {5102: 4}, 'second_product_display': {}}, + 201103: {'name': {'cn': '金枪鱼', 'en': 'Tuna', 'jp': 'マグロ'}, 'workload': 216000, 'commission_cost': {1203: 1}, 'production_limit': 5, 'commission_product': {5103: 2}, 'second_product_display': {}}, + 201104: {'name': {'cn': '三文鱼', 'en': 'Salmon', 'jp': 'サーモン'}, 'workload': 180000, 'commission_cost': {1204: 1}, 'production_limit': 5, 'commission_product': {5104: 4}, 'second_product_display': {}}, + 201105: {'name': {'cn': '红鲷鱼', 'en': 'Red Sea Bream', 'jp': 'マダイ'}, 'workload': 108000, 'commission_cost': {1205: 1}, 'production_limit': 5, 'commission_product': {5105: 4}, 'second_product_display': {}}, + 201106: {'name': {'cn': '黑鲷鱼', 'en': 'Black Porgy', 'jp': 'クロダイ'}, 'workload': 126000, 'commission_cost': {1206: 1}, 'production_limit': 5, 'commission_product': {5106: 4}, 'second_product_display': {}}, + 201107: {'name': {'cn': '黄鳍金枪鱼', 'en': 'Yellowfin Tuna', 'jp': 'キハダ'}, 'workload': 288000, 'commission_cost': {1207: 1}, 'production_limit': 5, 'commission_product': {5107: 2}, 'second_product_display': {}}, + 201108: {'name': {'cn': '海参', 'en': 'Sea Cucumber', 'jp': 'ナマコ'}, 'workload': 216000, 'commission_cost': {1208: 1}, 'production_limit': 5, 'commission_product': {5108: 2}, 'second_product_display': {}}, + 401001: {'name': {'cn': '煤炭', 'en': 'Coal', 'jp': '石炭'}, 'workload': 12000, 'commission_cost': {}, 'production_limit': 12, 'commission_product': {2700: 8}, 'second_product_display': {}}, + 401002: {'name': {'cn': '铜矿', 'en': 'Copper Ore', 'jp': '銅鉱石'}, 'workload': 24000, 'commission_cost': {}, 'production_limit': 12, 'commission_product': {2701: 4}, 'second_product_display': {}}, + 401004: {'name': {'cn': '铝矿', 'en': 'Bauxite Ore', 'jp': 'アルミ鉱石'}, 'workload': 60000, 'commission_cost': {}, 'production_limit': 5, 'commission_product': {2702: 4}, 'second_product_display': {}}, + 401005: {'name': {'cn': '铁矿', 'en': 'Iron Ore', 'jp': '鉄鉱石'}, 'workload': 36000, 'commission_cost': {}, 'production_limit': 8, 'commission_product': {2703: 4}, 'second_product_display': {}}, + 401006: {'name': {'cn': '硫矿', 'en': 'Sulfur', 'jp': '硫黄鉱石'}, 'workload': 120000, 'commission_cost': {}, 'production_limit': 5, 'commission_product': {2704: 4}, 'second_product_display': {}}, + 401007: {'name': {'cn': '银矿', 'en': 'Silver Ore', 'jp': '銀鉱石'}, 'workload': 240000, 'commission_cost': {}, 'production_limit': 5, 'commission_product': {2705: 4}, 'second_product_display': {}}, + 402001: {'name': {'cn': '自然之木', 'en': 'Raw Timber', 'jp': '大自然の原木'}, 'workload': 12000, 'commission_cost': {}, 'production_limit': 12, 'commission_product': {2800: 8}, 'second_product_display': {}}, + 402002: {'name': {'cn': '实用之木', 'en': 'Workable Wood', 'jp': '実用の木材'}, 'workload': 30000, 'commission_cost': {}, 'production_limit': 9, 'commission_product': {2801: 4}, 'second_product_display': {}}, + 402003: {'name': {'cn': '精选之木', 'en': 'Premium Wood', 'jp': 'プレミアム木材'}, 'workload': 72000, 'commission_cost': {}, 'production_limit': 5, 'commission_product': {2802: 4}, 'second_product_display': {}}, + 402004: {'name': {'cn': '典雅之木', 'en': 'Elegant Wood', 'jp': 'エレガント木材'}, 'workload': 180000, 'commission_cost': {}, 'production_limit': 5, 'commission_product': {2803: 4}, 'second_product_display': {}}, + 501001: {'name': {'cn': '苹果', 'en': 'Apple', 'jp': 'りんご'}, 'workload': 45000, 'commission_cost': {1016: 4}, 'production_limit': 6, 'commission_product': {2016: 32}, 'second_product_display': {}}, + 501002: {'name': {'cn': '柑橘', 'en': 'Citrus Fruit', 'jp': '柑橘フルーツ'}, 'workload': 45000, 'commission_cost': {1017: 4}, 'production_limit': 6, 'commission_product': {2017: 32}, 'second_product_display': {}}, + 501003: {'name': {'cn': '香蕉', 'en': 'Banana', 'jp': 'バナナ'}, 'workload': 60000, 'commission_cost': {1018: 4}, 'production_limit': 5, 'commission_product': {2018: 32}, 'second_product_display': {}}, + 501004: {'name': {'cn': '芒果', 'en': 'Mango', 'jp': 'マンゴー'}, 'workload': 90000, 'commission_cost': {1019: 4}, 'production_limit': 5, 'commission_product': {2019: 32}, 'second_product_display': {}}, + 501005: {'name': {'cn': '柠檬', 'en': 'Lemon', 'jp': 'レモン'}, 'workload': 36000, 'commission_cost': {1020: 4}, 'production_limit': 8, 'commission_product': {2020: 48}, 'second_product_display': {}}, + 501006: {'name': {'cn': '牛油果', 'en': 'Avocado', 'jp': 'アボカド'}, 'workload': 120000, 'commission_cost': {1021: 4}, 'production_limit': 5, 'commission_product': {2021: 16}, 'second_product_display': {}}, + 501007: {'name': {'cn': '橡胶', 'en': 'Rubber', 'jp': 'ゴム'}, 'workload': 96000, 'commission_cost': {1022: 4}, 'production_limit': 5, 'commission_product': {2022: 32}, 'second_product_display': {}}, + 502001: {'name': {'cn': '亚麻', 'en': 'Flax', 'jp': '亜麻'}, 'workload': 24000, 'commission_cost': {1010: 3}, 'production_limit': 12, 'commission_product': {2010: 18}, 'second_product_display': {}}, + 502002: {'name': {'cn': '草莓', 'en': 'Strawberries', 'jp': 'いちご'}, 'workload': 54000, 'commission_cost': {1011: 3}, 'production_limit': 5, 'commission_product': {2011: 54}, 'second_product_display': {}}, + 502003: {'name': {'cn': '棉花', 'en': 'Cotton', 'jp': '綿'}, 'workload': 36000, 'commission_cost': {1012: 3}, 'production_limit': 8, 'commission_product': {2012: 18}, 'second_product_display': {}}, + 502004: {'name': {'cn': '茶叶', 'en': 'Tea Leaves', 'jp': '茶葉'}, 'workload': 54000, 'commission_cost': {1014: 3}, 'production_limit': 5, 'commission_product': {2014: 36}, 'second_product_display': {}}, + 502005: {'name': {'cn': '薰衣草', 'en': 'Lavender', 'jp': 'ラベンダー'}, 'workload': 108000, 'commission_cost': {1015: 3}, 'production_limit': 5, 'commission_product': {2015: 24}, 'second_product_display': {}}, + 502006: {'name': {'cn': '胡萝卜', 'en': 'Carrot', 'jp': 'ニンジン'}, 'workload': 24000, 'commission_cost': {1004: 3}, 'production_limit': 12, 'commission_product': {2004: 36}, 'second_product_display': {}}, + 502007: {'name': {'cn': '洋葱', 'en': 'Onion', 'jp': '玉ねぎ'}, 'workload': 54000, 'commission_cost': {1007: 3}, 'production_limit': 5, 'commission_product': {2007: 12}, 'second_product_display': {}}, + 601001: {'name': {'cn': '豆腐', 'en': 'Tofu', 'jp': '豆腐'}, 'workload': 24000, 'commission_cost': {2006: 15}, 'production_limit': 12, 'commission_product': {3011: 1}, 'second_product_display': {}}, + 601002: {'name': {'cn': '肉末烧豆腐', 'en': 'Tofu with Minced Meat', 'jp': '肉そぼろ豆腐'}, 'workload': 18000, 'commission_cost': {3011: 2, 2600: 1}, 'production_limit': 12, 'commission_product': {3012: 1}, 'second_product_display': {}}, + 601003: {'name': {'cn': '蛋包饭', 'en': 'Omurice', 'jp': 'オムライス'}, 'workload': 12000, 'commission_cost': {2601: 4, 2002: 9}, 'production_limit': 12, 'commission_product': {3013: 1}, 'second_product_display': {}}, + 601004: {'name': {'cn': '白菜豆腐汤', 'en': 'Cabbage and Tofu Soup', 'jp': '白菜と豆腐のスープ'}, 'workload': 18000, 'commission_cost': {2003: 6, 3011: 1}, 'production_limit': 12, 'commission_product': {3014: 1}, 'second_product_display': {}}, + 601005: {'name': {'cn': '蔬菜沙拉', 'en': 'Vegetable Salad', 'jp': '野菜サラダ'}, 'workload': 6000, 'commission_cost': {2004: 2, 2003: 3, 2001: 1}, 'production_limit': 12, 'commission_product': {3015: 1}, 'second_product_display': {}}, + 601006: {'name': {'cn': '炸鱼薯条', 'en': 'Fish & Chips', 'jp': 'フィッシュ&チップス'}, 'workload': 3000, 'commission_cost': {2522: 1, 2005: 2}, 'production_limit': 12, 'commission_product': {3114: 1}, 'second_product_display': {}}, + 601007: {'name': {'cn': '洋葱蒸鱼', 'en': 'Steamed Fish with Onions', 'jp': '魚の玉ねぎ蒸し'}, 'workload': 18000, 'commission_cost': {2521: 3, 2007: 1}, 'production_limit': 12, 'commission_product': {3116: 1}, 'second_product_display': {}}, + 601008: {'name': {'cn': '佛跳墙', 'en': "Buddha's Temptation", 'jp': '佛跳牆'}, 'workload': 36000, 'commission_cost': {5108: 1, 2602: 3, 2522: 2}, 'production_limit': 8, 'commission_product': {3120: 1}, 'second_product_display': {}}, + 601101: {'name': {'cn': '经典豆腐套餐', 'en': 'Classic Tofu Combo', 'jp': '定番豆腐セット'}, 'workload': 6000, 'commission_cost': {3012: 1, 3014: 1}, 'production_limit': 12, 'commission_product': {3101: 1}, 'second_product_display': {}}, + 601102: {'name': {'cn': '绵玉定食', 'en': 'Hearty Meal', 'jp': 'ふんわり定食'}, 'workload': 6000, 'commission_cost': {3013: 1, 3011: 1}, 'production_limit': 12, 'commission_product': {3102: 1}, 'second_product_display': {}}, + 602001: {'name': {'cn': '苹果汁', 'en': 'Apple Juice', 'jp': 'りんごジュース'}, 'workload': 6000, 'commission_cost': {2016: 2}, 'production_limit': 12, 'commission_product': {3017: 1}, 'second_product_display': {}}, + 602002: {'name': {'cn': '香蕉芒果汁', 'en': 'Banana and Mango Juice', 'jp': 'バナナマンゴージュース'}, 'workload': 9000, 'commission_cost': {2018: 1, 2019: 1}, 'production_limit': 12, 'commission_product': {3018: 1}, 'second_product_display': {}}, + 602003: {'name': {'cn': '蜂蜜柠檬水', 'en': 'Honey and Lemon Water', 'jp': 'はちみつレモン水'}, 'workload': 6000, 'commission_cost': {2020: 3, 2606: 1}, 'production_limit': 12, 'commission_product': {3019: 1}, 'second_product_display': {}}, + 602004: {'name': {'cn': '草莓蜜沁', 'en': 'Strawberry Lemon Drink', 'jp': 'いちごレモンドリンク'}, 'workload': 12000, 'commission_cost': {2011: 5, 2020: 2}, 'production_limit': 12, 'commission_product': {3020: 1}, 'second_product_display': {}}, + 602005: {'name': {'cn': '薰衣草茶', 'en': 'Lavender Tea', 'jp': 'ラベンダーティー'}, 'workload': 24000, 'commission_cost': {2014: 6, 2015: 4}, 'production_limit': 12, 'commission_product': {3021: 1}, 'second_product_display': {}}, + 602006: {'name': {'cn': '草莓蜂蜜冰沙', 'en': 'Strawberry Honey Frappé', 'jp': 'いちごのハニーフラッペ'}, 'workload': 18000, 'commission_cost': {2011: 10, 2606: 4}, 'production_limit': 12, 'commission_product': {3022: 1}, 'second_product_display': {}}, + 602101: {'name': {'cn': '花香果韵', 'en': 'Floral and Fruity', 'jp': '香りも楽しめるフルーツセット'}, 'workload': 3000, 'commission_cost': {3021: 1, 3017: 1}, 'production_limit': 12, 'commission_product': {3103: 1}, 'second_product_display': {}}, + 602102: {'name': {'cn': '缤纷果乐园', 'en': 'Colorful Fruit Paradise', 'jp': 'カラフル果物パラダイス'}, 'workload': 3000, 'commission_cost': {3018: 1, 3022: 1}, 'production_limit': 12, 'commission_product': {3104: 1}, 'second_product_display': {}}, + 602103: {'name': {'cn': '阳光蜜水', 'en': 'Sunny Honey', 'jp': '太陽のハニー'}, 'workload': 3000, 'commission_cost': {3020: 1, 3019: 1}, 'production_limit': 12, 'commission_product': {3105: 1}, 'second_product_display': {}}, + 603001: {'name': {'cn': '玉米杯', 'en': 'Corn Cup', 'jp': 'コーンカップ'}, 'workload': 3000, 'commission_cost': {2001: 3, 2603: 1}, 'production_limit': 12, 'commission_product': {3023: 1}, 'second_product_display': {}}, + 603002: {'name': {'cn': '苹果派', 'en': 'Apple Pie', 'jp': 'アップルパイ'}, 'workload': 18000, 'commission_cost': {3004: 5, 2016: 3}, 'production_limit': 12, 'commission_product': {3009: 1}, 'second_product_display': {}}, + 603003: {'name': {'cn': '香橙派', 'en': 'Orange Pie', 'jp': 'オレンジパイ'}, 'workload': 18000, 'commission_cost': {2017: 3, 3004: 6}, 'production_limit': 12, 'commission_product': {3024: 1}, 'second_product_display': {}}, + 603004: {'name': {'cn': '芒果糯米饭', 'en': 'Sticky Rice with Mango', 'jp': 'マンゴーともち米の蒸し飯'}, 'workload': 12000, 'commission_cost': {2019: 3, 2002: 2}, 'production_limit': 12, 'commission_product': {3025: 1}, 'second_product_display': {}}, + 603005: {'name': {'cn': '香蕉可丽饼', 'en': 'Banana Crêpe', 'jp': 'バナナクレープ'}, 'workload': 9000, 'commission_cost': {2018: 2, 3004: 2}, 'production_limit': 12, 'commission_product': {3026: 1}, 'second_product_display': {}}, + 603006: {'name': {'cn': '草莓夏洛特', 'en': 'Strawberry Charlotte', 'jp': 'いちごシャルロット'}, 'workload': 21000, 'commission_cost': {2011: 1, 3006: 2, 3004: 2}, 'production_limit': 12, 'commission_product': {3028: 1}, 'second_product_display': {}}, + 603007: {'name': {'cn': '海鲜饭', 'en': 'Paella', 'jp': 'パエリア'}, 'workload': 18000, 'commission_cost': {5008: 1, 5101: 2, 2002: 5}, 'production_limit': 12, 'commission_product': {3118: 1}, 'second_product_display': {}}, + 603101: {'name': {'cn': '香甜组合', 'en': 'Succulently Sweet', 'jp': 'スイートテイスト'}, 'workload': 3000, 'commission_cost': {3025: 1, 3023: 1}, 'production_limit': 12, 'commission_product': {3106: 1}, 'second_product_display': {}}, + 603102: {'name': {'cn': '果园二重奏', 'en': 'Orchard Duo', 'jp': '果樹園二重奏'}, 'workload': 3000, 'commission_cost': {3026: 1, 3009: 1}, 'production_limit': 12, 'commission_product': {3107: 1}, 'second_product_display': {}}, + 603103: {'name': {'cn': '莓果香橙甜点组', 'en': 'Berry and Orange Dessert', 'jp': 'ベリーオレンジスイーツ'}, 'workload': 3000, 'commission_cost': {3028: 1, 3024: 1}, 'production_limit': 12, 'commission_product': {3108: 1}, 'second_product_display': {}}, + 604001: {'name': {'cn': '炭烤肉串', 'en': 'Coal-Roasted Skewer', 'jp': '炭火串焼き'}, 'workload': 12000, 'commission_cost': {2600: 4}, 'production_limit': 12, 'commission_product': {3029: 1}, 'second_product_display': {}}, + 604002: {'name': {'cn': '禽肉土豆拼盘', 'en': "Chicken and Potato Hors d'Oeuvre", 'jp': '鶏肉とポテトの盛り合わせ'}, 'workload': 18000, 'commission_cost': {2005: 6, 2602: 5}, 'production_limit': 12, 'commission_product': {3030: 1}, 'second_product_display': {}}, + 604004: {'name': {'cn': '爆炒禽肉', 'en': 'Stir-Fried Chicken', 'jp': '鶏肉炒め'}, 'workload': 15000, 'commission_cost': {2602: 3, 2007: 1}, 'production_limit': 12, 'commission_product': {3032: 1}, 'second_product_display': {}}, + 604005: {'name': {'cn': '胡萝卜厚蛋烧', 'en': 'Rolled Carrot Omelette', 'jp': 'ニンジン厚焼き玉子'}, 'workload': 6000, 'commission_cost': {2601: 5, 2004: 2}, 'production_limit': 12, 'commission_product': {3033: 1}, 'second_product_display': {}}, + 604006: {'name': {'cn': '汉堡肉饭', 'en': 'Steak Bowl', 'jp': 'ハンバーグ丼'}, 'workload': 15000, 'commission_cost': {2002: 12, 2600: 6, 2003: 2}, 'production_limit': 12, 'commission_product': {3034: 1}, 'second_product_display': {}}, + 604007: {'name': {'cn': '柠檬虾', 'en': 'Lemon Shrimp', 'jp': 'レモンエビ'}, 'workload': 6000, 'commission_cost': {5005: 4, 2020: 1}, 'production_limit': 12, 'commission_product': {3115: 1}, 'second_product_display': {}}, + 604008: {'name': {'cn': '爆炒小龙虾', 'en': 'Crayfish Stir-Fry', 'jp': 'ザリガニ炒め'}, 'workload': 9000, 'commission_cost': {5006: 5}, 'production_limit': 12, 'commission_product': {3119: 1}, 'second_product_display': {}}, + 604101: {'name': {'cn': '烤肉狂欢', 'en': 'The Carne-val', 'jp': '肉カーニバル'}, 'workload': 6000, 'commission_cost': {3029: 1, 3030: 1}, 'production_limit': 12, 'commission_product': {3109: 1}, 'second_product_display': {}}, + 604102: {'name': {'cn': '能量双拼套餐', 'en': 'Double Energy Combo', 'jp': 'エナジーダブルコンボ'}, 'workload': 6000, 'commission_cost': {3034: 1, 3032: 1}, 'production_limit': 12, 'commission_product': {3110: 1}, 'second_product_display': {}}, + 701001: {'name': {'cn': '布料', 'en': 'Cloth', 'jp': '布生地'}, 'workload': 18000, 'commission_cost': {2010: 4}, 'production_limit': 12, 'commission_product': {3035: 1}, 'second_product_display': {}}, + 701002: {'name': {'cn': '皮革', 'en': 'Leather', 'jp': '革'}, 'workload': 60000, 'commission_cost': {2604: 1}, 'production_limit': 5, 'commission_product': {3036: 1}, 'second_product_display': {}}, + 701003: {'name': {'cn': '绳索', 'en': 'Rope', 'jp': 'ロープ'}, 'workload': 108000, 'commission_cost': {2012: 1, 2010: 1}, 'production_limit': 5, 'commission_product': {3037: 1}, 'second_product_display': {}}, + 701004: {'name': {'cn': '手套', 'en': 'Gloves', 'jp': '手袋'}, 'workload': 72000, 'commission_cost': {2022: 1, 3035: 1}, 'production_limit': 5, 'commission_product': {3038: 1}, 'second_product_display': {}}, + 701005: {'name': {'cn': '香囊', 'en': 'Aroma Sachet', 'jp': '香り袋'}, 'workload': 108000, 'commission_cost': {3035: 1, 2015: 1}, 'production_limit': 5, 'commission_product': {3039: 1}, 'second_product_display': {}}, + 701006: {'name': {'cn': '鞋靴', 'en': 'Shoes', 'jp': '靴'}, 'workload': 144000, 'commission_cost': {3036: 1, 2022: 1, 2605: 1}, 'production_limit': 5, 'commission_product': {3040: 1}, 'second_product_display': {}}, + 701007: {'name': {'cn': '绷带', 'en': 'Wound Dressings', 'jp': '包帯'}, 'workload': 48000, 'commission_cost': {2705: 1, 3035: 1, 2012: 1}, 'production_limit': 6, 'commission_product': {3041: 1}, 'second_product_display': {}}, + 701008: {'name': {'cn': '炭笔', 'en': 'Charcoal Brush', 'jp': '木炭筆'}, 'workload': 36000, 'commission_cost': {2700: 1, 2801: 1}, 'production_limit': 8, 'commission_product': {3042: 1}, 'second_product_display': {}}, + 701009: {'name': {'cn': '电缆', 'en': 'Cable', 'jp': 'ケーブル'}, 'workload': 108000, 'commission_cost': {2701: 1, 2022: 1}, 'production_limit': 5, 'commission_product': {3043: 1}, 'second_product_display': {}}, + 701010: {'name': {'cn': '铁钉', 'en': 'Nails', 'jp': '鉄釘'}, 'workload': 144000, 'commission_cost': {2703: 1}, 'production_limit': 5, 'commission_product': {3044: 1}, 'second_product_display': {}}, + 701011: {'name': {'cn': '硫酸', 'en': 'Chemicals', 'jp': '化学品'}, 'workload': 72000, 'commission_cost': {2704: 1}, 'production_limit': 5, 'commission_product': {3045: 1}, 'second_product_display': {}}, + 701012: {'name': {'cn': '火药', 'en': 'Gunpowder', 'jp': '火薬'}, 'workload': 72000, 'commission_cost': {3045: 1, 2700: 1}, 'production_limit': 5, 'commission_product': {3046: 1}, 'second_product_display': {}}, + 701013: {'name': {'cn': '刀叉餐具', 'en': 'Utensils', 'jp': '食器'}, 'workload': 216000, 'commission_cost': {2705: 1}, 'production_limit': 5, 'commission_product': {3047: 1}, 'second_product_display': {}}, + 701014: {'name': {'cn': '纸张', 'en': 'Paper', 'jp': '紙'}, 'workload': 36000, 'commission_cost': {2800: 10}, 'production_limit': 8, 'commission_product': {3048: 1}, 'second_product_display': {}}, + 701015: {'name': {'cn': '记事本', 'en': 'Notebook', 'jp': 'メモ帳'}, 'workload': 72000, 'commission_cost': {3048: 4, 2801: 1}, 'production_limit': 5, 'commission_product': {3049: 1}, 'second_product_display': {}}, + 701016: {'name': {'cn': '桌椅', 'en': 'Chair and Desk', 'jp': '机と椅子'}, 'workload': 108000, 'commission_cost': {2801: 1, 2702: 1}, 'production_limit': 5, 'commission_product': {3050: 1}, 'second_product_display': {}}, + 701017: {'name': {'cn': '精选木桶', 'en': 'Choice Wooden Barrel', 'jp': 'セレクション樽'}, 'workload': 108000, 'commission_cost': {2802: 1, 3044: 1}, 'production_limit': 5, 'commission_product': {3051: 1}, 'second_product_display': {}}, + 701018: {'name': {'cn': '文件柜', 'en': 'Filing Cabinet', 'jp': 'ファイルキャビネット'}, 'workload': 180000, 'commission_cost': {2803: 1, 3044: 1}, 'production_limit': 5, 'commission_product': {3052: 1}, 'second_product_display': {}}, + 701019: {'name': {'cn': '墨盒', 'en': 'Ink Cartridge', 'jp': 'インクカートリッジ'}, 'workload': 72000, 'commission_cost': {2700: 1, 2702: 1}, 'production_limit': 5, 'commission_product': {3053: 1}, 'second_product_display': {}}, + 701020: {'name': {'cn': '钟表', 'en': 'Clock', 'jp': '時計'}, 'workload': 216000, 'commission_cost': {2705: 1, 2703: 1, 2701: 1}, 'production_limit': 5, 'commission_product': {3054: 1}, 'second_product_display': {}}, + 701021: {'name': {'cn': '蓄电池', 'en': 'Battery', 'jp': '蓄電池'}, 'workload': 108000, 'commission_cost': {3045: 1, 2701: 1, 2703: 1}, 'production_limit': 5, 'commission_product': {3055: 1}, 'second_product_display': {}}, + 701022: {'name': {'cn': '净水滤芯', 'en': 'Water Filter', 'jp': '浄水フィルター'}, 'workload': 144000, 'commission_cost': {2700: 1, 2705: 1, 2012: 1}, 'production_limit': 5, 'commission_product': {3056: 1}, 'second_product_display': {}}, + 701023: {'name': {'cn': '装饰画', 'en': 'Ornamental Painting', 'jp': '装飾画'}, 'workload': 36000, 'commission_cost': {5001: 3, 3048: 3}, 'production_limit': 8, 'commission_product': {3117: 1}, 'second_product_display': {}}, + 901001: {'name': {'cn': '欧姆蛋', 'en': 'Omelette', 'jp': 'オムレツ'}, 'workload': 3000, 'commission_cost': {2601: 1}, 'production_limit': 12, 'commission_product': {3059: 1}, 'second_product_display': {}}, + 901002: {'name': {'cn': '冰咖啡', 'en': 'Iced Coffee', 'jp': 'アイスコーヒー'}, 'workload': 9000, 'commission_cost': {2009: 2}, 'production_limit': 12, 'commission_product': {3005: 1}, 'second_product_display': {}}, + 901003: {'name': {'cn': '芝士', 'en': 'Cheese', 'jp': 'チーズ'}, 'workload': 24000, 'commission_cost': {2603: 8}, 'production_limit': 12, 'commission_product': {3006: 1}, 'second_product_display': {}}, + 901004: {'name': {'cn': '拿铁', 'en': 'Latte', 'jp': 'ラテ'}, 'workload': 12000, 'commission_cost': {2009: 3, 2603: 2}, 'production_limit': 12, 'commission_product': {3007: 1}, 'second_product_display': {}}, + 901005: {'name': {'cn': '柑橘咖啡', 'en': 'Citrus Coffee', 'jp': 'シトラスコーヒー'}, 'workload': 9000, 'commission_cost': {2017: 1, 2009: 3}, 'production_limit': 12, 'commission_product': {3008: 1}, 'second_product_display': {}}, + 901006: {'name': {'cn': '草莓奶绿', 'en': 'Strawberry Milkshake', 'jp': 'いちごミルクシェイク'}, 'workload': 12000, 'commission_cost': {2014: 1, 2011: 1, 2603: 1}, 'production_limit': 12, 'commission_product': {3010: 1}, 'second_product_display': {}}, + 901101: {'name': {'cn': '晨光活力组合', 'en': 'Morning Light Energy Combo', 'jp': '朝光活力コンビ'}, 'workload': 6000, 'commission_cost': {3059: 1, 3007: 1}, 'production_limit': 12, 'commission_product': {3111: 1}, 'second_product_display': {}}, + 901102: {'name': {'cn': '醒神套餐', 'en': 'The Wake-Up Call', 'jp': 'お目覚めブレックファスト'}, 'workload': 6000, 'commission_cost': {3005: 1, 3006: 1}, 'production_limit': 12, 'commission_product': {3112: 1}, 'second_product_display': {}}, + 901103: {'name': {'cn': '果香双杯乐', 'en': 'Fruity & Fruitier', 'jp': 'フルーツツインズ'}, 'workload': 6000, 'commission_cost': {3008: 1, 3010: 1}, 'production_limit': 12, 'commission_product': {3113: 1}, 'second_product_display': {}}, + 9900001: {'name': {'cn': '秋月梨', 'en': 'Yoizuki Pear', 'jp': '宵月梨'}, 'workload': 18000, 'commission_cost': {4006: 1}, 'production_limit': 5, 'commission_product': {4005: 8}, 'second_product_display': {}}, + 9900002: {'name': {'cn': '柿子', 'en': 'Kaki Persimmon', 'jp': '柿'}, 'workload': 18000, 'commission_cost': {4008: 1}, 'production_limit': 5, 'commission_product': {4007: 4}, 'second_product_display': {}}, + 9900003: {'name': {'cn': '柿子饼', 'en': 'Dried Persimmon', 'jp': '干し柿'}, 'workload': 18000, 'commission_cost': {4007: 1}, 'production_limit': 5, 'commission_product': {4009: 1}, 'second_product_display': {}}, + 9900004: {'name': {'cn': '松茸鸡汤', 'en': 'Matsutake and Chicken Soup', 'jp': '松茸と鶏のスープ'}, 'workload': 18000, 'commission_cost': {2602: 2, 4004: 1}, 'production_limit': 5, 'commission_product': {4010: 1}, 'second_product_display': {}}, + 9900005: {'name': {'cn': '秋季花束', 'en': 'Autumn Bouquet', 'jp': '秋のブーケ'}, 'workload': 18000, 'commission_cost': {4002: 2, 4001: 1}, 'production_limit': 5, 'commission_product': {4011: 1}, 'second_product_display': {}}, + 9900006: {'name': {'cn': '花生油', 'en': 'Peanut Oil', 'jp': '落花生油'}, 'workload': 18000, 'commission_cost': {4003: 8}, 'production_limit': 5, 'commission_product': {4012: 1}, 'second_product_display': {}}, + 9900007: {'name': {'cn': '胡萝卜秋梨汁', 'en': 'Carrot and Pear Juice', 'jp': 'ニンジンと梨のジュース'}, 'workload': 18000, 'commission_cost': {4005: 3, 2004: 2}, 'production_limit': 5, 'commission_product': {4013: 1}, 'second_product_display': {}}, + 9900008: {'name': {'cn': '菊花茶', 'en': 'Chrysanthemum Tea', 'jp': '菊花の茶'}, 'workload': 18000, 'commission_cost': {4001: 2}, 'production_limit': 5, 'commission_product': {4014: 1}, 'second_product_display': {}}, + 9900009: {'name': {'cn': '芦笋', 'en': 'Asparagus', 'jp': 'アスパラガス'}, 'workload': 96000, 'commission_cost': {4020: 3}, 'production_limit': 5, 'commission_product': {4019: 18}, 'second_product_display': {}}, + 9900010: {'name': {'cn': '凤梨', 'en': 'Pineapple', 'jp': 'パイナップル'}, 'workload': 54000, 'commission_cost': {4022: 3}, 'production_limit': 5, 'commission_product': {4021: 12}, 'second_product_display': {}}, + 9900011: {'name': {'cn': '鲜榨菠萝汁', 'en': 'Fresh Pineapple Juice', 'jp': '搾りたてパイナップルジュース'}, 'workload': 6000, 'commission_cost': {4021: 2}, 'production_limit': 5, 'commission_product': {4023: 1}, 'second_product_display': {}}, + 9900012: {'name': {'cn': '迎春花茶', 'en': 'Winter Jasmine Tea', 'jp': 'オウバイ茶'}, 'workload': 24000, 'commission_cost': {4017: 3, 2014: 1}, 'production_limit': 5, 'commission_product': {4024: 1}, 'second_product_display': {}}, + 9900013: {'name': {'cn': '凉拌双笋', 'en': 'Cold Mixed Bamboo Shoots and Asparagus', 'jp': 'タケノコとアスパラの冷菜'}, 'workload': 9000, 'commission_cost': {4015: 2, 4019: 1}, 'production_limit': 5, 'commission_product': {4025: 1}, 'second_product_display': {}}, + 9900014: {'name': {'cn': '芦笋炒虾仁', 'en': 'Shrimp and Asparagus Stir-Fry', 'jp': 'エビのアスパラ炒め物'}, 'workload': 12000, 'commission_cost': {4019: 3, 5005: 6}, 'production_limit': 5, 'commission_product': {4026: 1}, 'second_product_display': {}}, + 9900015: {'name': {'cn': '袋装荠菜干', 'en': "Dried Shepherd's Purse", 'jp': '乾燥ナズナ'}, 'workload': 48000, 'commission_cost': {4016: 2}, 'production_limit': 5, 'commission_product': {4027: 1}, 'second_product_display': {}}, + 9900016: {'name': {'cn': '春季花束', 'en': 'Spring Bouquet', 'jp': '春の花束'}, 'workload': 18000, 'commission_cost': {4017: 2, 4018: 1}, 'production_limit': 5, 'commission_product': {4028: 1}, 'second_product_display': {}}, + 9900017: {'name': {'cn': '番茄', 'en': 'Tomato', 'jp': 'トマト'}, 'workload': 72000, 'commission_cost': {4034: 3}, 'production_limit': 5, 'commission_product': {4033: 18}, 'second_product_display': {}}, + 9900018: {'name': {'cn': '黄瓜', 'en': 'Cucumber', 'jp': 'キュウリ'}, 'workload': 36000, 'commission_cost': {4036: 3}, 'production_limit': 5, 'commission_product': {4035: 12}, 'second_product_display': {}}, + 9900019: {'name': {'cn': '黄瓜汁', 'en': 'Cucumber Juice', 'jp': 'キュウリジュース'}, 'workload': 6000, 'commission_cost': {4035: 4}, 'production_limit': 5, 'commission_product': {4037: 1}, 'second_product_display': {}}, + 9900020: {'name': {'cn': '西瓜汁', 'en': 'Watermelon Juice', 'jp': 'スイカジュース'}, 'workload': 6000, 'commission_cost': {4031: 1}, 'production_limit': 5, 'commission_product': {4038: 1}, 'second_product_display': {}}, + 9900021: {'name': {'cn': '苋菜饭团', 'en': 'Amaranth Onigiri', 'jp': 'ヒユナ入りおにぎり'}, 'workload': 18000, 'commission_cost': {4032: 4, 2002: 6}, 'production_limit': 5, 'commission_product': {4039: 1}, 'second_product_display': {}}, + 9900022: {'name': {'cn': '番茄炒蛋', 'en': 'Tomato and Egg Stir-Fry', 'jp': 'トマトと卵の炒め'}, 'workload': 9000, 'commission_cost': {4033: 4, 2601: 8}, 'production_limit': 5, 'commission_product': {4040: 1}, 'second_product_display': {}}, + 9900023: {'name': {'cn': '茉莉精油', 'en': 'Jasmine Essential Oil', 'jp': 'ジャスミン精油'}, 'workload': 48000, 'commission_cost': {4029: 3}, 'production_limit': 5, 'commission_product': {4041: 1}, 'second_product_display': {}}, + 9900024: {'name': {'cn': '夏季花束', 'en': 'Summery Bouquet', 'jp': '夏の花束'}, 'workload': 6000, 'commission_cost': {4029: 2, 4030: 2}, 'production_limit': 5, 'commission_product': {4042: 1}, 'second_product_display': {}}, +} + +DIC_ISLAND_SLOT = { + 9001: {'attribute': 1, 'place': 101, 'formula': [101001, 101002, 101003, 101004, 101005, 101006, 101007, 101008], 'activity_formula': []}, + 9002: {'attribute': 1, 'place': 101, 'formula': [101001, 101002, 101003, 101004, 101005, 101006, 101007, 101008], 'activity_formula': []}, + 9003: {'attribute': 1, 'place': 101, 'formula': [101001, 101002, 101003, 101004, 101005, 101006, 101007, 101008], 'activity_formula': []}, + 9004: {'attribute': 1, 'place': 101, 'formula': [101001, 101002, 101003, 101004, 101005, 101006, 101007, 101008], 'activity_formula': []}, + 9011: {'attribute': 2, 'place': 401, 'formula': [401001, 401002, 401004, 401005, 401006, 401007], 'activity_formula': []}, + 9012: {'attribute': 2, 'place': 401, 'formula': [401001, 401002, 401004, 401005, 401006, 401007], 'activity_formula': []}, + 9013: {'attribute': 2, 'place': 401, 'formula': [401001, 401002, 401004, 401005, 401006, 401007], 'activity_formula': []}, + 9014: {'attribute': 2, 'place': 401, 'formula': [401001, 401002, 401004, 401005, 401006, 401007], 'activity_formula': []}, + 9021: {'attribute': 2, 'place': 402, 'formula': [402001, 402002, 402003, 402004], 'activity_formula': []}, + 9022: {'attribute': 2, 'place': 402, 'formula': [402001, 402002, 402003, 402004], 'activity_formula': []}, + 9023: {'attribute': 2, 'place': 402, 'formula': [402001, 402002, 402003, 402004], 'activity_formula': []}, + 9024: {'attribute': 2, 'place': 402, 'formula': [402001, 402002, 402003, 402004], 'activity_formula': []}, + 9031: {'attribute': 3, 'place': 102, 'formula': [101013], 'activity_formula': []}, + 9032: {'attribute': 3, 'place': 102, 'formula': [101015], 'activity_formula': []}, + 9033: {'attribute': 3, 'place': 102, 'formula': [101016], 'activity_formula': []}, + 9034: {'attribute': 3, 'place': 102, 'formula': [101018], 'activity_formula': []}, + 9041: {'attribute': 4, 'place': 901, 'formula': [901001, 901002, 901003, 901004, 901005, 901006, 901101, 901102, 901103], 'activity_formula': []}, + 9042: {'attribute': 4, 'place': 901, 'formula': [901001, 901002, 901003, 901004, 901005, 901006, 901101, 901102, 901103], 'activity_formula': []}, + 9061: {'attribute': 4, 'place': 601, 'formula': [601001, 601002, 601003, 601004, 601005, 601006, 601007, 601008, 601101, 601102], 'activity_formula': [9900021, 9900022]}, + 9062: {'attribute': 4, 'place': 601, 'formula': [601001, 601002, 601003, 601004, 601005, 601006, 601007, 601008, 601101, 601102], 'activity_formula': [9900021, 9900022]}, + 9071: {'attribute': 4, 'place': 602, 'formula': [602001, 602002, 602003, 602004, 602005, 602006, 602101, 602102, 602103], 'activity_formula': [9900019, 9900020]}, + 9072: {'attribute': 4, 'place': 602, 'formula': [602001, 602002, 602003, 602004, 602005, 602006, 602101, 602102, 602103], 'activity_formula': [9900019, 9900020]}, + 9081: {'attribute': 4, 'place': 603, 'formula': [603001, 603002, 603003, 603004, 603005, 603006, 603007, 603101, 603102, 603103], 'activity_formula': []}, + 9082: {'attribute': 4, 'place': 603, 'formula': [603001, 603002, 603003, 603004, 603005, 603006, 603007, 603101, 603102, 603103], 'activity_formula': []}, + 9091: {'attribute': 4, 'place': 604, 'formula': [604001, 604002, 604004, 604005, 604006, 604007, 604008, 604101, 604102], 'activity_formula': []}, + 9092: {'attribute': 4, 'place': 604, 'formula': [604001, 604002, 604004, 604005, 604006, 604007, 604008, 604101, 604102], 'activity_formula': []}, + 9101: {'attribute': 1, 'place': 501, 'formula': [501001, 501002, 501003, 501004, 501005, 501006, 501007], 'activity_formula': []}, + 9102: {'attribute': 1, 'place': 501, 'formula': [501001, 501002, 501003, 501004, 501005, 501006, 501007], 'activity_formula': []}, + 9103: {'attribute': 1, 'place': 501, 'formula': [501001, 501002, 501003, 501004, 501005, 501006, 501007], 'activity_formula': []}, + 9104: {'attribute': 1, 'place': 501, 'formula': [501001, 501002, 501003, 501004, 501005, 501006, 501007], 'activity_formula': []}, + 9111: {'attribute': 1, 'place': 502, 'formula': [502001, 502002, 502003, 502004, 502005, 502006, 502007], 'activity_formula': [9900017, 9900018]}, + 9112: {'attribute': 1, 'place': 502, 'formula': [502001, 502002, 502003, 502004, 502005, 502006, 502007], 'activity_formula': [9900017, 9900018]}, + 9201: {'attribute': 6, 'place': 703, 'formula': [701014, 701015, 701016, 701017, 701018, 701023], 'activity_formula': []}, + 9202: {'attribute': 6, 'place': 703, 'formula': [701014, 701015, 701016, 701017, 701018, 701023], 'activity_formula': []}, + 9203: {'attribute': 6, 'place': 704, 'formula': [701008, 701009, 701010, 701011, 701012, 701013], 'activity_formula': []}, + 9204: {'attribute': 6, 'place': 704, 'formula': [701008, 701009, 701010, 701011, 701012, 701013], 'activity_formula': []}, + 9205: {'attribute': 6, 'place': 705, 'formula': [701019, 701020, 701021, 701022], 'activity_formula': []}, + 9206: {'attribute': 6, 'place': 705, 'formula': [701019, 701020, 701021, 701022], 'activity_formula': []}, + 9207: {'attribute': 6, 'place': 706, 'formula': [701001, 701002, 701003, 701004, 701005, 701006, 701007], 'activity_formula': [9900023, 9900024]}, + 9208: {'attribute': 6, 'place': 706, 'formula': [701001, 701002, 701003, 701004, 701005, 701006, 701007], 'activity_formula': [9900023, 9900024]}, + 9211: {'attribute': 3, 'place': 201, 'formula': [201001, 201002, 201003, 201004, 201005, 201006, 201007, 201008, 201101, 201102, 201103, 201104, 201105, 201106, 201107, 201108], 'activity_formula': []}, + 9212: {'attribute': 3, 'place': 201, 'formula': [201001, 201002, 201003, 201004, 201005, 201006, 201007, 201008, 201101, 201102, 201103, 201104, 201105, 201106, 201107, 201108], 'activity_formula': []}, + 9213: {'attribute': 3, 'place': 201, 'formula': [201001, 201002, 201003, 201004, 201005, 201006, 201007, 201008, 201101, 201102, 201103, 201104, 201105, 201106, 201107, 201108], 'activity_formula': []}, +} + +DIC_ISLAND_ACTIVITY = { + 990001: {'type': 5001, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-04 23:59:59', 'jp': '2026-02-05 16:00:00'}, 'config_data': [1]}, + 990002: {'type': 5002, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-04 23:59:59', 'jp': '2026-02-05 16:00:00'}, 'config_data': [100001, 100002, 100003, 100004, 100005, 100006, 100007, 100008, 100009, 100010, 100011, 100012, 100013, 100014, 100015]}, + 990003: {'type': 5003, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-04 23:59:59', 'jp': '2026-02-05 16:00:00'}, 'config_data': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008]}, + 990004: {'type': 5004, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-04 23:59:59', 'jp': '2026-02-05 16:00:00'}, 'config_data': [9900001, 9900002, 9900003, 9900004, 9900005, 9900006, 9900007, 9900008]}, + 990005: {'type': 5001, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 00:00:00', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-07 16:00:00'}, 'config_data': [3]}, + 990006: {'type': 5002, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 00:00:00', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-07 16:00:00'}, 'config_data': [100016, 100017, 100018, 100019, 100020, 100021, 100022, 100023, 100024, 100025, 100026, 100027, 100028, 100029, 100030]}, + 990007: {'type': 5003, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 00:00:00', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-07 16:00:00'}, 'config_data': [1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016]}, + 990008: {'type': 5004, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 00:00:00', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-07 16:00:00'}, 'config_data': [9900009, 9900010, 9900011, 9900012, 9900013, 9900014, 9900015, 9900016]}, + 990011: {'type': 5001, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}, 'config_data': [2]}, + 990013: {'type': 801, 'start_time': {'cn': '2026-04-09 00:00:00', 'en': '2026-04-09 00:00:00', 'jp': '2026-04-09 00:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}, 'config_data': []}, + 990014: {'type': 89, 'start_time': {'cn': '2026-04-09 00:00:00', 'en': '2026-04-09 00:00:00', 'jp': '2026-04-09 00:00:00'}, 'end_time': {'cn': '2026-04-29 23:59:59', 'en': '2026-04-29 23:59:59', 'jp': '2026-04-29 23:59:59'}, 'config_data': [{0: 56248}, {0: 56249}, {0: 56250}, {0: 56251}, {0: 56252}]}, + 990015: {'type': 890, 'start_time': {'cn': '2026-04-09 00:00:00', 'en': '2026-04-09 00:00:00', 'jp': '2026-04-09 00:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}, 'config_data': []}, + 990016: {'type': 5001, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:01'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}, 'config_data': [4]}, + 990017: {'type': 5002, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:01'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}, 'config_data': [100031, 100032, 100033, 100034, 100035, 100036, 100037, 100038, 100039, 100040, 100041, 100042, 100043, 100044, 100045]}, + 990018: {'type': 5003, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:01'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}, 'config_data': [1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024]}, + 990019: {'type': 5004, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:01'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}, 'config_data': [9900017, 9900018, 9900019, 9900020, 9900021, 9900022, 9900023, 9900024]}, + 990021: {'type': 18, 'start_time': {'cn': '2026-06-05 00:00:00', 'en': '2026-06-05 00:00:00', 'jp': '2026-06-05 00:00:00'}, 'end_time': {'cn': '2026-06-18 12:00:00', 'en': '2026-06-18 12:00:00', 'jp': '2026-06-18 16:00:00'}, 'config_data': []}, +} + +DIC_ISLAND_WILD_GATHER = { + 1: {'activity_id': 0, 'product': {2606: 1}}, + 2: {'activity_id': 0, 'product': {2606: 1}}, + 3: {'activity_id': 0, 'product': {2606: 1}}, + 4: {'activity_id': 0, 'product': {2606: 1}}, + 5: {'activity_id': 0, 'product': {2606: 1}}, + 6: {'activity_id': 0, 'product': {2606: 1}}, + 1001: {'activity_id': 990003, 'product': {4001: 4}}, + 1002: {'activity_id': 990003, 'product': {4001: 4}}, + 1003: {'activity_id': 990003, 'product': {4002: 8}}, + 1004: {'activity_id': 990003, 'product': {4002: 8}}, + 1005: {'activity_id': 990003, 'product': {4003: 12}}, + 1006: {'activity_id': 990003, 'product': {4003: 12}}, + 1007: {'activity_id': 990003, 'product': {4004: 3}}, + 1008: {'activity_id': 990003, 'product': {4004: 3}}, + 1009: {'activity_id': 990007, 'product': {4015: 4}}, + 1010: {'activity_id': 990007, 'product': {4015: 4}}, + 1011: {'activity_id': 990007, 'product': {4016: 8}}, + 1012: {'activity_id': 990007, 'product': {4016: 8}}, + 1013: {'activity_id': 990007, 'product': {4017: 12}}, + 1014: {'activity_id': 990007, 'product': {4017: 12}}, + 1015: {'activity_id': 990007, 'product': {4018: 4}}, + 1016: {'activity_id': 990007, 'product': {4018: 4}}, + 1017: {'activity_id': 990018, 'product': {4029: 8}}, + 1018: {'activity_id': 990018, 'product': {4029: 8}}, + 1019: {'activity_id': 990018, 'product': {4030: 8}}, + 1020: {'activity_id': 990018, 'product': {4030: 8}}, + 1021: {'activity_id': 990018, 'product': {4031: 4}}, + 1022: {'activity_id': 990018, 'product': {4031: 4}}, + 1023: {'activity_id': 990018, 'product': {4032: 8}}, + 1024: {'activity_id': 990018, 'product': {4032: 8}}, +} + +DIC_ISLAND_PRODUCTION_MINING = { + 40101: {2700: 8}, + 40102: {2700: 8}, + 40103: {2700: 8}, + 40104: {2700: 8}, + 40105: {2700: 8}, + 40106: {2700: 8}, + 40107: {2700: 8}, + 40108: {2700: 8}, + 40109: {2700: 8}, +} + +DIC_ISLAND_PRODUCTION_LOGGING = { + 40201: {2800: 8}, + 40202: {2800: 8}, + 40203: {2800: 8}, + 40204: {2800: 8}, + 40205: {2800: 8}, + 40206: {2800: 8}, + 40207: {2800: 8}, + 40208: {2800: 8}, + 40209: {2800: 8}, +} + +DIC_ISLAND_SEASON = { + 1: {'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-04 23:59:59', 'jp': '2026-02-05 16:00:00'}, 'task_list': [80001101, 80001102, 80001103, 80001104, 80001105, 80001106, 80001107, 80001108, 80001109, 80001110, 80001111, 80001112, 80001113, 80001114, 80001115, 80001116, 80001117, 80001118, 80001119, 80001120, 80001121, 80001122, 80001123, 80001124, 80001125, 80001126, 80001127, 80001128, 80001129, 80001130], 'activity': [990001, 990002, 990003, 990004]}, + 2: {'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 00:00:00', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-07 16:00:00'}, 'task_list': [80001201, 80001202, 80001203, 80001204, 80001205, 80001206, 80001207, 80001208, 80001209, 80001210, 80001211, 80001212, 80001213, 80001214, 80001215, 80001216, 80001217, 80001218, 80001219, 80001220, 80001221, 80001222, 80001223, 80001224, 80001225, 80001226, 80001227, 80001228, 80001229, 80001230], 'activity': [990005, 990006, 990007, 990008]}, + 3: {'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:01'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}, 'task_list': [80001301, 80001302, 80001303, 80001304, 80001305, 80001306, 80001307, 80001308, 80001309, 80001310, 80001311, 80001312, 80001313, 80001314, 80001315, 80001316, 80001317, 80001318, 80001319, 80001320, 80001321, 80001322, 80001323, 80001324, 80001325, 80001326, 80001327, 80001328, 80001329, 80001330], 'activity': [990016, 990017, 990018, 990019]}, +} + +DIC_ISLAND_SEASON_ORDER = { + 2: {'activity_id': 0, 'next_order': 0, 'season_pt_num': 0, 'request': {2700: 1, 2800: 1}, 'award': {50: 200}}, + 100001: {'activity_id': 990002, 'next_order': 100002, 'season_pt_num': 1000, 'request': {4001: 4}, 'award': {0: 4000}}, + 100002: {'activity_id': 990002, 'next_order': 100003, 'season_pt_num': 1000, 'request': {4002: 4}, 'award': {0: 4000}}, + 100003: {'activity_id': 990002, 'next_order': 100004, 'season_pt_num': 1000, 'request': {4003: 4}, 'award': {0: 4000}}, + 100004: {'activity_id': 990002, 'next_order': 100005, 'season_pt_num': 1000, 'request': {4004: 4}, 'award': {0: 4000}}, + 100005: {'activity_id': 990002, 'next_order': 100006, 'season_pt_num': 1000, 'request': {4005: 10}, 'award': {0: 4000}}, + 100006: {'activity_id': 990002, 'next_order': 100007, 'season_pt_num': 1000, 'request': {4007: 10}, 'award': {0: 4000}}, + 100007: {'activity_id': 990002, 'next_order': 100008, 'season_pt_num': 2500, 'request': {4009: 5}, 'award': {0: 10000}}, + 100008: {'activity_id': 990002, 'next_order': 100009, 'season_pt_num': 2500, 'request': {4010: 5}, 'award': {0: 10000}}, + 100009: {'activity_id': 990002, 'next_order': 100010, 'season_pt_num': 2500, 'request': {4011: 5}, 'award': {0: 10000}}, + 100010: {'activity_id': 990002, 'next_order': 100011, 'season_pt_num': 2500, 'request': {4012: 5}, 'award': {0: 10000}}, + 100011: {'activity_id': 990002, 'next_order': 100012, 'season_pt_num': 2500, 'request': {4013: 5}, 'award': {0: 10000}}, + 100012: {'activity_id': 990002, 'next_order': 100013, 'season_pt_num': 2500, 'request': {4014: 5}, 'award': {0: 10000}}, + 100013: {'activity_id': 990002, 'next_order': 100014, 'season_pt_num': 8000, 'request': {4009: 5, 4014: 5}, 'award': {0: 32000}}, + 100014: {'activity_id': 990002, 'next_order': 100015, 'season_pt_num': 8000, 'request': {4010: 5, 4012: 5}, 'award': {0: 32000}}, + 100015: {'activity_id': 990002, 'next_order': 0, 'season_pt_num': 8000, 'request': {4011: 5, 4013: 5}, 'award': {0: 32000}}, + 100016: {'activity_id': 990006, 'next_order': 100017, 'season_pt_num': 1000, 'request': {4015: 4}, 'award': {0: 4000}}, + 100017: {'activity_id': 990006, 'next_order': 100018, 'season_pt_num': 1000, 'request': {4016: 4}, 'award': {0: 4000}}, + 100018: {'activity_id': 990006, 'next_order': 100019, 'season_pt_num': 1000, 'request': {4017: 4}, 'award': {0: 4000}}, + 100019: {'activity_id': 990006, 'next_order': 100020, 'season_pt_num': 1000, 'request': {4018: 4}, 'award': {0: 4000}}, + 100020: {'activity_id': 990006, 'next_order': 100021, 'season_pt_num': 1000, 'request': {4019: 10}, 'award': {0: 4000}}, + 100021: {'activity_id': 990006, 'next_order': 100022, 'season_pt_num': 1000, 'request': {4021: 10}, 'award': {0: 4000}}, + 100022: {'activity_id': 990006, 'next_order': 100023, 'season_pt_num': 2500, 'request': {4023: 5}, 'award': {0: 10000}}, + 100023: {'activity_id': 990006, 'next_order': 100024, 'season_pt_num': 2500, 'request': {4024: 5}, 'award': {0: 10000}}, + 100024: {'activity_id': 990006, 'next_order': 100025, 'season_pt_num': 2500, 'request': {4025: 5}, 'award': {0: 10000}}, + 100025: {'activity_id': 990006, 'next_order': 100026, 'season_pt_num': 2500, 'request': {4026: 5}, 'award': {0: 10000}}, + 100026: {'activity_id': 990006, 'next_order': 100027, 'season_pt_num': 2500, 'request': {4027: 5}, 'award': {0: 10000}}, + 100027: {'activity_id': 990006, 'next_order': 100028, 'season_pt_num': 2500, 'request': {4028: 5}, 'award': {0: 10000}}, + 100028: {'activity_id': 990006, 'next_order': 100029, 'season_pt_num': 8000, 'request': {4027: 5, 4025: 5}, 'award': {0: 32000}}, + 100029: {'activity_id': 990006, 'next_order': 100030, 'season_pt_num': 8000, 'request': {4026: 5, 4024: 5}, 'award': {0: 32000}}, + 100030: {'activity_id': 990006, 'next_order': 0, 'season_pt_num': 8000, 'request': {4023: 5, 4028: 5}, 'award': {0: 32000}}, + 100031: {'activity_id': 990017, 'next_order': 100032, 'season_pt_num': 1000, 'request': {4029: 4}, 'award': {0: 4000}}, + 100032: {'activity_id': 990017, 'next_order': 100033, 'season_pt_num': 1000, 'request': {4030: 4}, 'award': {0: 4000}}, + 100033: {'activity_id': 990017, 'next_order': 100034, 'season_pt_num': 1000, 'request': {4031: 4}, 'award': {0: 4000}}, + 100034: {'activity_id': 990017, 'next_order': 100035, 'season_pt_num': 1000, 'request': {4032: 4}, 'award': {0: 4000}}, + 100035: {'activity_id': 990017, 'next_order': 100036, 'season_pt_num': 1000, 'request': {4033: 10}, 'award': {0: 4000}}, + 100036: {'activity_id': 990017, 'next_order': 100037, 'season_pt_num': 1000, 'request': {4035: 10}, 'award': {0: 4000}}, + 100037: {'activity_id': 990017, 'next_order': 100038, 'season_pt_num': 2500, 'request': {4037: 5}, 'award': {0: 10000}}, + 100038: {'activity_id': 990017, 'next_order': 100039, 'season_pt_num': 2500, 'request': {4038: 5}, 'award': {0: 10000}}, + 100039: {'activity_id': 990017, 'next_order': 100040, 'season_pt_num': 2500, 'request': {4039: 5}, 'award': {0: 10000}}, + 100040: {'activity_id': 990017, 'next_order': 100041, 'season_pt_num': 2500, 'request': {4040: 5}, 'award': {0: 10000}}, + 100041: {'activity_id': 990017, 'next_order': 100042, 'season_pt_num': 2500, 'request': {4041: 5}, 'award': {0: 10000}}, + 100042: {'activity_id': 990017, 'next_order': 100043, 'season_pt_num': 2500, 'request': {4042: 5}, 'award': {0: 10000}}, + 100043: {'activity_id': 990017, 'next_order': 100044, 'season_pt_num': 8000, 'request': {4040: 5, 4037: 5}, 'award': {0: 32000}}, + 100044: {'activity_id': 990017, 'next_order': 100045, 'season_pt_num': 8000, 'request': {4042: 5, 4039: 5}, 'award': {0: 32000}}, + 100045: {'activity_id': 990017, 'next_order': 0, 'season_pt_num': 8000, 'request': {4041: 5, 4038: 5}, 'award': {0: 32000}}, +} + +DIC_ISLAND_SHOP = { + 10019: {'name': {'cn': '磨坊', 'en': 'Flour Mill', 'jp': '製粉所'}, 'tag_type': 1, 'order': 4, 'parent_id': None, 'currency': [], 'goods': []}, + 10020: {'name': {'cn': '加工', 'en': 'Products', 'jp': '加工'}, 'tag_type': 2, 'order': 1, 'parent_id': 10019, 'currency': [], 'goods': []}, + 10021: {'name': {'cn': '加工', 'en': 'Products', 'jp': '加工'}, 'tag_type': 3, 'order': 1, 'parent_id': 10020, 'currency': [2000, 2001, 2008], 'goods': [103000, 103001, 103002, 103003, 103004]}, + 10022: {'name': {'cn': '种子', 'en': 'Seeds', 'jp': '種'}, 'tag_type': 1, 'order': 1, 'parent_id': None, 'currency': [], 'goods': []}, + 10023: {'name': {'cn': '种子', 'en': 'Seeds', 'jp': '種'}, 'tag_type': 2, 'order': 1, 'parent_id': 10022, 'currency': [], 'goods': []}, + 10024: {'name': {'cn': '丰壤农田', 'en': 'Faircrop Fields', 'jp': '豊穣の畑'}, 'tag_type': 3, 'order': 1, 'parent_id': 10023, 'currency': [1], 'goods': [411000, 411001, 411002, 411003, 411005, 411006, 411008, 411009]}, + 10025: {'name': {'cn': '种子', 'en': 'Seeds', 'jp': '種'}, 'tag_type': 1, 'order': 1, 'parent_id': None, 'currency': [], 'goods': []}, + 10026: {'name': {'cn': '种子', 'en': 'Seeds', 'jp': '種'}, 'tag_type': 2, 'order': 1, 'parent_id': 10025, 'currency': [], 'goods': []}, + 10027: {'name': {'cn': '坠香果园', 'en': 'Sweetscent Orchard', 'jp': '薫る果樹園'}, 'tag_type': 3, 'order': 1, 'parent_id': 10026, 'currency': [1], 'goods': [411016, 411017, 411018, 411019, 411020, 411021, 411022]}, + 10028: {'name': {'cn': '种子', 'en': 'Seeds', 'jp': '種'}, 'tag_type': 1, 'order': 1, 'parent_id': None, 'currency': [], 'goods': []}, + 10029: {'name': {'cn': '种子', 'en': 'Seeds', 'jp': '種'}, 'tag_type': 2, 'order': 1, 'parent_id': 10028, 'currency': [], 'goods': []}, + 10030: {'name': {'cn': '青芽苗圃', 'en': 'Newsprout Nursery', 'jp': '青々苗場'}, 'tag_type': 3, 'order': 1, 'parent_id': 10029, 'currency': [1], 'goods': [411004, 411007, 411010, 411011, 411012, 411014, 411015, 411025, 411026, 411027, 411028]}, + 10031: {'name': {'cn': '渔具', 'en': 'Fishing Gear', 'jp': '釣具'}, 'tag_type': 1, 'order': 5, 'parent_id': None, 'currency': [], 'goods': []}, + 10032: {'name': {'cn': '鱼苗', 'en': 'Fish Fry', 'jp': '稚魚'}, 'tag_type': 2, 'order': 1, 'parent_id': 10031, 'currency': [], 'goods': []}, + 10033: {'name': {'cn': '淡水鱼苗', 'en': 'Freshwater Fish', 'jp': '淡水魚'}, 'tag_type': 3, 'order': 1, 'parent_id': 10032, 'currency': [1], 'goods': [111102, 111103, 111104, 111107]}, + 10034: {'name': {'cn': '海水鱼苗', 'en': 'Saltwater Fish', 'jp': '海水魚'}, 'tag_type': 3, 'order': 2, 'parent_id': 10032, 'currency': [1], 'goods': [111202, 111203, 111204, 111207, 111205, 111206]}, + 10035: {'name': {'cn': '其他鱼苗', 'en': 'Other', 'jp': 'その他'}, 'tag_type': 3, 'order': 3, 'parent_id': 10032, 'currency': [1], 'goods': [111101, 111105, 111106, 111108, 111201, 111208]}, + 10036: {'name': {'cn': '鱼饵', 'en': 'Fish Lures', 'jp': 'ルアー'}, 'tag_type': 2, 'order': 2, 'parent_id': 10031, 'currency': [], 'goods': []}, + 10037: {'name': {'cn': '鱼饵', 'en': 'Fish Lures', 'jp': 'ルアー'}, 'tag_type': 3, 'order': 3, 'parent_id': 10036, 'currency': [1], 'goods': [111500, 111502, 111503]}, + 10109: {'name': {'cn': '种子', 'en': 'Seeds', 'jp': '種'}, 'tag_type': 1, 'order': 3, 'parent_id': None, 'currency': [], 'goods': []}, + 10110: {'name': {'cn': '种子', 'en': 'Seeds', 'jp': '種'}, 'tag_type': 2, 'order': 1, 'parent_id': 10109, 'currency': [], 'goods': []}, + 10111: {'name': {'cn': '丰壤农田', 'en': 'Faircrop Fields', 'jp': '豊穣の畑'}, 'tag_type': 3, 'order': 1, 'parent_id': 10110, 'currency': [1], 'goods': [411000, 411001, 411002, 411003, 411005, 411006, 411008, 411009]}, + 10112: {'name': {'cn': '坠香果园', 'en': 'Sweetscent Orchard', 'jp': '薫る果樹園'}, 'tag_type': 3, 'order': 2, 'parent_id': 10110, 'currency': [1], 'goods': [411016, 411017, 411018, 411019, 411020, 411021, 411022]}, + 10113: {'name': {'cn': '青芽苗圃', 'en': 'Newsprout Nursery', 'jp': '青々苗場'}, 'tag_type': 3, 'order': 3, 'parent_id': 10110, 'currency': [1], 'goods': [411004, 411007, 411010, 411011, 411012, 411014, 411015, 411025, 411026, 411027, 411028]}, +} + +DIC_ISLAND_SHOP_RECIPE = { + 103000: {'name': {'cn': '咯咯鸡饲料', 'en': 'Clucky Clucky Bird Feed', 'jp': 'コッコートリの餌'}, 'resource_consume': {2000: 30}, 'items': {3000: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 103001: {'name': {'cn': '哼哼猪饲料', 'en': 'Oinky Oinky Pig Feed', 'jp': 'ブーブーブタの餌'}, 'resource_consume': {2001: 30}, 'items': {3001: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 103002: {'name': {'cn': '哞哞牛饲料', 'en': 'Moo Moo Cow Feed', 'jp': 'モーモーウシの餌'}, 'resource_consume': {2008: 30}, 'items': {3002: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 103003: {'name': {'cn': '咩咩羊饲料', 'en': 'Baa Baa Sheep Feed', 'jp': 'メェメーヒツジの餌'}, 'resource_consume': {2008: 30}, 'items': {3003: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 103004: {'name': {'cn': '面粉', 'en': 'Flour', 'jp': '小麦粉'}, 'resource_consume': {2000: 6}, 'items': {3004: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111500: {'name': {'cn': '蚯蚓', 'en': 'Earthworm', 'jp': 'ミミズ'}, 'resource_consume': {1: 10000}, 'items': {1500: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111502: {'name': {'cn': '虾仁', 'en': 'Shelled Shrimp', 'jp': 'むきエビ'}, 'resource_consume': {1: 25000}, 'items': {1502: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111503: {'name': {'cn': '章鱼须', 'en': 'Octopus Arm', 'jp': 'タコ足'}, 'resource_consume': {1: 25000}, 'items': {1503: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111101: {'name': {'cn': '贝苗', 'en': 'Shellfish Spat', 'jp': '稚貝'}, 'resource_consume': {1: 100}, 'items': {1101: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111102: {'name': {'cn': '鲶鱼苗', 'en': 'Catfish Fry', 'jp': 'ナマズの稚魚'}, 'resource_consume': {1: 100}, 'items': {1102: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111103: {'name': {'cn': '鲤鱼苗', 'en': 'Koi Carp Fry', 'jp': 'コイの稚魚'}, 'resource_consume': {1: 150}, 'items': {1103: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111104: {'name': {'cn': '鲫鱼苗', 'en': 'Common Carp Fry', 'jp': 'フナの稚魚'}, 'resource_consume': {1: 150}, 'items': {1104: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111105: {'name': {'cn': '小河虾苗', 'en': 'Freshwater Shrimp Fry', 'jp': '稚エビ'}, 'resource_consume': {1: 200}, 'items': {1105: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111106: {'name': {'cn': '小龙虾苗', 'en': 'Crayfish Fry', 'jp': '稚ザリ'}, 'resource_consume': {1: 200}, 'items': {1106: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111107: {'name': {'cn': '鲈鱼苗', 'en': 'Sea Bass Fry', 'jp': 'スズキの稚魚'}, 'resource_consume': {1: 200}, 'items': {1107: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111108: {'name': {'cn': '蟹苗', 'en': 'Juvenile Crab', 'jp': '稚ガニ'}, 'resource_consume': {1: 300}, 'items': {1108: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111201: {'name': {'cn': '鱿鱼苗', 'en': 'Squid Fry', 'jp': '稚イカ'}, 'resource_consume': {1: 120}, 'items': {1201: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111202: {'name': {'cn': '马鲛鱼苗', 'en': 'Mackerel Fry', 'jp': 'サワラの稚魚'}, 'resource_consume': {1: 180}, 'items': {1202: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111203: {'name': {'cn': '金枪鱼苗', 'en': 'Tuna Fry', 'jp': 'マグロの稚魚'}, 'resource_consume': {1: 240}, 'items': {1203: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111204: {'name': {'cn': '三文鱼苗', 'en': 'Salmon Fry', 'jp': 'サーモンの稚魚'}, 'resource_consume': {1: 240}, 'items': {1204: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111205: {'name': {'cn': '红鲷鱼苗', 'en': 'Red Sea Bream Fry', 'jp': 'マダイの稚魚'}, 'resource_consume': {1: 120}, 'items': {1205: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111206: {'name': {'cn': '黑鲷鱼苗', 'en': 'Black Porgy Fry', 'jp': 'クロダイの稚魚'}, 'resource_consume': {1: 180}, 'items': {1206: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111207: {'name': {'cn': '黄鳍金枪鱼苗', 'en': 'Yellowfin Tuna Fry', 'jp': 'キハダの稚魚'}, 'resource_consume': {1: 360}, 'items': {1207: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 111208: {'name': {'cn': '海参苗', 'en': 'Sea Cucumber Fry', 'jp': '稚ナマコ'}, 'resource_consume': {1: 360}, 'items': {1208: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411000: {'name': {'cn': '小麦种子', 'en': 'Wheat Seeds', 'jp': '小麦の種'}, 'resource_consume': {1: 20}, 'items': {1000: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411001: {'name': {'cn': '玉米种子', 'en': 'Corn Seeds', 'jp': 'とうもろこしの種'}, 'resource_consume': {1: 40}, 'items': {1001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411002: {'name': {'cn': '旱稻种子', 'en': 'Upland Rice Seeds', 'jp': '陸稲の種'}, 'resource_consume': {1: 40}, 'items': {1002: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411003: {'name': {'cn': '白菜种子', 'en': 'Napa Cabbage Seeds', 'jp': '白菜の種'}, 'resource_consume': {1: 60}, 'items': {1003: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411004: {'name': {'cn': '胡萝卜种子', 'en': 'Carrot Seeds', 'jp': 'ニンジンの種'}, 'resource_consume': {1: 100}, 'items': {1004: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411005: {'name': {'cn': '土豆种子', 'en': 'Potato Seeds', 'jp': 'じゃがいもの種'}, 'resource_consume': {1: 20}, 'items': {1005: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411006: {'name': {'cn': '大豆种子', 'en': 'Soy Bean Seeds', 'jp': '大豆の種'}, 'resource_consume': {1: 60}, 'items': {1006: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411007: {'name': {'cn': '洋葱种子', 'en': 'Onion Seeds', 'jp': '玉ねぎの種'}, 'resource_consume': {1: 120}, 'items': {1007: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411008: {'name': {'cn': '牧草种子', 'en': 'Grass Seeds', 'jp': '牧草の種'}, 'resource_consume': {1: 20}, 'items': {1008: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411009: {'name': {'cn': '咖啡树种', 'en': 'Coffee Tree Seeds', 'jp': 'コーヒーの木の種'}, 'resource_consume': {1: 120}, 'items': {1009: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411010: {'name': {'cn': '亚麻种子', 'en': 'Flax Seeds', 'jp': '亜麻の種'}, 'resource_consume': {1: 60}, 'items': {1010: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411011: {'name': {'cn': '草莓种子', 'en': 'Strawberry Seeds', 'jp': 'いちごの種'}, 'resource_consume': {1: 120}, 'items': {1011: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411012: {'name': {'cn': '棉花种子', 'en': 'Cotton Seeds', 'jp': '綿の種'}, 'resource_consume': {1: 80}, 'items': {1012: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411014: {'name': {'cn': '茶树种子', 'en': 'Tea Tree Seeds', 'jp': '茶の木の種'}, 'resource_consume': {1: 150}, 'items': {1014: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411015: {'name': {'cn': '薰衣草种子', 'en': 'Lavender Seeds', 'jp': 'ラベンダーの種'}, 'resource_consume': {1: 160}, 'items': {1015: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411016: {'name': {'cn': '苹果树种', 'en': 'Apple Tree Seeds', 'jp': 'りんごの木の種'}, 'resource_consume': {1: 100}, 'items': {1016: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411017: {'name': {'cn': '柑橘树种', 'en': 'Citrus Fruit Tree Seeds', 'jp': '柑橘類の木の種'}, 'resource_consume': {1: 120}, 'items': {1017: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411018: {'name': {'cn': '香蕉树种', 'en': 'Banana Tree Seed', 'jp': 'バナナの木の種'}, 'resource_consume': {1: 140}, 'items': {1018: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411019: {'name': {'cn': '芒果树种', 'en': 'Mango Tree Seeds', 'jp': 'マンゴーの木の種'}, 'resource_consume': {1: 180}, 'items': {1019: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411020: {'name': {'cn': '柠檬树种', 'en': 'Lemon Tree Seed', 'jp': 'レモンの木の種'}, 'resource_consume': {1: 80}, 'items': {1020: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411021: {'name': {'cn': '牛油果树种', 'en': 'Avocado Tree Seeds', 'jp': 'アボカドの木の種'}, 'resource_consume': {1: 240}, 'items': {1021: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411022: {'name': {'cn': '橡胶树种', 'en': 'Rubber Tree Seeds', 'jp': 'ゴムの木の種'}, 'resource_consume': {1: 280}, 'items': {1022: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 411023: {'name': {'cn': '秋月梨树种', 'en': 'Yoizuki Pear Seeds', 'jp': '宵月梨の種'}, 'resource_consume': {1: 120}, 'items': {4006: 1}, 'start_time': {'cn': '2025-09-25 00:00:00', 'en': '2025-09-25 00:00:00', 'jp': '2025-09-25 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-04 23:59:59', 'jp': '2026-02-05 16:00:00'}}, + 411024: {'name': {'cn': '柿子树种', 'en': 'Kaki Persimmon Seeds', 'jp': '柿の種'}, 'resource_consume': {1: 180}, 'items': {4008: 1}, 'start_time': {'cn': '2025-09-25 00:00:00', 'en': '2025-09-25 00:00:00', 'jp': '2025-09-25 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-04 23:59:59', 'jp': '2026-02-05 16:00:00'}}, + 411025: {'name': {'cn': '芦笋种子', 'en': 'Asparagus Seeds', 'jp': 'アスパラガスの種'}, 'resource_consume': {1: 120}, 'items': {4020: 1}, 'start_time': {'cn': '2026-02-05 00:00:00', 'en': '2026-02-05 00:00:00', 'jp': '2026-02-05 00:00:00'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-07 16:00:00'}}, + 411026: {'name': {'cn': '凤梨种子', 'en': 'Pineapple Seeds', 'jp': 'パイナップルの種'}, 'resource_consume': {1: 180}, 'items': {4022: 1}, 'start_time': {'cn': '2026-02-05 00:00:00', 'en': '2026-02-05 00:00:00', 'jp': '2026-02-05 00:00:00'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-07 16:00:00'}}, + 411027: {'name': {'cn': '番茄种子', 'en': 'Tomato Seeds', 'jp': 'トマトの種'}, 'resource_consume': {1: 120}, 'items': {4034: 1}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:01'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 411028: {'name': {'cn': '黄瓜种子', 'en': 'Cucumber Seeds', 'jp': 'キュウリの種'}, 'resource_consume': {1: 80}, 'items': {4036: 1}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:01'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, +} + +DIC_ISLAND_SHOP_ITEM_TO_RECIPE = { + 3000: 103000, + 3001: 103001, + 3002: 103002, + 3003: 103003, + 3004: 103004, + 1500: 111500, + 1502: 111502, + 1503: 111503, + 1101: 111101, + 1102: 111102, + 1103: 111103, + 1104: 111104, + 1105: 111105, + 1106: 111106, + 1107: 111107, + 1108: 111108, + 1201: 111201, + 1202: 111202, + 1203: 111203, + 1204: 111204, + 1205: 111205, + 1206: 111206, + 1207: 111207, + 1208: 111208, + 1000: 411000, + 1001: 411001, + 1002: 411002, + 1003: 411003, + 1004: 411004, + 1005: 411005, + 1006: 411006, + 1007: 411007, + 1008: 411008, + 1009: 411009, + 1010: 411010, + 1011: 411011, + 1012: 411012, + 1014: 411014, + 1015: 411015, + 1016: 411016, + 1017: 411017, + 1018: 411018, + 1019: 411019, + 1020: 411020, + 1021: 411021, + 1022: 411022, + 4006: 411023, + 4008: 411024, + 4020: 411025, + 4022: 411026, + 4034: 411027, + 4036: 411028, +} + +DIC_ISLAND_EXCHANGE_RECIPE = { + 101: {'resource_consume': {5002: 1}, 'items': {2521: 4}}, + 102: {'resource_consume': {5003: 1}, 'items': {2521: 3}}, + 103: {'resource_consume': {5004: 1}, 'items': {2521: 2}}, + 104: {'resource_consume': {5007: 1}, 'items': {2521: 3}}, + 201: {'resource_consume': {5102: 1}, 'items': {2522: 3}}, + 202: {'resource_consume': {5103: 1}, 'items': {2522: 12}}, + 203: {'resource_consume': {5104: 1}, 'items': {2522: 5}}, + 204: {'resource_consume': {5105: 1}, 'items': {2522: 1}}, + 205: {'resource_consume': {5106: 1}, 'items': {2522: 2}}, + 206: {'resource_consume': {5107: 1}, 'items': {2522: 18}}, +} + +DIC_ISLAND_TASK = { + 10001000: {'name': {'cn': '原野之上', 'en': 'In the Wilderness', 'jp': '原野にて'}, 'target_id': 100010001, 'target': {10040044: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001010: {'name': {'cn': '重逢之时', 'en': 'Reunion', 'jp': '再会の時'}, 'target_id': 100010101, 'target': {3100006: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001020: {'name': {'cn': '乔安的修复计划', 'en': "John's Repair Plan", 'jp': 'ジョンの修復計画'}, 'target_id': 100010201, 'target': {10040022: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001030: {'name': {'cn': '镐下煤炭', 'en': 'Pickaxes and Coal', 'jp': 'ツルハシで石炭を'}, 'target_id': 100010301, 'target': {2700: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001040: {'name': {'cn': '斧上林木', 'en': 'Hatches and Wood', 'jp': '斧で木材を'}, 'target_id': 100010401, 'target': {2800: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001050: {'name': {'cn': '原野上的协力', 'en': 'Teamwork on the Plains', 'jp': '原野での協力'}, 'target_id': 100010501, 'target': {3100011: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001060: {'name': {'cn': '向着远方', 'en': 'Off Into the Distance', 'jp': '遠方へ向かって'}, 'target_id': 100010601, 'target': {10020001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001070: {'name': {'cn': '原野的馈赠', 'en': 'Gifts from the Plains', 'jp': '原野からの贈り物'}, 'target_id': 100010701, 'target': {1902: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001071: {'name': {'cn': '原野的馈赠', 'en': 'Gifts from the Plains', 'jp': '原野からの贈り物'}, 'target_id': 100010711, 'target': {2: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001080: {'name': {'cn': '港口寻踪', 'en': 'Looking for Traces in the Harbor', 'jp': '港での手がかり探し'}, 'target_id': 100010801, 'target': {10020006: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001090: {'name': {'cn': '神秘的啾咖啡', 'en': 'The Mysterious Café Manjuu', 'jp': '謎の饅頭カフェ'}, 'target_id': 100010901, 'target': {10100001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001100: {'name': {'cn': '警报之围', 'en': 'Surrounded by Alarms', 'jp': '警報に囲まれて'}, 'target_id': 100011001, 'target': {10100007: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001110: {'name': {'cn': '消失的{namecode:98:明石}', 'en': 'The Missing Green Cat', 'jp': '消えた明石'}, 'target_id': 100011101, 'target': {3601: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001120: {'name': {'cn': '秘密基地?', 'en': 'A Secret Base?', 'jp': '秘密基地?'}, 'target_id': 100011201, 'target': {2101: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001130: {'name': {'cn': '把握权限', 'en': 'Rights Secured', 'jp': '権限掌握'}, 'target_id': 100011301, 'target': {2102: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001140: {'name': {'cn': '岛屿科技', 'en': 'Island Tech', 'jp': '離島技術'}, 'target_id': 100011401, 'target': {10070004: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001141: {'name': {'cn': '岛屿科技', 'en': 'Island Tech', 'jp': '離島技術'}, 'target_id': 100011411, 'target': {100001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001150: {'name': {'cn': '权限认证装置', 'en': 'Island Authorization Device', 'jp': '権限認証装置'}, 'target_id': 100011501, 'target': {10070003: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001151: {'name': {'cn': '权限认证装置', 'en': 'Island Authorization Device', 'jp': '権限認証装置'}, 'target_id': 100011511, 'target': {10703: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001160: {'name': {'cn': '远帆来港', 'en': "Saratoga's Arrival", 'jp': '遠方から'}, 'target_id': 100011601, 'target': {4401: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001170: {'name': {'cn': '生活经验指南', 'en': 'Island EXP Textbook', 'jp': '離島EXP教科書'}, 'target_id': 100011701, 'target': {3: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10001180: {'name': {'cn': '提升开发等级', 'en': 'Raise the Island Development Level', 'jp': '開発レベルを上げよう'}, 'target_id': 100011801, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002010: {'name': {'cn': '忙碌是一件好事', 'en': 'All Business Is Good Business', 'jp': '忙しいのはいいことだ'}, 'target_id': 100020101, 'target': {1903: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002020: {'name': {'cn': '宝藏的传闻', 'en': 'Tales of Treasure', 'jp': 'お宝の噂'}, 'target_id': 100020201, 'target': {10020035: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002030: {'name': {'cn': '斯蒂芬妮的踪迹', 'en': 'Trailing Stephen', 'jp': 'ステファンの足跡'}, 'target_id': 100020301, 'target': {10020031: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002031: {'name': {'cn': '斯蒂芬妮的踪迹', 'en': 'Trailing Stephen', 'jp': 'ステファンの足跡'}, 'target_id': 100020311, 'target': {10020032: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002032: {'name': {'cn': '斯蒂芬妮的踪迹', 'en': 'Trailing Stephen', 'jp': 'ステファンの足跡'}, 'target_id': 100020321, 'target': {10020033: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002040: {'name': {'cn': '劳逸结合不了', 'en': 'Work-Life Balance', 'jp': 'ワークライフバランス'}, 'target_id': 100020401, 'target': {2302: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002050: {'name': {'cn': '勤劳有什么用?', 'en': "What's the Point of Hard Work?", 'jp': '勤勉に何の意味がある?'}, 'target_id': 100020501, 'target': {2304: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002051: {'name': {'cn': '提升开发等级', 'en': 'Raise the Island Development Level', 'jp': '開発レベルを上げよう'}, 'target_id': 100020511, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002060: {'name': {'cn': '晨露农场', 'en': 'Morningdew Farm', 'jp': '朝露農場'}, 'target_id': 100020601, 'target': {10010064: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002070: {'name': {'cn': '农田里的守望', 'en': 'The Farm Girl', 'jp': '畑の見張り'}, 'target_id': 100020701, 'target': {5601: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002080: {'name': {'cn': '生长的作物', 'en': 'Growing Crops', 'jp': '成長する作物'}, 'target_id': 100020801, 'target': {1000: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002081: {'name': {'cn': '生长的作物', 'en': 'Growing Crops', 'jp': '成長する作物'}, 'target_id': 100020811, 'target': {101001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002090: {'name': {'cn': '牧场的宝藏', 'en': "The Ranch's Treasure", 'jp': '牧場の宝物'}, 'target_id': 100020901, 'target': {2902: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002100: {'name': {'cn': '追捕时间到!', 'en': 'On the Hunt!', 'jp': '追跡の時間だ!'}, 'target_id': 100021001, 'target': {4201: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002110: {'name': {'cn': '咯咯鸡的饲料', 'en': 'Feed for Clucky', 'jp': 'コッコートリの餌'}, 'target_id': 100021101, 'target': {2803: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002120: {'name': {'cn': '制作饲料', 'en': 'Producing Feed', 'jp': '飼料を制作'}, 'target_id': 100021201, 'target': {3000: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002130: {'name': {'cn': '动物之礼', 'en': 'Gift From the Animals', 'jp': '動物の贈り物'}, 'target_id': 100021301, 'target': {2905: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002131: {'name': {'cn': '动物之礼', 'en': 'Gift From the Animals', 'jp': '動物の贈り物'}, 'target_id': 100021311, 'target': {2910: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002140: {'name': {'cn': '欣欣向荣', 'en': 'Daily Progress', 'jp': '日々上々'}, 'target_id': 100021401, 'target': {3004: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002141: {'name': {'cn': '提升开发等级', 'en': 'Raise the Island Development Level', 'jp': '開発レベルを上げよう'}, 'target_id': 100021411, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002150: {'name': {'cn': '小憩时光', 'en': 'Break Time', 'jp': '小休憩の時間'}, 'target_id': 100021501, 'target': {10090001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002160: {'name': {'cn': '暖炉里的蛋香', 'en': 'The Smell of Eggs', 'jp': '卵の香り'}, 'target_id': 100021601, 'target': {1202: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002170: {'name': {'cn': '美味欧姆蛋!', 'en': 'A Delicious Omelette!', 'jp': '美味しいオムレツ!'}, 'target_id': 100021701, 'target': {3059: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002180: {'name': {'cn': '港口速递', 'en': 'Harbor Delivery', 'jp': '港の配達便'}, 'target_id': 100021801, 'target': {3059: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002190: {'name': {'cn': '购得到的美味', 'en': 'Deliciousness for Sale', 'jp': '購入できる美味しさ'}, 'target_id': 100021901, 'target': {1221: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002200: {'name': {'cn': '第一位顾客', 'en': 'Customer Number One', 'jp': 'お客さま第一号'}, 'target_id': 100022001, 'target': {3059: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002210: {'name': {'cn': '限时优选-{namecode:98:明石}', 'en': "Akashi's Exclusive Deals", 'jp': '明石の限定販売'}, 'target_id': 100022101, 'target': {3902: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002211: {'name': {'cn': '提升开发等级', 'en': 'Raise the Island Development Level', 'jp': '開発レベルを上げよう'}, 'target_id': 100022111, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002220: {'name': {'cn': '宝藏岛?', 'en': 'Treasure Island?', 'jp': '宝の島?'}, 'target_id': 100022201, 'target': {3903: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002230: {'name': {'cn': '飞行器的新生-港口', 'en': 'Aircraft Restoration - Harbor', 'jp': '飛行装置の復活-港'}, 'target_id': 100022301, 'target': {1010: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002240: {'name': {'cn': '飞行器的新生-栖风原野', 'en': 'Aircraft Restoration - Windswept Plains', 'jp': '飛行装置の復活-風の原野'}, 'target_id': 100022401, 'target': {1006: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002250: {'name': {'cn': '飞行器的新生-晨露农场', 'en': 'Aircraft Restoration - Morningdew Farm', 'jp': '飛行装置の復活-朝露農場'}, 'target_id': 100022501, 'target': {1001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002251: {'name': {'cn': '飞行器的新生-晨露农场', 'en': 'Aircraft Restoration - Morningdew Farm', 'jp': '飛行装置の復活-朝露農場'}, 'target_id': 100022511, 'target': {3904: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002260: {'name': {'cn': '碧空待航', 'en': 'Make for Blue Skies', 'jp': '青空レッツフライ'}, 'target_id': 100022601, 'target': {4702: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002270: {'name': {'cn': '出发,宝藏岛!', 'en': 'Off to Treasure Island!', 'jp': 'いざ、宝の島へ!'}, 'target_id': 100022701, 'target': {10030001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002271: {'name': {'cn': '出发,宝藏岛!', 'en': 'Off to Treasure Island!', 'jp': 'いざ、宝の島へ!'}, 'target_id': 100022711, 'target': {7802: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002280: {'name': {'cn': '所谓宝藏', 'en': 'So-Called Treasure', 'jp': 'いわゆる宝…'}, 'target_id': 100022801, 'target': {10030006: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002290: {'name': {'cn': '日常补给', 'en': 'Daily Supply', 'jp': '定期補給'}, 'target_id': 100022901, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002300: {'name': {'cn': '集会岛上的浪漫休憩', 'en': 'My Own Romantic Getaway', 'jp': '集会島でのロマンチックな憩い'}, 'target_id': 100023001, 'target': {2702: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002310: {'name': {'cn': '集会岛的宝藏', 'en': 'The Treasure of Get-Together Island', 'jp': '集会島のお宝'}, 'target_id': 100023101, 'target': {2704: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002320: {'name': {'cn': '最棒的集会岛', 'en': 'Get-Together Island at Its Best', 'jp': '最高の集会島'}, 'target_id': 100023201, 'target': {0: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002330: {'name': {'cn': '动物邻居', 'en': 'Animal Neighbors', 'jp': '動物の隣人'}, 'target_id': 100023301, 'target': {0: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002340: {'name': {'cn': '飞行器大升级', 'en': 'Aircraft Upgrade', 'jp': '飛行装置大アップグレード'}, 'target_id': 100023401, 'target': {2700: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002350: {'name': {'cn': '神秘专家', 'en': 'The Mysterious Expert', 'jp': '謎の専門家'}, 'target_id': 100023501, 'target': {5401: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002360: {'name': {'cn': '奇妙集会岛', 'en': 'The Wonder of Get-Together Island', 'jp': '不思議な集会島'}, 'target_id': 100023601, 'target': {2603: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002370: {'name': {'cn': '闪亮的未来', 'en': 'A Bright Future', 'jp': '輝く未来'}, 'target_id': 100023701, 'target': {10020009: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10002380: {'name': {'cn': '提升开发等级', 'en': 'Raise the Island Development Level', 'jp': '開発レベルを上げよう'}, 'target_id': 100023801, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003010: {'name': {'cn': '开发区的生活', 'en': 'Life in the Development', 'jp': '開発エリアでの生活'}, 'target_id': 100030101, 'target': {1904: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003011: {'name': {'cn': '开发区的生活', 'en': 'Life in the Development', 'jp': '開発エリアでの生活'}, 'target_id': 100030111, 'target': {10060001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003020: {'name': {'cn': '商区建设', 'en': 'Building the Commercial Area', 'jp': '商店街建設'}, 'target_id': 100030201, 'target': {4502: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003030: {'name': {'cn': '商区美食', 'en': 'Foods of the Commercial Area', 'jp': '商店街グルメ'}, 'target_id': 100030301, 'target': {3011: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003040: {'name': {'cn': '百业烟火', 'en': 'Commerce Revitalized', 'jp': '商業振興'}, 'target_id': 100030401, 'target': {4507: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003050: {'name': {'cn': '繁荫之下', 'en': 'To the Prosperous Plantation', 'jp': '繁茂農園にて'}, 'target_id': 100030501, 'target': {10050001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003051: {'name': {'cn': '繁荫之下', 'en': 'To the Prosperous Plantation', 'jp': '繁茂農園にて'}, 'target_id': 100030511, 'target': {5701: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003060: {'name': {'cn': '野地寻蜂', 'en': 'The Quest for Honey', 'jp': '野原でミツバチ探し'}, 'target_id': 100030601, 'target': {6401: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003070: {'name': {'cn': '蜜蜂与蜂蜜', 'en': 'Honeybees and Bee Honey', 'jp': 'ミツバチとはちみつ'}, 'target_id': 100030701, 'target': {2606: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003080: {'name': {'cn': '苹果树', 'en': 'Apple Trees', 'jp': 'りんごの木'}, 'target_id': 100030801, 'target': {501001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003090: {'name': {'cn': '丰收的苹果', 'en': 'Heaps of Apples', 'jp': '豊作のりんご'}, 'target_id': 100030901, 'target': {501001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003091: {'name': {'cn': '丰收的苹果', 'en': 'Heaps of Apples', 'jp': '豊作のりんご'}, 'target_id': 100030911, 'target': {2016: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003100: {'name': {'cn': '开拓苗圃', 'en': 'Building a Plant Nursery', 'jp': '苗場の開拓'}, 'target_id': 100031001, 'target': {320201: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10003110: {'name': {'cn': '提升开发等级', 'en': 'Raise the Island Development Level', 'jp': '開発レベルを上げよう'}, 'target_id': 100031101, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10004010: {'name': {'cn': '奇怪的{namecode:98:明石}', 'en': 'One Suspicious Feline', 'jp': '奇妙な明石'}, 'target_id': 100040101, 'target': {2104: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10004020: {'name': {'cn': '工业化的开端', 'en': 'The Industrial Revolution', 'jp': '工業化の始まり'}, 'target_id': 100040201, 'target': {2700: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10004030: {'name': {'cn': '岛屿工厂', 'en': 'Island Factory', 'jp': '離島工場'}, 'target_id': 100040301, 'target': {10070031: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 10019999: {'name': {'cn': '繁荣与债务', 'en': 'Prosperity and Debt', 'jp': '繁栄と債務'}, 'target_id': 40005, 'target': {1: 2000000}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 50000001: {'name': {'cn': '优化进行中', 'en': 'Improvement in Progress', 'jp': '改善進行中'}, 'target_id': 500000011, 'target': {5822: 1}, 'start_time': {'cn': '2025-11-06 00:00:00', 'en': '2025-11-06 00:00:00', 'jp': '2025-11-06 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50000002: {'name': {'cn': '新季度,新气象!', 'en': 'New Season, New Start!', 'jp': '新シーズン到来!'}, 'target_id': 500000021, 'target': {5826: 1}, 'start_time': {'cn': '2026-02-05 00:00:00', 'en': '2026-02-05 00:00:00', 'jp': '2026-02-05 00:00:00'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50000003: {'name': {'cn': '夏日奇遇', 'en': 'Summertime Adventure', 'jp': '夏の出会い'}, 'target_id': 500000031, 'target': {5845: 1}, 'start_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 51001000: {'name': {'cn': '养成登岛邀约', 'en': 'Invite Project Identity Characters', 'jp': '育成キャラ招待'}, 'target_id': 510010001, 'target': {12: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51001001: {'name': {'cn': '数据接入中Ⅰ', 'en': 'Importing Data - I', 'jp': 'データ導入中Ⅰ'}, 'target_id': 510010011, 'target': {402: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51001002: {'name': {'cn': '数据接入中Ⅱ', 'en': 'Importing Data - II', 'jp': 'データ導入中Ⅱ'}, 'target_id': 510010012, 'target': {901001: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51001003: {'name': {'cn': '数据接入中Ⅲ', 'en': 'Importing Data - III', 'jp': 'データ導入中Ⅲ'}, 'target_id': 510010013, 'target': {101016: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51002000: {'name': {'cn': '捣蛋鬼来袭', 'en': 'Attack of the Little Trickster', 'jp': 'いたずらっ子襲来'}, 'target_id': 510020001, 'target': {11: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51002001: {'name': {'cn': '捣蛋鬼来袭Ⅰ', 'en': 'Attack of the Little Trickster - I', 'jp': 'いたずらっ子襲来Ⅰ'}, 'target_id': 510020011, 'target': {101: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51002002: {'name': {'cn': '捣蛋鬼来袭Ⅱ', 'en': 'Attack of the Little Trickster - II', 'jp': 'いたずらっ子襲来Ⅱ'}, 'target_id': 510020012, 'target': {502002: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51002003: {'name': {'cn': '捣蛋鬼来袭Ⅲ', 'en': 'Attack of the Little Trickster - III', 'jp': 'いたずらっ子襲来Ⅲ'}, 'target_id': 510020013, 'target': {6201: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51003000: {'name': {'cn': '静悄悄访客', 'en': 'The Quiet Visitor', 'jp': '物静かな来訪者'}, 'target_id': 510030001, 'target': {13: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51003001: {'name': {'cn': '静悄悄访客Ⅰ', 'en': 'The Quiet Visitor - I', 'jp': '物静かな来訪者Ⅰ'}, 'target_id': 510030011, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51003002: {'name': {'cn': '静悄悄访客Ⅱ', 'en': 'The Quiet Visitor - II', 'jp': '物静かな来訪者Ⅱ'}, 'target_id': 510030012, 'target': {102: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 51003003: {'name': {'cn': '静悄悄访客Ⅲ', 'en': 'The Quiet Visitor - III', 'jp': '物静かな来訪者Ⅲ'}, 'target_id': 510030013, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 50001001: {'name': {'cn': '秋季特产采集(1/7)', 'en': 'Gather Autumn Specialties (1/7)', 'jp': '秋の土産採集(1/7)'}, 'target_id': 50001001, 'target': {990003: 8}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50001002: {'name': {'cn': '秋季特产采集(2/7)', 'en': 'Gather Autumn Specialties (2/7)', 'jp': '秋の土産採集(2/7)'}, 'target_id': 50001002, 'target': {990003: 24}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50001003: {'name': {'cn': '秋季特产采集(3/7)', 'en': 'Gather Autumn Specialties (3/7)', 'jp': '秋の土産採集(3/7)'}, 'target_id': 50001003, 'target': {990003: 48}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50001004: {'name': {'cn': '秋季特产采集(4/7)', 'en': 'Gather Autumn Specialties (4/7)', 'jp': '秋の土産採集(4/7)'}, 'target_id': 50001004, 'target': {990003: 96}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50001005: {'name': {'cn': '秋季特产采集(5/7)', 'en': 'Gather Autumn Specialties (5/7)', 'jp': '秋の土産採集(5/7)'}, 'target_id': 50001005, 'target': {990003: 120}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50001006: {'name': {'cn': '秋季特产采集(6/7)', 'en': 'Gather Autumn Specialties (6/7)', 'jp': '秋の土産採集(6/7)'}, 'target_id': 50001006, 'target': {990003: 160}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50001007: {'name': {'cn': '秋季特产采集(7/7)', 'en': 'Gather Autumn Specialties (7/7)', 'jp': '秋の土産採集(7/7)'}, 'target_id': 50001007, 'target': {990003: 240}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50001008: {'name': {'cn': '春季特产采集(1/7)', 'en': 'Gather Spring Specialties (1/7)', 'jp': '春の土産採集(1/7)'}, 'target_id': 50001008, 'target': {990007: 8}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50001009: {'name': {'cn': '春季特产采集(2/7)', 'en': 'Gather Spring Specialties (2/7)', 'jp': '春の土産採集(2/7)'}, 'target_id': 50001009, 'target': {990007: 24}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50001010: {'name': {'cn': '春季特产采集(3/7)', 'en': 'Gather Spring Specialties (3/7)', 'jp': '春の土産採集(3/7)'}, 'target_id': 50001010, 'target': {990007: 48}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50001011: {'name': {'cn': '春季特产采集(4/7)', 'en': 'Gather Spring Specialties (4/7)', 'jp': '春の土産採集(4/7)'}, 'target_id': 50001011, 'target': {990007: 96}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50001012: {'name': {'cn': '春季特产采集(5/7)', 'en': 'Gather Spring Specialties (5/7)', 'jp': '春の土産採集(5/7)'}, 'target_id': 50001012, 'target': {990007: 120}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50001013: {'name': {'cn': '春季特产采集(6/7)', 'en': 'Gather Spring Specialties (6/7)', 'jp': '春の土産採集(6/7)'}, 'target_id': 50001013, 'target': {990007: 160}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50001014: {'name': {'cn': '春季特产采集(7/7)', 'en': 'Gather Spring Specialties (7/7)', 'jp': '春の土産採集(7/7)'}, 'target_id': 50001014, 'target': {990007: 240}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50001015: {'name': {'cn': '夏季特产采集(1/7)', 'en': 'Gather Summer Specialties (1/7)', 'jp': '夏の土産採集(1/7)'}, 'target_id': 50001015, 'target': {990018: 8}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50001016: {'name': {'cn': '夏季特产采集(2/7)', 'en': 'Gather Summer Specialties (2/7)', 'jp': '夏の土産採集(2/7)'}, 'target_id': 50001016, 'target': {990018: 24}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50001017: {'name': {'cn': '夏季特产采集(3/7)', 'en': 'Gather Summer Specialties (3/7)', 'jp': '夏の土産採集(3/7)'}, 'target_id': 50001017, 'target': {990018: 48}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50001018: {'name': {'cn': '夏季特产采集(4/7)', 'en': 'Gather Summer Specialties (4/7)', 'jp': '夏の土産採集(4/7)'}, 'target_id': 50001018, 'target': {990018: 96}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50001019: {'name': {'cn': '夏季特产采集(5/7)', 'en': 'Gather Summer Specialties (5/7)', 'jp': '夏の土産採集(5/7)'}, 'target_id': 50001019, 'target': {990018: 120}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50001020: {'name': {'cn': '夏季特产采集(6/7)', 'en': 'Gather Summer Specialties (6/7)', 'jp': '夏の土産採集(6/7)'}, 'target_id': 50001020, 'target': {990018: 160}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50001021: {'name': {'cn': '夏季特产采集(7/7)', 'en': 'Gather Summer Specialties (7/7)', 'jp': '夏の土産採集(7/7)'}, 'target_id': 50001021, 'target': {990018: 240}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50002001: {'name': {'cn': '秋季特产制作(1/7)', 'en': 'Make Autumn Specialties (1/7)', 'jp': '秋の特産品制作(1/7)'}, 'target_id': 50002001, 'target': {4005: 40}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50002002: {'name': {'cn': '秋季特产制作(2/7)', 'en': 'Make Autumn Specialties (2/7)', 'jp': '秋の特産品制作(2/7)'}, 'target_id': 50002002, 'target': {4005: 80}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50002003: {'name': {'cn': '秋季特产制作(3/7)', 'en': 'Make Autumn Specialties (3/7)', 'jp': '秋の特産品制作(3/7)'}, 'target_id': 50002003, 'target': {4005: 100}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50002004: {'name': {'cn': '秋季特产制作(4/7)', 'en': 'Make Autumn Specialties (4/7)', 'jp': '秋の特産品制作(4/7)'}, 'target_id': 50002004, 'target': {4005: 200}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50002005: {'name': {'cn': '秋季特产制作(5/7)', 'en': 'Make Autumn Specialties (5/7)', 'jp': '秋の特産品制作(5/7)'}, 'target_id': 50002005, 'target': {4005: 400}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50002006: {'name': {'cn': '秋季特产制作(6/7)', 'en': 'Make Autumn Specialties (6/7)', 'jp': '秋の特産品制作(6/7)'}, 'target_id': 50002006, 'target': {4005: 600}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50002007: {'name': {'cn': '秋季特产制作(7/7)', 'en': 'Make Autumn Specialties (7/7)', 'jp': '秋の特産品制作(7/7)'}, 'target_id': 50002007, 'target': {4005: 800}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50002008: {'name': {'cn': '春季特产制作(1/7)', 'en': 'Make Spring Specialties (1/7)', 'jp': '春の特産品制作(1/7)'}, 'target_id': 50002008, 'target': {4019: 40}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50002009: {'name': {'cn': '春季特产制作(2/7)', 'en': 'Make Spring Specialties (2/7)', 'jp': '春の特産品制作(2/7)'}, 'target_id': 50002009, 'target': {4019: 80}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50002010: {'name': {'cn': '春季特产制作(3/7)', 'en': 'Make Spring Specialties (3/7)', 'jp': '春の特産品制作(3/7)'}, 'target_id': 50002010, 'target': {4019: 100}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50002011: {'name': {'cn': '春季特产制作(4/7)', 'en': 'Make Spring Specialties (4/7)', 'jp': '春の特産品制作(4/7)'}, 'target_id': 50002011, 'target': {4019: 200}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50002012: {'name': {'cn': '春季特产制作(5/7)', 'en': 'Make Spring Specialties (5/7)', 'jp': '春の特産品制作(5/7)'}, 'target_id': 50002012, 'target': {4019: 400}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50002013: {'name': {'cn': '春季特产制作(6/7)', 'en': 'Make Spring Specialties (6/7)', 'jp': '春の特産品制作(6/7)'}, 'target_id': 50002013, 'target': {4019: 600}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50002014: {'name': {'cn': '春季特产制作(7/7)', 'en': 'Make Spring Specialties (7/7)', 'jp': '春の特産品制作(7/7)'}, 'target_id': 50002014, 'target': {4019: 800}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50002015: {'name': {'cn': '夏季特产制作(1/7)', 'en': 'Make Summer Specialties (1/7)', 'jp': '夏の特産品制作(1/7)'}, 'target_id': 50002015, 'target': {4033: 40}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50002016: {'name': {'cn': '夏季特产制作(2/7)', 'en': 'Make Summer Specialties (2/7)', 'jp': '夏の特産品制作(2/7)'}, 'target_id': 50002016, 'target': {4033: 80}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50002017: {'name': {'cn': '夏季特产制作(3/7)', 'en': 'Make Summer Specialties (3/7)', 'jp': '夏の特産品制作(3/7)'}, 'target_id': 50002017, 'target': {4033: 100}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50002018: {'name': {'cn': '夏季特产制作(4/7)', 'en': 'Make Summer Specialties (4/7)', 'jp': '夏の特産品制作(4/7)'}, 'target_id': 50002018, 'target': {4033: 200}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50002019: {'name': {'cn': '夏季特产制作(5/7)', 'en': 'Make Summer Specialties (5/7)', 'jp': '夏の特産品制作(5/7)'}, 'target_id': 50002019, 'target': {4033: 400}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50002020: {'name': {'cn': '夏季特产制作(6/7)', 'en': 'Make Summer Specialties (6/7)', 'jp': '夏の特産品制作(6/7)'}, 'target_id': 50002020, 'target': {4033: 600}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50002021: {'name': {'cn': '夏季特产制作(7/7)', 'en': 'Make Summer Specialties (7/7)', 'jp': '夏の特産品制作(7/7)'}, 'target_id': 50002021, 'target': {4033: 800}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50003001: {'name': {'cn': '秋季特产订单(1/4)', 'en': 'Autumn Specialty Order (1/4)', 'jp': '秋の特産依頼(1/4)'}, 'target_id': 50003001, 'target': {990002: 1}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50003002: {'name': {'cn': '秋季特产订单(2/4)', 'en': 'Autumn Specialty Order (2/4)', 'jp': '秋の特産依頼(2/4)'}, 'target_id': 50003002, 'target': {990002: 5}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50003003: {'name': {'cn': '秋季特产订单(3/4)', 'en': 'Autumn Specialty Order (3/4)', 'jp': '秋の特産依頼(3/4)'}, 'target_id': 50003003, 'target': {990002: 10}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50003004: {'name': {'cn': '秋季特产订单(4/4)', 'en': 'Autumn Specialty Order (4/4)', 'jp': '秋の特産依頼(4/4)'}, 'target_id': 50003004, 'target': {990002: 15}, 'start_time': {'cn': '2025-09-29 00:00:00', 'en': '2025-09-29 00:00:00', 'jp': '2025-09-29 00:00:00'}, 'end_time': {'cn': '2026-02-05 12:00:00', 'en': '2026-02-05 12:00:00', 'jp': '2026-02-05 16:00:00'}}, + 50003005: {'name': {'cn': '春季特产订单(1/4)', 'en': 'Spring Specialty Order (1/4)', 'jp': '春の特産依頼(1/4)'}, 'target_id': 50003005, 'target': {990006: 1}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50003006: {'name': {'cn': '春季特产订单(2/4)', 'en': 'Spring Specialty Order (2/4)', 'jp': '春の特産依頼(2/4)'}, 'target_id': 50003006, 'target': {990006: 5}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50003007: {'name': {'cn': '春季特产订单(3/4)', 'en': 'Spring Specialty Order (3/4)', 'jp': '春の特産依頼(3/4)'}, 'target_id': 50003007, 'target': {990006: 10}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50003008: {'name': {'cn': '春季特产订单(4/4)', 'en': 'Spring Specialty Order (4/4)', 'jp': '春の特産依頼(4/4)'}, 'target_id': 50003008, 'target': {990006: 15}, 'start_time': {'cn': '2026-02-05 12:00:01', 'en': '2026-02-05 12:00:01', 'jp': '2026-02-05 16:00:01'}, 'end_time': {'cn': '2026-05-07 12:00:00', 'en': '2026-05-07 12:00:00', 'jp': '2026-05-07 16:00:00'}}, + 50003009: {'name': {'cn': '夏季特产订单(1/4)', 'en': 'Summer Specialty Order (1/4)', 'jp': '夏の特産依頼(1/4)'}, 'target_id': 50003009, 'target': {990017: 1}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50003010: {'name': {'cn': '夏季特产订单(2/4)', 'en': 'Summer Specialty Order (2/4)', 'jp': '夏の特産依頼(2/4)'}, 'target_id': 50003010, 'target': {990017: 5}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50003011: {'name': {'cn': '夏季特产订单(3/4)', 'en': 'Summer Specialty Order (3/4)', 'jp': '夏の特産依頼(3/4)'}, 'target_id': 50003011, 'target': {990017: 10}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 50003012: {'name': {'cn': '夏季特产订单(4/4)', 'en': 'Summer Specialty Order (4/4)', 'jp': '夏の特産依頼(4/4)'}, 'target_id': 50003012, 'target': {990017: 15}, 'start_time': {'cn': '2026-05-07 12:00:01', 'en': '2026-05-07 00:00:00', 'jp': '2026-05-07 16:00:00'}, 'end_time': {'cn': '2026-08-06 12:00:00', 'en': '2026-08-05 23:59:59', 'jp': '2026-08-06 16:00:00'}}, + 80001001: {'name': {'cn': '麦田守望', 'en': 'Watching the Fields', 'jp': '畑の見張り'}, 'target_id': 80010001, 'target': {2000: 500}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001002: {'name': {'cn': '动物食品', 'en': 'The Beasts Hunger', 'jp': 'アニマルフード'}, 'target_id': 80010002, 'target': {2008: 500}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001003: {'name': {'cn': '开拓豆源', 'en': 'Beans or Bust', 'jp': '豆供給開拓'}, 'target_id': 80010003, 'target': {2006: 500}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001004: {'name': {'cn': '稻米供应', 'en': 'More Rice!', 'jp': 'お米生産'}, 'target_id': 80010004, 'target': {2002: 500}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001005: {'name': {'cn': '黄金粮仓', 'en': 'Golden Granary', 'jp': '黄金米蔵'}, 'target_id': 80010005, 'target': {2001: 500}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001006: {'name': {'cn': '橙色活力', 'en': 'The Juiciest of Oranges', 'jp': '元気オレンジ'}, 'target_id': 80010006, 'target': {2004: 250}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001007: {'name': {'cn': '乳品补给', 'en': 'Donations of Dairy', 'jp': 'ミルク補給'}, 'target_id': 80010007, 'target': {2603: 250}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001008: {'name': {'cn': '甜蜜引擎', 'en': 'Sugar in the Tank', 'jp': 'スイートエンジン'}, 'target_id': 80010008, 'target': {3009: 250}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001009: {'name': {'cn': '咖啡供应', 'en': 'Needs More Coffee', 'jp': 'コーヒー供給'}, 'target_id': 80010009, 'target': {3005: 250}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001010: {'name': {'cn': '烤肉能量', 'en': 'Meat Is Energy', 'jp': '焼き肉エナジー'}, 'target_id': 80010010, 'target': {3029: 250}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001011: {'name': {'cn': '调味基础', 'en': 'Basic Seasoning', 'jp': '味付基本'}, 'target_id': 80010011, 'target': {2007: 100}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001012: {'name': {'cn': '健康饮食', 'en': 'A Healthy Menu', 'jp': 'ヘルシー献立'}, 'target_id': 80010012, 'target': {3015: 100}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001013: {'name': {'cn': '营养组合', 'en': 'Nutritional Combos', 'jp': '栄養セット'}, 'target_id': 80010013, 'target': {3012: 100}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001014: {'name': {'cn': '拿铁时光', 'en': 'Latte Time', 'jp': 'ラテタイム'}, 'target_id': 80010014, 'target': {3007: 100}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001015: {'name': {'cn': '禽肉快炒', 'en': 'Stir-Fry Resupply', 'jp': '肉を炒めて'}, 'target_id': 80010015, 'target': {3032: 100}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001016: {'name': {'cn': '便携快餐', 'en': 'Quick and Easy Meal', 'jp': 'タイパ食事'}, 'target_id': 80010016, 'target': {3034: 50}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001017: {'name': {'cn': '首次接收', 'en': 'Your First Receipt', 'jp': '初めての接収'}, 'target_id': 80010017, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001018: {'name': {'cn': '稳定入库', 'en': 'Safely Stored', 'jp': '安心納品'}, 'target_id': 80010018, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001019: {'name': {'cn': '定期补给', 'en': 'Regular Resupply', 'jp': '定期補充'}, 'target_id': 80010019, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001020: {'name': {'cn': '高效接收', 'en': 'Efficient Delivery', 'jp': '高効率納品'}, 'target_id': 80010020, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001021: {'name': {'cn': '补给充足', 'en': 'Well Stocked', 'jp': '在庫は余裕'}, 'target_id': 80010021, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001022: {'name': {'cn': '永续储备', 'en': 'Sustainable Reserves', 'jp': '備蓄は永久的'}, 'target_id': 80010022, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001023: {'name': {'cn': '发展根基', 'en': 'Foundation for Development', 'jp': '発展の礎'}, 'target_id': 80010023, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001024: {'name': {'cn': '初识订单', 'en': 'Your First Request', 'jp': '初めての依頼'}, 'target_id': 80010024, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001025: {'name': {'cn': '稳定交付', 'en': 'A Reliable Helper', 'jp': '安心依頼'}, 'target_id': 80010025, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001026: {'name': {'cn': '坚实后盾', 'en': 'Firm Support', 'jp': '堅実なサポート'}, 'target_id': 80010026, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001027: {'name': {'cn': '订单专家', 'en': 'Request Master', 'jp': '依頼のプロ'}, 'target_id': 80010027, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001028: {'name': {'cn': '发展支柱', 'en': 'Pillar of Development', 'jp': '発展の柱'}, 'target_id': 80010028, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001029: {'name': {'cn': '开发核心', 'en': 'Core of Development', 'jp': '発展の中心'}, 'target_id': 80010029, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001030: {'name': {'cn': '繁荣之基', 'en': 'Foundation for Prosperity', 'jp': '繁栄の礎'}, 'target_id': 80010030, 'target': {}, 'start_time': {'cn': None, 'en': None, 'jp': None}, 'end_time': {'cn': None, 'en': None, 'jp': None}}, + 80001101: {'name': {'cn': '麦田守望', 'en': 'Watching the Fields', 'jp': '畑の見張り'}, 'target_id': 80011001, 'target': {2000: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001102: {'name': {'cn': '动物食品', 'en': 'The Beasts Hunger', 'jp': 'アニマルフード'}, 'target_id': 80011002, 'target': {2008: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001103: {'name': {'cn': '开拓豆源', 'en': 'Beans or Bust', 'jp': '豆供給開拓'}, 'target_id': 80011003, 'target': {2006: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001104: {'name': {'cn': '稻米供应', 'en': 'More Rice!', 'jp': 'お米生産'}, 'target_id': 80011004, 'target': {2002: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001105: {'name': {'cn': '黄金粮仓', 'en': 'Golden Granary', 'jp': '黄金米蔵'}, 'target_id': 80011005, 'target': {2001: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001106: {'name': {'cn': '橙色活力', 'en': 'The Juiciest of Oranges', 'jp': '元気オレンジ'}, 'target_id': 80011006, 'target': {2004: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001107: {'name': {'cn': '乳品补给', 'en': 'Donations of Dairy', 'jp': 'ミルク補給'}, 'target_id': 80011007, 'target': {2603: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001108: {'name': {'cn': '甜蜜引擎', 'en': 'Sugar in the Tank', 'jp': 'スイートエンジン'}, 'target_id': 80011008, 'target': {3017: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001109: {'name': {'cn': '咖啡供应', 'en': 'Needs More Coffee', 'jp': 'コーヒー供給'}, 'target_id': 80011009, 'target': {3005: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001110: {'name': {'cn': '烤肉能量', 'en': 'Meat Is Energy', 'jp': '焼き肉エナジー'}, 'target_id': 80011010, 'target': {3029: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001111: {'name': {'cn': '调味基础', 'en': 'Basic Seasoning', 'jp': '味付基本'}, 'target_id': 80011011, 'target': {2007: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001112: {'name': {'cn': '健康饮食', 'en': 'A Healthy Menu', 'jp': 'ヘルシー献立'}, 'target_id': 80011012, 'target': {3015: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001113: {'name': {'cn': '营养组合', 'en': 'Nutritional Combos', 'jp': '栄養セット'}, 'target_id': 80011013, 'target': {3033: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001114: {'name': {'cn': '拿铁时光', 'en': 'Latte Time', 'jp': 'ラテタイム'}, 'target_id': 80011014, 'target': {3007: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001115: {'name': {'cn': '禽肉快炒', 'en': 'Stir-Fry Resupply', 'jp': '肉を炒めて'}, 'target_id': 80011015, 'target': {3032: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001116: {'name': {'cn': '便携快餐', 'en': 'Quick and Easy Meal', 'jp': 'タイパ食事'}, 'target_id': 80011016, 'target': {3034: 50}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001117: {'name': {'cn': '首次接收', 'en': 'Your First Receipt', 'jp': '初めての接収'}, 'target_id': 80011017, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001118: {'name': {'cn': '稳定入库', 'en': 'Safely Stored', 'jp': '安心納品'}, 'target_id': 80011018, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001119: {'name': {'cn': '定期补给', 'en': 'Regular Resupply', 'jp': '定期補充'}, 'target_id': 80011019, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001120: {'name': {'cn': '高效接收', 'en': 'Efficient Delivery', 'jp': '高効率納品'}, 'target_id': 80011020, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001121: {'name': {'cn': '补给充足', 'en': 'Well Stocked', 'jp': '在庫は余裕'}, 'target_id': 80011021, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001122: {'name': {'cn': '永续储备', 'en': 'Sustainable Reserves', 'jp': '備蓄は永久的'}, 'target_id': 80011022, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001123: {'name': {'cn': '发展根基', 'en': 'Foundation for Development', 'jp': '発展の礎'}, 'target_id': 80011023, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001124: {'name': {'cn': '初识订单', 'en': 'Your First Request', 'jp': '初めての依頼'}, 'target_id': 80011024, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001125: {'name': {'cn': '稳定交付', 'en': 'A Reliable Helper', 'jp': '安心依頼'}, 'target_id': 80011025, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001126: {'name': {'cn': '坚实后盾', 'en': 'Firm Support', 'jp': '堅実なサポート'}, 'target_id': 80011026, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001127: {'name': {'cn': '订单专家', 'en': 'Request Master', 'jp': '依頼のプロ'}, 'target_id': 80011027, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001128: {'name': {'cn': '发展支柱', 'en': 'Pillar of Development', 'jp': '発展の柱'}, 'target_id': 80011028, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001129: {'name': {'cn': '开发核心', 'en': 'Core of Development', 'jp': '発展の中心'}, 'target_id': 80011029, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001130: {'name': {'cn': '繁荣之基', 'en': 'Foundation for Prosperity', 'jp': '繁栄の礎'}, 'target_id': 80011030, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001201: {'name': {'cn': '田野坚盾', 'en': 'Garden Logistics', 'jp': '畑の兵站'}, 'target_id': 80012001, 'target': {2005: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001202: {'name': {'cn': '提神饮品', 'en': 'A Cup to Wake You Up', 'jp': '目覚まし一杯'}, 'target_id': 80012002, 'target': {2009: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001203: {'name': {'cn': '蔬菜供给', 'en': 'Vegetable Supplies', 'jp': '野菜供給'}, 'target_id': 80012003, 'target': {2003: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001204: {'name': {'cn': '甜蜜果味', 'en': 'Sweet Strawberries', 'jp': '甘いスイーツ'}, 'target_id': 80012004, 'target': {2011: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001205: {'name': {'cn': '纺织原料', 'en': 'Textile Materials', 'jp': '紡織の素材'}, 'target_id': 80012005, 'target': {2012: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001206: {'name': {'cn': '水果补给', 'en': 'Fruit Supplies', 'jp': '果物補給'}, 'target_id': 80012006, 'target': {2016: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001207: {'name': {'cn': '清爽活力', 'en': 'Zingy and Refreshing', 'jp': 'さわやか元気'}, 'target_id': 80012007, 'target': {2020: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001208: {'name': {'cn': '便携快餐', 'en': 'Quick and Easy Meal', 'jp': '便利な軽食'}, 'target_id': 80012008, 'target': {3114: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001209: {'name': {'cn': '清淡风味', 'en': 'Simply Seasoned', 'jp': 'あっさりした味付け'}, 'target_id': 80012009, 'target': {3116: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001210: {'name': {'cn': '甜味动力', 'en': 'Sugary Motivation Boost', 'jp': '甘物はモチベ'}, 'target_id': 80012010, 'target': {3020: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001211: {'name': {'cn': '果香醒神', 'en': 'Fragrant and Energizing', 'jp': '香りでさっぱり'}, 'target_id': 80012011, 'target': {3008: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001212: {'name': {'cn': '清新菜式', 'en': 'Bounty of the Sea', 'jp': '淡雅の料理'}, 'target_id': 80012012, 'target': {3115: 50}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001213: {'name': {'cn': '海鲜美味', 'en': 'Supreme Seafood', 'jp': '激ウマシーフード'}, 'target_id': 80012013, 'target': {3118: 25}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001214: {'name': {'cn': '麻辣激情', 'en': 'Spicy Extravagance', 'jp': '激辛パッション'}, 'target_id': 80012014, 'target': {3119: 25}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001215: {'name': {'cn': '艺术点缀', 'en': 'Artistic Flair', 'jp': '芸術的な彩り'}, 'target_id': 80012015, 'target': {3117: 25}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001216: {'name': {'cn': '珍馐汇聚', 'en': 'A Treat to Break Your Vows For', 'jp': 'あつまれ珍味'}, 'target_id': 80012016, 'target': {3120: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001217: {'name': {'cn': '首次接收', 'en': 'Your First Receipt', 'jp': '初めての接収'}, 'target_id': 80012017, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001218: {'name': {'cn': '稳定入库', 'en': 'Safely Stored', 'jp': '安心納品'}, 'target_id': 80012018, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001219: {'name': {'cn': '定期补给', 'en': 'Regular Resupply', 'jp': '定期補充'}, 'target_id': 80012019, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001220: {'name': {'cn': '高效接收', 'en': 'Efficient Delivery', 'jp': '高効率納品'}, 'target_id': 80012020, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001221: {'name': {'cn': '补给充足', 'en': 'Well Stocked', 'jp': '在庫は余裕'}, 'target_id': 80012021, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001222: {'name': {'cn': '永续储备', 'en': 'Sustainable Reserves', 'jp': '備蓄は永久的'}, 'target_id': 80012022, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001223: {'name': {'cn': '发展根基', 'en': 'Foundation for Development', 'jp': '発展の礎'}, 'target_id': 80012023, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001224: {'name': {'cn': '初识订单', 'en': 'Your First Request', 'jp': '初めての依頼'}, 'target_id': 80012024, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001225: {'name': {'cn': '稳定交付', 'en': 'A Reliable Helper', 'jp': '安心依頼'}, 'target_id': 80012025, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001226: {'name': {'cn': '坚实后盾', 'en': 'Firm Support', 'jp': '堅実なサポート'}, 'target_id': 80012026, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001227: {'name': {'cn': '订单专家', 'en': 'Request Master', 'jp': '依頼のプロ'}, 'target_id': 80012027, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001228: {'name': {'cn': '发展支柱', 'en': 'Pillar of Development', 'jp': '発展の柱'}, 'target_id': 80012028, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001229: {'name': {'cn': '开发核心', 'en': 'Core of Development', 'jp': '発展の中心'}, 'target_id': 80012029, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001230: {'name': {'cn': '繁荣之基', 'en': 'Foundation for Prosperity', 'jp': '繁栄の礎'}, 'target_id': 80012030, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001301: {'name': {'cn': '主粮储备', 'en': 'Stockpile Staple Foods', 'jp': '主食備蓄'}, 'target_id': 80013001, 'target': {2002: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001302: {'name': {'cn': '营养蛋白', 'en': 'Plant Proteins', 'jp': '栄養タンパク'}, 'target_id': 80013002, 'target': {2006: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001303: {'name': {'cn': '田野坚盾', 'en': 'Working the Fields', 'jp': '原野の支え'}, 'target_id': 80013003, 'target': {2001: 500}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001304: {'name': {'cn': '工业血脉', 'en': 'Industrial Roots', 'jp': '工業のルーツ'}, 'target_id': 80013004, 'target': {2022: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001305: {'name': {'cn': '香甜活力', 'en': 'Sweet Energy', 'jp': '甘い活力'}, 'target_id': 80013005, 'target': {2018: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001306: {'name': {'cn': '纤维补给', 'en': 'Finding Fibers', 'jp': '繊維補給'}, 'target_id': 80013006, 'target': {2010: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001307: {'name': {'cn': '芳香疗愈', 'en': 'Soothing Fragrances', 'jp': '香り豊かな癒し'}, 'target_id': 80013007, 'target': {2015: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001308: {'name': {'cn': '便携快餐', 'en': 'Bite-Sized Flavor', 'jp': 'タイパ食事'}, 'target_id': 80013008, 'target': {3033: 250}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001309: {'name': {'cn': '热带双响', 'en': 'Tropical Twins', 'jp': '南国ツイン'}, 'target_id': 80013009, 'target': {3018: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001310: {'name': {'cn': '甜味动力', 'en': 'Tasty Motivation', 'jp': '甘さはモチベ'}, 'target_id': 80013010, 'target': {3026: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001311: {'name': {'cn': '粉红奶香', 'en': 'Creamy Pink', 'jp': 'まろやかピンク'}, 'target_id': 80013011, 'target': {3010: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001312: {'name': {'cn': '经典正餐', 'en': 'Classic Dishes', 'jp': '定番な食事'}, 'target_id': 80013012, 'target': {3013: 50}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001313: {'name': {'cn': '白玉凝脂', 'en': 'Silky Tofu', 'jp': '白玉豆腐'}, 'target_id': 80013013, 'target': {3011: 50}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001314: {'name': {'cn': '炙烤香气', 'en': 'Roasted Delight', 'jp': '炭火の嬉しみ'}, 'target_id': 80013014, 'target': {3029: 50}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001315: {'name': {'cn': '安神茶饮', 'en': 'Calming Brews', 'jp': '落ち着いたお茶'}, 'target_id': 80013015, 'target': {3021: 25}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001316: {'name': {'cn': '净水耗材', 'en': 'Clean Water', 'jp': '浄水の消耗品'}, 'target_id': 80013016, 'target': {3056: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001317: {'name': {'cn': '首次接收', 'en': 'Your First Receipt', 'jp': '初めての接収'}, 'target_id': 80013017, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001318: {'name': {'cn': '稳定入库', 'en': 'Safely Stored', 'jp': '安心納品'}, 'target_id': 80013018, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001319: {'name': {'cn': '定期补给', 'en': 'Regular Resupply', 'jp': '定期補充'}, 'target_id': 80013019, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001320: {'name': {'cn': '高效接收', 'en': 'Efficient Delivery', 'jp': '高効率納品'}, 'target_id': 80013020, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001321: {'name': {'cn': '补给充足', 'en': 'Well Stocked', 'jp': '在庫は余裕'}, 'target_id': 80013021, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001322: {'name': {'cn': '永续储备', 'en': 'Sustainable Reserves', 'jp': '備蓄は永久的'}, 'target_id': 80013022, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001323: {'name': {'cn': '发展根基', 'en': 'Foundation for Development', 'jp': '発展の礎'}, 'target_id': 80013023, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001324: {'name': {'cn': '初识订单', 'en': 'Your First Request', 'jp': '初めての依頼'}, 'target_id': 80013024, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001325: {'name': {'cn': '稳定交付', 'en': 'A Reliable Helper', 'jp': '安心依頼'}, 'target_id': 80013025, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001326: {'name': {'cn': '坚实后盾', 'en': 'Firm Support', 'jp': '堅実なサポート'}, 'target_id': 80013026, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001327: {'name': {'cn': '订单专家', 'en': 'Request Master', 'jp': '依頼のプロ'}, 'target_id': 80013027, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001328: {'name': {'cn': '发展支柱', 'en': 'Pillar of Development', 'jp': '発展の柱'}, 'target_id': 80013028, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001329: {'name': {'cn': '开发核心', 'en': 'Core of Development', 'jp': '発展の中心'}, 'target_id': 80013029, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 80001330: {'name': {'cn': '繁荣之基', 'en': 'Foundation for Prosperity', 'jp': '繁栄の礎'}, 'target_id': 80013030, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20001001: {'name': {'cn': '森林里的声音', 'en': 'Sounds in the Woods', 'jp': '森の中の声'}, 'target_id': 200010011, 'target': {4801: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20001002: {'name': {'cn': '效率致胜', 'en': 'Efficiency Means Victory', 'jp': '効率こそ勝利'}, 'target_id': 200010021, 'target': {402001: 5}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20001003: {'name': {'cn': '效率致胜', 'en': 'Efficiency Means Victory', 'jp': '効率こそ勝利'}, 'target_id': 200010031, 'target': {1707: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20001004: {'name': {'cn': '叮咚——嘭!', 'en': 'Bang! Pow!', 'jp': 'ガラーン!ポン!'}, 'target_id': 200010041, 'target': {805: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20001005: {'name': {'cn': '折断的铁轨', 'en': 'Broken Track', 'jp': '折れたレール'}, 'target_id': 200010051, 'target': {1708: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20001006: {'name': {'cn': '临时轨道', 'en': 'Makeshift Track', 'jp': '仮設レール'}, 'target_id': 200010061, 'target': {2801: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20001007: {'name': {'cn': '今天必须完成', 'en': 'Must Be Done Today', 'jp': '今日は必ず完成させる'}, 'target_id': 200010071, 'target': {806: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20001008: {'name': {'cn': '最后的矿石', 'en': 'The Last Ore', 'jp': '最後の鉱石'}, 'target_id': 200010081, 'target': {2703: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20001009: {'name': {'cn': '河岸音符', 'en': 'Riverside Song', 'jp': '河岸の音符'}, 'target_id': 200010091, 'target': {10040056: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20002001: {'name': {'cn': '装饰画', 'en': 'Decorative Painting', 'jp': '装飾画'}, 'target_id': 200020011, 'target': {7401: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20002002: {'name': {'cn': '写生内容', 'en': 'Sketch Subject', 'jp': 'スケッチ内容'}, 'target_id': 200020021, 'target': {2305: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20002003: {'name': {'cn': '收集建议', 'en': 'Gathering Suggestions', 'jp': '意見収集'}, 'target_id': 200020031, 'target': {1207: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20002004: {'name': {'cn': '画笔制作', 'en': 'Making a Brush', 'jp': '絵筆制作'}, 'target_id': 200020041, 'target': {2605: 5}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20002005: {'name': {'cn': '画框制作', 'en': 'Making a Frame', 'jp': '額縁制作'}, 'target_id': 200020051, 'target': {2803: 4}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20002006: {'name': {'cn': '报酬', 'en': 'Your Reward', 'jp': '報酬'}, 'target_id': 200020061, 'target': {1909: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20003001: {'name': {'cn': '服务器的焦香味', 'en': "Something's Burned in the Servers", 'jp': 'サーバーの焦げた匂い'}, 'target_id': 200030011, 'target': {1208: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20003002: {'name': {'cn': '修复的原材料', 'en': 'Raw Materials for Repair', 'jp': '修復の原材料'}, 'target_id': 200030021, 'target': {2702: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20003003: {'name': {'cn': '寻找零件', 'en': 'Searching for Parts', 'jp': '部品探し'}, 'target_id': 200030031, 'target': {4901: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20003004: {'name': {'cn': '开服!', 'en': 'Function Restored!', 'jp': 'サーバー起動!'}, 'target_id': 200030041, 'target': {5001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20004000: {'name': {'cn': '提升开发等级', 'en': 'Raise the Island Development Level', 'jp': '開発レベルを上げよう'}, 'target_id': 200040000, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20004001: {'name': {'cn': '来自管理员的问候', 'en': 'Greetings From the Manager', 'jp': '管理者からの挨拶'}, 'target_id': 200040011, 'target': {1209: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20004002: {'name': {'cn': '新品研发要诀', 'en': 'Tips for Developing New Recipes', 'jp': '新商品開発の秘訣'}, 'target_id': 200040021, 'target': {3033: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20004003: {'name': {'cn': '商区的新招牌', 'en': "The Commercial Area's New Dish", 'jp': '商店街の新看板'}, 'target_id': 200040031, 'target': {4515: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20005001: {'name': {'cn': '闹鬼传闻', 'en': 'Ghastly Rumors', 'jp': 'お化け騒動'}, 'target_id': 200050011, 'target': {3007: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20005002: {'name': {'cn': '深夜的铃铛', 'en': 'Bells in the Night', 'jp': '深夜の鈴音'}, 'target_id': 200050021, 'target': {10010069: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20005003: {'name': {'cn': '深夜的铃铛', 'en': 'Bells in the Night', 'jp': '深夜の鈴音'}, 'target_id': 200050031, 'target': {3008: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20005004: {'name': {'cn': '另一阵怪声', 'en': 'Another Creepy Noise', 'jp': 'もう一つの怪しい音'}, 'target_id': 200050041, 'target': {2907: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20005005: {'name': {'cn': '是谁在深夜敲打?', 'en': "Who's Banging in the Night?", 'jp': '深夜に叩いてるのは誰?'}, 'target_id': 200050051, 'target': {7501: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20005006: {'name': {'cn': '短腿鬼影', 'en': 'Horned Figure', 'jp': '短足の怪しい影'}, 'target_id': 200050061, 'target': {2807: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20005007: {'name': {'cn': '捉鬼专家', 'en': 'Ghostbusting Specialist', 'jp': 'お化け退治専門家'}, 'target_id': 200050071, 'target': {10010071: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20005008: {'name': {'cn': '捉鬼专家', 'en': 'Ghostbusting Specialist', 'jp': 'お化け退治専門家'}, 'target_id': 200050081, 'target': {10010072: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20005009: {'name': {'cn': '捉鬼专家', 'en': 'Ghostbusting Specialist', 'jp': 'お化け退治専門家'}, 'target_id': 200050091, 'target': {6501: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20006001: {'name': {'cn': '羊丢了!!', 'en': 'The Sheep Are Gone!', 'jp': 'ヒツジがいなくなった!!'}, 'target_id': 200060011, 'target': {6601: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20006002: {'name': {'cn': '仔细调查', 'en': 'Closer Examination', 'jp': '詳しく調査'}, 'target_id': 200060021, 'target': {6602: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20006003: {'name': {'cn': '仔细调查', 'en': 'Closer Examination', 'jp': '詳しく調査'}, 'target_id': 200060031, 'target': {10010077: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20006004: {'name': {'cn': '找到你了!', 'en': 'Found You!', 'jp': '見つけた!'}, 'target_id': 200060041, 'target': {6603: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20006005: {'name': {'cn': '目击者', 'en': 'Eyewitness', 'jp': '目撃者'}, 'target_id': 200060051, 'target': {3009: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20006006: {'name': {'cn': '追到了!!', 'en': 'Chased It Down!', 'jp': '追いついた!!'}, 'target_id': 200060061, 'target': {10010081: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20006007: {'name': {'cn': '追到了!!', 'en': 'Chased It Down!', 'jp': '追いついた!!'}, 'target_id': 200060071, 'target': {6701: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20006008: {'name': {'cn': '追到了!!', 'en': 'Chased It Down!', 'jp': '追いついた!!'}, 'target_id': 200060081, 'target': {2912: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20006009: {'name': {'cn': '加固围栏', 'en': 'Reinforcing the Fences', 'jp': '柵の補強'}, 'target_id': 200060091, 'target': {2800: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007001: {'name': {'cn': '被困墙内的无名氏', 'en': 'The Person Stuck Behind the Wall', 'jp': '壁に閉じ込められた誰か'}, 'target_id': 200070011, 'target': {7001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007002: {'name': {'cn': '港口的蜜蜂防治', 'en': 'Removing Bees Around the Harbor', 'jp': '港のミツバチ駆除'}, 'target_id': 200070021, 'target': {7002: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007003: {'name': {'cn': '农场的蜜蜂防治', 'en': 'Removing Bees Around the Farm', 'jp': '農場のミツバチ駆除'}, 'target_id': 200070031, 'target': {7003: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007004: {'name': {'cn': '消除疲劳的蜂蜜水', 'en': 'Reinvigorating Honey Water', 'jp': '疲労回復のはちみつ水'}, 'target_id': 200070041, 'target': {1212: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007005: {'name': {'cn': '超美味蜂蜜水配方', 'en': 'The Best Honey Water in the World', 'jp': '超美味しいはちみつ水レシピ'}, 'target_id': 200070051, 'target': {7011: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007006: {'name': {'cn': '食材告急!', 'en': 'Missing Ingredients!', 'jp': '食材不足!'}, 'target_id': 200070061, 'target': {2020: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007007: {'name': {'cn': '寻找迷迭香', 'en': 'Find the Rosemary', 'jp': 'ローズマリーを探せ'}, 'target_id': 200070071, 'target': {4516: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007008: {'name': {'cn': '寻找迷迭香', 'en': 'Find the Rosemary', 'jp': 'ローズマリーを探せ'}, 'target_id': 200070081, 'target': {7012: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007009: {'name': {'cn': '新鲜的食材', 'en': 'Fresh Ingredients', 'jp': '新鮮な食材'}, 'target_id': 200070091, 'target': {1214: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20007010: {'name': {'cn': '无名之人', 'en': 'The Nameless Person', 'jp': '名無し'}, 'target_id': 200070101, 'target': {7013: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20008001: {'name': {'cn': '紧急订单', 'en': 'Urgent Request', 'jp': '緊急依頼'}, 'target_id': 200080011, 'target': {2306: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20008002: {'name': {'cn': '意外发生', 'en': 'Another Problem', 'jp': '事故発生'}, 'target_id': 200080021, 'target': {2800: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20008003: {'name': {'cn': '风险处理', 'en': 'Risk Management', 'jp': 'リスクマネジメント'}, 'target_id': 200080031, 'target': {2700: 10}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20008004: {'name': {'cn': '订单完成', 'en': 'Request Complete', 'jp': '依頼完了'}, 'target_id': 200080041, 'target': {1911: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20009001: {'name': {'cn': '神秘委托', 'en': 'No Details', 'jp': '不思議な依頼'}, 'target_id': 200090011, 'target': {10040059: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20009002: {'name': {'cn': '矿石运输', 'en': 'Transporting Ore', 'jp': '鉱石輸送'}, 'target_id': 200090021, 'target': {1711: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20009003: {'name': {'cn': '{namecode:98:明石}的计划', 'en': "Akashi's Plan", 'jp': '明石の計画'}, 'target_id': 200090031, 'target': {7101: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20009004: {'name': {'cn': '真相大白', 'en': 'The Truth Is Out', 'jp': '真相判明'}, 'target_id': 200090041, 'target': {2310: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20010001: {'name': {'cn': '给帕特莉的礼物', 'en': 'A Gift for Patrick', 'jp': 'パトリックへの贈り物'}, 'target_id': 200100011, 'target': {2805: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20010002: {'name': {'cn': '倾听心声', 'en': 'Advice', 'jp': '相談'}, 'target_id': 200100021, 'target': {2311: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20010003: {'name': {'cn': '元气的秘诀!', 'en': 'The Secret to High Energy!', 'jp': '元気の秘訣!'}, 'target_id': 200100031, 'target': {1217: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20010004: {'name': {'cn': '能量特饮制作中~', 'en': 'Energy Drink Under Preparation', 'jp': 'エナジードリンク制作中'}, 'target_id': 200100041, 'target': {1218: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20010005: {'name': {'cn': '寻找胡萝卜', 'en': 'Find Carrots', 'jp': 'ニンジンを探せ'}, 'target_id': 200100051, 'target': {3010: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20010006: {'name': {'cn': '睡得更香了~', 'en': 'Comfy Sleep!', 'jp': 'ぐっすり!'}, 'target_id': 200100061, 'target': {2806: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20010007: {'name': {'cn': '甜甜蛋白霜', 'en': 'Sweet Meringue', 'jp': '甘いメレンゲ'}, 'target_id': 200100071, 'target': {2601: 5}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20010008: {'name': {'cn': '心意送达', 'en': 'From Us to You', 'jp': '気持ちを届けよう'}, 'target_id': 200100081, 'target': {1913: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20011001: {'name': {'cn': '孤零零的餐馆', 'en': 'The Singular Restaurant', 'jp': 'ぽつんとレストラン'}, 'target_id': 200110011, 'target': {1224: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20011002: {'name': {'cn': '喜爱的美食', 'en': 'Favorite Foods', 'jp': '好きな料理'}, 'target_id': 200110021, 'target': {10020028: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20011003: {'name': {'cn': '可别忘了自己', 'en': "Don't Forget Yourself", 'jp': '自分のことも忘れずに'}, 'target_id': 200110031, 'target': {4518: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20011004: {'name': {'cn': '店铺建设中', 'en': 'Shop Under Construction', 'jp': '店舗建設中'}, 'target_id': 200110041, 'target': {2800: 5}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20011005: {'name': {'cn': '大家的商区', 'en': 'A Dining District for Everyone', 'jp': 'みんなの商店街'}, 'target_id': 200110051, 'target': {2602: 5}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012001: {'name': {'cn': '事已至此,先借一个吧', 'en': 'Just Borrow Them', 'jp': 'とりあえず借りる'}, 'target_id': 200120011, 'target': {3011: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012002: {'name': {'cn': '得赶紧买个新的才行', 'en': 'Buy New Ones Already', 'jp': '急いで新しいのを買わなければ'}, 'target_id': 200120021, 'target': {5306: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012003: {'name': {'cn': '果树当然也是要施肥的', 'en': 'Trees Need Fertilizer', 'jp': '果樹だって肥料がほしい'}, 'target_id': 200120031, 'target': {5307: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012004: {'name': {'cn': '果树当然也是要施肥的', 'en': 'Trees Need Fertilizer', 'jp': '果樹だって肥料がほしい'}, 'target_id': 200120041, 'target': {7201: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012005: {'name': {'cn': '丰收的喜悦精简版', 'en': 'The Joy of Farming (In a Nutshell)', 'jp': '豊作の喜び・簡略版'}, 'target_id': 200120051, 'target': {2017: 5}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012006: {'name': {'cn': '礼尚往来', 'en': 'Reciprocation', 'jp': '礼には礼を'}, 'target_id': 200120061, 'target': {1225: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012007: {'name': {'cn': '确认订单数量', 'en': 'Checking the Number of Orders', 'jp': '注文数量確認'}, 'target_id': 200120071, 'target': {5310: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012008: {'name': {'cn': '还有“意外收获”?', 'en': 'A Surprise', 'jp': '「意外な収穫」?'}, 'target_id': 200120081, 'target': {1920: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012009: {'name': {'cn': '不是我喜欢的虫子,直接拒绝', 'en': 'Bad Bugs Get the Repellent', 'jp': '好きな虫じゃないから駆除だ'}, 'target_id': 200120091, 'target': {5311: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20012010: {'name': {'cn': '不是我喜欢的虫子,直接拒绝', 'en': 'Bad Bugs Get the Repellent', 'jp': '好きな虫じゃないから駆除だ'}, 'target_id': 200120101, 'target': {7202: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20013000: {'name': {'cn': '提升开发等级', 'en': 'Raise the Island Development Level', 'jp': '開発レベルを上げよう'}, 'target_id': 200130000, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20013001: {'name': {'cn': '杳无音讯的肥料', 'en': 'Missing Fertilizer', 'jp': '行方不明の肥料'}, 'target_id': 200130011, 'target': {2312: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20013002: {'name': {'cn': '散落的货物', 'en': 'Scattered Cargo', 'jp': '散らばった貨物'}, 'target_id': 200130021, 'target': {5711: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20013003: {'name': {'cn': '重新订购……', 'en': 'Place a New Order...', 'jp': '再注文……'}, 'target_id': 200130031, 'target': {1921: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20013004: {'name': {'cn': '寻找肥料中……', 'en': 'The Search for Fertilizer...', 'jp': '肥料探し中……'}, 'target_id': 200130041, 'target': {7301: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20013005: {'name': {'cn': '得救了!', 'en': 'The Day Is Saved!', 'jp': '助かった!'}, 'target_id': 200130051, 'target': {5712: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20013006: {'name': {'cn': '一起播种吧', 'en': 'Sowing Together', 'jp': '一緒に種まきしよう'}, 'target_id': 200130061, 'target': {502005: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20013007: {'name': {'cn': '收获时间', 'en': 'Harvest Time', 'jp': '収穫時間'}, 'target_id': 200130071, 'target': {2015: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20013008: {'name': {'cn': '薰衣草包裹派送中!', 'en': 'Lavender Delivery!', 'jp': 'ラベンダーの配送!'}, 'target_id': 200130081, 'target': {1922: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20014001: {'name': {'cn': '拼合的秘密', 'en': 'The Secret of the Map Pieces', 'jp': '組み合わせの秘密'}, 'target_id': 200140011, 'target': {5: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20014002: {'name': {'cn': '拼合的秘密', 'en': 'The Secret of the Map Pieces', 'jp': '組み合わせの秘密'}, 'target_id': 200140021, 'target': {10040051: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20014003: {'name': {'cn': '猫的报恩', 'en': "The Cat's Payback", 'jp': '猫の恩返し'}, 'target_id': 200140031, 'target': {5201: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20015001: {'name': {'cn': '启动时刻!', 'en': "Let's Get Started!", 'jp': 'スタート!'}, 'target_id': 200150011, 'target': {1020001: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20015002: {'name': {'cn': '岛屿大开发', 'en': 'Great Island Development', 'jp': '離島大開発'}, 'target_id': 200150021, 'target': {8: 5}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20016001: {'name': {'cn': '布莉缇的渔场', 'en': "Britain's Fish Hatchery", 'jp': 'ブリテンのいけす'}, 'target_id': 200160011, 'target': {8005: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20016002: {'name': {'cn': '钓鱼!钓鱼!', 'en': 'Fishing Time!', 'jp': '釣りだ!'}, 'target_id': 200160012, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20016003: {'name': {'cn': '钓鱼!钓鱼!', 'en': 'Fishing Time!', 'jp': '釣りだ!'}, 'target_id': 200160013, 'target': {3000032: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20016004: {'name': {'cn': '布莉缇的渔场', 'en': "Britain's Fish Hatchery", 'jp': 'ブリテンのいけす'}, 'target_id': 200160041, 'target': {8002: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20017001: {'name': {'cn': '珍珠大调研!', 'en': 'Research Into the Pearl Trade', 'jp': '真珠の市場調査'}, 'target_id': 200170011, 'target': {5824: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20017002: {'name': {'cn': '珍珠大调研!', 'en': 'Research Into the Pearl Trade', 'jp': '真珠の市場調査'}, 'target_id': 200170021, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 20017003: {'name': {'cn': '珍珠大调研!', 'en': 'Research Into the Pearl Trade', 'jp': '真珠の市場調査'}, 'target_id': 200170031, 'target': {10030018: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30000001: {'name': {'cn': '矿物储备', 'en': 'A Delayed Order', 'jp': '鉱物の備蓄'}, 'target_id': 300000011, 'target': {401: 4}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30000002: {'name': {'cn': '生长的树木', 'en': 'Overgrowing Trees', 'jp': '生長する樹木'}, 'target_id': 300000021, 'target': {402: 4}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30000003: {'name': {'cn': '岛屿订单', 'en': 'Island Request', 'jp': '離島依頼'}, 'target_id': 300000031, 'target': {0: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30000004: {'name': {'cn': '科研计划', 'en': 'Research Project', 'jp': '研究計画'}, 'target_id': 300000041, 'target': {702: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30000005: {'name': {'cn': '愿此刻永存!', 'en': 'May This Moment Last Forever!', 'jp': 'この瞬間が永遠に!'}, 'target_id': 300000051, 'target': {0: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30000006: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 300000061, 'target': {1919: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30000007: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 300000071, 'target': {1717: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30000008: {'name': {'cn': '喵不可言', 'en': 'Everyone Loves Cats', 'jp': '猫はいいぞ'}, 'target_id': 300000081, 'target': {6201: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30101001: {'name': {'cn': '劳动光荣', 'en': 'Labor is Glorious', 'jp': '労働は誉'}, 'target_id': 301010011, 'target': {10123: 4}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30201001: {'name': {'cn': '管理有方', 'en': 'Good Management', 'jp': 'グッドマネジメント'}, 'target_id': 302010011, 'target': {0: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30301001: {'name': {'cn': '货运委托', 'en': 'Transport Job', 'jp': '輸送委託'}, 'target_id': 303010011, 'target': {0: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30401001: {'name': {'cn': '岛屿订单', 'en': 'Island Request', 'jp': '離島依頼'}, 'target_id': 304010011, 'target': {0: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30501001: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 305010011, 'target': {1915: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30501002: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 305010021, 'target': {1714: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30501011: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 305010111, 'target': {1916: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30501012: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 305010121, 'target': {5703: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30501021: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 305010211, 'target': {1917: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30501022: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 305010221, 'target': {4509: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30501031: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 305010311, 'target': {1914: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30501032: {'name': {'cn': "JUU'速运", 'en': 'Manjuu Logistics', 'jp': '饅頭配達'}, 'target_id': 305010321, 'target': {4510: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30502001: {'name': {'cn': '商区外送服务', 'en': 'Delivery Service', 'jp': '出前サービス'}, 'target_id': 305020011, 'target': {4511: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30502002: {'name': {'cn': '商区外送服务', 'en': 'Delivery Service', 'jp': '出前サービス'}, 'target_id': 305020021, 'target': {5705: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30502011: {'name': {'cn': '商区外送服务', 'en': 'Delivery Service', 'jp': '出前サービス'}, 'target_id': 305020111, 'target': {4512: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30502012: {'name': {'cn': '商区外送服务', 'en': 'Delivery Service', 'jp': '出前サービス'}, 'target_id': 305020121, 'target': {1715: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30502021: {'name': {'cn': '商区外送服务', 'en': 'Delivery Service', 'jp': '出前サービス'}, 'target_id': 305020211, 'target': {4513: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30502022: {'name': {'cn': '商区外送服务', 'en': 'Delivery Service', 'jp': '出前サービス'}, 'target_id': 305020221, 'target': {3006: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30502031: {'name': {'cn': '商区外送服务', 'en': 'Delivery Service', 'jp': '出前サービス'}, 'target_id': 305020311, 'target': {4514: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30502032: {'name': {'cn': '商区外送服务', 'en': 'Delivery Service', 'jp': '出前サービス'}, 'target_id': 305020321, 'target': {2712: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30504001: {'name': {'cn': '喵不可言', 'en': 'Everyone Loves Cats', 'jp': '猫はいいぞ'}, 'target_id': 305040011, 'target': {6201: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30505001: {'name': {'cn': '愿此刻永存!', 'en': 'May This Moment Last Forever!', 'jp': 'この瞬間が永遠に!'}, 'target_id': 305050011, 'target': {0: 1}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30508001: {'name': {'cn': '美好的一天', 'en': 'A Beautiful Day', 'jp': '美しい一日'}, 'target_id': 305080011, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30601001: {'name': {'cn': '日常补给', 'en': 'Daily Supply', 'jp': '定期補給'}, 'target_id': 306010011, 'target': {}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30701001: {'name': {'cn': '餐品制作', 'en': 'Making a Meal', 'jp': '料理制作'}, 'target_id': 307010011, 'target': {601: 5}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30702001: {'name': {'cn': '灶台上的烟火', 'en': 'Smoke From the Stove', 'jp': 'かまどの煙火'}, 'target_id': 307020011, 'target': {601: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30703001: {'name': {'cn': '商区里的甜蜜', 'en': 'Commercial Area Desserts', 'jp': '商店街スイーツ'}, 'target_id': 307030011, 'target': {602: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30704001: {'name': {'cn': '简餐救援', 'en': 'Small Meals to the Rescue', 'jp': '軽食レスキュー'}, 'target_id': 307040011, 'target': {603: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30705001: {'name': {'cn': '炭火美食', 'en': 'Coal-Fired Deliciousness', 'jp': '炭火グルメ'}, 'target_id': 307050011, 'target': {604: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 30706001: {'name': {'cn': '啾啾时光', 'en': 'Manjuu Time', 'jp': '饅頭タイム'}, 'target_id': 307060011, 'target': {901: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40101001: {'name': {'cn': '更多的储备', 'en': 'Growing Stockpiles', 'jp': '更なる備蓄'}, 'target_id': 401010011, 'target': {401: 30}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40102001: {'name': {'cn': '源源不断的木材', 'en': "Who's Lumbering?", 'jp': 'いつまでも木材'}, 'target_id': 401020011, 'target': {402: 30}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40103001: {'name': {'cn': '丰收一日', 'en': 'Harvest Festa', 'jp': '豊作の日'}, 'target_id': 401030011, 'target': {101: 30}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40104001: {'name': {'cn': '苗圃的帮手', 'en': "Nursery's Assisant", 'jp': '苗場の手伝い'}, 'target_id': 401040011, 'target': {502: 30}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40105001: {'name': {'cn': '缤纷果园计划', 'en': 'To Taste the Rainbow', 'jp': 'カラフル果樹園計画'}, 'target_id': 401050011, 'target': {501: 30}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40106001: {'name': {'cn': '动物之友', 'en': 'Animals are Friends', 'jp': '動物は友達'}, 'target_id': 401060011, 'target': {102: 30}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40107001: {'name': {'cn': '手工制作设备检修', 'en': 'Crafts Production Equipment Inspection', 'jp': '手工製作設備点検'}, 'target_id': 401070011, 'target': {706: 20}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40108001: {'name': {'cn': '工业生产设备检修', 'en': 'Industrial Production Equipment Inspection', 'jp': '工業生産設備点検'}, 'target_id': 401080011, 'target': {704: 20}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40109001: {'name': {'cn': '木料加工设备检修', 'en': 'Lumber Production Equipment Inspection', 'jp': '木材加工設備点検'}, 'target_id': 401090011, 'target': {703: 20}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40110001: {'name': {'cn': '电子加工设备检修', 'en': 'Electronics Production Equipment Inspection', 'jp': '電子加工設備点検'}, 'target_id': 401100011, 'target': {705: 20}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40111001: {'name': {'cn': '啾啾渔场水产养殖', 'en': 'Manjuu Aquaculture', 'jp': '饅頭いけすで水産養殖'}, 'target_id': 401110011, 'target': {201: 30}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40201001: {'name': {'cn': '套餐制作', 'en': 'Combo (Hunger) Breaker', 'jp': '料理セット'}, 'target_id': 402010011, 'target': {1001: 20}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40301001: {'name': {'cn': '热销商品补货中', 'en': 'Restocking Best Sellers', 'jp': '人気商品補充中'}, 'target_id': 403010011, 'target': {0: 100}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40401001: {'name': {'cn': '岛屿印象', 'en': 'Island Impressions', 'jp': '島の印象'}, 'target_id': 404010011, 'target': {0: 3}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40501001: {'name': {'cn': '勤勉每一天', 'en': 'Working Hard Every Day', 'jp': '動物のいる毎日'}, 'target_id': 405010011, 'target': {3: 25}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 40601001: {'name': {'cn': '每周小目标', 'en': 'Weekly Goals', 'jp': '毎週の目標'}, 'target_id': 406010011, 'target': {1: 120000}, 'start_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}, 'end_time': {'cn': 'always', 'en': 'always', 'jp': 'always'}}, + 90001001: {'name': {'cn': '推演入门I', 'en': 'Simulation Novice I', 'jp': '模擬戦闘入門I'}, 'target_id': 900010011, 'target': {101: 1050}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001002: {'name': {'cn': '推演入门II', 'en': 'Simulation Novice II', 'jp': '模擬戦闘入門II'}, 'target_id': 900010021, 'target': {101: 1100}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001003: {'name': {'cn': '推演入门III', 'en': 'Simulation Novice III', 'jp': '模擬戦闘入門III'}, 'target_id': 900010031, 'target': {101: 1150}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001004: {'name': {'cn': '推演入门IV', 'en': 'Simulation Novice IV', 'jp': '模擬戦闘入門IV'}, 'target_id': 900010041, 'target': {101: 1200}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001005: {'name': {'cn': '推演进阶I', 'en': 'Simulation Adept I', 'jp': '模擬戦闘熟練I'}, 'target_id': 900010051, 'target': {101: 1250}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001006: {'name': {'cn': '推演进阶II', 'en': 'Simulation Adept II', 'jp': '模擬戦闘熟練II'}, 'target_id': 900010061, 'target': {101: 1300}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001007: {'name': {'cn': '推演进阶III', 'en': 'Simulation Adept III', 'jp': '模擬戦闘熟練III'}, 'target_id': 900010071, 'target': {101: 1350}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001008: {'name': {'cn': '推演进阶IV', 'en': 'Simulation Adept IV', 'jp': '模擬戦闘熟練IV'}, 'target_id': 900010081, 'target': {101: 1400}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001009: {'name': {'cn': '推演专精I', 'en': 'Simulation Expert I', 'jp': '模擬戦闘専門I'}, 'target_id': 900010091, 'target': {101: 1450}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001010: {'name': {'cn': '推演专精II', 'en': 'Simulation Expert II', 'jp': '模擬戦闘専門II'}, 'target_id': 900010101, 'target': {101: 1500}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001011: {'name': {'cn': '推演专精III', 'en': 'Simulation Expert III', 'jp': '模擬戦闘専門III'}, 'target_id': 900010111, 'target': {101: 1550}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001012: {'name': {'cn': '推演专精IV', 'en': 'Simulation Expert IV', 'jp': '模擬戦闘専門IV'}, 'target_id': 900010121, 'target': {101: 1600}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90001020: {'name': {'cn': '推演大师', 'en': 'Simulation Master', 'jp': '模擬戦闘達人'}, 'target_id': 900010201, 'target': {101: 3000}, 'start_time': {'cn': '2026-04-09 12:00:00', 'en': '2026-04-09 03:00:00', 'jp': '2026-04-09 12:00:00'}, 'end_time': {'cn': '2026-05-06 23:59:59', 'en': '2026-05-06 23:59:59', 'jp': '2026-05-06 23:59:59'}}, + 90002001: {'name': {'cn': '抚摸猫咪', 'en': 'Pet a cat.', 'jp': 'ねこを撫でる'}, 'target_id': 900020011, 'target': {6201: 1}, 'start_time': {'cn': '2026-06-05 00:00:00', 'en': '2026-06-05 00:00:00', 'jp': '2026-06-05 00:00:00'}, 'end_time': {'cn': '2026-06-18 12:00:00', 'en': '2026-06-17 23:59:59', 'jp': '2026-06-18 12:00:00'}}, + 90002002: {'name': {'cn': '获得任意农田作物个数*30', 'en': 'Collect 30 crops.', 'jp': '任意の農作物を30個入手する'}, 'target_id': 900020021, 'target': {10121: 30}, 'start_time': {'cn': '2026-06-05 00:00:00', 'en': '2026-06-05 00:00:00', 'jp': '2026-06-05 00:00:00'}, 'end_time': {'cn': '2026-06-18 12:00:00', 'en': '2026-06-17 23:59:59', 'jp': '2026-06-18 12:00:00'}}, + 90002003: {'name': {'cn': '交付任意岛屿订单', 'en': 'Complete any Island Request.', 'jp': '任意の離島依頼を1個こなす'}, 'target_id': 900020031, 'target': {0: 1}, 'start_time': {'cn': '2026-06-05 00:00:00', 'en': '2026-06-05 00:00:00', 'jp': '2026-06-05 00:00:00'}, 'end_time': {'cn': '2026-06-18 12:00:00', 'en': '2026-06-17 23:59:59', 'jp': '2026-06-18 12:00:00'}}, + 90002004: {'name': {'cn': '委派生产任意资材', 'en': 'Assign a character to a material production slot.', 'jp': '資源生産枠に仲間を配置する'}, 'target_id': 900020041, 'target': {0: 1}, 'start_time': {'cn': '2026-06-05 00:00:00', 'en': '2026-06-05 00:00:00', 'jp': '2026-06-05 00:00:00'}, 'end_time': {'cn': '2026-06-18 12:00:00', 'en': '2026-06-17 23:59:59', 'jp': '2026-06-18 12:00:00'}}, + 90002005: {'name': {'cn': '和角色进行一次互动', 'en': 'Interact with any character.', 'jp': '仲間とインタラクトする'}, 'target_id': 900020051, 'target': {}, 'start_time': {'cn': '2026-06-05 00:00:00', 'en': '2026-06-05 00:00:00', 'jp': '2026-06-05 00:00:00'}, 'end_time': {'cn': '2026-06-18 12:00:00', 'en': '2026-06-17 23:59:59', 'jp': '2026-06-18 12:00:00'}}, + 90002006: {'name': {'cn': '完成任意货运委托', 'en': 'Complete any Transport Job.', 'jp': '任意の輸送委託を1回こなす'}, 'target_id': 900020061, 'target': {0: 1}, 'start_time': {'cn': '2026-06-05 00:00:00', 'en': '2026-06-05 00:00:00', 'jp': '2026-06-05 00:00:00'}, 'end_time': {'cn': '2026-06-18 12:00:00', 'en': '2026-06-17 23:59:59', 'jp': '2026-06-18 12:00:00'}}, + 90002007: {'name': {'cn': '获得任意餐品三次', 'en': 'Collect 3 meals.', 'jp': '任意の料理を3個入手する'}, 'target_id': 900020071, 'target': {20000: 3}, 'start_time': {'cn': '2026-06-05 00:00:00', 'en': '2026-06-05 00:00:00', 'jp': '2026-06-05 00:00:00'}, 'end_time': {'cn': '2026-06-18 12:00:00', 'en': '2026-06-17 23:59:59', 'jp': '2026-06-18 12:00:00'}}, +} + +DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE = { + 601: {3011: 601001, 3012: 601002, 3013: 601003, 3014: 601004, 3015: 601005, 3114: 601006, 3116: 601007, 3120: 601008, 3101: 601101, 3102: 601102, 4039: 9900021, 4040: 9900022}, + 602: {3017: 602001, 3018: 602002, 3019: 602003, 3020: 602004, 3021: 602005, 3022: 602006, 3103: 602101, 3104: 602102, 3105: 602103, 4037: 9900019, 4038: 9900020}, + 603: {3023: 603001, 3009: 603002, 3024: 603003, 3025: 603004, 3026: 603005, 3028: 603006, 3106: 603101, 3107: 603102, 3108: 603103, 3118: 603007}, + 604: {3029: 604001, 3030: 604002, 3032: 604004, 3033: 604005, 3034: 604006, 3109: 604101, 3110: 604102, 3115: 604007, 3119: 604008}, + 901: {3059: 901001, 3005: 901002, 3006: 901003, 3007: 901004, 3008: 901005, 3010: 901006, 3111: 901101, 3112: 901102, 3113: 901103}, +} + +DIC_ISLAND_TECHNOLOGY = { + 210101: {'name': {'cn': '解锁林场伐木岗位', 'en': 'Unlock: Logging Slot', 'jp': '開放:伐採配置枠'}, 'tech_belong': 2, 'axis': (1, 1), 'island_level': 5}, + 210102: {'name': {'cn': '林场伐木岗位增加I', 'en': 'Logging Slot+ I', 'jp': '伐採配置枠+I'}, 'tech_belong': 2, 'axis': (5, 1), 'island_level': 11}, + 210103: {'name': {'cn': '林场伐木岗位增加II', 'en': 'Logging Slot+ II', 'jp': '伐採配置枠+II'}, 'tech_belong': 2, 'axis': (14, 1), 'island_level': 24}, + 210104: {'name': {'cn': '林场伐木岗位增加III', 'en': 'Logging Slot+ III', 'jp': '伐採配置枠+III'}, 'tech_belong': 2, 'axis': (17, 1), 'island_level': 26}, + 210201: {'name': {'cn': '实用之木生产技术', 'en': 'Workable Wood Harvesting Techniques', 'jp': '実用の木材伐採'}, 'tech_belong': 2, 'axis': (4, 3), 'island_level': 11}, + 210202: {'name': {'cn': '精选之木生产技术', 'en': 'Premium Wood Harvesting Techniques', 'jp': 'プレミアム木材伐採'}, 'tech_belong': 2, 'axis': (9, 3), 'island_level': 17}, + 210203: {'name': {'cn': '典雅之木生产技术', 'en': 'Elegant Wood Harvesting Techniques', 'jp': 'エレガント木材伐採'}, 'tech_belong': 2, 'axis': (14, 3), 'island_level': 24}, + 210401: {'name': {'cn': '手动伐木恢复加快', 'en': 'Manual Logging Resource Recovery+', 'jp': '手動伐採資源回復+'}, 'tech_belong': 2, 'axis': (3.5, 4.5), 'island_level': 8}, + 210501: {'name': {'cn': '手动伐木效率提升I', 'en': 'Manual Logging Efficiency+ I', 'jp': '手動伐採効率+I'}, 'tech_belong': 2, 'axis': (6.5, 4.5), 'island_level': 13}, + 210502: {'name': {'cn': '手动伐木效率提升II', 'en': 'Manual Logging Efficiency+ II', 'jp': '手動伐採効率+II'}, 'tech_belong': 2, 'axis': (11.5, 4.5), 'island_level': 18}, + 210601: {'name': {'cn': '伐木岗位效率提升', 'en': 'Logging Slot Efficiency+', 'jp': '伐採配置枠効率+'}, 'tech_belong': 2, 'axis': (20, 4.5), 'island_level': 35}, + 220101: {'name': {'cn': '解锁矿山采矿岗位', 'en': 'Unlock: Mining Slot', 'jp': '開放:鉱山採掘配置枠'}, 'tech_belong': 2, 'axis': (1, 6.5), 'island_level': 6}, + 220102: {'name': {'cn': '矿山采矿岗位增加I', 'en': 'Mining Slot+ I', 'jp': '採掘配置枠+I'}, 'tech_belong': 2, 'axis': (9, 6.5), 'island_level': 16}, + 220103: {'name': {'cn': '矿山采矿岗位增加II', 'en': 'Mining Slot+ II', 'jp': '採掘配置枠+II'}, 'tech_belong': 2, 'axis': (14, 6.5), 'island_level': 21}, + 220104: {'name': {'cn': '矿山采矿岗位增加III', 'en': 'Mining Slot+ III', 'jp': '採掘配置枠+III'}, 'tech_belong': 2, 'axis': (17, 6.5), 'island_level': 29}, + 220201: {'name': {'cn': '铝矿勘探技术', 'en': 'Bauxite Mining Techniques', 'jp': 'アルミ鉱石探鉱'}, 'tech_belong': 2, 'axis': (4, 8.5), 'island_level': 9}, + 220202: {'name': {'cn': '铁矿勘探技术', 'en': 'Iron Ore Exploration Techniques', 'jp': '鉄鉱石探鉱'}, 'tech_belong': 2, 'axis': (8, 8.5), 'island_level': 12}, + 220203: {'name': {'cn': '硫矿勘探技术', 'en': 'Sulfur Deposit Exploration Techniques', 'jp': '硫黄鉱石探鉱'}, 'tech_belong': 2, 'axis': (11, 8.5), 'island_level': 16}, + 220204: {'name': {'cn': '银矿勘探技术', 'en': 'Silver Ore Exploration Techniques', 'jp': '銀鉱石探鉱'}, 'tech_belong': 2, 'axis': (14, 8.5), 'island_level': 21}, + 220401: {'name': {'cn': '手动采矿恢复加快', 'en': 'Manual Mining Resource Recovery+', 'jp': '手動採掘資源回復+'}, 'tech_belong': 2, 'axis': (6, 10), 'island_level': 9}, + 220501: {'name': {'cn': '手动采矿效率提升I', 'en': 'Manual Mining Efficiency+ I', 'jp': '手動採掘効率+I'}, 'tech_belong': 2, 'axis': (3, 10), 'island_level': 7}, + 220502: {'name': {'cn': '手动采矿效率提升II', 'en': 'Manual Mining Efficiency+ II', 'jp': '手動採掘効率+II'}, 'tech_belong': 2, 'axis': (9, 10), 'island_level': 15}, + 220601: {'name': {'cn': '采矿岗位效率提升', 'en': 'Mining Slot Efficiency+', 'jp': '採掘配置枠効率+'}, 'tech_belong': 2, 'axis': (20, 10), 'island_level': 41}, + 310001: {'name': {'cn': '手动播种范围增加', 'en': 'Manual Sowing Range+', 'jp': '手動種まき範囲+'}, 'tech_belong': 3, 'axis': (19, 3), 'island_level': 20}, + 310101: {'name': {'cn': '解锁农场管理岗位', 'en': 'Unlock: Farm Slot', 'jp': '開放:農場配置枠'}, 'tech_belong': 3, 'axis': (7, 3), 'island_level': 8}, + 310102: {'name': {'cn': '农场管理岗位增加I', 'en': 'Farm Slot+ I', 'jp': '農場配置枠+I'}, 'tech_belong': 3, 'axis': (16, 3), 'island_level': 18}, + 310103: {'name': {'cn': '农场管理岗位增加II', 'en': 'Farm Slot+ II', 'jp': '農場配置枠+II'}, 'tech_belong': 3, 'axis': (25, 3), 'island_level': 25}, + 310104: {'name': {'cn': '农场管理岗位增加III', 'en': 'Farm Slot+ III', 'jp': '農場配置枠+III'}, 'tech_belong': 3, 'axis': (28, 3), 'island_level': 39}, + 310201: {'name': {'cn': '牧草种植技术', 'en': 'Grass Cultivation Techniques', 'jp': '牧草栽培'}, 'tech_belong': 3, 'axis': (1, 9), 'island_level': 6}, + 310202: {'name': {'cn': '旱稻种植技术', 'en': 'Upland Rice Cultivation Techniques', 'jp': '陸稲栽培技術'}, 'tech_belong': 3, 'axis': (4, 9), 'island_level': 8}, + 310301: {'name': {'cn': '扩建晨露农场I', 'en': 'Morningdew Farm Expansion I', 'jp': '朝露農場拡張I'}, 'tech_belong': 3, 'axis': (1, 1), 'island_level': 6}, + 310302: {'name': {'cn': '扩建晨露农场II', 'en': 'Morningdew Farm Expansion II', 'jp': '朝露農場拡張II'}, 'tech_belong': 3, 'axis': (4, 1), 'island_level': 7}, + 310303: {'name': {'cn': '扩建晨露农场III', 'en': 'Morningdew Farm Expansion III', 'jp': '朝露農場拡張III'}, 'tech_belong': 3, 'axis': (7, 1), 'island_level': 11}, + 310304: {'name': {'cn': '扩建晨露农场IV', 'en': 'Morningdew Farm Expansion IV', 'jp': '朝露農場拡張IV'}, 'tech_belong': 3, 'axis': (10, 1), 'island_level': 12}, + 310305: {'name': {'cn': '扩建晨露农场V', 'en': 'Morningdew Farm Expansion V', 'jp': '朝露農場拡張V'}, 'tech_belong': 3, 'axis': (13, 1), 'island_level': 13}, + 310306: {'name': {'cn': '扩建晨露农场VI', 'en': 'Morningdew Farm Expansion VI', 'jp': '朝露農場拡張VI'}, 'tech_belong': 3, 'axis': (16, 1), 'island_level': 16}, + 310307: {'name': {'cn': '扩建晨露农场VII', 'en': 'Morningdew Farm Expansion VII', 'jp': '朝露農場拡張VII'}, 'tech_belong': 3, 'axis': (19, 1), 'island_level': 22}, + 310308: {'name': {'cn': '扩建晨露农场VIII', 'en': 'Morningdew Farm Expansion VIII', 'jp': '朝露農場拡張VIII'}, 'tech_belong': 3, 'axis': (22, 1), 'island_level': 23}, + 310309: {'name': {'cn': '扩建晨露农场IX', 'en': 'Morningdew Farm Expansion IX', 'jp': '朝露農場拡張IX'}, 'tech_belong': 3, 'axis': (25, 1), 'island_level': 27}, + 320101: {'name': {'cn': '解锁苗圃管理岗位', 'en': 'Unlock: Nursery Slot', 'jp': '開放:苗場配置枠'}, 'tech_belong': 3, 'axis': (10, 5), 'island_level': 14}, + 320102: {'name': {'cn': '苗圃管理岗位增加', 'en': 'Nursery Slot+', 'jp': '苗場配置枠+'}, 'tech_belong': 3, 'axis': (28, 5), 'island_level': 34}, + 320201: {'name': {'cn': '草莓种植技术', 'en': 'Strawberry Cultivation Techniques', 'jp': 'いちご栽培'}, 'tech_belong': 5, 'axis': (10, 1), 'island_level': 12}, + 320202: {'name': {'cn': '棉花种植技术', 'en': 'Cotton Cultivation Techniques', 'jp': '綿栽培'}, 'tech_belong': 3, 'axis': (10, 9), 'island_level': 13}, + 320203: {'name': {'cn': '茶树种植技术', 'en': 'Tea Tree Cultivation Techniques', 'jp': '茶の木栽培'}, 'tech_belong': 5, 'axis': (22, 1), 'island_level': 16}, + 320204: {'name': {'cn': '胡萝卜种植技术', 'en': 'Carrot Cultivation Techniques', 'jp': 'ニンジン栽培技術'}, 'tech_belong': 5, 'axis': (16, 10), 'island_level': 21}, + 320205: {'name': {'cn': '薰衣草种植技术', 'en': 'Lavender Cultivation Techniques', 'jp': 'ラベンダー栽培'}, 'tech_belong': 3, 'axis': (22, 9), 'island_level': 22}, + 320206: {'name': {'cn': '洋葱种植技术', 'en': 'Onion Cultivation Techniques', 'jp': '玉ねぎ栽培技術'}, 'tech_belong': 5, 'axis': (25, 10), 'island_level': 25}, + 320301: {'name': {'cn': '扩建青芽苗圃I', 'en': 'Newsprout Nursery Expansion I', 'jp': '青々苗場拡張I'}, 'tech_belong': 3, 'axis': (4, 5), 'island_level': 7}, + 320302: {'name': {'cn': '扩建青芽苗圃II', 'en': 'Newsprout Nursery Expansion II', 'jp': '青々苗場拡張II'}, 'tech_belong': 3, 'axis': (13, 5), 'island_level': 15}, + 320303: {'name': {'cn': '扩建青芽苗圃III', 'en': 'Newsprout Nursery Expansion III', 'jp': '青々苗場拡張III'}, 'tech_belong': 3, 'axis': (16, 5), 'island_level': 19}, + 320304: {'name': {'cn': '扩建青芽苗圃IV', 'en': 'Newsprout Nursery Expansion IV', 'jp': '青々苗場拡張IV'}, 'tech_belong': 3, 'axis': (25, 5), 'island_level': 32}, + 330101: {'name': {'cn': '解锁果园管理岗位', 'en': 'Unlock: Orchard Slot', 'jp': '開放:果樹園配置枠'}, 'tech_belong': 3, 'axis': (10, 7), 'island_level': 12}, + 330102: {'name': {'cn': '果园管理岗位增加I', 'en': 'Orchard Slot+ I', 'jp': '果樹園配置枠+I'}, 'tech_belong': 3, 'axis': (16, 7), 'island_level': 17}, + 330103: {'name': {'cn': '果园管理岗位增加II', 'en': 'Orchard Slot+ II', 'jp': '果樹園配置枠+II'}, 'tech_belong': 3, 'axis': (25, 7), 'island_level': 26}, + 330104: {'name': {'cn': '果园管理岗位增加III', 'en': 'Orchard Slot+ III', 'jp': '果樹園配置枠+III'}, 'tech_belong': 3, 'axis': (28, 7), 'island_level': 37}, + 330201: {'name': {'cn': '橡胶树种植技术', 'en': 'Rubber Tree Cultivation Techniques', 'jp': 'ゴムの木栽培'}, 'tech_belong': 3, 'axis': (16, 9), 'island_level': 19}, + 330301: {'name': {'cn': '扩建坠香果园I', 'en': 'Sweetscent Orchard Expansion I', 'jp': '薫る果樹園拡張I'}, 'tech_belong': 3, 'axis': (7, 7), 'island_level': 9}, + 330302: {'name': {'cn': '扩建坠香果园II', 'en': 'Sweetscent Orchard Expansion II', 'jp': '薫る果樹園拡張II'}, 'tech_belong': 3, 'axis': (13, 7), 'island_level': 15}, + 330303: {'name': {'cn': '扩建坠香果园III', 'en': 'Sweetscent Orchard Expansion III', 'jp': '薫る果樹園拡張III'}, 'tech_belong': 3, 'axis': (19, 7), 'island_level': 24}, + 400001: {'name': {'cn': '牧场额外产出', 'en': 'Ranch Product Range+', 'jp': '牧場生産品目+'}, 'tech_belong': 4, 'axis': (1, 6), 'island_level': 9}, + 410301: {'name': {'cn': '更多的鸡!I', 'en': 'More Chickens! I', 'jp': 'もっと鶏を!I'}, 'tech_belong': 4, 'axis': (1, 1), 'island_level': 6}, + 410302: {'name': {'cn': '更多的鸡!II', 'en': 'More Chickens! II', 'jp': 'もっと鶏を!II'}, 'tech_belong': 4, 'axis': (4, 1), 'island_level': 8}, + 410303: {'name': {'cn': '更多的鸡!III', 'en': 'More Chickens! III', 'jp': 'もっと鶏を!III'}, 'tech_belong': 4, 'axis': (11, 1), 'island_level': 14}, + 410304: {'name': {'cn': '更多的鸡!IV', 'en': 'More Chickens! IV', 'jp': 'もっと鶏を!IV'}, 'tech_belong': 4, 'axis': (14, 1), 'island_level': 16}, + 410305: {'name': {'cn': '更多的鸡!V', 'en': 'More Chickens! V', 'jp': 'もっと鶏を!V'}, 'tech_belong': 4, 'axis': (17, 1), 'island_level': 21}, + 420301: {'name': {'cn': '哼哼猪养殖', 'en': 'Oinky Oinky Pig Raising', 'jp': 'ブーブーブタ養殖'}, 'tech_belong': 4, 'axis': (1, 3), 'island_level': 7}, + 420302: {'name': {'cn': '更多的猪!I', 'en': 'More Pigs! I', 'jp': 'もっと豚を!I'}, 'tech_belong': 4, 'axis': (4, 3), 'island_level': 8}, + 420303: {'name': {'cn': '更多的猪!II', 'en': 'More Pigs! II', 'jp': 'もっと豚を!II'}, 'tech_belong': 4, 'axis': (11, 3), 'island_level': 13}, + 420304: {'name': {'cn': '更多的猪!III', 'en': 'More Pigs! III', 'jp': 'もっと豚を!III'}, 'tech_belong': 4, 'axis': (17, 3), 'island_level': 22}, + 430301: {'name': {'cn': '哞哞牛养殖', 'en': 'Moo Moo Cow Raising', 'jp': 'モーモーウシ養殖'}, 'tech_belong': 4, 'axis': (6, 4.5), 'island_level': 9}, + 430302: {'name': {'cn': '更多的牛!I', 'en': 'More Cows! I', 'jp': 'もっと牛を!I'}, 'tech_belong': 4, 'axis': (9, 4.5), 'island_level': 10}, + 430303: {'name': {'cn': '更多的牛!II', 'en': 'More Cows! II', 'jp': 'もっと牛を!II'}, 'tech_belong': 4, 'axis': (13, 4.5), 'island_level': 15}, + 430304: {'name': {'cn': '更多的牛!III', 'en': 'More Cows! III', 'jp': 'もっと牛を!III'}, 'tech_belong': 4, 'axis': (19, 4.5), 'island_level': 25}, + 440301: {'name': {'cn': '咩咩羊养殖', 'en': 'Baa Baa Sheep Raising', 'jp': 'メェメーヒツジ養殖'}, 'tech_belong': 4, 'axis': (4, 6), 'island_level': 11}, + 440302: {'name': {'cn': '更多的羊!I', 'en': 'More Sheep! I', 'jp': 'もっと羊を!I'}, 'tech_belong': 4, 'axis': (7, 6), 'island_level': 12}, + 440303: {'name': {'cn': '更多的羊!II', 'en': 'More Sheep! II', 'jp': 'もっと羊を!II'}, 'tech_belong': 4, 'axis': (10, 6), 'island_level': 17}, + 440304: {'name': {'cn': '更多的羊!III', 'en': 'More Sheep! III', 'jp': 'もっと羊を!III'}, 'tech_belong': 4, 'axis': (13, 6), 'island_level': 27}, + 450301: {'name': {'cn': '蜂蜜采集点增加I', 'en': 'Honey Gathering Sites+ I', 'jp': 'はちみつ採集地+I'}, 'tech_belong': 4, 'axis': (16, 6), 'island_level': 26}, + 450302: {'name': {'cn': '蜂蜜采集点增加II', 'en': 'Honey Gathering Sites+ II', 'jp': 'はちみつ採集地+II'}, 'tech_belong': 4, 'axis': (19, 6), 'island_level': 30}, + 460001: {'name': {'cn': '钓竿升级I', 'en': 'Fishing Rod Upgrade I', 'jp': '釣り竿強化Ⅰ'}, 'tech_belong': 4, 'axis': (1, 8), 'island_level': 18}, + 460002: {'name': {'cn': '钓竿升级II', 'en': 'Fishing Rod Upgrade II', 'jp': '釣り竿強化Ⅱ'}, 'tech_belong': 4, 'axis': (4, 8), 'island_level': 27}, + 460101: {'name': {'cn': '鱼池管理岗位增加I', 'en': 'Fish Hatchery Slot+ I', 'jp': 'いけす管理枠+Ⅰ'}, 'tech_belong': 4, 'axis': (7, 8), 'island_level': 20}, + 460102: {'name': {'cn': '鱼池管理岗位增加II', 'en': 'Fish Hatchery Slot+ II', 'jp': 'いけす管理枠+Ⅱ'}, 'tech_belong': 4, 'axis': (13, 8), 'island_level': 31}, + 460201: {'name': {'cn': '炸鱼薯条食谱', 'en': 'Fish & Chips', 'jp': 'フィッシュ&チップス'}, 'tech_belong': 4, 'axis': (1, 9.5), 'island_level': 12}, + 460202: {'name': {'cn': '洋葱蒸鱼食谱', 'en': 'Steamed Fish with Onions', 'jp': '魚の玉ねぎ蒸し'}, 'tech_belong': 4, 'axis': (4, 9.5), 'island_level': 18}, + 460203: {'name': {'cn': '柠檬虾食谱', 'en': 'Lemon Shrimp', 'jp': 'レモンエビ'}, 'tech_belong': 4, 'axis': (7, 9.5), 'island_level': 21}, + 460204: {'name': {'cn': '爆炒小龙虾食谱', 'en': 'Crayfish Stir-Fry', 'jp': 'ザリガニ炒め'}, 'tech_belong': 4, 'axis': (10, 9.5), 'island_level': 25}, + 460205: {'name': {'cn': '海鲜饭食谱', 'en': 'Paella', 'jp': 'パエリア'}, 'tech_belong': 4, 'axis': (13, 9.5), 'island_level': 29}, + 460206: {'name': {'cn': '佛跳墙食谱', 'en': "Buddha's Temptation", 'jp': '佛跳牆'}, 'tech_belong': 4, 'axis': (16, 9.5), 'island_level': 36}, + 460301: {'name': {'cn': '养鱼效率提升I', 'en': 'Fish Cultivation Efficiency+ I', 'jp': '魚養殖効率+Ⅰ'}, 'tech_belong': 4, 'axis': (10, 8), 'island_level': 29}, + 460302: {'name': {'cn': '养鱼效率提升II', 'en': 'Fish Cultivation Efficiency+ II', 'jp': '魚養殖効率+Ⅱ'}, 'tech_belong': 4, 'axis': (16, 8), 'island_level': 34}, + 500001: {'name': {'cn': '解锁美食搭配', 'en': 'Unlock: Dish Arrangement', 'jp': '開放:料理組み合わせ'}, 'tech_belong': 5, 'axis': (1, 10), 'island_level': 11}, + 500211: {'name': {'cn': '咖啡树种植技术', 'en': 'Coffee Tree Cultivation Techniques', 'jp': 'コーヒーの木栽培技術'}, 'tech_belong': 5, 'axis': (1, 1), 'island_level': 6}, + 500212: {'name': {'cn': '玉米种植技术', 'en': 'Corn Cultivation Techniques', 'jp': 'とうもろこし栽培'}, 'tech_belong': 5, 'axis': (1, 5), 'island_level': 7}, + 500213: {'name': {'cn': '大豆种植技术', 'en': 'Soy Bean Cultivation Techniques', 'jp': '大豆栽培'}, 'tech_belong': 5, 'axis': (4, 8), 'island_level': 9}, + 500214: {'name': {'cn': '土豆种植技术', 'en': 'Potato Cultivation Techniques', 'jp': 'じゃがいも栽培'}, 'tech_belong': 5, 'axis': (6, 10), 'island_level': 10}, + 500215: {'name': {'cn': '白菜种植技术', 'en': 'Napa Cabbage Cultivation Techniques', 'jp': '白菜栽培'}, 'tech_belong': 5, 'axis': (7, 5), 'island_level': 9}, + 500231: {'name': {'cn': '苹果树种植技术', 'en': 'Apple Tree Cultivation Techniques', 'jp': 'りんごの木栽培'}, 'tech_belong': 5, 'axis': (1, 3), 'island_level': 8}, + 500232: {'name': {'cn': '柑橘树种植技术', 'en': 'Citrus Tree Cultivation Techniques', 'jp': '柑橘類の木栽培'}, 'tech_belong': 5, 'axis': (7, 3), 'island_level': 10}, + 500233: {'name': {'cn': '香蕉树种植技术', 'en': 'Banana Tree Cultivation Techniques', 'jp': 'バナナの木栽培'}, 'tech_belong': 5, 'axis': (10, 3), 'island_level': 13}, + 500234: {'name': {'cn': '芒果树种植技术', 'en': 'Mango Tree Cultivation Techniques', 'jp': 'マンゴーの木栽培'}, 'tech_belong': 5, 'axis': (10, 5), 'island_level': 14}, + 500235: {'name': {'cn': '柠檬树种植技术', 'en': 'Lemon Tree Cultivation Techniques', 'jp': 'レモンの木栽培'}, 'tech_belong': 5, 'axis': (13, 2), 'island_level': 15}, + 500236: {'name': {'cn': '牛油果树种植技术', 'en': 'Avocado Tree Cultivation Techniques', 'jp': 'アボカドの木栽培'}, 'tech_belong': 5, 'axis': (13, 10), 'island_level': 18}, + 510101: {'name': {'cn': '有鱼餐馆岗位增加', 'en': 'Golden Koi Restaurant Slot+', 'jp': '有魚飯店配置枠+'}, 'tech_belong': 5, 'axis': (29, 8), 'island_level': 30}, + 510201: {'name': {'cn': '肉沫烧豆腐食谱', 'en': 'Tofu with Minced Meat', 'jp': '肉そぼろ豆腐'}, 'tech_belong': 5, 'axis': (7, 8), 'island_level': 11}, + 510202: {'name': {'cn': '蛋包饭食谱', 'en': 'Omurice', 'jp': 'オムライス'}, 'tech_belong': 5, 'axis': (10, 9), 'island_level': 11}, + 510203: {'name': {'cn': '白菜豆腐汤食谱', 'en': 'Cabbage and Tofu Soup', 'jp': '白菜と豆腐のスープ'}, 'tech_belong': 5, 'axis': (10, 7), 'island_level': 12}, + 510204: {'name': {'cn': '蔬菜沙拉食谱', 'en': 'Vegetable Salad', 'jp': '野菜サラダ'}, 'tech_belong': 5, 'axis': (13, 8), 'island_level': 14}, + 520001: {'name': {'cn': '建设白熊饮品', 'en': 'Unlock: Polar Bear Teahouse', 'jp': '開放:白クマ茶房'}, 'tech_belong': 5, 'axis': (4, 3), 'island_level': 9}, + 520101: {'name': {'cn': '白熊饮品岗位增加', 'en': 'Polar Bear Teahouse Slot+', 'jp': '白クマ茶房配置枠+'}, 'tech_belong': 5, 'axis': (34, 3), 'island_level': 35}, + 520201: {'name': {'cn': '香蕉芒果汁食谱', 'en': 'Banana and Mango Juice', 'jp': 'バナナマンゴージュース'}, 'tech_belong': 5, 'axis': (13, 4), 'island_level': 15}, + 520202: {'name': {'cn': '蜂蜜柠檬水食谱', 'en': 'Honey and Lemon Water', 'jp': 'はちみつレモン水'}, 'tech_belong': 5, 'axis': (16, 3), 'island_level': 16}, + 520203: {'name': {'cn': '草莓蜂蜜冰沙食谱', 'en': 'Strawberry Honey Frappé', 'jp': 'ストロベリーハニーフラッペ'}, 'tech_belong': 5, 'axis': (19, 3), 'island_level': 17}, + 520204: {'name': {'cn': '薰衣草茶食谱', 'en': 'Lavender Tea', 'jp': 'ラベンダーティー'}, 'tech_belong': 5, 'axis': (25, 3), 'island_level': 24}, + 520205: {'name': {'cn': '草莓蜜沁食谱', 'en': 'Strawberry Lemon Drink', 'jp': 'いちごレモンドリンク'}, 'tech_belong': 5, 'axis': (22, 3), 'island_level': 19}, + 530001: {'name': {'cn': '建设啾啾简餐', 'en': 'Unlock: Manjuu Eatery', 'jp': '開放:饅頭軽食'}, 'tech_belong': 5, 'axis': (16, 5), 'island_level': 16}, + 530101: {'name': {'cn': '啾啾简餐岗位增加', 'en': 'Manjuu Eatery Slot+', 'jp': '饅頭軽食配置枠+'}, 'tech_belong': 5, 'axis': (37, 5), 'island_level': 41}, + 530202: {'name': {'cn': '芒果糯米饭食谱', 'en': 'Sticky Rice with Mango', 'jp': 'マンゴーともち米の蒸し飯'}, 'tech_belong': 5, 'axis': (22, 5), 'island_level': 20}, + 530203: {'name': {'cn': '香蕉可丽饼食谱', 'en': 'Banana Crêpe', 'jp': 'バナナクレープ'}, 'tech_belong': 5, 'axis': (28, 5), 'island_level': 24}, + 530204: {'name': {'cn': '草莓夏洛特食谱', 'en': 'Strawberry Charlotte', 'jp': 'いちごシャルロット'}, 'tech_belong': 5, 'axis': (31, 5), 'island_level': 28}, + 530205: {'name': {'cn': '苹果派食谱', 'en': 'Apple Pie', 'jp': 'アップルパイ'}, 'tech_belong': 5, 'axis': (19, 5), 'island_level': 18}, + 530206: {'name': {'cn': '香橙派食谱', 'en': 'Orange Pie', 'jp': 'オレンジパイ'}, 'tech_belong': 5, 'axis': (19, 7), 'island_level': 19}, + 540001: {'name': {'cn': '建设乌鱼烤肉', 'en': "Unlock: Fin-'n'-Feather Grill", 'jp': '開放:烏魚焼肉'}, 'tech_belong': 5, 'axis': (19, 10), 'island_level': 22}, + 540101: {'name': {'cn': '乌鱼烤肉岗位增加', 'en': "Fin-'n'-Feather Grill Slot+", 'jp': '烏魚焼肉配置枠+'}, 'tech_belong': 5, 'axis': (37, 10), 'island_level': 47}, + 540201: {'name': {'cn': '禽肉土豆拼盘食谱', 'en': "Chicken and Potato Hors d'Oeuvre", 'jp': '鶏肉とポテトの盛り合わせ'}, 'tech_belong': 5, 'axis': (22, 10), 'island_level': 23}, + 540202: {'name': {'cn': '爆炒禽肉食谱', 'en': 'Stir-Fried Chicken', 'jp': '鶏肉炒め'}, 'tech_belong': 5, 'axis': (28, 10), 'island_level': 27}, + 540204: {'name': {'cn': '胡萝卜厚蛋烧食谱', 'en': 'Rolled Carrot Omelette', 'jp': 'ニンジン厚焼き玉子'}, 'tech_belong': 5, 'axis': (31, 10), 'island_level': 29}, + 540205: {'name': {'cn': '汉堡肉饭食谱', 'en': 'Steak Bowl', 'jp': 'ハンバーグ丼'}, 'tech_belong': 5, 'axis': (34, 10), 'island_level': 32}, + 550201: {'name': {'cn': '起司食谱', 'en': 'Cheese', 'jp': 'チーズ'}, 'tech_belong': 5, 'axis': (4, 1), 'island_level': 8}, + 550202: {'name': {'cn': '拿铁食谱', 'en': 'Latte', 'jp': 'ラテ'}, 'tech_belong': 5, 'axis': (7, 1), 'island_level': 10}, + 550203: {'name': {'cn': '柑橘咖啡食谱', 'en': 'Citrus Coffee', 'jp': 'シトラスコーヒー'}, 'tech_belong': 5, 'axis': (16, 1), 'island_level': 12}, + 550204: {'name': {'cn': '草莓奶绿食谱', 'en': 'Strawberry Milkshake', 'jp': 'いちごミルクシェイク'}, 'tech_belong': 5, 'axis': (25, 1), 'island_level': 21}, + 610101: {'name': {'cn': '货运委托上限提升I', 'en': 'Transport Job Limit+ I', 'jp': '輸送依頼上限+I'}, 'tech_belong': 6, 'axis': (1, 1), 'island_level': 6}, + 610102: {'name': {'cn': '货运委托上限提升II', 'en': 'Transport Job Limit+ II', 'jp': '輸送依頼上限+II'}, 'tech_belong': 6, 'axis': (7, 1), 'island_level': 11}, + 610401: {'name': {'cn': '货运效率提升I', 'en': 'Transport Efficiency+ I', 'jp': '輸送効率+I'}, 'tech_belong': 6, 'axis': (4, 1), 'island_level': 8}, + 610402: {'name': {'cn': '货运效率提升II', 'en': 'Transport Efficiency+ II', 'jp': '輸送効率+II'}, 'tech_belong': 6, 'axis': (10, 1), 'island_level': 13}, + 610403: {'name': {'cn': '货运效率提升III', 'en': 'Transport Efficiency+ III', 'jp': '輸送効率+III'}, 'tech_belong': 6, 'axis': (13, 1), 'island_level': 17}, + 620101: {'name': {'cn': '啾咖啡岗位增加', 'en': 'Café Manjuu Slot+', 'jp': '饅頭カフェ配置枠+'}, 'tech_belong': 6, 'axis': (1, 9), 'island_level': 15}, + 630101: {'name': {'cn': '木料加工岗位增加', 'en': 'Lumber Processing Slot+', 'jp': '木材加工配置枠+'}, 'tech_belong': 6, 'axis': (13, 3), 'island_level': 16}, + 630201: {'name': {'cn': '记事本生产工艺', 'en': 'Notebook', 'jp': 'メモ帳'}, 'tech_belong': 6, 'axis': (4, 3), 'island_level': 11}, + 630202: {'name': {'cn': '桌椅生产工艺', 'en': 'Chair and Desk', 'jp': '机と椅子'}, 'tech_belong': 6, 'axis': (7, 3), 'island_level': 13}, + 630203: {'name': {'cn': '精选木桶生产工艺', 'en': 'Choice Wooden Barrel', 'jp': 'オーク樽'}, 'tech_belong': 6, 'axis': (16, 3), 'island_level': 20}, + 630204: {'name': {'cn': '文件柜生产工艺', 'en': 'Filing Cabinet', 'jp': 'ファイルキャビネット'}, 'tech_belong': 6, 'axis': (22, 3), 'island_level': 26}, + 630205: {'name': {'cn': '装饰画生产工艺', 'en': 'Ornamental Painting', 'jp': '装飾画製作'}, 'tech_belong': 6, 'axis': (19, 3), 'island_level': 24}, + 640001: {'name': {'cn': '解锁工业生产设备', 'en': 'Unlock: Manufactured Items', 'jp': '開放:工業アイテム'}, 'tech_belong': 6, 'axis': (4, 5), 'island_level': 15}, + 640101: {'name': {'cn': '工业生产岗位增加', 'en': 'Industrial Production Slot+', 'jp': '工業生産配置枠+'}, 'tech_belong': 6, 'axis': (13, 5), 'island_level': 20}, + 640201: {'name': {'cn': '铁钉生产工艺', 'en': 'Nails', 'jp': '鉄釘'}, 'tech_belong': 6, 'axis': (7, 5), 'island_level': 18}, + 640202: {'name': {'cn': '电缆生产工艺', 'en': 'Cable', 'jp': 'ケーブル'}, 'tech_belong': 6, 'axis': (10, 5), 'island_level': 20}, + 640203: {'name': {'cn': '硫酸生产工艺', 'en': 'Chemicals', 'jp': '化学品'}, 'tech_belong': 6, 'axis': (16, 5), 'island_level': 22}, + 640204: {'name': {'cn': '火药生产工艺', 'en': 'Gunpowder', 'jp': '火薬'}, 'tech_belong': 6, 'axis': (19, 5), 'island_level': 27}, + 640205: {'name': {'cn': '餐具生产工艺', 'en': 'Utensils', 'jp': '食器'}, 'tech_belong': 6, 'axis': (22, 5), 'island_level': 30}, + 650001: {'name': {'cn': '解锁电子加工设备', 'en': 'Unlock: Electronic Items', 'jp': '開放:電化アイテム'}, 'tech_belong': 6, 'axis': (10, 9), 'island_level': 24}, + 650101: {'name': {'cn': '电子加工岗位增加', 'en': 'Electronics Production Slot+', 'jp': '電子加工配置枠+'}, 'tech_belong': 6, 'axis': (22, 9), 'island_level': 33}, + 650201: {'name': {'cn': '钟表生产工艺', 'en': 'Clock', 'jp': '時計'}, 'tech_belong': 6, 'axis': (16, 9), 'island_level': 27}, + 650202: {'name': {'cn': '蓄电池生产工艺', 'en': 'Battery', 'jp': '蓄電池'}, 'tech_belong': 6, 'axis': (25, 9), 'island_level': 36}, + 650203: {'name': {'cn': '净水滤芯生产工艺', 'en': 'Water Filter', 'jp': '浄水フィルター'}, 'tech_belong': 6, 'axis': (28, 9), 'island_level': 42}, + 660001: {'name': {'cn': '解锁手工制作设备', 'en': 'Unlock: Arts & Crafts Items', 'jp': '開放:手工業アイテム'}, 'tech_belong': 6, 'axis': (4, 7), 'island_level': 19}, + 660101: {'name': {'cn': '手工制作岗位增加', 'en': 'Arts & Crafts Slot+', 'jp': '手工制作配置枠+'}, 'tech_belong': 6, 'axis': (19, 7), 'island_level': 28}, + 660201: {'name': {'cn': '皮革生产工艺', 'en': 'Leather', 'jp': '革'}, 'tech_belong': 6, 'axis': (7, 7), 'island_level': 21}, + 660202: {'name': {'cn': '绳索生产工艺', 'en': 'Rope', 'jp': 'ロープ'}, 'tech_belong': 6, 'axis': (10, 7), 'island_level': 22}, + 660203: {'name': {'cn': '手套生产工艺', 'en': 'Gloves', 'jp': '手袋'}, 'tech_belong': 6, 'axis': (13, 7), 'island_level': 23}, + 660204: {'name': {'cn': '香囊生产工艺', 'en': 'Aroma Sachet', 'jp': '香り袋'}, 'tech_belong': 6, 'axis': (16, 7), 'island_level': 26}, + 660205: {'name': {'cn': '鞋靴生产工艺', 'en': 'Shoes', 'jp': '靴'}, 'tech_belong': 6, 'axis': (22, 7), 'island_level': 31}, + 660206: {'name': {'cn': '绷带生产工艺', 'en': 'Wound Dressings', 'jp': '包帯'}, 'tech_belong': 6, 'axis': (25, 7), 'island_level': 35}, +} + +DIC_ISLAND_PRODUCTION_PLACE = { + 101: {'name': {'cn': '丰壤农田', 'en': 'Faircrop Fields', 'jp': '豊穣の畑'}, 'slot': [9001, 9002, 9003, 9004]}, + 102: {'name': {'cn': '悠然牧场', 'en': 'Laidback Ranch', 'jp': '悠々牧場'}, 'slot': [9031, 9032, 9033, 9034]}, + 201: {'name': {'cn': '啾啾渔场', 'en': 'Manjuu Fish Hatchery', 'jp': '饅頭いけす'}, 'slot': [9211, 9212, 9213]}, + 401: {'name': {'cn': '沉石矿山', 'en': 'Rockheap Mine', 'jp': '積岩鉱山'}, 'slot': [9011, 9012, 9013, 9014]}, + 402: {'name': {'cn': '翠土林场', 'en': 'Verdant Woods', 'jp': '翠緑の森'}, 'slot': [9021, 9022, 9023, 9024]}, + 501: {'name': {'cn': '坠香果园', 'en': 'Sweetscent Orchard', 'jp': '薫る果樹園'}, 'slot': [9101, 9102, 9103, 9104]}, + 502: {'name': {'cn': '青芽苗圃', 'en': 'Newsprout Nursery', 'jp': '青々苗場'}, 'slot': [9111, 9112]}, + 601: {'name': {'cn': '有鱼餐馆', 'en': 'Golden Koi Restaurant', 'jp': '有魚飯店'}, 'slot': [9061, 9062]}, + 602: {'name': {'cn': '白熊饮品', 'en': 'Polar Bear Teahouse', 'jp': '白クマ茶房'}, 'slot': [9071, 9072]}, + 603: {'name': {'cn': '啾啾简餐', 'en': 'Manjuu Eatery', 'jp': '饅頭軽食'}, 'slot': [9081, 9082]}, + 604: {'name': {'cn': '乌鱼烤肉', 'en': "Fin-'n'-Feather Grill", 'jp': '烏魚焼肉'}, 'slot': [9091, 9092]}, + 702: {'name': {'cn': '岛屿科技', 'en': 'Island Technologies', 'jp': '離島技術'}, 'slot': [70201, 70202]}, + 703: {'name': {'cn': '木料加工设备', 'en': 'Lumber Processing', 'jp': '木材加工'}, 'slot': [9201, 9202]}, + 704: {'name': {'cn': '工业生产设备', 'en': 'Machinery Production', 'jp': '工作機械'}, 'slot': [9203, 9204]}, + 705: {'name': {'cn': '电子加工设备', 'en': 'Electronic Production', 'jp': '電子部品加工'}, 'slot': [9205, 9206]}, + 706: {'name': {'cn': '手工制作设备', 'en': 'Arts & Crafts Production', 'jp': '手工作業台'}, 'slot': [9207, 9208]}, + 901: {'name': {'cn': '啾咖啡', 'en': 'Café Manjuu', 'jp': '饅頭カフェ'}, 'slot': [9041, 9042]}, +} diff --git a/module/island/freebie.py b/module/island/freebie.py new file mode 100644 index 0000000000..c78e7e9912 --- /dev/null +++ b/module/island/freebie.py @@ -0,0 +1,119 @@ +from module.base.timer import Timer +from module.exception import GameStuckError +from module.handler.assets import STORY_SKIP +from module.island.ui import IslandUI +from module.island.assets import * +from module.logger import logger +from module.ui.page import page_island, page_island_manage, page_island_phone + + +class IslandFreebie(IslandUI): + def island_freebie_notice_appear(self): + """ + Returns: + bool: If appear. + + Page: + in: page_island_manage + """ + for _ in self.loop(timeout=5): + if self.match_template_color(ISLAND_FREEBIE_AVAILABLE, offset=(20, 20)): + return True + if self.match_template_color(ISLAND_FREEBIE_UNAVAILABLE, offset=(20, 20)): + return False + return False + + def freebie_move_to(self): + """ + Page: + in: page_island_manage + out: page_island + """ + for _ in self.loop(timeout=30): + if self.appear_then_click(ISLAND_FREEBIE_AVAILABLE, offset=(20, 20), interval=3): + continue + + if self.ui_page_appear(page_island_phone): + logger.info('Moved to location of freebie') + self.ui_goto(page_island) + break + else: + logger.warning('Failed to move to location of freebie after 30 seconds') + raise GameStuckError(f'Failed to move to location of freebie after 30 seconds') + + def freebie_claim(self): + if not self.appear(ISLAND_FREEBIE_CLAIM, offset=(20, 20)): + logger.warning('No freebie claim button') + return False + retry_wait = Timer(3, count=5).reset() + for _ in self.loop(timeout=30): + if self.appear_then_click(ISLAND_FREEBIE_CLAIM, offset=(20, 20), interval=3): + continue + + if self.appear(ISLAND_FREEBIE_COOLDOWN, offset=(20, 20)): + logger.info('Claimed freebie') + break + elif self.ui_page_appear(page_island_phone): + logger.info('Misclicked into page_island_phone, go back') + self.ui_goto(page_island) + continue + elif retry_wait.reached_and_reset(): + self.device.click(STORY_SKIP) + continue + + def freebie_receive(self): + p1 = (217, 507) + p2 = (217 - 8, 507 + 36) + self.device.drag(p1, p2, point_random=(0, 0, 0, 0), shake_random=(0, 0, 0, 0), hold_duration=1.05) + self.device.screenshot() + if not self.appear(ISLAND_FREEBIE_RECEIVE, offset=(20, 20)): + logger.warning('Failed to receive freebie') + return False + confirm_timer = Timer(3, count=5).reset() + for _ in self.loop(skip_first=False): + if self.appear_then_click(ISLAND_FREEBIE_RECEIVE, offset=(20, 20), interval=3): + confirm_timer.reset() + continue + + if self.handle_island_additional(): + confirm_timer.reset() + continue + + # End + if self.appear(ISLAND_FREEBIE_SHARE, offset=(20, 20)): + if confirm_timer.reached(): + logger.info('Received freebie') + return True + + def freebie_share(self): + if not self.appear(ISLAND_FREEBIE_SHARE, offset=(20, 20)): + logger.warning('No freebie share button') + return False + + for _ in self.loop(skip_first=False): + if self.appear_then_click(ISLAND_FREEBIE_SHARE, offset=(20, 20), interval=3): + continue + if self.appear_then_click(ISLAND_FREEBIE_SHARE_ALL, offset=(20, 20), interval=3): + break + + for _ in self.loop(skip_first=False): + if self.appear_then_click(ISLAND_FREEBIE_SHARE_BACK, offset=(20, 20), interval=3): + continue + if self.ui_page_appear(page_island): + logger.info('Shared freebie') + return True + + def run(self): + self.ui_ensure(page_island_manage) + + if self.island_freebie_notice_appear(): + self.freebie_move_to() + self.freebie_claim() + self.freebie_receive() + if self.config.IslandFreebie_Share: + self.freebie_share() + else: + logger.info('No freebie notice') + + self.config.task_delay(server_update=True) + \ No newline at end of file diff --git a/module/island/order.py b/module/island/order.py new file mode 100644 index 0000000000..2db6888117 --- /dev/null +++ b/module/island/order.py @@ -0,0 +1,397 @@ +from datetime import datetime, timedelta + +import cv2 +from jellyfish import levenshtein_distance +import numpy as np + +import module.config.server as server +from module.base.button import Button, ButtonGrid +from module.base.decorator import cached_property +from module.base.timer import Timer +from module.base.utils import color_similarity_2d +from module.island.assets import * +from module.island.data import DIC_ISLAND_ITEM, DIC_ISLAND_SEASON_ORDER +from module.island.ui import IslandUI +from module.island_handler.production_planner import load_hard_floor_items, load_reserve_items, normalize_item_keys +from module.island_handler.recipe import IslandReversedDigitCounter +from module.logger import logger +from module.map_detection.utils import Points +from module.ocr.ocr import Duration, Ocr +from module.ui.page import page_island_order + + +COLOR_REGULAR = (57, 189, 255) # Blue +COLOR_REGULAR_COOLDOWN = (173, 227, 255) # Light Blue +COLOR_SEASON = (255, 173, 27) # Yellow +COLOR_URGENT = (135, 122, 239) # Purple +# COLOR_EASY = (114, 225, 167) # Green +# COLOR_HARD = (237, 128, 102) # Red +EMPTY_SEASON_ORDER_ID = 0 + + +def get_circles(image, color, inner_radius, outer_radius): + """ + First use color_similarity_2d to create a mask of the image, then + Use cv2.HoughCircles to detect circles in the image, + and return the circles that are detected. + """ + mask = color_similarity_2d(image, color=color) + cv2.threshold(mask, 240, 255, cv2.THRESH_BINARY, dst=mask) + blurred = cv2.GaussianBlur(mask, (7, 7), sigmaX=1.5, sigmaY=1.5) + circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, dp=1, minDist=2*inner_radius, + param1=100, param2=30, minRadius=inner_radius-2, maxRadius=outer_radius+2) + if circles is not None: + circles = circles[0] + else: + circles = [] + return circles + + +def get_season_order_id(requirements): + required_items = { + item_id: counter[1] if isinstance(counter, (list, tuple)) and len(counter) > 1 else counter + for item_id, counter in requirements.items() + } + for id, order in DIC_ISLAND_SEASON_ORDER.items(): + if required_items == order.get('request', {}): + return id + logger.warning(f'Cannot find season order id for requirements: {requirements}') + return None + + +class IslandOrder(IslandUI): + def detect_orders(self, color=COLOR_REGULAR): + """ + Returns: + (List[Button]): A list of Button objects that represent the detected orders in the page.""" + inner_radius = 44 + outer_radius = 52 + circles = get_circles(self.device.image, color, inner_radius, outer_radius) + image_copy = cv2.cvtColor(self.device.image.copy(), cv2.COLOR_BGR2RGB) + for circle in circles: + x, y, r = int(circle[0]), int(circle[1]), int(circle[2]) + cv2.circle(image_copy, (x, y), r, (0, 255, 0), 2) + cv2.imwrite(f'test/island_order/detected_orders_{color}.png', image_copy) + button_list = [] + for index, circle in enumerate(circles): + x, y, _ = circle + x = int(x) + y = int(y) + detect_area = (x - outer_radius, y - outer_radius, x + outer_radius, y + outer_radius) + click_area = (x - inner_radius, y - inner_radius, x + inner_radius, y + inner_radius) + button = Button(area=detect_area, color=(), button=click_area, name=f'ORDER_{index}') + button_list.append(button) + return button_list + + @property + def requirement_grid(self): + return ButtonGrid( + origin=(884, 240), + delta=(0, 79), + button_shape=(334, 78), + grid_shape=(1, 3), + name="REQUIREMENT_GRID" + ) + + @property + def requirement_name_grid(self): + name_grid = self.requirement_grid.crop((168, 12, 328, 40)) + return name_grid + + @property + def requirement_counter_grid(self): + counter_grid = self.requirement_grid.crop((238, 44, 327, 65)) + return counter_grid + + @cached_property + def requirement_name_ocr(self): + if server.server == 'jp': + lang = 'jp' + elif server.server == 'cn': + lang = 'cnocr' + else: + lang = 'azur_lane' + return Ocr(self.requirement_name_grid.buttons, lang=lang, letter=(57, 59, 61), threshold=160, name='REQUIREMENTS_NAME_OCR') + + @cached_property + def requirement_counter_ocr(self): + return IslandReversedDigitCounter(self.requirement_counter_grid.buttons, lang='cnocr', letter=(57, 59, 61), sub_letter=(253, 97, 96), name='REQUIREMENTS_COUNTER_OCR') + + def item_name_to_item_id(self, name): + if name == '': + return None + if server.server == 'jp': + # While we have 果樹園二重奏 and 朝光活力コンビ, the problem of カニ as 力二 is worse, + # so we replace 力 with カ before matching, + # which can fix most of the misrecognition without causing new issues. + name = name.replace('二', 'ニ').replace('力', 'カ') + min_distance = float('inf') + corrected_name = None + corrected_id = None + item_lists = DIC_ISLAND_ITEM.keys() + + for item in item_lists: + item_name = DIC_ISLAND_ITEM[item]['name'][server.server] + distance = levenshtein_distance(name, item_name) + if distance < min_distance: + min_distance = distance + corrected_name = item_name + corrected_id = item + + if name != corrected_name: + logger.info(f'Recipe product name OCR result "{name}" corrected to "{corrected_name}" with distance {min_distance}') + return corrected_id + + def scan_current_order_requirements(self): + name_ocr = self.requirement_name_ocr + counter_ocr = self.requirement_counter_ocr + names = name_ocr.ocr(self.device.image) + ids = [self.item_name_to_item_id(name) for name in names] + counters = counter_ocr.ocr(self.device.image) + requirements = {} + for id, counter in zip(ids, counters): + if id is not None and counter is not None: + requirements[id] = counter + return requirements + + def get_order_remain_time(self, order_button: Button): + time_button = order_button.crop((10, 119, 100, 145)) + print(f'OCR area: {time_button.area}') + time_ocr = Duration(time_button, name='ORDER_REMAIN_TIME_OCR') + remain_time = time_ocr.ocr(self.device.image) + logger.info(f'Order remain time: {remain_time}') + return remain_time + + @cached_property + def hard_floor(self): + hard_floor_items_text = load_hard_floor_items( + self.config.cross_get("IslandProduction.IslandProduction.HardFloorItems", "") + ) + return normalize_item_keys(hard_floor_items_text) + + @cached_property + def reserve(self): + reserve_items_text = load_reserve_items( + self.config.cross_get("IslandProduction.IslandProduction.ReserveItems", "") + ) + return normalize_item_keys(reserve_items_text) + + def is_order_satisfied(self, order_requirements, is_urgent=False): + for item, counter in order_requirements.items(): + stock, required, _ = counter + if not is_urgent: + hard_floor = self.hard_floor.get(item, 0) + reserve = self.reserve.get(item, 0) + effective_stock = stock - hard_floor - reserve + else: + hard_floor = 0 + reserve = 0 + effective_stock = stock + if required > effective_stock: + logger.warning( + f'Item {item} does not meet the requirement: stock {stock}, ' + f'hard floor {hard_floor}, reserve {reserve}, ' + f'effective stock {effective_stock}, required {required}' + ) + return False + return True + + def handle_island_order_level_up(self): + if self.appear(ISLAND_ORDER_LEVEL_UP, offset=(20, 20), interval=1): + logger.info('Detected island order level up') + self.device.click(ISLAND_CLICK_SAFE_AREA) + return True + return False + + def click_order(self, order_button): + click_timer = Timer(1, count=3) + for _ in self.loop(timeout=5, skip_first=False): + if click_timer.reached(): + self.device.click(order_button) + click_timer.reset() + continue + if self.appear(ISLAND_ORDER_REQUIREMENTS_CHECK, offset=(20, 20)): + logger.info('Clicked order button, requirements page appeared') + break + if self.appear(ISLAND_ORDER_COOLDOWN_SPEED_UP, offset=(20, 20)): + logger.info('Clicked order button, cooldown speed up page appeared') + break + else: + logger.info('click timer timeout, assuming requirements page appeared') + + def submit_order(self, is_urgent=False): + if is_urgent: + submit_button = ISLAND_ORDER_ACCEPT_URGENT + else: + submit_button = ISLAND_ORDER_ACCEPT + confirm_timer = Timer(3, count=6).start() + clicked = False + for _ in self.loop(timeout=20): + if self.handle_island_additional(): + clicked = True + confirm_timer.reset() + continue + if self.handle_island_order_level_up(): + confirm_timer.reset() + continue + if not clicked and self.match_template_color(submit_button, offset=(20, 20), interval=1): + self.device.click(submit_button) + confirm_timer.reset() + continue + if not confirm_timer.reached(): + continue + if self.match_template_color(ISLAND_ORDER_BACKGROUND, offset=(20, 20)): + logger.info('Submit success') + return True + if is_urgent and self.match_template_color(ISLAND_ORDER_ACCEPT, offset=(20, 20)): + logger.info('Urgent order submit success') + return True + if not is_urgent and self.match_template_color(ISLAND_ORDER_COOLDOWN_SPEED_UP, offset=(20, 20)): + logger.info('Previous order submitted, wait for next order to appear') + continue + if clicked and self.match_template_color(ISLAND_ORDER_ACCEPT, offset=(20, 20)): + logger.info('Confirm timer timeout, assuming submit success') + return True + else: + logger.warning('Submit order timeout, something may be wrong') + return False + + @property + def cooldown_time_ocr(self): + time_ocr = Duration(ISLAND_ORDER_COOLDOWN_REMAIN_TIME, letter=(57, 59, 61), name='ORDER_COOLDOWN_REMAIN_TIME_OCR') + return time_ocr + + def reject_order(self): + for _ in self.loop(): + if self.appear_then_click(ISLAND_ORDER_REJECT, offset=(20, 20), interval=1): + continue + if self.appear(ISLAND_ORDER_COOLDOWN_SPEED_UP, offset=(20, 20)): + break + cooldown_remain_time = self.cooldown_time_ocr.ocr(self.device.image) + logger.info(f'Order cooldown remain time: {cooldown_remain_time}') + return cooldown_remain_time + + next_runtime = [] + update_production_plan = False + + def update_stuck_season_order(self, order_id): + order_id = order_id or EMPTY_SEASON_ORDER_ID + previous_id = self.config.cross_get("IslandOrder.IslandOrder.StuckSeasonOrderId", EMPTY_SEASON_ORDER_ID) + if order_id != previous_id: + logger.info(f'Updating stuck season order id from {previous_id} to {order_id}') + self.config.cross_set("IslandOrder.IslandOrder.StuckSeasonOrderId", order_id) + self.update_production_plan = True + + def execute_order(self, order_button, is_urgent=False, is_season=False): + """ + Returns: + (bool): True if the order is dealt. + """ + self.click_order(order_button) + if self.appear(ISLAND_ORDER_COOLDOWN_SPEED_UP, offset=(20, 20)): + logger.warning('Order is in cooldown, cannot submit') + remain_time = self.cooldown_time_ocr.ocr(self.device.image) + next_runtime = datetime.now() + remain_time + self.next_runtime.append(next_runtime) + return False + requirements = self.scan_current_order_requirements() + if self.is_order_satisfied(requirements, is_urgent=is_urgent): + return self.submit_order(is_urgent=is_urgent) + else: + logger.warning('Order requirements not satisfied due to low stock') + if is_urgent: + logger.warning('Urgent order can only be delayed') + remain_time = self.get_order_remain_time(order_button) + if remain_time == timedelta(0): + logger.warning('Urgent order remain time ocr error, default to 8 hours') + remain_time = timedelta(hours=8) + else: + logger.warning(f'Order remain time: {remain_time}') + next_runtime = datetime.now() + remain_time + self.next_runtime.append(next_runtime) + return False + elif is_season: + logger.warning('Season order can only be abandoned, skip this order') + stuck_season_order_id = get_season_order_id(requirements) + self.update_stuck_season_order(stuck_season_order_id) + return False + else: + remain_time = self.reject_order() + next_runtime = datetime.now() + remain_time + self.next_runtime.append(next_runtime) + return True + + regular_orders = [] + regular_cooldown_orders = [] + urgent_orders = [] + season_orders = [] + + def detect_all_orders(self): + self.regular_cooldown_orders = self.detect_orders(color=COLOR_REGULAR_COOLDOWN) + self.regular_orders = self.detect_orders(color=COLOR_REGULAR) + for order in self.regular_orders: + for cooldown_order in self.regular_cooldown_orders: + if np.linalg.norm(np.array(order.button) - np.array(cooldown_order.button)) < 10: + self.regular_orders.remove(order) + break + self.urgent_orders = self.detect_orders(color=COLOR_URGENT) + if len(self.urgent_orders) > 1: + logger.warning(f'Multiple urgent orders detected, which is unexpected: {len(self.urgent_orders)}') + self.urgent_orders = self.urgent_orders[:1] + self.season_orders = self.detect_orders(color=COLOR_SEASON) + if len(self.season_orders) > 1: + logger.warning(f'Multiple season orders detected, which is unexpected: {len(self.season_orders)}') + self.season_orders = self.season_orders[:1] + + def run_any_order(self): + for order in self.urgent_orders: + if self.execute_order(order, is_urgent=True, is_season=False): + logger.info('Urgent order submitted') + else: + logger.info('Urgent order delayed') + for order in self.regular_orders: + if self.execute_order(order, is_urgent=False, is_season=False): + logger.info('Regular order submitted') + return True + else: + logger.info('Regular order rejected') + for order in self.season_orders: + if self.execute_order(order, is_urgent=False, is_season=True): + logger.info('Season order submitted') + return True + else: + logger.info('Season order abandoned') + return False + + def run(self): + self.ui_ensure(page_island_order) + self.next_runtime = [] + self.update_production_plan = False + for _ in self.loop(): + self.detect_all_orders() + if self.run_any_order(): + continue + else: + logger.info('No order can be submitted') + break + if not self.season_orders: + self.update_stuck_season_order(EMPTY_SEASON_ORDER_ID) + if self.update_production_plan: + logger.info('Production plan needs to be updated due to stuck season order change') + from module.island_handler.production_planner import IslandProductionPlanner + IslandProductionPlanner(self.config, self.device).run() + self.update_production_plan = False + for order in self.regular_cooldown_orders: + remain_time = self.get_order_remain_time(order) + if remain_time == timedelta(0): + logger.warning('Order remain time ocr error, click to check') + self.click_order(order) + remain_time = self.cooldown_time_ocr.ocr(self.device.image) + next_runtime = datetime.now() + remain_time + self.next_runtime.append(next_runtime) + if self.next_runtime: + next_runtime = min(self.next_runtime) + logger.info(f'Next order runtime: {next_runtime}') + self.config.task_delay(target=next_runtime, server_update=True) + else: + self.config.task_delay(server_update=True) diff --git a/module/island/production.py b/module/island/production.py new file mode 100644 index 0000000000..3295c751e0 --- /dev/null +++ b/module/island/production.py @@ -0,0 +1,312 @@ +from datetime import datetime + +from jellyfish import levenshtein_distance + +import module.config.server as server +from module.base.button import ButtonGrid +from module.base.decorator import cached_property, del_cached_property, has_cached_property +from module.base.timer import Timer +from module.base.utils import random_rectangle_vector_opted +from module.island.assets import * +from module.island.data import DIC_ISLAND_PRODUCTION_PLACE +from module.island_handler.dock import IslandDock +from module.island_handler.dock_scanner import CharacterScanner +from module.island_handler.recipe import IslandProductionRestart, IslandRecipe +from module.logger import logger +from module.map_detection.utils import Points +from module.ocr.ocr import Ocr +from module.ui.page import page_island_manage + + +ANCHOR_AREA = (452, 7, 481, 36) +DETECT_AREA = (192, 69, 1221, 653) +NAME_AREA = (0, 0, 240, 39) +TAB_SIZE = (490, 159) +TAB_DELTA = (539, 183.5) +SLOT_ORIGIN = (62, 59) +SLOT_SIZE = (86, 86) +SLOT_DELTA = (95 - 1/3, 0) +TICK_AREA = (30, 35, 52, 51) +CHARACTER_SELECT_TITLE_AREA = (515, 144, 765, 202) +if server.server == 'jp': + lang = 'jp' +elif server.server == 'en': + lang = 'azur_lane' +else: + lang = 'cnocr' +PRODUCTION_NAME_OCR = Ocr([], lang=lang, name='production_name_ocr') + + +class IslandProduction(IslandRecipe, IslandDock): + slot_finish_time = {} + # main menu related methods + def _get_tabs(self): + area = (DETECT_AREA[0] + ANCHOR_AREA[0], DETECT_AREA[1] + ANCHOR_AREA[1], DETECT_AREA[2] - TAB_SIZE[0] + ANCHOR_AREA[2], DETECT_AREA[3] - TAB_SIZE[1] + ANCHOR_AREA[3]) + image = self.image_crop(area, copy=True) + anchors = TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.match_multi(image, similarity=0.92, threshold=5) + logger.attr('Production_tabs', len(anchors)) + rows = Points([(0., a.area[1]) for a in anchors]).group(threshold=5) + return rows + + @cached_property + def production_grid(self): + for _ in self.loop(timeout=6): + grid = self.get_production_grid() + if len(grid.buttons) >= 4: + return grid + return grid + + def get_production_grid(self): + rows = self._get_tabs() + count = len(rows) + if count < 2: + logger.warning('Unable to find enough production tab anchors, assume tabs are at top') + origin_y = 70 + delta_y = TAB_DELTA[1] + elif count < 4: + y_list = rows[:, 1] + y1, y2 = y_list[0], y_list[-1] + origin_y = min(y1, y2) + DETECT_AREA[1] + delta_y = TAB_DELTA[1] + else: + logger.warning(f'Unexpected production tab anchor match result: {[a for a in rows]}') + origin_y = 70 + delta_y = TAB_DELTA[1] + + production_grid = ButtonGrid( + origin=(192, origin_y), delta=(TAB_DELTA[0], delta_y), button_shape=TAB_SIZE, + grid_shape=(2, count), name='PRODUCTION_GRID' + ) + return production_grid + + @cached_property + def production_names(self): + return self.get_production_codename() + + def get_production_codename(self): + name_grid = self.production_grid.crop(NAME_AREA, name='PRODUCTION_NAME_GRID') + name_images = [self.image_crop(button.area, copy=True) for button in name_grid.buttons] + names = PRODUCTION_NAME_OCR.ocr(name_images, direct_ocr=True) + codenames = [self.production_name_to_codename(name) for name in names] + logger.attr('Codenames', codenames) + return codenames + + def production_name_to_codename(self, name): + min_distance = float('inf') + code = None + corrected_name = None + for key, item in DIC_ISLAND_PRODUCTION_PLACE.items(): + distance = levenshtein_distance(name, item['name'][server.server]) + if distance < min_distance: + min_distance = distance + code = key + corrected_name = item['name'][server.server] + if name != corrected_name: + logger.info(f'Production name OCR result "{name}" corrected to "{corrected_name}" with distance {min_distance}') + return code + + @cached_property + def slot_grids(self): + return self.get_production_slot_grids() + + def get_production_slot_grids(self): + slot_grids = {} + names = self.production_names + for codename, button in zip(names, self.production_grid.buttons): + slot_length = len(DIC_ISLAND_PRODUCTION_PLACE[codename]['slot']) + slot_grid = ButtonGrid( + origin=(button.area[0] + SLOT_ORIGIN[0], button.area[1] + SLOT_ORIGIN[1]), delta=SLOT_DELTA, button_shape=SLOT_SIZE, + grid_shape=(slot_length, 1), name=f'{codename}_SLOT_GRID' + ) + slot_grids[codename] = slot_grid + return slot_grids + + def is_slot_finished(self, slot_button: Button): + tick_button = slot_button.crop(area=TICK_AREA, name=f'{slot_button.name}_TICK') + return self.image_color_count(tick_button, (255, 255, 255), threshold=240, count=70) + + def is_slot_empty(self, slot_button: Button): + image = self.image_crop(slot_button.area, copy=True) + return TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.match(image) + + def next_page(self): + if 901 in self.production_names: + logger.info('Already at last page, cannot swipe to next page') + return False + p1, p2 = random_rectangle_vector_opted((0, -450), box=(690, 70, 720, 650), padding=0) + self.device.drag(p1, p2, hold_duration=0.1, name='PRODUCTION_NEXT_PAGE_SWIPE') + del_cached_property(self, 'production_grid') + del_cached_property(self, 'production_names') + del_cached_property(self, 'slot_grids') + for _ in self.loop(timeout=1.5): + pass + return True + + def prev_page(self): + if 101 in self.production_names: + logger.info('Already at first page, cannot swipe to previous page') + return False + p1, p2 = random_rectangle_vector_opted((0, 450), box=(690, 70, 720, 650), padding=0) + self.device.drag(p1, p2, name='PRODUCTION_PREV_PAGE_SWIPE') + del_cached_property(self, 'production_grid') + del_cached_property(self, 'production_names') + del_cached_property(self, 'slot_grids') + for _ in self.loop(timeout=1): + pass + return True + + def ensure_top_page(self): + for _ in self.loop(): + if 101 in self.production_names: + break + if not self.prev_page(): + break + self.device.click_record_clear() + + def is_enter_window_shown(self): + return self.image_color_count(CHARACTER_SELECT_TITLE_AREA, (62, 193, 255), threshold=221, count=6000) + + def claim_slot_reward(self, slot_button: Button): + if not self.is_slot_finished(slot_button): + logger.warning(f'Slot {slot_button} is not finished, cannot receive reward') + return False + for _ in self.loop(): + if self.is_enter_window_shown(): + break + if self.ui_page_appear(page_island_manage, interval=2): + self.device.click(slot_button) + continue + click_timer = Timer(1, count=2).reset() + for _ in self.loop(timeout=7): + if self.handle_island_additional(): + click_timer.reset() + continue + if self.appear_then_click(ISLAND_PRODUCTION_RECEIVE, offset=(120, 20), interval=2): + click_timer.reset() + continue + if self.is_enter_window_shown() and (self.appear(ISLAND_PRODUCTION_RERUN, offset=(20, 20)) or self.appear(ISLAND_PRODUCTION_SELECT_CHARACTER, offset=(60, 20))): + if click_timer.reached(): + break + else: + logger.warning(f'Failed to claim reward for slot {slot_button}') + click_timer = Timer(1, count=2).reset() + for _ in self.loop(): + if not self.is_enter_window_shown(): + return True + if click_timer.reached_and_reset(): + self.device.click(ISLAND_CLICK_SAFE_AREA) + + def claim_reward_in_page(self, finished_slots=[]): + for place_id, slot_grid in self.slot_grids.items(): + for slot_id, slot_button in zip(DIC_ISLAND_PRODUCTION_PLACE[place_id]['slot'], slot_grid.buttons): + if self.is_slot_finished(slot_button) or self.slot_finish_time.get(slot_id, datetime.max) <= datetime.now(): + self.claim_slot_reward(slot_button) + if slot_id in self.slot_finish_time: + del self.slot_finish_time[slot_id] + for _ in self.loop(timeout=0.6): + pass + + def claim_all_rewards(self): + logger.hr("Claim Production Rewards", level=2) + self.ensure_top_page() + while 1: + self.claim_reward_in_page() + if not self.next_page(): + break + + def dispatch_slot(self, slot_id, slot_button, worker='manjuu'): + if slot_id in DIC_ISLAND_PRODUCTION_PLACE[102]['slot']: + del_cached_property(super(), 'recipe_id_sequence') + del_cached_property(super(), 'all_recipe_stocks') + click_timer = Timer(1, count=2) + for _ in self.loop(): + if self.is_enter_window_shown(): + break + if click_timer.reached_and_reset(): + self.device.click(slot_button) + for _ in self.loop(): + if self.is_in_island_dock(): + break + if self.appear_then_click(ISLAND_PRODUCTION_SELECT_CHARACTER, offset=(60, 20), interval=2): + continue + if worker != 'manjuu': + character = self.island_dock_find_character(worker) + if not character or character.status != 'free': + logger.warning(f'Failed to select character {worker} for slot {slot_id}, try to select manjuu instead') + worker = 'manjuu' + self.ensure_dock_page_at_top() + if worker == 'manjuu': + self.island_dock_select_manjuu() + self.island_dock_select_confirm(self.is_in_recipe_menu) + target_time = super().run(slot_id=slot_id) + if target_time is not None: + target_time = target_time.replace(microsecond=0) + self.slot_finish_time[slot_id] = target_time + for _ in self.loop(timeout=5): + if self.ui_page_appear(page_island_manage, offset=(0, 20)): + return True + else: + logger.warning(f'Failed to start production for slot {slot_id}') + self.ui_back(check_button=page_island_manage.check_button) + self.ensure_island_production_page() + return False + + def dispatch_place(self, place_id): + if place_id not in self.slot_grids: + logger.error(f'Place id {place_id} not found in current production page') + return False + slot_grid = self.slot_grids[place_id] + is_first = True + for slot_id, slot_button in zip(DIC_ISLAND_PRODUCTION_PLACE[place_id]['slot'], slot_grid.buttons): + if not is_first and has_cached_property(super(), 'recipe_id_sequence') and not self.recipe_id_sequence: + logger.info(f'No more recipe for place {place_id}, skip remaining slots') + break + if self.is_slot_empty(slot_button): + self.dispatch_slot(slot_id=slot_id, slot_button=slot_button, worker='manjuu') + if is_first: + is_first = False + del_cached_property(super(), 'recipe_id_sequence') + del_cached_property(super(), 'all_recipe_stocks') + + def dispatch_all(self): + logger.hr("Dispatch Production", level=2) + self.ensure_top_page() + dispatched_places = set() + while 1: + try: + for place_id in self.slot_grids.keys(): + if place_id not in dispatched_places: + self.dispatch_place(place_id) + dispatched_places.add(place_id) + if not self.next_page(): + break + except IslandProductionRestart: + logger.info('Production restarted, continue from current page') + del_cached_property(self, 'production_grid') + del_cached_property(self, 'production_names') + del_cached_property(self, 'slot_grids') + continue + + def ensure_island_production_page(self): + self.ui_ensure(page_island_manage) + for _ in self.loop(timeout=10): + if self.ui_page_appear(page_island_manage, offset=(0, 20)): + break + self.island_manage_side_navbar_ensure(upper=1) + + def run(self): + self.ensure_island_production_page() + slot_finish_time = self.config.cross_get("IslandProduction.Storage.Storage.SlotFinishTime", default={}) + self.slot_finish_time = {int(k): datetime.fromisoformat(v) for k, v in slot_finish_time.items()} + self.claim_all_rewards() + self.dispatch_all() + next_run_time = min(self.slot_finish_time.values()) if self.slot_finish_time else None + logger.info(f'Next expected finish time: {next_run_time}') + with self.config.multi_set(): + sorted_items = sorted(self.slot_finish_time.items(), key=lambda kv: (kv[1], kv[0])) + slot_finish_time = {k: v.isoformat(sep=' ') for k, v in sorted_items} + self.config.cross_set("IslandProduction.Storage.Storage.SlotFinishTime", slot_finish_time) + if next_run_time: + self.config.task_delay(target=next_run_time) + else: + self.config.task_delay(minute=30) diff --git a/module/island/season_task.py b/module/island/season_task.py new file mode 100644 index 0000000000..8ebc33b541 --- /dev/null +++ b/module/island/season_task.py @@ -0,0 +1,228 @@ +from jellyfish import levenshtein_distance + +from module.base.timer import Timer +from module.combat.assets import GET_ITEMS_1 +import module.config.server as server +from module.base.button import ButtonGrid +from module.base.decorator import cached_property, del_cached_property +from module.island.assets import * +from module.island.data import DIC_ISLAND_TASK +from module.island.ui import IslandUI +from module.island_handler.production_planner import item_mapping_to_yaml, load_item_mapping, normalize_item_keys +from module.logger import logger +from module.map_detection.utils import Points +from module.ocr.ocr import Ocr +from module.ui.navbar import Navbar +from module.ui.page import page_island_season +from module.ui.scroll import Scroll + + +DETECT_AREA = (42, 171, 1206, 604) +ICON_AREA = (21, 133, 225, 195) +TAB_DELTA = (395, 230) +TAB_SIZE = (376, 211) +NAME_AREA = (29, 25, 200, 53) +ISLAND_SEASON_TASK_SCROLL = Scroll( + ISLAND_SEASON_TASK_SCROLL_AREA.button, + color=(128, 128, 128), + name="ISLAND_SEASON_TASK_SCROLL" +) + +class IslandSeasonTask(IslandUI): + @cached_property + def _island_season_bottom_navbar(self): + """ + 6 options: + homepage, + pt_reward, + season_task, + season_shop, + season_rank, + season_history + """ + island_season_bottom_navbar = ButtonGrid( + origin=(14, 677), delta=(213, 0), + button_shape=(186, 33), grid_shape=(6, 1), + name='ISLAND_SEASON_BOTTOM_NAVBAR' + ) + return Navbar(grids=island_season_bottom_navbar, + active_color=(237, 237, 237), + inactive_color=(65, 78, 96), + active_count=500, + inactive_count=500) + + def island_season_bottom_navbar_ensure(self, left=None, right=None): + """ + Args: + left (int): + 1 for homepage, + 2 for pt_reward, + 3 for season_task, + 4 for season_shop, + 5 for season_rank, + 6 for season_history + right (int): + 1 for season_history, + 2 for season_rank, + 3 for season_shop, + 4 for season_task, + 5 for pt_reward, + 6 for homepage + + """ + if self._island_season_bottom_navbar.set(self, left=left, right=right): + return True + return False + + def _get_icons(self): + area = (DETECT_AREA[0] + ICON_AREA[0], DETECT_AREA[1] + ICON_AREA[1] - NAME_AREA[1], DETECT_AREA[2], DETECT_AREA[3]) + image = self.image_crop(area, copy=True) + icons = TEMPLATE_ISLAND_SEASON_REWARD.match_multi(image, similarity=0.7, threshold=5) + icons = Points([(0., icon.area[1]) for icon in icons]).group(threshold=5) + logger.attr('icons_count', len(icons)) + return icons + + @cached_property + def season_task_grid(self): + for _ in self.loop(timeout=3): + grid = self.get_season_task_grid() + if len(grid.buttons) >= 3: + return grid + return grid + + def get_season_task_grid(self): + rows = self._get_icons() + count = len(rows) + if count >= 2: + y_list = rows[:, 1] + y1, y2 = y_list[0], y_list[-1] + origin_y = min(y1, y2) + DETECT_AREA[1] - NAME_AREA[1] + delta_y = TAB_DELTA[1] + elif count < 1: + logger.warning('No icons detected, assume at top') + origin_y = DETECT_AREA[1] - NAME_AREA[1] + delta_y = TAB_DELTA[1] + else: + origin_y = rows[0][1] + DETECT_AREA[1] - NAME_AREA[1] + delta_y = TAB_DELTA[1] + + season_task_grid = ButtonGrid( + origin=(DETECT_AREA[0], origin_y), + delta=(TAB_DELTA[0], delta_y), + button_shape=TAB_SIZE, + grid_shape=(3, count), name='SEASON_TASK_GRID' + ) + return season_task_grid + + @cached_property + def task_names(self): + return self.get_task_codename() + + @property + def task_name_ocr(self): + if server.server == 'jp': + lang = 'jp' + elif server.server == 'en': + lang = 'azur_lane' + else: + lang = 'cnocr' + ocr = Ocr([], lang=lang, letter=(57, 58, 60), name='task_name_ocr') + return ocr + + def get_task_codename(self): + name_grid = self.season_task_grid.crop(NAME_AREA, name='TASK_NAME_GRID') + name_images = [self.image_crop(button.area, copy=True) for button in name_grid.buttons] + names = self.task_name_ocr.ocr(name_images, direct_ocr=True) + codenames = [self.task_name_to_codename(name) for name in names] + logger.attr('Codenames', codenames) + return codenames + + def task_name_to_codename(self, name): + min_distance = float('inf') + code = None + corrected_name = None + for key, item in DIC_ISLAND_TASK.items(): + distance = levenshtein_distance(name, item['name'][server.server]) + if distance < min_distance: + min_distance = distance + code = key + corrected_name = item['name'][server.server] + if name != corrected_name: + logger.warning(f'Task name corrected: {name} -> {corrected_name}') + return code + + def handle_island_get_items(self): + if self.appear(GET_ITEMS_1, offset=(20, 100), interval=3): + self.device.click(ISLAND_SEASON_TASK_RECEIVE_ALL) + return True + if self.has_white_band(): + if self.appear(page_island_season.check_button): + return False + self.device.click(ISLAND_SEASON_TASK_RECEIVE_ALL) + return True + return False + + def receive_all_reward(self): + clicked = False + confirm_timer = Timer(3, count=6) + for _ in self.loop(skip_first=False): + if self.appear_then_click(ISLAND_SEASON_TASK_RECEIVE_ALL, interval=2, offset=(20, 20)): + clicked = True + confirm_timer.reset() + continue + if self.handle_island_additional(): + confirm_timer.reset() + continue + if confirm_timer.reached(): + if clicked: + logger.info('Receive all rewards confirmed') + else: + logger.info('No rewards to receive') + break + + def scan_all(self): + ISLAND_SEASON_TASK_SCROLL.set_top(main=self, skip_first_screenshot=False) + logger.hr('scanning season tasks') + unfinished_tasks = [] + early_stop = False + while 1: + for task_id, button in zip(self.task_names, self.season_task_grid.buttons): + image = self.image_crop(button.area, copy=True) + if TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.match(image): + early_stop = True + break + else: + unfinished_tasks.append(task_id) + if early_stop: + logger.info(f'Detect obtained task, early stop scanning') + break + if ISLAND_SEASON_TASK_SCROLL.at_bottom(main=self): + break + else: + ISLAND_SEASON_TASK_SCROLL.next_page(main=self) + del_cached_property(self, 'season_task_grid') + del_cached_property(self, 'task_names') + continue + logger.info(f'Unfinished tasks: {unfinished_tasks}') + return unfinished_tasks + + def run(self): + self.ui_ensure(page_island_season) + self.island_season_bottom_navbar_ensure(left=3) + self.receive_all_reward() + yaml_text = self.config.cross_get("IslandSeasonTask.IslandSeasonTask.TaskTarget", "{}") + old_target = normalize_item_keys(load_item_mapping(yaml_text, config_name='TaskTarget')) + new_target = {} + unfinished_tasks = self.scan_all() + for task_id in unfinished_tasks: + target = DIC_ISLAND_TASK[task_id]['target'] + if target: + item_id = list(target.keys())[0] + new_target[item_id] = new_target.get(item_id, 0) + target[item_id] + new_target = normalize_item_keys(new_target) + if new_target != old_target: + yaml_text = item_mapping_to_yaml(new_target, use_item_name=True) + self.config.cross_set("IslandSeasonTask.IslandSeasonTask.TaskTarget", yaml_text) + from module.island_handler.production_planner import IslandProductionPlanner + IslandProductionPlanner(self.config, self.device).run() + self.config.task_delay(server_update=True) diff --git a/module/island/ui.py b/module/island/ui.py new file mode 100644 index 0000000000..e3f2aedd70 --- /dev/null +++ b/module/island/ui.py @@ -0,0 +1,316 @@ +import cv2 +import numpy as np +from typing import List + +from module.base.base import ModuleBase +from module.base.button import ButtonGrid +from module.base.decorator import cached_property +from module.base.timer import Timer +from module.base.utils import area_offset, color_similarity_2d, rgb2luma +from module.combat.assets import GET_ITEMS_1 +from module.island.assets import * +from module.logger import logger +from module.ui.navbar import Navbar +from module.ui.ui import UI +from module.ui_white.assets import BACK_ARROW_WHITE + + +class NestedNavbar: + # Navbar with level 2 option buttons which will span and make level 1 buttons move apart. + # For example if level 1 has 5 buttons with submenu of options 0, 2, 1, 2, 1, then when the 2nd submenu of the second button is chosen, it will be + # 1 + # [[2]] + # 2.1 + # [2.2] + # 3 + # 4 + # 5 + # When the 1st submenu of the 3rd button is chosen, it will be + # 1 + # 2 + # [[3]] + # [3.1] + # 4 + # 5 + # Since the level 1 buttons will move, before clicking the level 1 button we will need to check the current acvive level 1 button and the number of active submenu options to calculate the position of the level 1 button to click. + def __init__(self, grids: ButtonGrid, + subgrid_delta: tuple = None, subgrid_button_shape: tuple = None, + subgrid_shapes: List[tuple] = None, direction: str = 'vertical', + main_active_color=(57, 189, 255), main_inactive_color=(38, 39, 40), + main_active_threshold=221, main_inactive_threshold=221, + main_active_count=2000, main_inactive_count=2000, + sub_active_color=(125, 126, 126), sub_inactive_color=(38, 39, 40), + sub_active_threshold=221, sub_inactive_threshold=221, + sub_active_count=500, sub_inactive_count=500, name: str = None): + """ + Parameters: + grids (ButtonGrid): The ButtonGrid instance for the main level buttons. + subbutton (Button): The Button instance for the submenu options. + subgrid_shapes (list of tuples): A list of grid shapes for each submenu. Each tuple represents the grid shape (rows, cols) for the corresponding submenu. + main_active_color (tuple): The RGB color for active main buttons. + main_inactive_color (tuple): The RGB color for inactive main buttons. + main_active_threshold (int): The threshold for detecting active main buttons. + main_inactive_threshold (int): The threshold for detecting inactive main buttons. + main_active_count (int): The minimum count of pixels to confirm an active main button. + main_inactive_count (int): The maximum count of pixels to confirm an inactive main button. + sub_active_color (tuple): The RGB color for active submenu options. + sub_inactive_color (tuple): The RGB color for inactive submenu options. + sub_active_threshold (int): The threshold for detecting active submenu options. + sub_inactive_threshold (int): The threshold for detecting inactive submenu options. + sub_active_count (int): The minimum count of pixels to confirm an active submenu option. + sub_inactive_count (int): The maximum count of pixels to confirm an inactive submenu option. + """ + self.grids = grids + self.subgrid_delta = subgrid_delta if subgrid_delta is not None else grids.delta + self.subgrid_button_shape = subgrid_button_shape if subgrid_button_shape is not None else grids.button_shape + self.subgrid_shapes = subgrid_shapes if subgrid_shapes is not None else [(0, 0)] * len(grids.buttons) + self.direction = direction + self.main_active_color = main_active_color + self.main_inactive_color = main_inactive_color + self.main_active_threshold = main_active_threshold + self.main_inactive_threshold = main_inactive_threshold + self.main_active_count = main_active_count + self.main_inactive_count = main_inactive_count + self.sub_active_color = sub_active_color + self.sub_inactive_color = sub_inactive_color + self.sub_active_threshold = sub_active_threshold + self.sub_inactive_threshold = sub_inactive_threshold + self.sub_active_count = sub_active_count + self.sub_inactive_count = sub_inactive_count + self.name = name if name is not None else self.grids._name + self._construct_subgrids() + + def _construct_subgrids(self): + self.subgrids = [] + for idx, (main_button, subgrid_shape) in enumerate(zip(self.grids.buttons, self.subgrid_shapes)): + if self.direction == 'vertical': + subgrid_origin = (main_button.area[0], main_button.area[1] + self.grids.delta[1]) + else: + subgrid_origin = (main_button.area[0] + self.grids.delta[0], main_button.area[1]) + subgrid = ButtonGrid(origin=subgrid_origin, delta=self.subgrid_delta, + button_shape=self.subgrid_button_shape, grid_shape=subgrid_shape, + name=f'{self.name}_SUB_GRID_{idx}') + self.subgrids.append(subgrid) + + def is_main_button_active(self, button, main): + return main.image_color_count( + button, color=self.main_active_color, threshold=self.main_active_threshold, count=self.main_active_count) + + def is_main_button_inactive(self, button, main): + return main.image_color_count( + button, color=self.main_inactive_color, threshold=self.main_inactive_threshold, count=self.main_inactive_count) + + def is_subbutton_active(self, button, main): + return main.image_color_count( + button, color=self.sub_active_color, threshold=self.sub_active_threshold, count=self.sub_active_count) + + def is_subbutton_inactive(self, button, main): + return main.image_color_count( + button, color=self.sub_inactive_color, threshold=self.sub_inactive_threshold, count=self.sub_inactive_count) + + def get_info(self, main): + """ + Get the index of the active main button and the active submenu option. + Parameters: + main (ModuleBase): The main module instance to capture screenshots. + Returns: + tuple: A tuple containing the index of the active main button and the index of the active submenu option. If no submenu is active, the second element will be None. + """ + total_main = [] + active_main = [] + total_sub = [] + active_sub = [] + current_offset = (0, 0) + for index, button in enumerate(self.grids.buttons): + button._button_offset = area_offset(button._button, offset=current_offset) + if self.is_main_button_active(button, main=main): + total_main.append(index) + active_main.append(index) + subgrid = self.subgrids[index] + for sub_index, sub_button in enumerate(subgrid.buttons): + if self.is_subbutton_active(sub_button, main=main): + total_sub.append((index, sub_index)) + active_sub.append((index, sub_index)) + elif self.is_subbutton_inactive(sub_button, main=main): + total_sub.append((index, sub_index)) + if self.direction == 'vertical': + current_offset = ( + current_offset[0], + current_offset[1] + self.subgrid_button_shape[1] * subgrid.grid_shape[1], + ) + else: + current_offset = ( + current_offset[0] + self.subgrid_button_shape[0] * subgrid.grid_shape[0], + current_offset[1], + ) + elif self.is_main_button_inactive(button, main=main): + total_main.append(index) + + if len(active_main) == 0: + active_main_index = None + elif len(active_main) == 1: + active_main_index = active_main[0] + else: + logger.warning(f'Too many active nav items found in {self.name}, items: {active_main}') + active_main_index = active_main[0] + + active_sub_index = None + if active_main_index is not None: + active_subs_for_active_main = [sub for sub in active_sub if sub[0] == active_main_index] + if len(active_subs_for_active_main) == 0: + active_sub_index = None + elif len(active_subs_for_active_main) == 1: + active_sub_index = active_subs_for_active_main[0][1] + else: + logger.warning(f'Too many active sub nav items found in {self.name} for main index {active_main_index}, items: {active_subs_for_active_main}') + active_sub_index = active_subs_for_active_main[0][1] + + if len(total_main) < 2: + logger.warning(f'Too few nav items found in {self.name}, items: {total_main}') + if len(total_main) == 0: + main_begin, main_end = None, None + else: + main_begin, main_end = total_main[0], total_main[-1] + + return active_main_index, active_sub_index, main_begin, main_end + + def set(self, main: ModuleBase, main_index: int, sub_index: int = None, skip_first_screenshot: bool = False): + """ + Click the main button and submenu option based on the provided indices. + Should be used after calling get_info to get the current active main and submenu indices to calculate the position to click. + Parameters: + main (ModuleBase): The main module instance to perform clicks and capture screenshots. + main_index (int): The index of the main button to click. Starts from 0. + sub_index (int, optional): The index of the submenu option to click. If None, no submenu option will be clicked. Starts from 0. + skip_first_screenshot (bool): + """ + logger.info(f'Setting {self.name} to main index {main_index} and sub index {sub_index}') + click_timer = Timer(2, count=4).reset() + confirm_timer = Timer(2, count=4).reset() + for _ in main.loop(skip_first=skip_first_screenshot, timeout=10): + active_main_index, active_sub_index, main_begin, main_end = self.get_info(main) + if active_main_index == main_index and (sub_index is None or active_sub_index == sub_index): + if confirm_timer.reached(): + logger.info(f'Successfully set {self.name} to main index {main_index} and sub index {sub_index}') + return True + continue + if active_main_index is None or main_begin is None or main_end is None: + confirm_timer.reset() + continue + if not (main_begin <= main_index <= main_end): + logger.warning(f'Main index {main_index} out of range for {self.name}, appears ({main_begin}, {main_end})') + continue + if click_timer.reached_and_reset(): + if active_main_index != main_index: + main.device.click(self.grids.buttons[main_index]) + confirm_timer.reset() + else: + subgrid = self.subgrids[main_index] + if sub_index is not None: + main.device.click(subgrid.buttons[sub_index]) + confirm_timer.reset() + else: + logger.warning(f'Failed to set {self.name} to main index {main_index} and sub index {sub_index}') + return False + + +class IslandUI(UI): + def ui_back(self, check_button, appear_button=None, offset=(30, 30), retry_wait=10, skip_first_screenshot=False): + return self.ui_click( + click_button=BACK_ARROW_WHITE, + check_button=check_button, + appear_button=appear_button, + offset=offset, + retry_wait=retry_wait, + skip_first_screenshot=skip_first_screenshot, + ) + + def has_white_band(self, threshold=1200): + min_y = 405 + max_y = 560 + image = self.image_crop((0, min_y, 1280, max_y), copy=False) + luma = rgb2luma(image) + white_mask = cv2.inRange(luma, 200, 255) + kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (31, 3)) + white_mask = cv2.morphologyEx(white_mask, cv2.MORPH_CLOSE, kernel) + row_white_width = (white_mask > 0).sum(axis=1) + candidate_rows = row_white_width > threshold + min_continuous_height = 50 + count = 0 + for ok in candidate_rows: + if ok: + count += 1 + if count >= min_continuous_height: + return True + else: + count = 0 + return False + + def handle_island_get_items(self): + if self.appear(GET_ITEMS_1, offset=(20, 20), interval=3): + self.device.click(ISLAND_CLICK_SAFE_AREA) + return True + if self.has_white_band(): + self.device.click(ISLAND_CLICK_SAFE_AREA) + return True + return False + + def handle_island_level_up(self): + if self.appear(ISLAND_LEVEL_UP, offset=(20, 20), interval=3): + self.device.click(ISLAND_CLICK_SAFE_AREA) + return True + return False + + def handle_island_additional(self): + if self.handle_island_get_items(): + return True + if self.handle_island_level_up(): + return True + return False + + def is_button_selected(self, button, color=(57, 189, 255), threshold=221, count=100): + """ + Detects if the button is surrounded by a blue border, + which indicates that the button is chosen. + + Args: + button (Button, tuple): Button instance or area. + + Returns: + bool: True if the button is chosen, False otherwise. + """ + if isinstance(button, np.ndarray): + image = button + else: + image = self.image_crop(button, copy=False) + mask = color_similarity_2d(image, color) + cv2.inRange(mask, threshold, 255, dst=mask) + mask[2:-2, 2:-2] = 0 + sum_ = cv2.countNonZero(mask) + return sum_ > count + + @cached_property + def _island_manage_side_navbar(self): + island_manage_side_navbar = ButtonGrid( + origin=(13, 107), delta=(0, 196/3), + button_shape=(128, 43), grid_shape=(1, 3) + ) + return Navbar(grids=island_manage_side_navbar, + active_color=(57, 189, 255), + inactive_color=(50, 52, 55), + active_count=500, + inactive_count=500) + + def island_manage_side_navbar_ensure(self, upper=1, skip_first_screenshot=True): + """ + Args: + upper (int): + 1 for production, + 2 for restaurant, + 3 for collect + bottom (int): + 1 for collect, + 2 for restaurant, + 3 for production + """ + return self._island_manage_side_navbar.set(self, upper=upper, skip_first_screenshot=skip_first_screenshot) diff --git a/module/island_handler/assets.py b/module/island_handler/assets.py new file mode 100644 index 0000000000..d46e07b685 --- /dev/null +++ b/module/island_handler/assets.py @@ -0,0 +1,51 @@ +from module.base.button import Button +from module.base.template import Template + +# This file was automatically generated by dev_tools/button_extract.py. +# Don't modify it manually. + +ISLAND_DOCK_CHARACTER_CONFIRM = Button(area={'cn': (1054, 593, 1127, 625), 'en': (1054, 593, 1127, 625), 'jp': (1054, 593, 1127, 625), 'tw': (1054, 593, 1127, 625)}, color={'cn': (89, 200, 255), 'en': (89, 200, 255), 'jp': (89, 200, 255), 'tw': (89, 200, 255)}, button={'cn': (1054, 593, 1127, 625), 'en': (1054, 593, 1127, 625), 'jp': (1054, 593, 1127, 625), 'tw': (1054, 593, 1127, 625)}, file={'cn': './assets/cn/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png', 'en': './assets/cn/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png', 'jp': './assets/jp/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png', 'tw': './assets/cn/island_handler/ISLAND_DOCK_CHARACTER_CONFIRM.png'}) +ISLAND_DOCK_CHECK = Button(area={'cn': (114, 20, 231, 46), 'en': (114, 20, 231, 46), 'jp': (114, 20, 231, 46), 'tw': (114, 20, 231, 46)}, color={'cn': (64, 73, 87), 'en': (64, 73, 87), 'jp': (64, 73, 87), 'tw': (64, 73, 87)}, button={'cn': (114, 20, 231, 46), 'en': (114, 20, 231, 46), 'jp': (114, 20, 231, 46), 'tw': (114, 20, 231, 46)}, file={'cn': './assets/cn/island_handler/ISLAND_DOCK_CHECK.png', 'en': './assets/cn/island_handler/ISLAND_DOCK_CHECK.png', 'jp': './assets/jp/island_handler/ISLAND_DOCK_CHECK.png', 'tw': './assets/cn/island_handler/ISLAND_DOCK_CHECK.png'}) +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_INFO_GOTO_SHOP = Button(area={'cn': (815, 472, 868, 495), 'en': (815, 472, 868, 495), 'jp': (815, 472, 868, 495), 'tw': (815, 472, 868, 495)}, color={'cn': (95, 201, 255), 'en': (95, 201, 255), 'jp': (95, 201, 255), 'tw': (95, 201, 255)}, button={'cn': (815, 472, 868, 495), 'en': (815, 472, 868, 495), 'jp': (815, 472, 868, 495), 'tw': (815, 472, 868, 495)}, file={'cn': './assets/cn/island_handler/ISLAND_INFO_GOTO_SHOP.png', 'en': './assets/cn/island_handler/ISLAND_INFO_GOTO_SHOP.png', 'jp': './assets/jp/island_handler/ISLAND_INFO_GOTO_SHOP.png', 'tw': './assets/cn/island_handler/ISLAND_INFO_GOTO_SHOP.png'}) +ISLAND_RECIPE_AMOUNT = Button(area={'cn': (726, 353, 857, 382), 'en': (726, 353, 857, 382), 'jp': (726, 353, 857, 382), 'tw': (726, 353, 857, 382)}, color={'cn': (214, 206, 208), 'en': (214, 206, 208), 'jp': (214, 206, 208), 'tw': (214, 206, 208)}, button={'cn': (726, 353, 857, 382), 'en': (726, 353, 857, 382), 'jp': (726, 353, 857, 382), 'tw': (726, 353, 857, 382)}, file={'cn': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT.png', 'en': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT.png', 'jp': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT.png', 'tw': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT.png'}) +ISLAND_RECIPE_AMOUNT_MAX = Button(area={'cn': (961, 381, 987, 406), 'en': (961, 381, 987, 406), 'jp': (961, 381, 987, 406), 'tw': (961, 381, 987, 406)}, color={'cn': (69, 69, 75), 'en': (69, 69, 75), 'jp': (69, 69, 75), 'tw': (69, 69, 75)}, button={'cn': (961, 381, 987, 406), 'en': (961, 381, 987, 406), 'jp': (961, 381, 987, 406), 'tw': (961, 381, 987, 406)}, file={'cn': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MAX.png', 'en': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MAX.png', 'jp': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MAX.png', 'tw': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MAX.png'}) +ISLAND_RECIPE_AMOUNT_MINUS = Button(area={'cn': (646, 381, 670, 405), 'en': (646, 381, 670, 405), 'jp': (646, 381, 670, 405), 'tw': (646, 381, 670, 405)}, color={'cn': (80, 80, 86), 'en': (80, 80, 86), 'jp': (80, 80, 86), 'tw': (80, 80, 86)}, button={'cn': (646, 381, 670, 405), 'en': (646, 381, 670, 405), 'jp': (646, 381, 670, 405), 'tw': (646, 381, 670, 405)}, file={'cn': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MINUS.png', 'en': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MINUS.png', 'jp': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MINUS.png', 'tw': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_MINUS.png'}) +ISLAND_RECIPE_AMOUNT_PLUS = Button(area={'cn': (913, 381, 937, 405), 'en': (913, 381, 937, 405), 'jp': (913, 381, 937, 405), 'tw': (913, 381, 937, 405)}, color={'cn': (101, 101, 106), 'en': (101, 101, 106), 'jp': (101, 101, 106), 'tw': (101, 101, 106)}, button={'cn': (913, 381, 937, 405), 'en': (913, 381, 937, 405), 'jp': (913, 381, 937, 405), 'tw': (913, 381, 937, 405)}, file={'cn': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_PLUS.png', 'en': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_PLUS.png', 'jp': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_PLUS.png', 'tw': './assets/cn/island_handler/ISLAND_RECIPE_AMOUNT_PLUS.png'}) +ISLAND_RECIPE_CHECK = Button(area={'cn': (114, 19, 232, 46), 'en': (114, 19, 232, 46), 'jp': (114, 19, 232, 46), 'tw': (114, 19, 232, 46)}, color={'cn': (80, 88, 102), 'en': (80, 88, 102), 'jp': (80, 88, 102), 'tw': (80, 88, 102)}, button={'cn': (114, 19, 232, 46), 'en': (114, 19, 232, 46), 'jp': (114, 19, 232, 46), 'tw': (114, 19, 232, 46)}, file={'cn': './assets/cn/island_handler/ISLAND_RECIPE_CHECK.png', 'en': './assets/cn/island_handler/ISLAND_RECIPE_CHECK.png', 'jp': './assets/cn/island_handler/ISLAND_RECIPE_CHECK.png', 'tw': './assets/cn/island_handler/ISLAND_RECIPE_CHECK.png'}) +ISLAND_RECIPE_DRAG_CHECK = Button(area={'cn': (201, 68, 296, 653), 'en': (201, 68, 296, 653), 'jp': (201, 68, 296, 653), 'tw': (201, 68, 296, 653)}, color={'cn': (205, 199, 196), 'en': (205, 199, 196), 'jp': (205, 199, 196), 'tw': (205, 199, 196)}, button={'cn': (201, 68, 296, 653), 'en': (201, 68, 296, 653), 'jp': (201, 68, 296, 653), 'tw': (201, 68, 296, 653)}, file={'cn': './assets/cn/island_handler/ISLAND_RECIPE_DRAG_CHECK.png', 'en': './assets/cn/island_handler/ISLAND_RECIPE_DRAG_CHECK.png', 'jp': './assets/cn/island_handler/ISLAND_RECIPE_DRAG_CHECK.png', 'tw': './assets/cn/island_handler/ISLAND_RECIPE_DRAG_CHECK.png'}) +ISLAND_RECIPE_TIME = Button(area={'cn': (654, 605, 749, 634), 'en': (654, 605, 749, 634), 'jp': (654, 605, 749, 634), 'tw': (654, 605, 749, 634)}, color={'cn': (89, 200, 255), 'en': (89, 200, 255), 'jp': (89, 200, 255), 'tw': (89, 200, 255)}, button={'cn': (654, 605, 749, 634), 'en': (654, 605, 749, 634), 'jp': (654, 605, 749, 634), 'tw': (654, 605, 749, 634)}, file={'cn': './assets/cn/island_handler/ISLAND_RECIPE_TIME.png', 'en': './assets/cn/island_handler/ISLAND_RECIPE_TIME.png', 'jp': './assets/cn/island_handler/ISLAND_RECIPE_TIME.png', 'tw': './assets/cn/island_handler/ISLAND_RECIPE_TIME.png'}) +ISLAND_RECIPE_TIME_ANCHOR = Button(area={'cn': (624, 607, 650, 633), 'en': (624, 607, 650, 633), 'jp': (624, 607, 650, 633), 'tw': (624, 607, 650, 633)}, color={'cn': (100, 203, 255), 'en': (100, 203, 255), 'jp': (100, 203, 255), 'tw': (100, 203, 255)}, button={'cn': (624, 607, 650, 633), 'en': (624, 607, 650, 633), 'jp': (624, 607, 650, 633), 'tw': (624, 607, 650, 633)}, file={'cn': './assets/cn/island_handler/ISLAND_RECIPE_TIME_ANCHOR.png', 'en': './assets/cn/island_handler/ISLAND_RECIPE_TIME_ANCHOR.png', 'jp': './assets/cn/island_handler/ISLAND_RECIPE_TIME_ANCHOR.png', 'tw': './assets/cn/island_handler/ISLAND_RECIPE_TIME_ANCHOR.png'}) +ISLAND_RESTAURANT_CHECK = Button(area={'cn': (122, 20, 219, 47), 'en': (122, 20, 219, 47), 'jp': (122, 20, 219, 47), 'tw': (122, 20, 219, 47)}, color={'cn': (99, 103, 114), 'en': (99, 103, 114), 'jp': (99, 103, 114), 'tw': (99, 103, 114)}, button={'cn': (122, 20, 219, 47), 'en': (122, 20, 219, 47), 'jp': (122, 20, 219, 47), 'tw': (122, 20, 219, 47)}, 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': (592, 611, 687, 641), 'en': (592, 611, 687, 641), 'jp': (592, 611, 687, 641), 'tw': (592, 611, 687, 641)}, color={'cn': (113, 207, 252), 'en': (113, 207, 252), 'jp': (113, 207, 252), 'tw': (113, 207, 252)}, button={'cn': (592, 611, 687, 641), 'en': (592, 611, 687, 641), 'jp': (592, 611, 687, 641), 'tw': (592, 611, 687, 641)}, 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': (284, 612, 383, 640), 'en': (284, 612, 383, 640), 'jp': (284, 612, 383, 640), 'tw': (284, 612, 383, 640)}, color={'cn': (89, 120, 140), 'en': (89, 120, 140), 'jp': (89, 120, 140), 'tw': (89, 120, 140)}, button={'cn': (284, 612, 383, 640), 'en': (284, 612, 383, 640), 'jp': (284, 612, 383, 640), 'tw': (284, 612, 383, 640)}, 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': (604, 613, 676, 640), 'en': (604, 613, 676, 640), 'jp': (604, 613, 676, 640), 'tw': (604, 613, 676, 640)}, color={'cn': (179, 179, 177), 'en': (179, 179, 177), 'jp': (179, 179, 177), 'tw': (179, 179, 177)}, button={'cn': (604, 613, 676, 640), 'en': (604, 613, 676, 640), 'jp': (604, 613, 676, 640), 'tw': (604, 613, 676, 640)}, 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': (713, 612, 810, 640), 'en': (713, 612, 810, 640), 'jp': (713, 612, 810, 640), 'tw': (713, 612, 810, 640)}, color={'cn': (115, 208, 252), 'en': (115, 208, 252), 'jp': (115, 208, 252), 'tw': (115, 208, 252)}, button={'cn': (713, 612, 810, 640), 'en': (713, 612, 810, 640), 'jp': (713, 612, 810, 640), 'tw': (713, 612, 810, 640)}, 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_SHOP_BUY_AMOUNT = Button(area={'cn': (748, 351, 858, 391), 'en': (748, 351, 858, 391), 'jp': (748, 351, 858, 391), 'tw': (748, 351, 858, 391)}, color={'cn': (225, 225, 225), 'en': (225, 225, 225), 'jp': (225, 225, 225), 'tw': (225, 225, 225)}, button={'cn': (748, 351, 858, 391), 'en': (748, 351, 858, 391), 'jp': (748, 351, 858, 391), 'tw': (748, 351, 858, 391)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT.png', 'jp': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT.png'}) +ISLAND_SHOP_BUY_AMOUNT_MINUS_ONE = Button(area={'cn': (706, 356, 735, 385), 'en': (706, 356, 735, 385), 'jp': (706, 356, 735, 385), 'tw': (706, 356, 735, 385)}, color={'cn': (67, 68, 70), 'en': (67, 68, 70), 'jp': (67, 68, 70), 'tw': (67, 68, 70)}, button={'cn': (706, 356, 735, 385), 'en': (706, 356, 735, 385), 'jp': (706, 356, 735, 385), 'tw': (706, 356, 735, 385)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_ONE.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_ONE.png', 'jp': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_ONE.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_ONE.png'}) +ISLAND_SHOP_BUY_AMOUNT_MINUS_TEN = Button(area={'cn': (612, 355, 682, 387), 'en': (612, 355, 682, 387), 'jp': (612, 355, 682, 387), 'tw': (612, 355, 682, 387)}, color={'cn': (69, 70, 72), 'en': (69, 70, 72), 'jp': (69, 70, 72), 'tw': (69, 70, 72)}, button={'cn': (612, 355, 682, 387), 'en': (612, 355, 682, 387), 'jp': (612, 355, 682, 387), 'tw': (612, 355, 682, 387)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_TEN.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_TEN.png', 'jp': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_TEN.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_MINUS_TEN.png'}) +ISLAND_SHOP_BUY_AMOUNT_PLUS_ONE = Button(area={'cn': (872, 355, 902, 386), 'en': (872, 355, 902, 386), 'jp': (872, 355, 902, 386), 'tw': (872, 355, 902, 386)}, color={'cn': (72, 73, 75), 'en': (72, 73, 75), 'jp': (72, 73, 75), 'tw': (72, 73, 75)}, button={'cn': (872, 355, 902, 386), 'en': (872, 355, 902, 386), 'jp': (872, 355, 902, 386), 'tw': (872, 355, 902, 386)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_ONE.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_ONE.png', 'jp': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_ONE.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_ONE.png'}) +ISLAND_SHOP_BUY_AMOUNT_PLUS_TEN = Button(area={'cn': (925, 356, 995, 386), 'en': (925, 356, 995, 386), 'jp': (925, 356, 995, 386), 'tw': (925, 356, 995, 386)}, color={'cn': (72, 73, 74), 'en': (72, 73, 74), 'jp': (72, 73, 74), 'tw': (72, 73, 74)}, button={'cn': (925, 356, 995, 386), 'en': (925, 356, 995, 386), 'jp': (925, 356, 995, 386), 'tw': (925, 356, 995, 386)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_TEN.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_TEN.png', 'jp': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_TEN.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_BUY_AMOUNT_PLUS_TEN.png'}) +ISLAND_SHOP_BUY_CHECK = Button(area={'cn': (298, 121, 350, 150), 'en': (298, 121, 350, 150), 'jp': (298, 121, 350, 150), 'tw': (298, 121, 350, 150)}, color={'cn': (76, 76, 76), 'en': (76, 76, 76), 'jp': (76, 76, 76), 'tw': (76, 76, 76)}, button={'cn': (298, 121, 350, 150), 'en': (298, 121, 350, 150), 'jp': (298, 121, 350, 150), 'tw': (298, 121, 350, 150)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_BUY_CHECK.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_BUY_CHECK.png', 'jp': './assets/jp/island_handler/ISLAND_SHOP_BUY_CHECK.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_BUY_CHECK.png'}) +ISLAND_SHOP_BUY_CONFIRM = Button(area={'cn': (892, 554, 959, 585), 'en': (892, 554, 959, 585), 'jp': (892, 554, 959, 585), 'tw': (892, 554, 959, 585)}, color={'cn': (82, 197, 255), 'en': (82, 197, 255), 'jp': (82, 197, 255), 'tw': (82, 197, 255)}, button={'cn': (892, 554, 959, 585), 'en': (892, 554, 959, 585), 'jp': (892, 554, 959, 585), 'tw': (892, 554, 959, 585)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_BUY_CONFIRM.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_BUY_CONFIRM.png', 'jp': './assets/jp/island_handler/ISLAND_SHOP_BUY_CONFIRM.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_BUY_CONFIRM.png'}) +ISLAND_SHOP_LOADING = Button(area={'cn': (1217, 411, 1260, 455), 'en': (1217, 411, 1260, 455), 'jp': (1217, 411, 1260, 455), 'tw': (1217, 411, 1260, 455)}, color={'cn': (121, 125, 121), 'en': (121, 125, 121), 'jp': (121, 125, 121), 'tw': (121, 125, 121)}, button={'cn': (1217, 411, 1260, 455), 'en': (1217, 411, 1260, 455), 'jp': (1217, 411, 1260, 455), 'tw': (1217, 411, 1260, 455)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_LOADING.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_LOADING.png', 'jp': './assets/cn/island_handler/ISLAND_SHOP_LOADING.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_LOADING.png'}) +ISLAND_SHOP_MILL_CHECK = Button(area={'cn': (997, 27, 1013, 42), 'en': (997, 27, 1013, 42), 'jp': (997, 27, 1013, 42), 'tw': (997, 27, 1013, 42)}, color={'cn': (125, 168, 56), 'en': (125, 168, 56), 'jp': (125, 168, 56), 'tw': (125, 168, 56)}, button={'cn': (997, 27, 1013, 42), 'en': (997, 27, 1013, 42), 'jp': (997, 27, 1013, 42), 'tw': (997, 27, 1013, 42)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_MILL_CHECK.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_MILL_CHECK.png', 'jp': './assets/cn/island_handler/ISLAND_SHOP_MILL_CHECK.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_MILL_CHECK.png'}) +ISLAND_SHOP_RECOMMEND = Button(area={'cn': (134, 20, 219, 45), 'en': (134, 20, 219, 45), 'jp': (134, 20, 219, 45), 'tw': (134, 20, 219, 45)}, color={'cn': (73, 79, 93), 'en': (73, 79, 93), 'jp': (73, 79, 93), 'tw': (73, 79, 93)}, button={'cn': (134, 20, 219, 45), 'en': (134, 20, 219, 45), 'jp': (134, 20, 219, 45), 'tw': (134, 20, 219, 45)}, file={'cn': './assets/cn/island_handler/ISLAND_SHOP_RECOMMEND.png', 'en': './assets/cn/island_handler/ISLAND_SHOP_RECOMMEND.png', 'jp': './assets/jp/island_handler/ISLAND_SHOP_RECOMMEND.png', 'tw': './assets/cn/island_handler/ISLAND_SHOP_RECOMMEND.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'}) +TEMPLATE_ISLAND_DOCK_GRADE_B = Template(file={'cn': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_B.png', 'en': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_B.png', 'jp': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_B.png', 'tw': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_B.png'}) +TEMPLATE_ISLAND_DOCK_GRADE_C = Template(file={'cn': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_C.png', 'en': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_C.png', 'jp': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_C.png', 'tw': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_C.png'}) +TEMPLATE_ISLAND_DOCK_GRADE_D = Template(file={'cn': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_D.png', 'en': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_D.png', 'jp': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_D.png', 'tw': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_D.png'}) +TEMPLATE_ISLAND_DOCK_GRADE_E = Template(file={'cn': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_E.png', 'en': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_E.png', 'jp': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_E.png', 'tw': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_GRADE_E.png'}) +TEMPLATE_ISLAND_DOCK_OCCUPIED = Template(file={'cn': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png', 'en': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png', 'jp': './assets/jp/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png', 'tw': './assets/cn/island_handler/TEMPLATE_ISLAND_DOCK_OCCUPIED.png'}) +TEMPLATE_ISLAND_RECIPE_ANCHOR = Template(file={'cn': './assets/cn/island_handler/TEMPLATE_ISLAND_RECIPE_ANCHOR.png', 'en': './assets/cn/island_handler/TEMPLATE_ISLAND_RECIPE_ANCHOR.png', 'jp': './assets/cn/island_handler/TEMPLATE_ISLAND_RECIPE_ANCHOR.png', 'tw': './assets/cn/island_handler/TEMPLATE_ISLAND_RECIPE_ANCHOR.png'}) diff --git a/module/island_handler/dock.py b/module/island_handler/dock.py new file mode 100644 index 0000000000..46bf09f8ee --- /dev/null +++ b/module/island_handler/dock.py @@ -0,0 +1,207 @@ +from module.base.button import Button, ButtonGrid +from module.base.decorator import cached_property, del_cached_property +from module.base.utils import random_rectangle_vector_opted +from module.island.ui import IslandUI +from module.island_handler.assets import * +from module.island_handler.dock_scanner import CharacterScanner +from module.logger import logger +from module.map_detection.utils import Points +from module.ui.switch import Switch + +ISLAND_DOCK_SORTING = Switch('island_dock_sorting') +ISLAND_DOCK_SORTING.add_state('Ascending', check_button=ISLAND_DOCK_SORT_ASC, + click_button=ISLAND_DOCK_SORTING_CLICK, offset=(20, 5)) +ISLAND_DOCK_SORTING.add_state('Descending', check_button=ISLAND_DOCK_SORT_DESC, + click_button=ISLAND_DOCK_SORTING_CLICK, offset=(20, 5)) +ISLAND_DOCK_DETECT_AREA = (56, 128, 880, 561) +ISLAND_DOCK_DETECT = Button(ISLAND_DOCK_DETECT_AREA, color=(), button=ISLAND_DOCK_DETECT_AREA, name='ISLAND_DOCK_DETECT') +ISLAND_DOCK_CARD_ANCHOR_AREA = (11, 9, 29, 27) +ISLAND_DOCK_CARD_DELTA = (140, 180) +ISLAND_DOCK_CARD_SIZE = (124, 164) + +class IslandDock(IslandUI): + def is_in_island_dock(self): + return self.appear(ISLAND_DOCK_CHECK, offset=(0, 20)) + + def handle_island_dock_loading(self): + for _ in self.loop(timeout=1.2): + pass + + def _island_dock_quit_check_func(self): + return not self.appear(ISLAND_DOCK_CHECK, offset=(20, 20)) + + def island_dock_quit(self): + self.ui_back(check_button=self._island_dock_quit_check_func, skip_first_screenshot=True) + + def island_dock_sort_method_dsc_set(self, enable=True, wait_loading=True): + """ + Args: + enable (bool): True to set descending sorting + wait_loading (bool): Default to True, use False on continuous operation + """ + if ISLAND_DOCK_SORTING.set('Descending' if enable else 'Ascending', main=self): + if wait_loading: + self.handle_island_dock_loading() + return True + return False + + def _get_dock_buttons(self): + area = (ISLAND_DOCK_DETECT_AREA[0] + ISLAND_DOCK_CARD_ANCHOR_AREA[0], + ISLAND_DOCK_DETECT_AREA[1] + ISLAND_DOCK_CARD_ANCHOR_AREA[1], + ISLAND_DOCK_DETECT_AREA[2] - ISLAND_DOCK_CARD_SIZE[0] + ISLAND_DOCK_CARD_ANCHOR_AREA[2], + ISLAND_DOCK_DETECT_AREA[3] - ISLAND_DOCK_CARD_SIZE[1] + ISLAND_DOCK_CARD_ANCHOR_AREA[3]) + image = self.image_crop(area, copy=True) + anchors = TEMPLATE_ISLAND_DOCK_CARD_ANCHOR.match_multi(image, threshold=5) + logger.attr('cards_in_view', len(anchors)) + rows = Points([(0., a.area[1]) for a in anchors]).group(threshold=5) + return rows + + @cached_property + def dock_grid(self): + for _ in self.loop(timeout=2): + grid = self.get_dock_grid() + if len(grid.buttons) >= 6: + return grid + return grid + + def get_dock_grid(self): + rows = self._get_dock_buttons() + count = len(rows) + delta_y = ISLAND_DOCK_CARD_DELTA[1] + if count >= 3: + logger.warning(f'Unexpected card count in view: {count}, assume 2 rows') + count = 2 + if count > 0: + y_list = rows[:, 1] + origin_y = y_list.min() + ISLAND_DOCK_DETECT_AREA[1] + else: + logger.warning('No cards detected, use default position') + origin_y = 139 + count = 2 + + grid = ButtonGrid( + origin=(ISLAND_DOCK_DETECT_AREA[0], origin_y), + delta=ISLAND_DOCK_CARD_DELTA, + button_shape=ISLAND_DOCK_CARD_SIZE, + grid_shape=(6, count), + name='CARD' + ) + return grid + + def next_dock_page(self): + p1, p2 = random_rectangle_vector_opted((0, -250), box=ISLAND_DOCK_DETECT_AREA, padding=-10) + self.device.drag(p1, p2, hold_duration=0.1, name='ISLAND_DOCK_NEXT_PAGE_SWIPE') + del_cached_property(self, 'dock_grid') + self.device.screenshot() + + def prev_dock_page(self): + p1, p2 = random_rectangle_vector_opted((0, 250), box=ISLAND_DOCK_DETECT_AREA, padding=-10) + self.device.drag(p1, p2, hold_duration=0.1, name='ISLAND_DOCK_PREV_PAGE_SWIPE') + del_cached_property(self, 'dock_grid') + self.device.screenshot() + + def ensure_dock_page_at_top(self): + ISLAND_DOCK_DETECT.load_color(self.device.image) + ISLAND_DOCK_DETECT._match_init = True + for _ in self.loop(timeout=10): + self.prev_dock_page() + if self.appear(ISLAND_DOCK_DETECT, offset=(20, 20)): + logger.warning('Reached top of dock page') + return True + else: + ISLAND_DOCK_DETECT.load_color(self.device.image) + return False + + def island_dock_select_one(self, button, skip_first=True): + """ + Args: + button (Button): Character button to select + skip_first (bool): + """ + self.interval_clear(ISLAND_DOCK_CHECK) + for _ in self.loop(skip_first=skip_first): + if self.is_button_selected(button, color=(19, 181, 231)): + break + + if self.appear(ISLAND_DOCK_CHECK, offset=(20, 20), interval=5): + self.device.click(button) + continue + + def island_dock_select_confirm(self, check_button, skip_first=True): + """ + Args: + check_button (callable, Button): + skip_first (bool): + """ + for _ in self.loop(skip_first=skip_first): + if self.ui_process_check_button(check_button): + del_cached_property(self, 'dock_grid') + break + + if self.appear_then_click(ISLAND_DOCK_CHARACTER_CONFIRM, offset=(20, 20), interval=5): + continue + + def island_dock_select_manjuu(self): + self.island_dock_sort_method_dsc_set(enable=False) + scanner = CharacterScanner(self.dock_grid, identity=['Manjuu'], status='free') + candidates = scanner.scan(self.device.image) + if candidates: + self.island_dock_select_one(candidates[0].button) + return True + else: + logger.warning('No Manjuu found in dock') + return False + + def island_dock_find_character(self, identity): + self.island_dock_sort_method_dsc_set(enable=True) + ISLAND_DOCK_DETECT.load_color(self.device.image) + ISLAND_DOCK_DETECT._match_init = True + for _ in self.loop(timeout=40, skip_first=False): + scanner = CharacterScanner(self.dock_grid, identity=identity, status=None) + candidates = scanner.scan(self.device.image) + for candidate in candidates: + if candidate.identity != identity: + continue + return candidate + self.next_dock_page() + if self.appear(ISLAND_DOCK_DETECT, offset=(20, 20)): + logger.warning('Reached end of dock page') + break + else: + ISLAND_DOCK_DETECT.load_color(self.device.image) + else: + logger.warning('Failed to find all requested characters') + return None + + def island_dock_select_character_with_blacklist(self, blacklist): + self.island_dock_sort_method_dsc_set(enable=True) + ISLAND_DOCK_DETECT.load_color(self.device.image) + ISLAND_DOCK_DETECT._match_init = True + for _ in self.loop(timeout=40, skip_first=False): + scanner = CharacterScanner(self.dock_grid, identity='any', status='free') + candidates = scanner.scan(self.device.image) + candidates = ( + [c for c in candidates if c.grade == 'S'] + + [c for c in candidates if c.grade == 'A'] + + [c for c in candidates if c.grade == 'B'] + + [c for c in candidates if c.grade == 'C'] + + [c for c in candidates if c.grade == 'D'] + + [c for c in candidates if c.grade == 'E'] + ) + for candidate in candidates: + if candidate.identity in blacklist: + logger.warning(f'Candidate {candidate.identity} is in blacklist, skip') + continue + elif self.is_button_selected(candidate.button, color=(19, 181, 231)): + continue + else: + self.island_dock_select_one(candidate.button) + return candidate.identity + self.next_dock_page() + if self.appear(ISLAND_DOCK_DETECT, offset=(20, 20)): + logger.warning('Reached end of dock page') + break + else: + ISLAND_DOCK_DETECT.load_color(self.device.image) + logger.warning('Failed to find any character not in blacklist') + return None \ No newline at end of file diff --git a/module/island_handler/dock_scanner.py b/module/island_handler/dock_scanner.py new file mode 100644 index 0000000000..1a89bb0a56 --- /dev/null +++ b/module/island_handler/dock_scanner.py @@ -0,0 +1,380 @@ +import re +from abc import ABCMeta, abstractmethod +from dataclasses import dataclass +from typing import Any, Dict, List, Tuple, Union + +import cv2 + +from module.base.button import ButtonGrid +from module.base.utils import color_similarity_2d, crop, limit_in, load_image +from module.island_handler.assets import * +from module.logger import logger +from module.ocr.ocr import Ocr +from module.statistics.utils import load_folder + +ISLAND_DOCK_CARD_GRIDS = ButtonGrid( + origin=(56, 139), delta=(140, 180), button_shape=(124, 164), grid_shape=(6, 2), name='CARD' +) + + +@dataclass(frozen=True) +class Character: + identity: str = '' + emotion: int = 0 + emotion_limit: int = 0 + grade: str = '' + status: str = '' + button: Any = None + + def satisfy_limitation(self, limitation) -> bool: + for key in self.__dict__: + value = limitation.get(key) + if self.__dict__[key] is not None and value is not None: + # str and int should be exactly equal to + if isinstance(value, (str, int)): + if value == 'any': + continue + if self.__dict__[key] != value: + return False + # tuple means should be in range + elif isinstance(value, tuple): + if not (value[0] <= self.__dict__[key] <= value[1]): + return False + # list means should be in list + elif isinstance(value, list): + if self.__dict__[key] not in value: + return False + return True + + +class IslandEmotionCounterOcr(Ocr): + def __init__(self, buttons, lang='azur_lane', letter=(255, 255, 255), threshold=128, alphabet='0123456789/IDSB', + name=None): + super().__init__(buttons, lang=lang, letter=letter, threshold=threshold, alphabet=alphabet, name=name) + + def pre_process(self, image, background_color_main=(18, 209, 183), background_color_sub=(207, 207, 207)): + mask = color_similarity_2d(image, background_color_sub) + mask2 = color_similarity_2d(image, self.letter) + mask[mask < mask2] = 0 + cv2.inRange(mask, 221, 255, dst=mask) + image[mask > 0] = background_color_main + return super().pre_process(image) + + def after_process(self, result): + result = super().after_process(result) + result = result.replace('I', '1').replace('D', '0').replace('S', '5') + result = result.replace('B', '8') + result = re.search(r'(\d+)/(\d+)', result) + if result: + result = [int(s) for s in result.groups()] + current, total = int(result[0]), int(result[1]) + current = min(current, total) + return current, total - current, total + else: + logger.warning(f'Unexpected ocr result: {result}') + return 0, 0, 0 + + +class Scanner(metaclass=ABCMeta): + _results: List = None + _enabled: bool = True + _disabled_value: List[None] = [None] * 12 + grids: ButtonGrid = None + + @property + def results(self) -> List: + return self._results + + @abstractmethod + def _scan(self, image) -> List: + pass + + @abstractmethod + def limit_value(self, value) -> Any: + pass + + def clear(self) -> None: + """ + Clear all cached results. + """ + self._results.clear() + + def scan(self, image, cached=False, output=False) -> Union[List, None]: + """ + If scanner is enabled, return the real results. + Otherwise, return a series of None. + + For multi-scan, caching the results is recommended. + If cached is set, results will be cached. + """ + results: List = self._scan(image) if self._enabled else self._disabled_value + + if output: + for result in results: + logger.info(f'{result}') + + if cached: + self._results.extend(results) + else: + return results + + def move(self, vector) -> None: + """ + Call ButtonGrid.move for property grids. + """ + self.grids = self.grids.move(vector) + + def enable(self) -> None: + self._enabled = True + + def disable(self) -> None: + self._enabled = False + + +class IdentityScanner(Scanner): + def __init__(self, grids: ButtonGrid) -> None: + super().__init__() + self._results = [] + self.grids = grids + self.templates = {} + data = load_folder('./assets/island/character/') + for name, image in data.items(): + self.templates[name] = load_image(image) + + def _match(self, image) -> str: + for name, template_image in self.templates.items(): + res = cv2.matchTemplate(image, template_image, cv2.TM_CCOEFF_NORMED) + _, sim, _, _ = cv2.minMaxLoc(res) + if sim > 0.75: + return name + return 'unknown' + + def _scan(self, image) -> List: + image_list = [crop(image, button.area) for button in self.grids.buttons] + return [self._match(image) for image in image_list] + + def limit_value(self, value) -> str: + return value if value in self.templates.keys() else 'any' + + +class EmotionCounterScanner(Scanner): + def __init__(self, grids: ButtonGrid) -> None: + super().__init__() + self._results = [] + self.grids = grids.crop((7, 141, 59, 154), name='EMOTION') + self.ocr_model = IslandEmotionCounterOcr(self.grids.buttons, name='EMOTION_COUNTER_OCR') + + def _scan(self, image) -> List: + return self.ocr_model.ocr(image) + + +class EmotionScanner(EmotionCounterScanner): + def _scan(self, image) -> List: + results = super()._scan(image) + return [result[0] for result in results] + + def limit_value(self, value): + if value == 999: + return 999 + return limit_in(value, 0, 150) + + +class EmotionLimitScanner(EmotionCounterScanner): + def _scan(self, image) -> List: + results = super()._scan(image) + return [result[2] for result in results] + + def limit_value(self, value): + if value == 999: + return 999 + return limit_in(value, 100, 150) + + +class GradeScanner(Scanner): + def __init__(self, grids: ButtonGrid) -> None: + super().__init__() + self._results = [] + self.grids = grids.crop((98, 138, 117, 157), name='GRADE') + self.templates = { + # TEMPLATE_ISLAND_DOCK_GRADE_S: 'S', + TEMPLATE_ISLAND_DOCK_GRADE_A: 'A', + TEMPLATE_ISLAND_DOCK_GRADE_B: 'B', + TEMPLATE_ISLAND_DOCK_GRADE_C: 'C', + TEMPLATE_ISLAND_DOCK_GRADE_D: 'D', + TEMPLATE_ISLAND_DOCK_GRADE_E: 'E', + } + + def _match(self, image) -> str: + for template, grade in self.templates.items(): + if template.match(image, similarity=0.95): + return grade + return 'unknown' + + def _scan(self, image) -> List: + image_list = [crop(image, button.area) for button in self.grids.buttons] + return [self._match(image) for image in image_list] + + def limit_value(self, value) -> str: + return value if value in self.templates.values() else 'any' + + +class StatusScanner(Scanner): + def __init__(self, grids: ButtonGrid) -> None: + super().__init__() + self._results = [] + self.grids = grids + self.value_list: List[str] = ['free', 'occupied'] + self.templates = { + TEMPLATE_ISLAND_DOCK_OCCUPIED: 'occupied' + } + + def _match(self, image) -> str: + for template, status in self.templates.items(): + if template.match(image, similarity=0.75): + return status + return 'free' + + def _scan(self, image) -> List: + image_list = [crop(image, button.area) for button in self.grids.buttons] + return [self._match(image) for image in image_list] + + def limit_value(self, value) -> str: + return value if value in self.value_list else 'any' + + +class CharacterScanner(Scanner): + """ + CharacterScanner is designed to use with an 'Initial' page at island_dock, + which means there cannot be any move once a dock filter was set. + Otherwise it may return untrustable results. + + By default all properties of the character are scanned. + You can set the required properties by calling enable() or disable(). + disable() will simply skip scanning and set those properties to None. + To keey them and ignore limitations, use set_limitation(property=None). + + Args: + emotion (tuple): (min, max) of emotion level. Will be limited in range [0, 150]. + emotion_limit (tuple): (min, max) of emotion limit. Will be limited in range [100, 150]. + status (list): ['any', 'free', 'occupied']. + """ + def __init__( + self, + grids=ISLAND_DOCK_CARD_GRIDS, + identity: str = 'any', + emotion: Tuple[int, int] = (0, 999), + emotion_limit: Tuple[int, int] = (100, 999), + grade: str = 'any', + status: str = 'any' + ) -> None: + super().__init__() + self._results = [] + self.grids = grids + self.limitation: Dict[str, Union[None, Tuple[int, int], List[str], str]] = { + 'identity': 'any', + 'emotion': (0, 999), + 'emotion_limit': (100, 999), + 'grade': 'any', + 'status': 'any' + } + + self.sub_scanners: Dict[str, Scanner] = { + 'identity': IdentityScanner(grids), + 'emotion': EmotionScanner(grids), + 'emotion_limit': EmotionLimitScanner(grids), + 'grade': GradeScanner(grids), + 'status': StatusScanner(grids), + } + + self.set_limitation(identity=identity, emotion=emotion, emotion_limit=emotion_limit, grade=grade, status=status) + + def _scan(self, image) -> List[Character]: + for scanner in self.sub_scanners.values(): + scanner.scan(image, cached=True) + + candidates: List[Character] = [ + Character( + identity=identity, + emotion=emotion, + emotion_limit=emotion_limit, + grade=grade, + status=status, + button=button + ) + for identity, emotion, emotion_limit, grade, status, button in zip( + self.sub_scanners['identity'].results, + self.sub_scanners['emotion'].results, + self.sub_scanners['emotion_limit'].results, + self.sub_scanners['grade'].results, + self.sub_scanners['status'].results, + self.grids.buttons + ) + ] + + for scanner in self.sub_scanners.values(): + scanner.clear() + + return candidates + + def scan(self, image, cached=False, output=True) -> Union[List[Character], None]: + candidates = super().scan(image, cached=cached, output=output) + if not cached: + return [candidate for candidate in candidates + if candidate.satisfy_limitation(self.limitation)] + + def move(self, vector) -> None: + """ + Apply moving to both sub-scanners and self. + """ + for scanner in self.sub_scanners.values(): + scanner.move(vector) + super().move(vector) + + def limit_value(self, key, value) -> None: + if value is None: + self.limitation[key] = None + elif isinstance(value, tuple): + lower, upper = value + lower = self.sub_scanners[key].limit_value(lower) + upper = self.sub_scanners[key].limit_value(upper) + self.limitation[key] = (lower, upper) + elif isinstance(value, list): + self.limitation[key] = [self.sub_scanners[key].limit_value(v) for v in value] + else: + self.limitation[key] = self.sub_scanners[key].limit_value(value) + + def enable(self, *args) -> None: + """ + Enable property sub-scanners. + + Supported properties includes: + ['emotion', 'emotion_limit', 'grade', 'status'] + """ + for name, scanner in self.sub_scanners.items(): + if name in args: + scanner.enable() + + def disable(self, *args) -> None: + """ + Disable property sub-scanners. + + Supported properties includes: + ['emotion', 'emotion_limit', 'status'] + """ + for name, scanner in self.sub_scanners.items(): + if name in args: + scanner.disable() + + def set_limitation(self, **kwargs) -> None: + """ + Args: + emotion (tuple): (min, max) of emotion level. Will be limited in range [0, 999]. + emotion_limit (tuple): (min, max) of emotion limit. Will be limited in range [100, 999]. + grade (str): ['any', 'S', 'A', 'B', 'C', 'D', 'E'] + status (str): ['any', 'free', 'occupied'] + """ + for attr in self.limitation.keys(): + value = kwargs.get(attr, self.limitation[attr]) + self.limit_value(attr, value) + + logger.info(f'Limitations set to {self.limitation}') diff --git a/module/island_handler/production_planner.py b/module/island_handler/production_planner.py new file mode 100644 index 0000000000..8a00f3a0a3 --- /dev/null +++ b/module/island_handler/production_planner.py @@ -0,0 +1,1564 @@ +import re +from collections import defaultdict +from datetime import datetime +from typing import Dict + +import numpy as np +from scipy.optimize import linprog +from yaml import safe_dump, safe_load + +import module.config.server as server +from module.base.decorator import cached_property +from module.config.utils import server_time_offset +from module.daemon.daemon_base import DaemonBase +from module.island.data import * +from module.island_handler.technology_scanner import IslandTechnologyScanner +from module.logger import logger + + +def normalize_item_name(name): + return str(name).strip() + + +def normalize_item_id(item_id): + if isinstance(item_id, (int, np.integer)): + normalized_id = int(item_id) + if normalized_id in DIC_ISLAND_ITEM: + return normalized_id + raise ValueError(f'Unknown item id: {item_id}') + + item_text = normalize_item_name(item_id) + if not item_text: + raise ValueError('Empty item key') + + if item_text.isdigit(): + normalized_id = int(item_text) + if normalized_id in DIC_ISLAND_ITEM: + return normalized_id + raise ValueError(f'Unknown item id: {item_text}') + + match = re.match(r'^(.*)\((\d+)\)$', item_text) + if match: + normalized_id = int(match.group(2)) + if normalized_id in DIC_ISLAND_ITEM: + return normalized_id + raise ValueError(f'Unknown item id: {item_text}') + + for normalized_id, item_data in DIC_ISLAND_ITEM.items(): + if item_data['name'][server.server] == item_text: + return normalized_id + + raise ValueError(f'Unknown item key: {item_text}') + + +def normalize_item_keys(items=None): + if not items: + return {} + normalized = {} + for raw_item_id, raw_value in items.items(): + item_id = normalize_item_id(raw_item_id) + normalized[item_id] = raw_value + return normalized + + +def item_name(item_id): + return DIC_ISLAND_ITEM[item_id]['name'][server.server] + + +def item_export_key(item_id, use_item_name=False): + if use_item_name: + return f'{item_name(item_id)} ({item_id})' + return item_id + + +def item_mapping_to_yaml(items, use_item_name=False): + payload = { + item_export_key(item_id, use_item_name=use_item_name): value + for item_id, value in sorted(items.items()) + } + return safe_dump(payload, allow_unicode=True, sort_keys=False) + + +def load_item_mapping(yaml_text=None, config_name='Items'): + if not yaml_text: + return {} + items = safe_load(yaml_text) + if not items: + return {} + if not isinstance(items, dict): + raise ValueError(f'{config_name} must be a YAML mapping') + return items + + +def load_reserve_items(reserve_items_yaml=None): + return load_item_mapping(reserve_items_yaml, config_name='ReserveItems') + + +def load_hard_floor_items(hard_floor_items_yaml=None): + return load_item_mapping(hard_floor_items_yaml, config_name='HardFloorItems') + + +def load_request_buffer_items(request_buffer_items_yaml=None): + return load_item_mapping(request_buffer_items_yaml, config_name='RequestBufferItems') + + +def normalize_item_needs(items=None, default_period=1): + if not items: + return {} + requirements = defaultdict(list) + for raw_item_id, raw_value in items.items(): + item_id = normalize_item_id(raw_item_id) + if isinstance(raw_value, (list, tuple)) or (isinstance(raw_value, dict) and 'deadlines' in raw_value): + requirements[item_id].extend(item_need_input_to_requirements(raw_value, default_period=default_period)) + continue + elif isinstance(raw_value, dict): + total_need_count = raw_value.get('count', 0) + rate_per_day = raw_value.get('rate_per_day', 0) + if rate_per_day: + period = float(total_need_count) / float(rate_per_day) + else: + period = raw_value.get('period', raw_value.get('days', default_period)) + else: + total_need_count = raw_value + period = default_period + total_need_count = int(total_need_count) + period = float(period) + if total_need_count <= 0 or period <= 0: + continue + requirements[item_id].append((total_need_count, period)) + return { + item_id: build_item_need_data(item_requirements) + for item_id, item_requirements in requirements.items() + } + + +def normalize_reserve_items(reserve_items=None): + return normalize_item_needs(reserve_items, default_period=1) + + +def merge_item_needs(*item_needs): + requirements = defaultdict(list) + for needs in item_needs: + for item_id, data in needs.items(): + requirements[item_id].extend(item_need_data_to_requirements(data)) + return { + item_id: build_item_need_data(item_requirements) + for item_id, item_requirements in requirements.items() + } + + +def build_item_need_data(requirements): + period_counts = defaultdict(int) + for count, period in requirements: + count = int(count) + period = float(period) + if count <= 0 or period <= 0: + continue + period_counts[period] += count + + deadlines = [] + total_need_count = 0 + rate_per_day = 0.0 + for period, count in sorted(period_counts.items()): + total_need_count += count + rate_per_day = max(rate_per_day, total_need_count / period) + deadlines.append({ + 'count': total_need_count, + 'period': period, + }) + + if not deadlines: + return { + 'total_need_count': 0, + 'period': 1, + 'rate_per_day': 0.0, + } + + return { + 'total_need_count': total_need_count, + 'period': deadlines[-1]['period'], + 'rate_per_day': rate_per_day, + 'deadlines': deadlines, + } + + +def item_need_data_to_requirements(data): + deadlines = data.get('deadlines') + if not deadlines: + return [(data['total_need_count'], data['period'])] + + requirements = [] + previous_count = 0 + for deadline in sorted(deadlines, key=lambda x: x['period']): + count = int(deadline.get('count', deadline.get('total_need_count', 0))) + period = float(deadline.get('period', deadline.get('days', data['period']))) + marginal_count = count - previous_count + if marginal_count > 0: + requirements.append((marginal_count, period)) + previous_count = max(previous_count, count) + return requirements + + +def item_need_input_to_requirements(data, default_period=1): + if isinstance(data, dict): + entries = data.get('deadlines', []) + else: + entries = data + requirements = [] + for deadline in entries: + if isinstance(deadline, dict): + count = deadline.get('count', deadline.get('total_need_count', 0)) + period = deadline.get('period', deadline.get('days', default_period)) + else: + count = deadline + period = default_period + count = int(count) + period = float(period) + if count > 0 and period > 0: + requirements.append((count, period)) + return requirements + + +def parse_item_need_deadlines(item_need, default_period=1): + if not item_need: + return [] + if isinstance(item_need, dict) and item_need.get('deadlines') and ( + 'total_need_count' in item_need or 'rate_per_day' in item_need + ): + return [ + ( + int(deadline.get('count', deadline.get('total_need_count', 0))), + float(deadline.get('period', deadline.get('days', item_need['period']))), + ) + for deadline in sorted(item_need['deadlines'], key=lambda x: x['period']) + if int(deadline.get('count', deadline.get('total_need_count', 0))) > 0 + ] + + if isinstance(item_need, (list, tuple)) or (isinstance(item_need, dict) and 'deadlines' in item_need): + data = build_item_need_data(item_need_input_to_requirements(item_need, default_period=default_period)) + return [ + (deadline['count'], deadline['period']) + for deadline in data.get('deadlines', []) + ] + + if isinstance(item_need, dict): + total_need_count = item_need.get('count', item_need.get('total_need_count', 0)) + period = item_need.get('period', item_need.get('days', default_period)) + rate_per_day = item_need.get('rate_per_day', 0) + if rate_per_day: + period = float(total_need_count) / float(rate_per_day) + else: + total_need_count = item_need + period = default_period + total_need_count = int(total_need_count) + period = float(period) + if total_need_count <= 0 or period <= 0: + return [] + return [(total_need_count, period)] + + +def merge_task_target_reserve_items(reserve_items, task_target_items): + return merge_item_needs( + normalize_item_needs(reserve_items), + normalize_item_needs(task_target_items, default_period=10), + ) + + +def get_stuck_season_order_requirements(stuck_order_id): + stuck_order_id = normalize_stuck_season_order_id(stuck_order_id) + if not stuck_order_id: + return {} + + requirements = defaultdict(int) + visited = set() + order_id = stuck_order_id + while order_id: + if order_id in visited: + logger.warning(f'Detected season order loop at order id {order_id}') + break + visited.add(order_id) + order = DIC_ISLAND_SEASON_ORDER.get(order_id) + if order is None: + logger.warning(f'Cannot find stuck season order id {order_id}') + break + for item_id, count in order.get('request', {}).items(): + requirements[item_id] += count + order_id = order.get('next_order', 0) + return dict(requirements) + + +def normalize_stuck_season_order_id(stuck_order_id): + try: + stuck_order_id = int(stuck_order_id) + except (TypeError, ValueError): + logger.warning(f'Invalid stuck season order id: {stuck_order_id}') + return 0 + return stuck_order_id + + +def get_target_stock_load_rate(stock, reserve, target_deadlines): + effective_stock = max(stock - reserve, 0) + rate_per_day = 0 + target_stock = 0 + for target_count, target_period in target_deadlines: + deadline_rate = target_count / target_period + if deadline_rate > rate_per_day + 1e-9: + rate_per_day = deadline_rate + target_stock = target_count + elif abs(deadline_rate - rate_per_day) <= 1e-9: + target_stock = max(target_stock, target_count) + if effective_stock >= target_stock: + return 0 + return rate_per_day + + +def is_integer_value(value): + if isinstance(value, (int, np.integer)): + return True + if isinstance(value, (float, np.floating)): + return float(value).is_integer() + return False + + +def ceil_div_or_ceil(numerator, denominator): + if is_integer_value(numerator) and is_integer_value(denominator): + numerator = int(numerator) + denominator = int(denominator) + return (numerator + denominator - 1) // denominator + return ceil_with_epsilon(numerator / denominator) + + +def ceil_with_epsilon(amount, epsilon=1e-9): + from math import ceil + return int(ceil(float(amount) - epsilon)) + + +def format_item_need_data(data, format_amount): + deadlines = data.get('deadlines') + if not deadlines: + return f'{format_amount(data["total_need_count"])} in {format_amount(data["period"])} day(s)' + return ', '.join( + f'{format_amount(deadline["count"])} in {format_amount(deadline["period"])} day(s)' + for deadline in deadlines + ) + + +def item_need_data_to_yaml_entry(data, round_up_int): + deadlines = data.get('deadlines') + if deadlines and len(deadlines) > 1: + previous_count = 0 + entries = [] + for deadline in deadlines: + count = round_up_int(deadline['count']) + period = round_up_int(deadline['period']) + entries.append({ + 'count': count - previous_count, + 'period': period, + }) + previous_count = count + return entries + return { + 'count': round_up_int(data['total_need_count']), + 'period': round_up_int(data['period']), + } + + +def get_sub_dict(raw_dict: Dict[int, bool], keys: list) -> Dict[int, bool]: + return {key: raw_dict.get(key, False) for key in keys} + +def count_level(substatus: Dict[int, bool]): + return sum(1 for status in substatus.values() if status) + + +class IslandProductionPlanner(DaemonBase): + NET_ACCUMULATING_EPSILON = 1e-3 + EXCHANGE_REQUIRES_MANUAL_OPERATION = True + SLOT_TO_GROUP = { + 9001: 'field', 9002: 'field', 9003: 'field', 9004: 'field', + 9011: 'mine', 9012: 'mine', 9013: 'mine', 9014: 'mine', + 9021: 'wood', 9022: 'wood', 9023: 'wood', 9024: 'wood', + 9031: 'ranch_chicken', 9032: 'ranch_pig', 9033: 'ranch_cow', 9034: 'ranch_sheep', + 9041: 'cafe', 9042: 'cafe', + 9061: 'koi', 9062: 'koi', + 9071: 'bear', 9072: 'bear', + 9081: 'eatery', 9082: 'eatery', + 9091: 'grill', 9092: 'grill', + 9101: 'orchard', 9102: 'orchard', 9103: 'orchard', 9104: 'orchard', + 9111: 'nursery', 9112: 'nursery', + 9201: 'manufacturing_lumber', 9202: 'manufacturing_lumber', + 9203: 'manufacturing_machinery', 9204: 'manufacturing_machinery', + 9205: 'manufacturing_electronic', 9206: 'manufacturing_electronic', + 9207: 'manufacturing_crafts', 9208: 'manufacturing_crafts', + 9211: 'fishery', 9212: 'fishery', 9213: 'fishery', + } + GROUP_TO_PLACE = { + 'field': 101, + 'ranch_chicken': 102, + 'ranch_pig': 102, + 'ranch_cow': 102, + 'ranch_sheep': 102, + 'fishery': 201, + 'mine': 401, + 'wood': 402, + 'orchard': 501, + 'nursery': 502, + 'koi': 601, + 'bear': 602, + 'eatery': 603, + 'grill': 604, + 'manufacturing_lumber': 703, + 'manufacturing_machinery': 704, + 'manufacturing_electronic': 705, + 'manufacturing_crafts': 706, + 'cafe': 901, + } + GROUP_TO_SLOTS = { + 'field': [9001, 9002, 9003, 9004], + 'mine': [9011, 9012, 9013, 9014], + 'wood': [9021, 9022, 9023, 9024], + 'ranch_chicken': [9031], + 'ranch_pig': [9032], + 'ranch_cow': [9033], + 'ranch_sheep': [9034], + 'cafe': [9041, 9042], + 'koi': [9061, 9062], + 'bear': [9071, 9072], + 'eatery': [9081, 9082], + 'grill': [9091, 9092], + 'orchard': [9101, 9102, 9103, 9104], + 'nursery': [9111, 9112], + 'manufacturing_lumber': [9201, 9202], + 'manufacturing_machinery': [9203, 9204], + 'manufacturing_electronic': [9205, 9206], + 'manufacturing_crafts': [9207, 9208], + 'fishery': [9211, 9212, 9213], + } + RESTAURANT_MENU_CONFIG = { + 601: "IslandBusiness.IslandRestaurant.KoiMenu", + 602: "IslandBusiness.IslandRestaurant.BearMenu", + 603: "IslandBusiness.IslandRestaurant.EateryMenu", + 604: "IslandBusiness.IslandRestaurant.GrillMenu", + 901: "IslandBusiness.IslandRestaurant.CafeMenu", + } + RECIPE_PRODUCT_IDS = { + next(iter(recipe['commission_product'])) + for recipe in DIC_ISLAND_RECIPE.values() + if recipe['commission_product'] + } + EXCHANGE_PRODUCT_IDS = { + item_id + for recipe in DIC_ISLAND_EXCHANGE_RECIPE.values() + for item_id in recipe['items'] + } + DISH_ITEM_TO_SLOT = { + item_id: slot + for slot, menu in DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE.items() + for item_id in menu + } + + @cached_property + def current_activity_list(self): + time = datetime.now() + server_time_offset() + for season, content in DIC_ISLAND_SEASON.items(): + start_time = datetime.strptime(content['start_time'][server.server], "%Y-%m-%d %H:%M:%S") + end_time = datetime.strptime(content['end_time'][server.server], "%Y-%m-%d %H:%M:%S") + if start_time <= time < end_time: + return content['activity'] + + def analyze_technology_status(self, technology_status: Dict[int, bool]): + self.recipe_available = {} + self.wild_gather_available = {} + for activity_id in self.current_activity_list: + if DIC_ISLAND_ACTIVITY[activity_id]['type'] == 5003: + for gather_id in DIC_ISLAND_ACTIVITY[activity_id]['config_data']: + self.wild_gather_available[gather_id] = True + if DIC_ISLAND_ACTIVITY[activity_id]['type'] == 5004: + for recipe_id in DIC_ISLAND_ACTIVITY[activity_id]['config_data']: + self.recipe_available[recipe_id] = True + + self.slot_available = { + # Fields + 9001: technology_status.get(310101, False), + 9002: technology_status.get(310102, False), + 9003: technology_status.get(310103, False), + 9004: technology_status.get(310104, False), + # Mine + 9011: technology_status.get(220101, False), + 9012: technology_status.get(220102, False), + 9013: technology_status.get(220103, False), + 9014: technology_status.get(220104, False), + # Woods + 9021: technology_status.get(210101, False), + 9022: technology_status.get(210102, False), + 9023: technology_status.get(210103, False), + 9024: technology_status.get(210104, False), + # Ranch + 9031: True, + 9032: technology_status.get(420301, False), + 9033: technology_status.get(430301, False), + 9034: technology_status.get(440301, False), + # Cafe + 9041: True, + 9042: technology_status.get(620101, False), + # Koi + 9061: True, + 9062: technology_status.get(510101, False), + # Bear + 9071: technology_status.get(520001, False), + 9072: technology_status.get(520101, False), + # Eatery + 9081: technology_status.get(530001, False), + 9082: technology_status.get(530101, False), + # Grill + 9091: technology_status.get(540001, False), + 9092: technology_status.get(540101, False), + # Orchard + 9101: technology_status.get(330101, False), + 9102: technology_status.get(330102, False), + 9103: technology_status.get(330103, False), + 9104: technology_status.get(330104, False), + # Nursery + 9111: technology_status.get(320101, False), + 9112: technology_status.get(320102, False), + # Lumber + 9201: True, + 9202: technology_status.get(630101, False), + # Machinery + 9203: technology_status.get(640001, False), + 9204: technology_status.get(640101, False), + # Electronic + 9205: technology_status.get(650001, False), + 9206: technology_status.get(650101, False), + # Crafts + 9207: technology_status.get(660001, False), + 9208: technology_status.get(660101, False), + # Fish + 9211: True, + 9212: technology_status.get(460101, False), + 9213: technology_status.get(460102, False) + } + self.recipe_group = {} + self.available_slot_recipes = set() + for slot, slot_data in DIC_ISLAND_SLOT.items(): + if not self.slot_available.get(slot, False): + continue + group = self.SLOT_TO_GROUP.get(slot) + if group is None: + continue + for recipe_id in slot_data.get('formula', []): + self.available_slot_recipes.add(recipe_id) + self.recipe_group.setdefault(recipe_id, group) + for recipe_id in slot_data.get('activity_formula', []): + self.available_slot_recipes.add(recipe_id) + self.recipe_group.setdefault(recipe_id, group) + self.recipe_available.update({ + # Farm + 101002: technology_status.get(500212, False), # 玉米 <- 玉米种植技术 + 101003: technology_status.get(310201, False), # 牧草 <- 牧草种植技术 + 101004: technology_status.get(500211, False), # 咖啡豆 <- 咖啡树种植技术 + 101005: technology_status.get(310202, False), # 大米 <- 旱稻种植技术 + 101006: technology_status.get(500215, False), # 白菜 <- 白菜种植技术 + 101007: technology_status.get(500214, False), # 土豆 <- 土豆种植技术 + 101008: technology_status.get(500213, False), # 大豆 <- 大豆种植技术 + # Mining + 401004: technology_status.get(220201, False), # 铝矿 <- 铝矿勘探技术 + 401005: technology_status.get(220202, False), # 铁矿 <- 铁矿勘探技术 + 401006: technology_status.get(220203, False), # 硫矿 <- 硫矿勘探技术 + 401007: technology_status.get(220204, False), # 银矿 <- 银矿勘探技术 + # Woods + 402002: technology_status.get(210201, False), # 实用之木 <- 实用之木生产技术 + 402003: technology_status.get(210202, False), # 精选之木 <- 精选之木生产技术 + 402004: technology_status.get(210203, False), # 典雅之木 <- 典雅之木生产技术 + # Orchard + 501001: technology_status.get(500231, False), # 苹果 <- 苹果树种植技术 + 501002: technology_status.get(500232, False), # 柑橘 <- 柑橘树种植技术 + 501003: technology_status.get(500233, False), # 香蕉 <- 香蕉树种植技术 + 501004: technology_status.get(500234, False), # 芒果 <- 芒果树种植技术 + 501005: technology_status.get(500235, False), # 柠檬 <- 柠檬树种植技术 + 501006: technology_status.get(500236, False), # 牛油果 <- 牛油果树种植技术 + 501007: technology_status.get(330201, False), # 橡胶 <- 橡胶树种植技术 + # Nursery + 502002: technology_status.get(320201, False), # 草莓 <- 草莓种植技术 + 502003: technology_status.get(320202, False), # 棉花 <- 棉花种植技术 + 502004: technology_status.get(320203, False), # 茶叶 <- 茶树种植技术 + 502005: technology_status.get(320205, False), # 薰衣草 <- 薰衣草种植技术 + 502006: technology_status.get(320204, False), # 胡萝卜 <- 胡萝卜种植技术 + 502007: technology_status.get(320206, False), # 洋葱 <- 洋葱种植技术 + # Food + 601002: technology_status.get(510201, False), # 肉末烧豆腐 + 601003: technology_status.get(510202, False), # 蛋包饭 + 601004: technology_status.get(510203, False), # 白菜豆腐汤 + 601005: technology_status.get(510204, False), # 蔬菜沙拉 + 601006: technology_status.get(460201, False), # 炸鱼薯条 + 601007: technology_status.get(460202, False), # 洋葱蒸鱼 + 601008: technology_status.get(460206, False), # 佛跳墙 + 602002: technology_status.get(520201, False), # 香蕉芒果汁 + 602003: technology_status.get(520202, False), # 蜂蜜柠檬水 + 602004: technology_status.get(520205, False), # 草莓蜜沁 + 602005: technology_status.get(520204, False), # 薰衣草茶 + 602006: technology_status.get(520203, False), # 草莓蜂蜜冰沙 + 603002: technology_status.get(530205, False), # 苹果派 + 603003: technology_status.get(530206, False), # 香橙派 + 603004: technology_status.get(530202, False), # 芒果糯米饭 + 603005: technology_status.get(530203, False), # 香蕉可丽饼 + 603006: technology_status.get(530204, False), # 草莓夏洛特 + 603007: technology_status.get(460205, False), # 海鲜饭 + 604002: technology_status.get(540201, False), # 禽肉土豆拼盘 + 604004: technology_status.get(540202, False), # 爆炒禽肉 + 604005: technology_status.get(540204, False), # 胡萝卜厚蛋烧 + 604006: technology_status.get(540205, False), # 汉堡肉饭 + 604007: technology_status.get(460203, False), # 柠檬虾 + 604008: technology_status.get(460204, False), # 爆炒小龙虾 + 901003: technology_status.get(550201, False), # 芝士 + 901004: technology_status.get(550202, False), # 拿铁 + 901005: technology_status.get(550203, False), # 柑橘咖啡 + 901006: technology_status.get(550204, False), # 草莓奶绿 + # Food combination (all uses 500001) + 601101: technology_status.get(500001, False), + 601102: technology_status.get(500001, False), + 602101: technology_status.get(500001, False), + 602102: technology_status.get(500001, False), + 602103: technology_status.get(500001, False), + 603101: technology_status.get(500001, False), + 603102: technology_status.get(500001, False), + 603103: technology_status.get(500001, False), + 604101: technology_status.get(500001, False), + 604102: technology_status.get(500001, False), + 901101: technology_status.get(500001, False), + 901102: technology_status.get(500001, False), + 901103: technology_status.get(500001, False), + # Manufacturing + 701002: technology_status.get(660201, False), # 皮革 + 701003: technology_status.get(660202, False), # 绳索 + 701004: technology_status.get(660203, False), # 手套 + 701005: technology_status.get(660204, False), # 香囊 + 701006: technology_status.get(660205, False), # 鞋靴 + 701007: technology_status.get(660206, False), # 绷带 + 701009: technology_status.get(640202, False), # 电缆 + 701010: technology_status.get(640201, False), # 铁钉 + 701011: technology_status.get(640203, False), # 硫酸 + 701012: technology_status.get(640204, False), # 火药 + 701013: technology_status.get(640205, False), # 刀叉餐具 + 701015: technology_status.get(630201, False), # 记事本 + 701016: technology_status.get(630202, False), # 桌椅 + 701017: technology_status.get(630203, False), # 精选木桶 + 701018: technology_status.get(630204, False), # 文件柜 + 701020: technology_status.get(650201, False), # 钟表 + 701021: technology_status.get(650202, False), # 蓄电池 + 701022: technology_status.get(650203, False), # 净水滤芯 + 701023: technology_status.get(630205, False), # 装饰画 + }) + for recipe_id in DIC_ISLAND_RECIPE: + if recipe_id < 9900000: + self.recipe_available.setdefault(recipe_id, True) + else: + self.recipe_available.setdefault(recipe_id, False) + for recipe_id in self.available_slot_recipes: + if recipe_id >= 9900000: + self.recipe_available[recipe_id] = True + + self.mining_additional = technology_status.get(220401, False) + self.wood_additional = technology_status.get(210401, False) + self.ranch_additional = technology_status.get(400001, False) + self.ranch_level = { + 9031: count_level(get_sub_dict(technology_status, [410301, 410302, 410303, 410304, 410305])), + 9032: count_level(get_sub_dict(technology_status, [420302, 420303, 420304])), + 9033: count_level(get_sub_dict(technology_status, [430302, 430303, 430304])), + 9034: count_level(get_sub_dict(technology_status, [440302, 440303, 440304])) + } + self.wild_gather_available.update({ + 5: technology_status.get(450301, False), + 6: technology_status.get(450302, False), + }) + for id, item in DIC_ISLAND_WILD_GATHER.items(): + if id < 10: + self.wild_gather_available.setdefault(id, True) + else: + self.wild_gather_available.setdefault(id, False) + + self.place_efficiency_bonus = { + 101: self.config.cross_get("IslandProductionPlanner.IslandProductionPlanner.FieldsEfficiency"), + 401: 0.05 if technology_status.get(220601, False) else 0, + 402: 0.05 if technology_status.get(210601, False) else 0, + 501: self.config.cross_get("IslandProductionPlanner.IslandProductionPlanner.OrchardEfficiency"), + 502: self.config.cross_get("IslandProductionPlanner.IslandProductionPlanner.NurseryEfficiency"), + } + + @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}") + + def has_waitress(self, config_key, waitress_name): + value = self.config.cross_get(config_key) + if not isinstance(value, str): + return False + return waitress_name in value.split('+') + + @cached_property + def restaurant_capacity(self): + capacity = { + 601: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.KoiGrade")), + 602: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.BearGrade")), + 603: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.EateryGrade")), + 604: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.GrillGrade")), + 901: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.CafeGrade")), + } + if self.has_waitress("IslandBusiness.IslandRestaurant.KoiWaitress", 'Chao_Ho'): + capacity[601] += 1 + if self.has_waitress("IslandBusiness.IslandRestaurant.BearWaitress", 'Cheshire'): + capacity[602] += 1 + if self.has_waitress("IslandBusiness.IslandRestaurant.EateryWaitress", 'Helena'): + capacity[603] += 1 + if self.has_waitress("IslandBusiness.IslandRestaurant.GrillWaitress", 'August_von_Parseval'): + capacity[604] += 1 + if self.has_waitress("IslandBusiness.IslandRestaurant.CafeWaitress", 'Cheshire'): + capacity[901] += 1 + 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 = { + 601: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.KoiGrade")), + 602: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.BearGrade")), + 603: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.EateryGrade")), + 604: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.GrillGrade")), + 901: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.CafeGrade")), + } + return quantity + + @cached_property + def restaurant_sales_bonus(self): + bonus = {key: 0 for key in [601, 602, 603, 604, 901]} + if self.has_waitress("IslandBusiness.IslandRestaurant.KoiWaitress", 'Chao_Ho'): + bonus[601] += 0.1 + if self.has_waitress("IslandBusiness.IslandRestaurant.BearWaitress", 'Cheshire'): + bonus[602] += 0.05 + if self.has_waitress("IslandBusiness.IslandRestaurant.EateryWaitress", 'Helena'): + bonus[603] += 0.1 + if self.has_waitress("IslandBusiness.IslandRestaurant.EateryWaitress", 'Prinz_Eugen'): + bonus[603] += 0.1 + if self.has_waitress("IslandBusiness.IslandRestaurant.GrillWaitress", 'Prinz_Eugen'): + bonus[604] += 0.1 + if self.has_waitress("IslandBusiness.IslandRestaurant.GrillWaitress", 'August_von_Parseval'): + bonus[604] += 0.1 + if self.has_waitress("IslandBusiness.IslandRestaurant.CafeWaitress", 'Cheshire'): + bonus[901] += 0.05 + return bonus + + def _reset_lp_result(self): + self.lp_status = None + self.lp_success = False + self.lp_message = '' + self._clear_solution_state() + + def _clear_solution_state(self): + self.production_plan = {} + self.shop_plan = {} + self.exchange_plan = {} + self.sell_plan = {} + self.net_items = {} + self.net_idle_accumulating_items = {} + self.product_daily_buffer_items = {} + self.idle_accumulating_items_per_day = {} + self.inventory_levels_yaml_text = '' + self.idle_accumulating_items_yaml_text = '' + self.hard_floor_items_yaml_text = '' + self.reserve_items_yaml_text = '' + self.request_buffer_items_yaml_text = '' + self.group_usage_summary = {} + self.total_pt = 0 + self.daily_profit = 0 + self.daily_coin_cost = 0 + self.daily_coin_revenue = 0 + self.wild_gather_plan = {} + self.mining_supply_plan = {} + self.logging_supply_plan = {} + self.demand_items = {} + self.hard_floor_items = {} + self.reserve_items = {} + self.request_buffer_items = {} + self.task_target_items = {} + self.stuck_season_order_id = 0 + self.stuck_season_order_items = {} + + @staticmethod + def _format_amount(amount): + rounded = round(amount) + if abs(amount - rounded) <= 1e-6: + return str(int(rounded)) + return f'{amount:.3f}'.rstrip('0').rstrip('.') + + @staticmethod + def _round_output_amount(amount): + return float(round(float(amount) + 1e-10, 3)) + + @staticmethod + def _round_up_int(amount): + return ceil_with_epsilon(amount) + + def _get_natural_idle_accumulating_amount(self, item_id, amount): + if DIC_ISLAND_ITEM[item_id].get('pt_num', 0) <= 0: + return 0 + reserved_rate = self.demand_items.get(item_id, {}).get('rate_per_day', 0) + idle_accumulating_amount = amount - reserved_rate + if idle_accumulating_amount <= self.NET_ACCUMULATING_EPSILON: + return 0 + return idle_accumulating_amount + + def _redirect_exchange_idle_accumulating_items(self): + if not self.EXCHANGE_REQUIRES_MANUAL_OPERATION or not self.exchange_plan: + return + + exchange_inputs = defaultdict(float) + exchange_outputs = defaultdict(float) + for exchange_id, amount in self.exchange_plan.items(): + recipe = DIC_ISLAND_EXCHANGE_RECIPE[exchange_id] + for item_id, input_amount in recipe['resource_consume'].items(): + exchange_inputs[item_id] += input_amount * amount + for item_id, output_amount in recipe['items'].items(): + exchange_outputs[item_id] += output_amount * amount + + for item_id, amount in exchange_outputs.items(): + current = self.net_idle_accumulating_items.get(item_id, 0) + if current <= self.NET_ACCUMULATING_EPSILON: + continue + remaining = current - min(current, amount) + if remaining > self.NET_ACCUMULATING_EPSILON: + self.net_idle_accumulating_items[item_id] = remaining + else: + self.net_idle_accumulating_items.pop(item_id, None) + + for item_id, amount in exchange_inputs.items(): + if amount <= self.NET_ACCUMULATING_EPSILON: + continue + self.net_idle_accumulating_items[item_id] = self.net_idle_accumulating_items.get(item_id, 0) + amount + + def _item_name(self, item_id): + return item_name(item_id) + + def _recipe_name(self, recipe_id): + return DIC_ISLAND_RECIPE[recipe_id]['name'][server.server] + + def _shop_name(self, shop_id): + return DIC_ISLAND_SHOP_RECIPE[shop_id]['name'][server.server] + + def _exchange_name(self, exchange_id): + recipe = DIC_ISLAND_EXCHANGE_RECIPE[exchange_id] + output_id = next(iter(recipe['items'])) + return self._item_name(output_id) + + def _slot_group_name(self, group): + place_id = self.GROUP_TO_PLACE.get(group) + if place_id is None: + return group + name = DIC_ISLAND_PRODUCTION_PLACE[place_id]['name'][server.server] + slots = self.GROUP_TO_SLOTS.get(group, []) + if slots: + slot_text = ','.join(str(slot) for slot in slots) + return f'{name} ({slot_text})' + return name + + def _slot_group_sort_key(self, key): + if isinstance(key, int): + return key + slots = self.GROUP_TO_SLOTS.get(key, []) + if slots: + return min(slots) + return 999999 + + def _build_production_problem(self, demand_items=None): + if demand_items is None: + demand_items = {} + daily_workload = 24 * 60 * 60 * 10 + + group_slots = defaultdict(list) + for slot, available in self.slot_available.items(): + group = self.SLOT_TO_GROUP.get(slot) + if available and group is not None: + group_slots[group].append(slot) + group_capacity = { + group: len(slots) * daily_workload if not group.startswith('ranch_') else daily_workload + for group, slots in group_slots.items() + } + group_efficiency = { + 'field': 1 + self.place_efficiency_bonus.get(101, 0), + 'mine': 1 + self.place_efficiency_bonus.get(401, 0), + 'wood': 1 + self.place_efficiency_bonus.get(402, 0), + 'orchard': 1 + self.place_efficiency_bonus.get(501, 0), + 'nursery': 1 + self.place_efficiency_bonus.get(502, 0), + 'ranch_chicken': 1, + 'ranch_pig': 1, + 'ranch_cow': 1, + 'ranch_sheep': 1, + 'cafe': 1, + 'koi': 1, + 'bear': 1, + 'eatery': 1, + 'grill': 1, + 'manufacturing_lumber': 1, + 'manufacturing_machinery': 1, + 'manufacturing_electronic': 1, + 'manufacturing_crafts': 1, + 'fishery': 1, + } + + recipe_group = getattr(self, 'recipe_group', {}) + activities = [] + + for recipe_id, recipe in DIC_ISLAND_RECIPE.items(): + if not self.recipe_available.get(recipe_id, False): + continue + group = recipe_group.get(recipe_id) + if group is None or group not in group_capacity: + continue + inputs = dict(recipe['commission_cost']) + outputs = dict(recipe['commission_product']) + if group.startswith('ranch_'): + multiplier = 1 + { + 'ranch_chicken': self.ranch_level[9031], + 'ranch_pig': self.ranch_level[9032], + 'ranch_cow': self.ranch_level[9033], + 'ranch_sheep': self.ranch_level[9034], + }[group] + inputs = {item_id: amount * multiplier for item_id, amount in inputs.items()} + outputs = {item_id: amount * multiplier for item_id, amount in outputs.items()} + if self.ranch_additional: + for item_id, amount in recipe['second_product_display'].items(): + outputs[item_id] = outputs.get(item_id, 0) + amount * multiplier + activities.append({ + 'kind': 'recipe', + 'id': recipe_id, + 'group': group, + 'workload': recipe['workload'] / group_efficiency[group], + 'inputs': inputs, + 'outputs': outputs, + }) + + for shop_id, recipe in DIC_ISLAND_SHOP_RECIPE.items(): + activities.append({ + 'kind': 'shop', + 'id': shop_id, + 'group': None, + 'workload': 0, + 'inputs': dict(recipe['resource_consume']), + 'outputs': dict(recipe['items']), + }) + for exchange_id, recipe in DIC_ISLAND_EXCHANGE_RECIPE.items(): + activities.append({ + 'kind': 'exchange', + 'id': exchange_id, + 'group': None, + 'workload': 0, + 'inputs': dict(recipe['resource_consume']), + 'outputs': dict(recipe['items']), + }) + + initial_supply = defaultdict(float) + wild_gather_plan = {} + for gather_id, gather in DIC_ISLAND_WILD_GATHER.items(): + if self.wild_gather_available.get(gather_id, False): + wild_gather_plan[gather_id] = dict(gather['product']) + for item_id, amount in gather['product'].items(): + initial_supply[item_id] += amount + mining_multiplier = 2 if self.mining_additional else 1 + mining_supply_plan = defaultdict(float) + for product in DIC_ISLAND_PRODUCTION_MINING.values(): + for item_id, amount in product.items(): + mining_supply_plan[item_id] += amount * mining_multiplier + initial_supply[item_id] += amount * mining_multiplier + wood_multiplier = 2 if self.wood_additional else 1 + logging_supply_plan = defaultdict(float) + for product in DIC_ISLAND_PRODUCTION_LOGGING.values(): + for item_id, amount in product.items(): + logging_supply_plan[item_id] += amount * wood_multiplier + initial_supply[item_id] += amount * wood_multiplier + + sell_slots = { + 601: DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE[601], + 602: DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE[602], + 603: DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE[603], + 604: DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE[604], + 901: DIC_ISLAND_RESTAURANT_MENU_TO_RECIPE[901], + } + sale_entries = [] + for slot, menu in sell_slots.items(): + for item_id in menu: + sale_entries.append((slot, item_id)) + + item_ids = {1} + for activity in activities: + item_ids.update(activity['inputs']) + item_ids.update(activity['outputs']) + item_ids.update(initial_supply) + item_ids.update(demand_items) + item_ids.update(item_id for _, item_id in sale_entries) + item_ids = sorted(item_ids) + + activity_count = len(activities) + sale_count = len(sale_entries) + item_count = len(item_ids) + total_vars = activity_count + sale_count + item_count + item_index = {item_id: idx for idx, item_id in enumerate(item_ids)} + end_offset = activity_count + sale_count + + c = np.zeros(total_vars) + for item_id, idx in item_index.items(): + c[end_offset + idx] = -DIC_ISLAND_ITEM.get(item_id, {}).get('pt_num', 0) + + a_eq = np.zeros((item_count, total_vars)) + b_eq = np.zeros(item_count) + for col, activity in enumerate(activities): + for item_id, amount in activity['outputs'].items(): + a_eq[item_index[item_id], col] += amount + for item_id, amount in activity['inputs'].items(): + a_eq[item_index[item_id], col] -= amount + for sale_col, (slot, item_id) in enumerate(sale_entries, start=activity_count): + a_eq[item_index[item_id], sale_col] -= 1 + revenue = DIC_ISLAND_ITEM[item_id]['order_price'] * (1 + self.restaurant_sales_bonus[slot]) + a_eq[item_index[1], sale_col] += revenue + for item_id, idx in item_index.items(): + a_eq[idx, end_offset + idx] -= 1 + b_eq[idx] = -initial_supply.get(item_id, 0) + + a_ub = [] + b_ub = [] + for group, capacity in group_capacity.items(): + row = np.zeros(total_vars) + for col, activity in enumerate(activities): + if activity['group'] == group and activity['workload'] > 0: + row[col] = activity['workload'] + if row.any(): + a_ub.append(row) + b_ub.append(capacity) + + for slot, menu in sell_slots.items(): + slot_sales = [idx for idx, entry in enumerate(sale_entries) if entry[0] == slot] + if slot_sales: + row = np.zeros(total_vars) + for idx in slot_sales: + row[activity_count + idx] = 1 + a_ub.append(row) + b_ub.append(self.restaurant_quantity[slot] * self.restaurant_capacity[slot]) + for idx in slot_sales: + cap_row = np.zeros(total_vars) + cap_row[activity_count + idx] = 1 + a_ub.append(cap_row) + b_ub.append(self.restaurant_capacity[slot]) + + profit_row = np.zeros(total_vars) + profit_row[end_offset + item_index[1]] = -1 + a_ub.append(profit_row) + b_ub.append(-self.daily_profit_lower_limit) + + for item_id, demand_data in demand_items.items(): + demand_row = np.zeros(total_vars) + demand_row[end_offset + item_index[item_id]] = -1 + a_ub.append(demand_row) + b_ub.append(-demand_data['rate_per_day']) + + return { + 'daily_workload': daily_workload, + 'group_slots': group_slots, + 'activities': activities, + 'sale_entries': sale_entries, + 'item_index': item_index, + 'end_offset': end_offset, + 'wild_gather_plan': wild_gather_plan, + 'mining_supply_plan': dict(mining_supply_plan), + 'logging_supply_plan': dict(logging_supply_plan), + 'demand_items': demand_items, + 'hard_floor_items': getattr(self, 'hard_floor_items', {}), + 'reserve_items': getattr(self, 'reserve_items', {}), + 'request_buffer_items': getattr(self, 'request_buffer_items', {}), + 'task_target_items': getattr(self, 'task_target_items', {}), + 'stuck_season_order_id': getattr(self, 'stuck_season_order_id', 0), + 'stuck_season_order_items': getattr(self, 'stuck_season_order_items', {}), + 'c': c, + 'A_ub': np.array(a_ub) if a_ub else None, + 'b_ub': np.array(b_ub) if b_ub else None, + 'A_eq': a_eq, + 'b_eq': b_eq, + 'bounds': [(0, None)] * total_vars, + } + + def _apply_production_lp_result(self, result, problem): + self.lp_status = result.status + self.lp_success = result.success + self.lp_message = result.message + self._clear_solution_state() + self.wild_gather_plan = problem['wild_gather_plan'] + self.mining_supply_plan = problem['mining_supply_plan'] + self.logging_supply_plan = problem['logging_supply_plan'] + self.demand_items = problem['demand_items'] + self.hard_floor_items = problem['hard_floor_items'] + self.reserve_items = problem['reserve_items'] + self.request_buffer_items = problem['request_buffer_items'] + self.task_target_items = problem['task_target_items'] + self.stuck_season_order_id = problem['stuck_season_order_id'] + self.stuck_season_order_items = problem['stuck_season_order_items'] + if not result.success: + return + + activities = problem['activities'] + sale_entries = problem['sale_entries'] + item_index = problem['item_index'] + end_offset = problem['end_offset'] + group_slots = problem['group_slots'] + daily_workload = problem['daily_workload'] + activity_count = len(activities) + solution = result.x + + for col, activity in enumerate(activities): + amount = solution[col] + if amount <= 1e-6: + continue + target = { + 'recipe': self.production_plan, + 'shop': self.shop_plan, + 'exchange': self.exchange_plan, + }[activity['kind']] + target[activity['id']] = amount + + for idx, (slot, item_id) in enumerate(sale_entries, start=activity_count): + amount = solution[idx] + if amount > 1e-6: + self.sell_plan[(slot, item_id)] = amount + self.daily_coin_revenue += DIC_ISLAND_ITEM[item_id]['order_price'] * (1 + self.restaurant_sales_bonus[slot]) * amount + + for item_id, idx in item_index.items(): + amount = solution[end_offset + idx] + if amount > 1e-6: + self.net_items[item_id] = amount + idle_accumulating_amount = self._get_natural_idle_accumulating_amount(item_id, amount) + if idle_accumulating_amount > 0: + self.net_idle_accumulating_items[item_id] = idle_accumulating_amount + self._redirect_exchange_idle_accumulating_items() + self.total_pt = sum( + DIC_ISLAND_ITEM[item_id].get('pt_num', 0) * amount + for item_id, amount in self.net_idle_accumulating_items.items() + ) + self.daily_profit = self.net_items.get(1, 0) + for col, activity in enumerate(activities): + amount = solution[col] + if amount <= 1e-6: + continue + coin_cost = activity['inputs'].get(1, 0) + if coin_cost > 0: + self.daily_coin_cost += coin_cost * amount + self.product_daily_buffer_items = self._calculate_product_daily_buffer_items( + solution=solution, + activities=activities, + sale_entries=sale_entries, + activity_count=activity_count, + ) + self.idle_accumulating_items_per_day = { + item_id: self._round_output_amount(amount) + for item_id, amount in sorted(self.net_idle_accumulating_items.items()) + if self._round_output_amount(amount) > 0 + } + + grouped_recipe_plan = defaultdict(list) + for activity in activities: + if activity['kind'] == 'recipe': + amount = self.production_plan.get(activity['id'], 0) + if amount > 1e-6: + grouped_recipe_plan[activity['group']].append((activity['id'], amount, activity['workload'])) + for group, entries in grouped_recipe_plan.items(): + total_workload = sum(amount * workload for _, amount, workload in entries) + slot_count = len(group_slots.get(group, [])) + if slot_count <= 0: + continue + self.group_usage_summary[group] = { + 'total_workload': total_workload, + 'slot_count': slot_count, + 'hours_per_slot': total_workload / 36000 / slot_count, + 'total_hours': total_workload / 36000, + 'recipes': { + recipe_id: { + 'batches': amount, + 'hours_total': amount * workload / 36000, + } + for recipe_id, amount, workload in entries + } + } + + def _calculate_product_daily_buffer_items(self, solution, activities, sale_entries, activity_count): + daily_product_demand = defaultdict(float) + + for col, activity in enumerate(activities): + amount = solution[col] + if amount <= self.NET_ACCUMULATING_EPSILON: + continue + for item_id, input_amount in activity['inputs'].items(): + if item_id in self.RECIPE_PRODUCT_IDS or ( + self.EXCHANGE_REQUIRES_MANUAL_OPERATION and item_id in self.EXCHANGE_PRODUCT_IDS + ): + daily_product_demand[item_id] += input_amount * amount + + for idx, (slot, item_id) in enumerate(sale_entries, start=activity_count): + amount = solution[idx] + if amount <= self.NET_ACCUMULATING_EPSILON: + continue + if item_id in self.RECIPE_PRODUCT_IDS: + daily_product_demand[item_id] += amount + + daily_buffer_items = {} + for item_id, amount in sorted(daily_product_demand.items()): + if amount <= self.NET_ACCUMULATING_EPSILON: + continue + daily_buffer = ceil_with_epsilon(amount * (1 + self.daily_buffer_safety_margin)) + daily_buffer_items[item_id] = daily_buffer + return daily_buffer_items + + def format_solved_production_plan(self): + lines = [ + f'LP success: {self.lp_success}', + f'LP status: {self.lp_status}', + f'LP message: {self.lp_message}', + f'Total PT: {self._format_amount(self.total_pt)}', + f'Daily coin revenue: {self._format_amount(self.daily_coin_revenue)}', + f'Daily coin cost: {self._format_amount(self.daily_coin_cost)}', + f'Daily profit: {self._format_amount(self.daily_profit)}', + '', + '[production]', + ] + if self.production_plan: + for recipe_id, amount in sorted(self.production_plan.items()): + lines.append(f'{self._recipe_name(recipe_id)} ({recipe_id}): {self._format_amount(amount)} batches') + else: + lines.append('-') + + lines.append('') + lines.append('[wild_gather/mining/logging]') + has_passive = False + for gather_id, product in sorted(self.wild_gather_plan.items()): + has_passive = True + product_text = ', '.join( + f'{self._item_name(item_id)}({item_id}) x{self._format_amount(amount)}' + for item_id, amount in product.items() + ) + lines.append(f'wild_gather {gather_id}: {product_text}') + if self.mining_supply_plan: + has_passive = True + product_text = ', '.join( + f'{self._item_name(item_id)}({item_id}) x{self._format_amount(amount)}' + for item_id, amount in sorted(self.mining_supply_plan.items()) + ) + lines.append(f'mining: {product_text}') + if self.logging_supply_plan: + has_passive = True + product_text = ', '.join( + f'{self._item_name(item_id)}({item_id}) x{self._format_amount(amount)}' + for item_id, amount in sorted(self.logging_supply_plan.items()) + ) + lines.append(f'logging: {product_text}') + if not has_passive: + lines.append('-') + + lines.append('') + lines.append('[exchange/buy]') + has_trade = False + for shop_id, amount in sorted(self.shop_plan.items()): + has_trade = True + lines.append(f'buy {self._shop_name(shop_id)} ({shop_id}): {self._format_amount(amount)}') + for exchange_id, amount in sorted(self.exchange_plan.items()): + has_trade = True + lines.append(f'exchange {self._exchange_name(exchange_id)} ({exchange_id}): {self._format_amount(amount)}') + if not has_trade: + lines.append('-') + + lines.append('') + lines.append('[sell]') + if self.sell_plan: + for (slot, item_id), amount in sorted(self.sell_plan.items()): + lines.append( + f'slot {slot} {self._item_name(item_id)} ({item_id}): {self._format_amount(amount)}' + ) + else: + lines.append('-') + + lines.append('') + lines.append('[slot_usage_by_type]') + if self.group_usage_summary: + for group, data in sorted(self.group_usage_summary.items(), key=lambda item: self._slot_group_sort_key(item[0])): + lines.append( + f'{self._slot_group_name(group)}: {self._format_amount(data["total_hours"])}h total, ' + f'{self._format_amount(data["hours_per_slot"])}h/24h per slot x{data["slot_count"]}' + ) + for recipe_id, recipe_data in sorted(data['recipes'].items()): + lines.append( + f' {self._recipe_name(recipe_id)} ({recipe_id}): ' + f'{self._format_amount(recipe_data["batches"])} batches, ' + f'{self._format_amount(recipe_data["hours_total"])}h total' + ) + else: + lines.append('-') + + lines.append('') + lines.append('[demand_items]') + if self.demand_items: + for item_id, data in sorted(self.demand_items.items()): + need_text = format_item_need_data(data, self._format_amount) + lines.append( + f'{self._item_name(item_id)} ({item_id}): ' + f'{need_text}, ' + f'{self._format_amount(data["rate_per_day"])} per day' + ) + else: + lines.append('-') + + lines.append('') + lines.append('[stuck_season_order_items]') + if self.stuck_season_order_items: + lines.append(f'order id: {self.stuck_season_order_id}') + for item_id, data in sorted(self.stuck_season_order_items.items()): + need_text = format_item_need_data(data, self._format_amount) + lines.append( + f'{self._item_name(item_id)} ({item_id}): ' + f'{need_text}, ' + f'{self._format_amount(data["rate_per_day"])} per day' + ) + else: + lines.append('-') + + lines.append('') + lines.append('[hard_floor_items]') + hard_floor_items = self._get_hard_floor_export_items() + if hard_floor_items: + for item_id, amount in sorted(hard_floor_items.items()): + lines.append(f'{self._item_name(item_id)} ({item_id}): {self._format_amount(amount)}') + else: + lines.append('-') + + lines.append('') + lines.append('[reserve_items]') + if self.reserve_items: + for item_id, amount in sorted(self.reserve_items.items()): + lines.append(f'{self._item_name(item_id)} ({item_id}): {self._format_amount(amount)}') + else: + lines.append('-') + + lines.append('') + lines.append('[daily_buffer_items]') + if self.product_daily_buffer_items: + for item_id, amount in sorted(self.product_daily_buffer_items.items()): + lines.append(f'{self._item_name(item_id)} ({item_id}): {self._format_amount(amount)}') + else: + lines.append('-') + + lines.append('') + lines.append('[net_idle_accumulating_items]') + if self.idle_accumulating_items_per_day: + for item_id, amount in sorted(self.idle_accumulating_items_per_day.items()): + lines.append(f'{self._item_name(item_id)} ({item_id}): {self._format_amount(amount)}') + else: + lines.append('-') + return '\n'.join(lines) + + def print_solved_production_plan(self): + for line in self.format_solved_production_plan().split('\n'): + logger.info(line) + + def _get_daily_buffer_export_entry(self, item_id): + return self.product_daily_buffer_items.get(item_id, 0) + + def daily_buffer_items_to_yaml(self, use_item_name=False): + item_ids = sorted(self.product_daily_buffer_items) + return item_mapping_to_yaml( + { + item_id: self._get_daily_buffer_export_entry(item_id) + for item_id in item_ids + }, + use_item_name=use_item_name, + ) + + def idle_accumulating_items_to_yaml(self, use_item_name=False): + return item_mapping_to_yaml(self.idle_accumulating_items_per_day, use_item_name=use_item_name) + + def _get_hard_floor_export_items(self): + hard_floor_items = dict(self.hard_floor_items) + for (slot, item_id), amount in self.sell_plan.items(): + if amount <= self.NET_ACCUMULATING_EPSILON: + continue + hard_floor_items[item_id] = max( + hard_floor_items.get(item_id, 0), + self.restaurant_capacity[slot], + ) + return hard_floor_items + + def hard_floor_items_to_yaml(self, use_item_name=False): + return item_mapping_to_yaml(self._get_hard_floor_export_items(), use_item_name=use_item_name) + + def reserve_items_to_yaml(self, use_item_name=False): + return item_mapping_to_yaml(self.reserve_items, use_item_name=use_item_name) + + def request_buffer_items_to_yaml(self, use_item_name=False): + return item_mapping_to_yaml(self.request_buffer_items, use_item_name=use_item_name) + + def restaurant_menus_to_yaml(self): + menus = {slot: {} for slot in self.RESTAURANT_MENU_CONFIG} + for (slot, item_id), amount in sorted(self.sell_plan.items()): + if amount <= self.NET_ACCUMULATING_EPSILON: + continue + menus[slot][item_id] = self._round_output_amount(amount) + return { + slot: item_mapping_to_yaml(menu, use_item_name=True) + for slot, menu in menus.items() + } + + def solve_production_plan( + self, + hard_floor_items=None, + reserve_items=None, + request_buffer_items=None, + task_target_items=None, + stuck_season_order_id=None, + ): + self.daily_profit_lower_limit = self.config.cross_get("IslandProductionPlanner.IslandProductionPlanner.DailyProfitLowerLimit", 0) + self.daily_buffer_safety_margin = max( + float(self.config.cross_get("IslandProductionPlanner.IslandProductionPlanner.DailyBufferSafetyMargin", 0)), + 0, + ) + if hard_floor_items is None: + hard_floor_items = load_hard_floor_items( + self.config.cross_get("IslandProduction.IslandProduction.HardFloorItems", "") + ) + if reserve_items is None: + reserve_items = load_reserve_items( + self.config.cross_get("IslandProduction.IslandProduction.ReserveItems", "") + ) + if request_buffer_items is None: + request_buffer_items = load_request_buffer_items( + self.config.cross_get("IslandProduction.IslandProduction.RequestBufferItems", "") + ) + if task_target_items is None: + task_target_items = load_item_mapping( + self.config.cross_get("IslandSeasonTask.IslandSeasonTask.TaskTarget", "{}"), + config_name='TaskTarget', + ) + if stuck_season_order_id is None: + stuck_season_order_id = self.config.cross_get("IslandOrder.IslandOrder.StuckSeasonOrderId", 0) + stuck_season_order_items = get_stuck_season_order_requirements(stuck_season_order_id) + self.hard_floor_items = normalize_item_keys(hard_floor_items) + self.reserve_items = normalize_item_keys(reserve_items) + self.request_buffer_items = normalize_item_keys(request_buffer_items) + self.task_target_items = normalize_item_needs(task_target_items, default_period=10) + self.stuck_season_order_id = normalize_stuck_season_order_id(stuck_season_order_id) + self.stuck_season_order_items = normalize_item_needs(stuck_season_order_items, default_period=10) + if self.stuck_season_order_items: + logger.info( + f'Adding stuck season order {self.stuck_season_order_id} ' + f'as 10-day production planner demand' + ) + demand_items = merge_item_needs(self.task_target_items, self.stuck_season_order_items) + problem = self._build_production_problem(demand_items=demand_items) + self._reset_lp_result() + result = None + solver_attempts = [ + ('interior-point', {'tol': 1e-9}), + ('revised simplex', {'tol': 1e-9}), + ] + for method, options in solver_attempts: + result = linprog( + problem['c'], + A_ub=problem['A_ub'], + b_ub=problem['b_ub'], + A_eq=problem['A_eq'], + b_eq=problem['b_eq'], + bounds=problem['bounds'], + method=method, + options=options, + ) + if result.success: + break + self._apply_production_lp_result(result, problem) + + def run( + self, + tech_status_yaml=None, + hard_floor_items_yaml=None, + reserve_items_yaml=None, + request_buffer_items_yaml=None, + task_target_items=None, + stuck_season_order_id=None, + export=True, + use_item_name_in_export=True, + ): + if tech_status_yaml is not None: + technology_status = safe_load(tech_status_yaml) if tech_status_yaml else None + else: + technology_status = self.config.cross_get("IslandProductionPlanner.Storage.Storage.IslandTechnologyStatus", None) + if technology_status is None or self.config.cross_get("IslandProductionPlanner.IslandProductionPlanner.RescanIslandTechnology", False): + technology_status = IslandTechnologyScanner(self.config).get_technology_status() + else: + technology_status = {int(k): v for k, v in technology_status.items()} + if hard_floor_items_yaml is None: + hard_floor_items = load_hard_floor_items( + self.config.cross_get("IslandProduction.IslandProduction.HardFloorItems", "") + ) + else: + hard_floor_items = load_hard_floor_items(hard_floor_items_yaml) + if reserve_items_yaml is None: + reserve_items = load_reserve_items( + self.config.cross_get("IslandProduction.IslandProduction.ReserveItems", "") + ) + else: + reserve_items = load_reserve_items(reserve_items_yaml) + if request_buffer_items_yaml is None: + request_buffer_items = load_request_buffer_items( + self.config.cross_get("IslandProduction.IslandProduction.RequestBufferItems", "") + ) + else: + request_buffer_items = load_request_buffer_items(request_buffer_items_yaml) + if task_target_items is None: + task_target_items = load_item_mapping( + self.config.cross_get("IslandSeasonTask.IslandSeasonTask.TaskTarget", "{}"), + config_name='TaskTarget', + ) + self.analyze_technology_status(technology_status) + self.solve_production_plan( + hard_floor_items=hard_floor_items, + reserve_items=reserve_items, + request_buffer_items=request_buffer_items, + task_target_items=task_target_items, + stuck_season_order_id=stuck_season_order_id, + ) + self.print_solved_production_plan() + if export: + inventory_levels_yaml_text = self.daily_buffer_items_to_yaml(use_item_name=use_item_name_in_export) + idle_accumulating_items_yaml_text = self.idle_accumulating_items_to_yaml(use_item_name=use_item_name_in_export) + hard_floor_items_yaml_text = self.hard_floor_items_to_yaml(use_item_name=use_item_name_in_export) + reserve_items_yaml_text = self.reserve_items_to_yaml(use_item_name=use_item_name_in_export) + request_buffer_items_yaml_text = self.request_buffer_items_to_yaml(use_item_name=use_item_name_in_export) + restaurant_menu_yaml_texts = self.restaurant_menus_to_yaml() + self.inventory_levels_yaml_text = inventory_levels_yaml_text + self.idle_accumulating_items_yaml_text = idle_accumulating_items_yaml_text + self.hard_floor_items_yaml_text = hard_floor_items_yaml_text + self.reserve_items_yaml_text = reserve_items_yaml_text + self.request_buffer_items_yaml_text = request_buffer_items_yaml_text + with self.config.multi_set(): + self.config.cross_set("IslandProductionPlanner.Storage.Storage.IslandTechnologyStatus", technology_status) + self.config.cross_set("IslandProduction.IslandProduction.DailyBufferItems", inventory_levels_yaml_text) + self.config.cross_set("IslandProduction.IslandProduction.IdleAccumulatingItems", idle_accumulating_items_yaml_text) + self.config.cross_set("IslandProduction.IslandProduction.HardFloorItems", hard_floor_items_yaml_text) + self.config.cross_set("IslandProduction.IslandProduction.ReserveItems", reserve_items_yaml_text) + self.config.cross_set("IslandProduction.IslandProduction.RequestBufferItems", request_buffer_items_yaml_text) + for slot, config_key in self.RESTAURANT_MENU_CONFIG.items(): + self.config.cross_set(config_key, restaurant_menu_yaml_texts[slot]) + self.config.cross_set("IslandProductionPlanner.IslandProductionPlanner.RescanIslandTechnology", False) diff --git a/module/island_handler/recipe.py b/module/island_handler/recipe.py new file mode 100644 index 0000000000..866d29b5a5 --- /dev/null +++ b/module/island_handler/recipe.py @@ -0,0 +1,713 @@ +from datetime import datetime +import re + +import cv2 +from jellyfish import levenshtein_distance +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_similarity_2d, extract_letters, random_rectangle_vector_opted +from module.exception import GameTooManyClickError +from module.island.data import DIC_ISLAND_ITEM, DIC_ISLAND_RECIPE, DIC_ISLAND_SHOP_ITEM_TO_RECIPE, DIC_ISLAND_SLOT +from module.island_handler.assets import * +from module.island_handler.production_planner import ( + ceil_div_or_ceil, + get_target_stock_load_rate, + load_hard_floor_items, + load_item_mapping, + load_request_buffer_items, + load_reserve_items, + normalize_item_keys, + normalize_item_needs, + parse_item_need_deadlines, +) +from module.island_handler.shop import IslandShop +from module.logger import logger +from module.map_detection.utils import Points +from module.ocr.ocr import Digit, Duration, Ocr +from module.ui.page import page_island, page_island_manage, page_island_shop + + +class IslandProductionRestart(Exception): + pass + + +RECIPE_SIZE = (280, 134) +RECIPE_DELTA = (0, 149) +RECIPE_DETECT_AREA = (181, 55, 460, 668) +RECIPE_DRAG_AREA = (300, 55, 350, 668) +RECIPE_ANCHOR_AREA = (58, 97, 96, 115) +RECIPE_PRODUCT_NAME_AREA = (123, 23, 269, 46) +RECIPE_PRODUCT_STOCK_AREA = (212, 92, 275, 110) +if server.server == 'jp': + lang = 'jp' +elif server.server == 'en': + lang = 'azur_lane' +else: + lang = 'cnocr' +RECIPE_PRODUCT_NAME_OCR = Ocr([], lang=lang, letter=(57, 59, 61), threshold=160, name='product_name_ocr') +RECIPE_PRODUCT_STOCK_OCR = Digit([], lang='cnocr', letter=(57, 59, 61), threshold=160, name='product_stock_ocr') +ISLAND_RECIPE_AMOUNT_OCR = Digit(ISLAND_RECIPE_AMOUNT, letter=(50, 50, 57), name='recipe_amount_ocr') + + +class IslandReversedDigitCounter(Ocr): + def __init__(self, buttons, lang='cnocr', letter=(255, 255, 255), sub_letter=None, threshold=128, sub_threshold=128, alphabet='0123456789/IDSB()+', + name=None): + super().__init__(buttons, lang=lang, letter=letter, threshold=threshold, alphabet=alphabet, name=name) + self.sub_letter = sub_letter + self.sub_threshold = sub_threshold + + def pre_process(self, image, background_color=(80, 80, 80)): + mask = color_similarity_2d(image, background_color) + mask[mask < self.threshold] = 0 + line = cv2.bitwise_and(mask[0], mask[-1]).flatten() + indices = np.where(line > 200)[0] + left = indices[0] if len(indices) > 0 else 0 + right = indices[-1] + 1 if len(indices) > 0 else len(line) + image = image[:, left:right] + + main_image = extract_letters(image, letter=self.letter, threshold=self.threshold) + if self.sub_letter is not None and isinstance(self.sub_letter, tuple): + mask = color_similarity_2d(image, self.sub_letter) + mask[mask < self.sub_threshold] = 0 + if np.count_nonzero(mask) > 50: + sub_image = extract_letters(image, letter=self.sub_letter, threshold=self.sub_threshold) + cv2.bitwise_and(main_image, sub_image, dst=main_image) + + return main_image + + def after_process(self, result): + result = super().after_process(result) + result = result.replace('I', '1').replace('D', '0').replace('S', '5') + result = result.replace('B', '8') + if re.fullmatch(r'\d+/?', result): + normalized = f'{result.rstrip("/")}/0' + logger.warning(f'Unexpected ocr result: {result}, normalized to {normalized}') + result = normalized + result = re.search(r'(\d+)/([\d+\-\*\(\)]+)', result) + if result: + numerator = int(result.group(1)) + denominator_str = result.group(2) + try: + denominator = eval(denominator_str) + current, total = denominator, numerator + return total, current, total - current + except (ValueError, SyntaxError): + logger.warning(f'Unexpected ocr result: {result.group(0)}') + return 0, 0, 0 + else: + logger.warning(f'Unexpected ocr result: {result}') + return 0, 0, 0 + + +RECIPE_INGREDIENT_COUNTER_OCR = IslandReversedDigitCounter( + [], lang='cnocr', letter=(255, 255, 255), sub_letter=(253, 171, 34), + threshold=160, sub_threshold=160, name='ingredient_counter_ocr' +) + + +def get_recipe_product_id(recipe_id): + return next(iter(DIC_ISLAND_RECIPE[recipe_id]['commission_product'])) + + +def get_target_stock_weight(stock, target_stock): + if stock >= target_stock or target_stock <= 0: + return 0 + else: + return (target_stock - stock) / target_stock + + +def get_target_stock_batch_weight(stock, target_stock, batch_size): + if stock >= target_stock: + return 0 + else: + return (target_stock - stock) / batch_size + + +def get_demand_weight(stock, target_stock, batch_size, demand): + demand_deadlines = demand + if len(demand_deadlines) == 2 and isinstance(demand_deadlines[0], (int, float, np.integer, np.floating)): + demand_deadlines = [demand_deadlines] + rate_per_day = get_target_stock_load_rate(stock, target_stock, demand_deadlines) + if rate_per_day <= 0: + return 0 + else: + return rate_per_day / batch_size + + +def get_idle_accumulating_weight(stock, target_stock, demand, idle_accumulating): + if idle_accumulating <= 0: + return float('inf') + else: + if len(demand) == 2 and isinstance(demand[0], (int, float, np.integer, np.floating)): + demand_stock, _ = demand + else: + demand_stock = demand[-1][0] if demand else 0 + effective_stock = max(stock - target_stock - demand_stock, 0) + return effective_stock / idle_accumulating + + +def get_recipe_entry_weight(entry): + _, (stock, target_stock, _hard_floor, _reserve, batch_size, demand, idle_accumulating) = entry + return ( + get_demand_weight(stock, target_stock, batch_size, demand), + get_target_stock_weight(stock, target_stock), + get_target_stock_batch_weight(stock, target_stock, batch_size), + -get_idle_accumulating_weight(stock, target_stock, demand, idle_accumulating), + -stock + ) + + +class IslandRecipe(IslandShop): + working_slot_id = None + + # recipe related methods + def is_in_recipe_menu(self): + return self.appear(ISLAND_RECIPE_CHECK, offset=(20, 20)) + + def _get_recipe_buttons(self): + area = (RECIPE_DETECT_AREA[0] + RECIPE_ANCHOR_AREA[0], + RECIPE_DETECT_AREA[1] + RECIPE_ANCHOR_AREA[1], + RECIPE_DETECT_AREA[2] - RECIPE_SIZE[0] + RECIPE_ANCHOR_AREA[2], + RECIPE_DETECT_AREA[3] - RECIPE_SIZE[1] + RECIPE_ANCHOR_AREA[3]) + image = self.image_crop(area, copy=True) + anchors = TEMPLATE_ISLAND_RECIPE_ANCHOR.match_multi(image, similarity=0.5, threshold=5) + logger.attr('Recipe_in_view', len(anchors)) + rows = Points([(0., a.area[1]) for a in anchors]).group(threshold=5) + return rows + + @cached_property + def recipe_grid(self): + for _ in self.loop(timeout=2): + grid = self.get_recipe_grid() + if len(grid.buttons) >= 3: + return grid + return grid + + def get_recipe_grid(self): + rows = self._get_recipe_buttons() + count = len(rows) + delta_y = RECIPE_DELTA[1] + if count > 4: + logger.warning(f'Found {count} recipe anchors, which is more than expected, fixing count to 4') + count = 4 + if count >= 2: + y_list = rows[:, 1] + y1, y2 = y_list[0], y_list[-1] + origin_y = min(y1, y2) + RECIPE_DETECT_AREA[1] + else: + logger.warning('Unable to find enough recipe anchors, assume recipes are at top') + origin_y = 114 + + recipe_grid = ButtonGrid( + origin=(181, origin_y), delta=(0, delta_y), button_shape=RECIPE_SIZE, + grid_shape=(1, count), name='RECIPE_GRID' + ) + return recipe_grid + + @cached_property + def recipe_ids(self): + return self.get_recipe_ids() + + def get_recipe_ids(self): + product_name_grid = self.recipe_grid.crop(RECIPE_PRODUCT_NAME_AREA, name='RECIPE_PRODUCT_NAME_GRID') + product_name_images = [self.image_crop(button.area, copy=True) for button in product_name_grid.buttons] + product_names = RECIPE_PRODUCT_NAME_OCR.ocr(product_name_images, direct_ocr=True) + corrected_ids = [self.recipe_product_name_to_recipe_id(name, slotcode=self.working_slot_id) for name in product_names] + return corrected_ids + + def recipe_product_name_to_recipe_id(self, name, slotcode=None): + if server.server == 'jp': + # While we have 果樹園二重奏 and 朝光活力コンビ, the problem of カニ as 力二 is worse, + # so we replace 力 with カ before matching, + # which can fix most of the misrecognition without causing new issues. + name = name.replace('二', 'ニ').replace('力', 'カ') + min_distance = float('inf') + min_real_distance = float('inf') + corrected_name = None + corrected_id = None + if isinstance(slotcode, int): + recipe_lists = DIC_ISLAND_SLOT[slotcode]['formula'] + DIC_ISLAND_SLOT[slotcode]['activity_formula'] + else: + recipe_lists = DIC_ISLAND_RECIPE.keys() + + for recipe_id in recipe_lists: + product_id = get_recipe_product_id(recipe_id) + product_name = DIC_ISLAND_ITEM[product_id]['name'][server.server] + + # If product_name is longer than OCR result name, try matching any substring + # of product_name with the same length as name and take the minimal distance. + if isinstance(name, str) and isinstance(product_name, str) and len(product_name) >= len(name) and len(name) > 0: + best_sub_distance = float('inf') + L = len(name) + for i in range(len(product_name) - L + 1): + sub = product_name[i:i+L] + d = levenshtein_distance(name, sub) + if d < best_sub_distance: + best_sub_distance = d + distance = best_sub_distance + real_distance = levenshtein_distance(name, product_name) + else: + distance = levenshtein_distance(name, product_name) + real_distance = distance + if distance < min_distance or (distance == min_distance and real_distance < min_real_distance): + min_distance = distance + min_real_distance = real_distance + corrected_name = product_name + corrected_id = recipe_id + + if name != corrected_name: + logger.info(f'Recipe product name OCR result "{name}" corrected to "{corrected_name}" with distance {min_distance} and real distance {min_real_distance}') + return corrected_id + + def get_recipe_product_stocks(self): + stock_grid = self.recipe_grid.crop(RECIPE_PRODUCT_STOCK_AREA, name='RECIPE_PRODUCT_STOCK_GRID') + stock_images = [self.image_crop(button.area, copy=True) for button in stock_grid.buttons] + stocks = RECIPE_PRODUCT_STOCK_OCR.ocr(stock_images, direct_ocr=True) + return stocks + + def next_recipe_page(self): + if len(self.recipe_grid.buttons) < 3: + logger.info('Less than 3 recipes in current page, no need to swipe to next page') + return + else: + p1, p2 = random_rectangle_vector_opted((0, -250), box=RECIPE_DRAG_AREA, padding=0) + self.device.drag(p1, p2, hold_duration=0.1, name='RECIPE_NEXT_PAGE_SWIPE') + del_cached_property(self, 'recipe_grid') + del_cached_property(self, 'recipe_ids') + self.device.screenshot() + + def prev_recipe_page(self): + if len(self.recipe_grid.buttons) < 3: + logger.info('Less than 3 recipes in current page, no need to swipe to previous page') + return + else: + p1, p2 = random_rectangle_vector_opted((0, 250), box=RECIPE_DRAG_AREA, padding=0) + self.device.drag(p1, p2, hold_duration=0.1, name='RECIPE_PREV_PAGE_SWIPE') + del_cached_property(self, 'recipe_grid') + del_cached_property(self, 'recipe_ids') + self.device.screenshot() + + def scan_all_recipe_stocks(self): + all_stocks = {} + drag_count = 0 + for _ in self.loop(timeout=30): + new_stocks = dict(zip(self.recipe_ids, self.get_recipe_product_stocks())) + all_stocks.update(new_stocks) + self.next_recipe_page() + drag_count += 1 + if ISLAND_RECIPE_DRAG_CHECK.match(self.device.image): + if drag_count > 1: + logger.info(f'Ensured recipe page bottom after dragging {drag_count} times') + self.device.click_record_clear() + break + else: + drag_count = 0 + ISLAND_RECIPE_DRAG_CHECK.load_color(self.device.image) + return all_stocks + + @cached_property + def all_recipe_stocks(self): + return self.scan_all_recipe_stocks() + + @cached_property + def recipe_id_sequence(self): + return self.get_recipe_id_sequence_to_run() + + @cached_property + def hard_floor_items(self): + yaml_text = self.config.cross_get("IslandProduction.IslandProduction.HardFloorItems", "") + return normalize_item_keys(load_hard_floor_items(yaml_text)) + + @cached_property + def reserve_items(self): + yaml_text = self.config.cross_get("IslandProduction.IslandProduction.ReserveItems", "") + return normalize_item_keys(load_reserve_items(yaml_text)) + + def get_recipe_id_sequence_to_run( + self, + daily_buffer_items_dict=None, + request_buffer_items_dict=None, + hard_floor_items_dict=None, + reserve_items_dict=None, + idle_accumulating_items_dict=None, + task_target_items_dict=None, + ): + stocks_dict = self.all_recipe_stocks + if daily_buffer_items_dict is None: + yaml_text = self.config.cross_get("IslandProduction.IslandProduction.DailyBufferItems", "") + if yaml_text is None: + yaml_text = "" + daily_buffer_items_dict = safe_load(yaml_text) or {} + daily_buffer_items_dict = normalize_item_keys(daily_buffer_items_dict) + if request_buffer_items_dict is None: + yaml_text = self.config.cross_get("IslandProduction.IslandProduction.RequestBufferItems", "") + request_buffer_items_dict = load_request_buffer_items(yaml_text) + request_buffer_items_dict = normalize_item_keys(request_buffer_items_dict) + if hard_floor_items_dict is None: + hard_floor_items_dict = self.hard_floor_items + else: + hard_floor_items_dict = normalize_item_keys(hard_floor_items_dict) + if reserve_items_dict is None: + reserve_items_dict = self.reserve_items + else: + reserve_items_dict = normalize_item_keys(reserve_items_dict) + if task_target_items_dict is None: + yaml_text = self.config.cross_get("IslandSeasonTask.IslandSeasonTask.TaskTarget", "{}") + task_target_items_dict = load_item_mapping(yaml_text, config_name='TaskTarget') + task_target_items_dict = normalize_item_needs(task_target_items_dict, default_period=10) + if idle_accumulating_items_dict is None: + yaml_text = self.config.cross_get("IslandProduction.IslandProduction.IdleAccumulatingItems", "") + if yaml_text is None: + yaml_text = "" + idle_accumulating_items_dict = safe_load(yaml_text) or {} + idle_accumulating_items_dict = normalize_item_keys(idle_accumulating_items_dict) + recipe_entry_sequence = [] + for recipe_id, stock in stocks_dict.items(): + recipe_product = DIC_ISLAND_RECIPE[recipe_id]['commission_product'] + product_id = get_recipe_product_id(recipe_id) + batch_size = recipe_product[product_id] + daily_consumed_stock = daily_buffer_items_dict.get(product_id, 0) + request_buffer = request_buffer_items_dict.get(product_id, 0) + hard_floor = hard_floor_items_dict.get(product_id, 0) + reserve = reserve_items_dict.get(product_id, 0) + target_stock = hard_floor + reserve + max(daily_consumed_stock, request_buffer) + demand = task_target_items_dict.get(product_id, {}) + demand = parse_item_need_deadlines(demand) + demand_text = ', '.join( + f'{count} items in {period:g} days' + for count, period in demand + ) or '0 items in 1 days' + idle_accumulating = idle_accumulating_items_dict.get(product_id, 0) + recipe_entry_sequence.append(( + recipe_id, + (stock, target_stock, hard_floor, reserve, batch_size, demand, idle_accumulating) + )) + logger.info( + f'Recipe {recipe_id} stock: {stock}, ' + f'daily_consumed_stock: {daily_consumed_stock}, ' + f'request_buffer: {request_buffer}, ' + f'hard_floor: {hard_floor}, ' + f'reserve: {reserve}, ' + f'target_stock: {target_stock}, ' + f'batch_size: {batch_size}, task_target: {demand_text}, ' + f'idle accumulating rate: {idle_accumulating}' + ) + logger.info(f'Recipe {recipe_id} weight: {get_recipe_entry_weight(recipe_entry_sequence[-1])}') + recipe_entry_sequence.sort(key=lambda x: get_recipe_entry_weight(x), reverse=True) + sorted_recipe_id_sequence = [x[0] for x in recipe_entry_sequence] + logger.info(f'Calculated recipe id sequence to run: {sorted_recipe_id_sequence}') + return recipe_entry_sequence + + @staticmethod + def calculate_recipe_run_count(info): + stock, target_stock, _hard_floor, _reserve, batch_size, demand, idle_accumulating = info + rate_per_day = get_target_stock_load_rate(stock, target_stock, demand) + if rate_per_day > 0: + return ceil_div_or_ceil(rate_per_day, batch_size) + if stock < target_stock: + return (target_stock - stock - 1) // batch_size + 1 + if idle_accumulating <= 0: + return 0 + return float('inf') + + def get_active_recipe_id(self): + for recipe_id, button in zip(self.recipe_ids, self.recipe_grid.buttons): + if self.is_button_selected(button): + product_id = get_recipe_product_id(recipe_id) + product_name = DIC_ISLAND_ITEM[product_id]['name'][server.server] + logger.info(f'Selected recipe id: {recipe_id}, product name: {product_name}') + return recipe_id + logger.warning('Unable to determine selected recipe, assume no active recipe') + return None + + def get_recipe_ingredient_grids(self, recipe_id=None): + if recipe_id is None: + recipe_id = self.get_active_recipe_id() + if recipe_id is None: + return None + + recipe_cost = DIC_ISLAND_RECIPE[recipe_id]['commission_cost'] + count = len(recipe_cost) + logger.attr('Ingredient_count', count) + if count == 0: + return ButtonGrid( + origin=(750, 474), delta=(0, 0), button_shape=(82, 83), grid_shape=(0, 1), name='RECIPE_INGREDIENT_GRID' + ) + elif count == 1: + return ButtonGrid( + origin=(750, 474), delta=(0, 0), button_shape=(82, 83), grid_shape=(1, 1), name='RECIPE_INGREDIENT_GRID' + ) + elif count == 2: + return ButtonGrid( + origin=(663, 474), delta=(175, 0), button_shape=(82, 83), grid_shape=(2, 1), name='RECIPE_INGREDIENT_GRID' + ) + elif count == 3: + return ButtonGrid( + origin=(634, 474), delta=(116, 0), button_shape=(82, 83), grid_shape=(3, 1), name='RECIPE_INGREDIENT_GRID' + ) + else: + logger.warning(f'Unexpected ingredient count {count}, unable to determine ingredient grid') + return None + + def get_recipe_ingredient_counters(self): + active_recipe_id = self.get_active_recipe_id() + ingredient_grids = self.get_recipe_ingredient_grids(active_recipe_id) + if ingredient_grids is None: + return None + + counter_grids = ingredient_grids.crop((-10, 66, 92, 83), name='counter_grids') + counter_images = [self.image_crop(button.area, copy=True) for button in counter_grids.buttons] + counters = RECIPE_INGREDIENT_COUNTER_OCR.ocr(counter_images, direct_ocr=True) + return counters + + def set_recipe(self, recipe_id): + all_recipe_ids = list(self.all_recipe_stocks.keys()) + clicked = False + for _ in self.loop(timeout=30): + if clicked and self.get_active_recipe_id() == recipe_id: + self.device.click_record_clear() + return True + if recipe_id in self.recipe_ids: + index = self.recipe_ids.index(recipe_id) + button = self.recipe_grid.buttons[index] + self.device.click(button) + clicked = True + continue + if all_recipe_ids.index(recipe_id) < all_recipe_ids.index(self.recipe_ids[0]): + self.prev_recipe_page() + else: + self.next_recipe_page() + clicked = False + else: + logger.warning(f'Unable to find recipe id {recipe_id} after looping through recipe pages, failed to set recipe') + self.device.click_record_clear() + return False + + def goto_ingredient_shop_page(self, entrance_button): + click_timer = Timer(1, count=3).reset() + for _ in self.loop(timeout=15): + if self.appear(ISLAND_INFO_GOTO_SHOP, offset=(20, 20)): + break + if self.is_in_recipe_menu(): + if click_timer.reached_and_reset(): + self.device.click(entrance_button) + else: + logger.warning('Unable to find ingredient button, failed to goto ingredient shop page') + return False + for _ in self.loop(timeout=15, skip_first=False): + if self.appear_then_click(ISLAND_INFO_GOTO_SHOP, offset=(20, 20), interval=2): + continue + if self._island_shop_side_navbar.get_info(main=self)[0] == 0: + return True + else: + logger.warning('Unable to find shop page in ingredient info page, failed to goto ingredient shop page') + return False + + def prepare_ingredients(self, recipe_id, batch_count=float('inf')): + # use this before setting recipe batch count to read out current ingredient need per batch, + # since ranch recipes may have boosted ingredient requirement for higher batch production. + counters = self.get_recipe_ingredient_counters() + recipe_cost = DIC_ISLAND_RECIPE[recipe_id]['commission_cost'] + if batch_count == float('inf'): + max_count = DIC_ISLAND_RECIPE[recipe_id]['production_limit'] + for ingredient_key, counter in zip(recipe_cost, counters): + if ingredient_key in DIC_ISLAND_SHOP_ITEM_TO_RECIPE: + continue + available_stock = max(counter[0] - self.hard_floor_items.get(ingredient_key, 0), 0) + count = available_stock // counter[1] if counter[1] > 0 else float('inf') + if count < max_count: + max_count = count + batch_count = max_count + logger.info(f'Calculated max batch count to produce with current ingredient stock: {batch_count}') + success = True + real_count = batch_count + ingredient_buttons = self.get_recipe_ingredient_grids(recipe_id).buttons + for ingredient_key, counter, button in zip(recipe_cost, counters, ingredient_buttons): + hard_floor = self.hard_floor_items.get(ingredient_key, 0) + available_stock = max(counter[0] - hard_floor, 0) + if available_stock < real_count * counter[1]: + if ingredient_key in DIC_ISLAND_SHOP_ITEM_TO_RECIPE: + if ingredient_key == 3004: # flour cannot be bought via jumping page, need to go to shop page to buy + self.ui_back(check_button=page_island_manage.check_button) + self.ui_goto_island_shop() + isolated = False + else: + self.goto_ingredient_shop_page(entrance_button=button) + isolated = True + delta = real_count * counter[1] - available_stock + success = super().island_shop_buy({ingredient_key: delta}, isolated=isolated) and success + if not isolated: + # We need an exception for inherited class to handle ui switch + # and restart the ingredient preparation after buying flour, + # since flour purchase requires going into shop page and back, + # which may cause the recipe page to lose the set recipe and ingredient states. + self.ui_back(check_button=page_island.check_button) + self.ui_goto(page_island_manage) + raise IslandProductionRestart + else: + self.ui_back(check_button=self.is_in_recipe_menu) + if not success: + logger.warning(f'Failed to buy ingredient {ingredient_key} from shop, insufficient ingredient for recipe production') + real_count = min(real_count, available_stock // counter[1]) if counter[1] > 0 else 0 + else: + logger.warning( + f'Ingredient {ingredient_key} cannot be bought from shop, ' + f'insufficient ingredient for recipe production after hard floor {hard_floor}' + ) + real_count = min(real_count, available_stock // counter[1]) if counter[1] > 0 else 0 + success = False + return success, real_count + + def set_recipe_batch_count(self, batch_count=float('inf')): + for _ in self.loop(): + if self.appear_then_click(ISLAND_RECIPE_AMOUNT_MAX, offset=(20, 20)): + self.device.screenshot() + break + if batch_count == float('inf'): + logger.info('Set recipe amount to max') + return True + else: + logger.info(f'Set recipe amount to {batch_count}') + if ISLAND_RECIPE_AMOUNT_OCR.ocr(self.device.image) < batch_count: + # Check if already at max, if so, cannot set to desired amount + counters = self.get_recipe_ingredient_counters() + for counter in counters: + if counter[2] < 0: + logger.warning('Insufficient ingredient for next recipe amount, cannot set recipe amount to desired value') + return False + try: + self.ui_ensure_index(index=batch_count, letter=ISLAND_RECIPE_AMOUNT_OCR, + next_button=ISLAND_RECIPE_AMOUNT_PLUS, + prev_button=ISLAND_RECIPE_AMOUNT_MINUS,) + return True + except GameTooManyClickError: + logger.warning('Too many clicks when setting recipe amount, failed to set recipe amount') + return False + + def get_recipe_remain_time(self): + if self.match_template_color(ISLAND_RECIPE_TIME_ANCHOR, offset=(100, 20)): + ISLAND_RECIPE_TIME.load_offset(ISLAND_RECIPE_TIME_ANCHOR) + remain_time = Duration(ISLAND_RECIPE_TIME.button, name='recipe_remain_time').ocr(self.device.image) + return remain_time + else: + logger.warning('Unable to find recipe time anchor, failed to execute recipe') + return None + + def run_recipe(self, recipe_id, batch_count=float('inf')): + """ + Returns: + tuple[timedelta, int | float]: remain time and actual batch count after starting, + or None if failed to start recipe + """ + self.last_unavailable_product = None + if not self.is_in_recipe_menu(): + logger.warning('Not in recipe menu, cannot run recipe') + return None + if not self.set_recipe(recipe_id): + logger.warning(f'Failed to set recipe to {recipe_id}, cannot run recipe') + return None + success, real_count = self.prepare_ingredients(recipe_id, batch_count=batch_count) + if not success: + logger.warning(f'Failed to prepare enough ingredient for {batch_count} batch(es) production') + if real_count == 0: + logger.warning('No batch can be produced with current ingredient stock, cannot run recipe') + self.last_unavailable_product = get_recipe_product_id(recipe_id) + return None + else: + logger.info(f'Can only produce {real_count} batch(es) with current ingredient stock, will try to run with this amount') + if not self.set_recipe_batch_count(real_count): + logger.warning(f'Failed to set recipe batch count to {"max" if real_count == float("inf") else real_count}, cannot run recipe') + return None + remain_time = self.get_recipe_remain_time() + logger.attr('Recipe_remain_time', remain_time) + if remain_time is not None: + for _ in self.loop(): + if self.appear_then_click(ISLAND_RECIPE_TIME_ANCHOR, offset=(100, 20), interval=2): + continue + if not self.is_in_recipe_menu(): + break + logger.info(f'Recipe {recipe_id} started with amount {"max" if real_count == float("inf") else real_count}, remain time: {remain_time}') + return remain_time, real_count + else: + logger.warning('Failed to get recipe remain time, recipe may not have started successfully') + return None + + def update_recipe_id_sequence_and_stock(self, recipe_id, batch_count): + old_list = self.recipe_id_sequence + new_list = [] + recipe_cost = DIC_ISLAND_RECIPE[recipe_id]['commission_cost'] + consumed_items = { + item_id: amount * batch_count + for item_id, amount in recipe_cost.items() + } + for old_recipe_id, info in old_list: + product_id = get_recipe_product_id(old_recipe_id) + if old_recipe_id == recipe_id: + stock, target_stock, hard_floor, reserve, batch_size, demand, idle_accumulating = info + new_stock = stock + batch_size * batch_count + new_list.append((old_recipe_id, ( + new_stock, target_stock, hard_floor, reserve, batch_size, demand, idle_accumulating + ))) + logger.info(f'Updated recipe {recipe_id} stock from {stock} to {new_stock} after running recipe for {batch_count} batch(es)') + elif product_id in consumed_items: + stock, target_stock, hard_floor, reserve, batch_size, demand, idle_accumulating = info + new_stock = stock - consumed_items[product_id] + new_list.append((old_recipe_id, ( + new_stock, target_stock, hard_floor, reserve, batch_size, demand, idle_accumulating + ))) + logger.info( + f'Updated recipe {old_recipe_id} stock from {stock} to {new_stock} ' + f'after consuming {consumed_items[product_id]} item(s) for recipe {recipe_id}' + ) + else: + new_list.append((old_recipe_id, info)) + # sort new_list by weight and update recipe_id_sequence + new_recipe_entry_sequence = sorted(new_list, key=lambda x: get_recipe_entry_weight(x), reverse=True) + self.recipe_id_sequence = new_recipe_entry_sequence + self.all_recipe_stocks[recipe_id] += batch_size * batch_count + for old_recipe_id, info in old_list: + product_id = get_recipe_product_id(old_recipe_id) + if product_id in consumed_items: + self.all_recipe_stocks[old_recipe_id] -= consumed_items[product_id] + + def run(self, slot_id=None): + """ + Returns: + target_time (datetime): timestamp of when the recipe will finish, or None if failed to run any recipe + """ + self.working_slot_id = slot_id + del_cached_property(self, 'recipe_grid') + del_cached_property(self, 'recipe_ids') + while self.recipe_id_sequence: + recipe_id, info = self.recipe_id_sequence[0] + production_limit = DIC_ISLAND_RECIPE[recipe_id]['production_limit'] + batch_count = self.calculate_recipe_run_count(info) + logger.info(f"Plan to run {batch_count} batch(es) of recipe {recipe_id}, limitation: {production_limit} batches") + if batch_count <= 0: + logger.info(f'Recipe {recipe_id} has no production demand, skip') + self.recipe_id_sequence.pop(0) + continue + if batch_count > production_limit: + logger.info(f"Will try to run {production_limit} batches") + batch_count = production_limit + result = self.run_recipe(recipe_id, batch_count) + if result is not None: + remain_time, real_count = result + target_time = datetime.now() + remain_time + logger.info(f'Will run recipe {recipe_id} for {real_count} batch(es), expected target time: {target_time}') + self.update_recipe_id_sequence_and_stock(recipe_id, real_count) + return target_time + else: + unavailable_product = getattr(self, 'last_unavailable_product', None) + if unavailable_product is not None: + logger.info( + f'Product {unavailable_product} is unavailable after recipe {recipe_id} failed' + ) + self.recipe_id_sequence.pop(0) + logger.info('No recipe to run or failed to run any recipe') + return None diff --git a/module/island_handler/restaurant.py b/module/island_handler/restaurant.py new file mode 100644 index 0000000000..7c157bab41 --- /dev/null +++ b/module/island_handler/restaurant.py @@ -0,0 +1,398 @@ +from datetime import datetime, timedelta + +import cv2 +import numpy as np +from yaml import safe_load + +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_handler.assets import * +from module.island_handler.dock import IslandDock +from module.island_handler.dock_scanner import CharacterScanner +from module.island_handler.production_planner import normalize_item_keys +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, 221, dst=mask) + sum_ = np.count_nonzero(mask) + return sum_ > 400 + + +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=(38, 67, 83, 86), + tag_area=(66, 2, 72, 5) + ) + self.amount_ocr = Digit([], 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}") + + def has_waitress(self, config_key, waitress_name): + value = self.config.cross_get(config_key) + if not isinstance(value, str): + return False + return waitress_name in value.split('+') + + @cached_property + def restaurant_capacity(self): + capacity = { + 601: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.KoiGrade")), + 602: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.BearGrade")), + 603: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.EateryGrade")), + 604: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.GrillGrade")), + 901: self.get_initial_capacity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.CafeGrade")), + } + if self.has_waitress("IslandBusiness.IslandRestaurant.KoiWaitress", 'Chao_Ho'): + capacity[601] += 1 + if self.has_waitress("IslandBusiness.IslandRestaurant.BearWaitress", 'Cheshire'): + capacity[602] += 1 + if self.has_waitress("IslandBusiness.IslandRestaurant.EateryWaitress", 'Helena'): + capacity[603] += 1 + if self.has_waitress("IslandBusiness.IslandRestaurant.GrillWaitress", 'August_von_Parseval'): + capacity[604] += 1 + if self.has_waitress("IslandBusiness.IslandRestaurant.CafeWaitress", 'Cheshire'): + capacity[901] += 1 + 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 = { + 601: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.KoiGrade")), + 602: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.BearGrade")), + 603: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.EateryGrade")), + 604: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.GrillGrade")), + 901: self.get_quantity_from_grade(self.config.cross_get("IslandBusiness.IslandRestaurant.CafeGrade")), + } + 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)) + + 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_x - 20, -20, self._restaurant_offset_x + 20, 20), 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_x - 20, -20, self._restaurant_offset_x + 20, 20)) + or self.appear(ISLAND_RESTAURANT_RESTING, offset=(self._restaurant_offset_x - 20, -20, self._restaurant_offset_x + 20, 20))): + if confirm_timer.reached(): + return True + + # 364 and 214 + @cached_property + def _restaurant_offset_x(self): + if self.restaurant_has_event: + return 150 + else: + return 0 + + @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.appear(ISLAND_RESTAURANT_SCROLL_TOP, offset=(self._restaurant_offset_x - 20, 0, self._restaurant_offset_x + 20, 0)): + 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.appear(ISLAND_RESTAURANT_SCROLL_BOTTOM, offset=(self._restaurant_offset_x - 20, 0, self._restaurant_offset_x + 20, 0)): + 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_text = { + 601: self.config.cross_get("IslandBusiness.IslandRestaurant.KoiMenu", default="{}"), + 602: self.config.cross_get("IslandBusiness.IslandRestaurant.BearMenu", default="{}"), + 603: self.config.cross_get("IslandBusiness.IslandRestaurant.EateryMenu", default="{}"), + 604: self.config.cross_get("IslandBusiness.IslandRestaurant.GrillMenu", default="{}"), + 901: self.config.cross_get("IslandBusiness.IslandRestaurant.CafeMenu", default="{}"), + }[self.working_restaurant_id] + menu = safe_load(menu_text) + 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 + items = self.scan_all_items() + menu_items = [item for item in items if item.id in normalize_item_keys(menu).keys()] + quantity = self.restaurant_quantity[self.working_restaurant_id] + items = sorted(menu_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 waitress_lists(self): + waitress_lists = { + 601: self.config.cross_get("IslandBusiness.IslandRestaurant.KoiWaitress").split("+") if isinstance(self.config.cross_get("IslandBusiness.IslandRestaurant.KoiWaitress"), str) else [], + 602: self.config.cross_get("IslandBusiness.IslandRestaurant.BearWaitress").split("+") if isinstance(self.config.cross_get("IslandBusiness.IslandRestaurant.BearWaitress"), str) else [], + 603: self.config.cross_get("IslandBusiness.IslandRestaurant.EateryWaitress").split("+") if isinstance(self.config.cross_get("IslandBusiness.IslandRestaurant.EateryWaitress"), str) else [], + 604: self.config.cross_get("IslandBusiness.IslandRestaurant.GrillWaitress").split("+") if isinstance(self.config.cross_get("IslandBusiness.IslandRestaurant.GrillWaitress"), str) else [], + 901: self.config.cross_get("IslandBusiness.IslandRestaurant.CafeWaitress").split("+") if isinstance(self.config.cross_get("IslandBusiness.IslandRestaurant.CafeWaitress"), str) else [], + } + return waitress_lists + + @cached_property + def unavailable_waitress_list(self): + lst = set() + for restaurant_id, waitress_list in self.waitress_lists.items(): + if restaurant_id == self.working_restaurant_id: + continue + for waitress in waitress_list: + if waitress == 'none': + break + elif waitress == 'any': + continue + else: + lst.add(waitress) + lst = list(lst) + return lst + + def restaurant_resting(self): + return self.appear(ISLAND_RESTAURANT_RESTING, offset=(self._restaurant_offset_x - 20, -20, self._restaurant_offset_x + 20, 20)) + + def choose_waitress(self): + waitress_list = self.waitress_lists[self.working_restaurant_id] + if waitress_list == ['none']: + return False + unavailable_waitress_list = self.unavailable_waitress_list + for waitress in waitress_list: + if waitress in unavailable_waitress_list: + unavailable_waitress_list.remove(waitress) + if unavailable_waitress_list: + logger.warning(f"Unavailable waitress list: {unavailable_waitress_list}") + for _ in self.loop(): + if self.appear_then_click(ISLAND_RESTAURANT_SELECT_CHARACTER, offset=(self._restaurant_offset_x - 20, -20, self._restaurant_offset_x + 20, 20), interval=2): + continue + if self.is_in_island_dock(): + break + success = True + for waitress in waitress_list: + if waitress != 'any': + candidate = self.island_dock_find_character(waitress) + if candidate and candidate.status == 'free': + self.island_dock_select_one(candidate.button) + success = success and True + else: + if candidate and candidate.status != 'free': + 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 {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 {self.working_restaurant_id} for 8 hours") + else: + # Less than 8 hours, find any available character instead + success = self.island_dock_select_character_with_blacklist(self.unavailable_waitress_list) and success + else: + success = self.island_dock_select_character_with_blacklist(self.unavailable_waitress_list) and success + 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() + 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.appear_then_click(ISLAND_RESTAURANT_START, offset=(self._restaurant_offset_x - 20, -20, self._restaurant_offset_x + 20, 20), interval=2): + continue + if self.appear(ISLAND_RESTAURANT_RUNNING, offset=(self._restaurant_offset_x - 20, -20, self._restaurant_offset_x + 20, 20)): + return True + + def run(self): + 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 + \ No newline at end of file diff --git a/module/island_handler/shop.py b/module/island_handler/shop.py new file mode 100644 index 0000000000..248239078d --- /dev/null +++ b/module/island_handler/shop.py @@ -0,0 +1,306 @@ +from jellyfish import levenshtein_distance + +import module.config.server as server +from module.base.decorator import cached_property, del_cached_property +from module.base.button import ButtonGrid +from module.island.data import DIC_ISLAND_SHOP, DIC_ISLAND_SHOP_ITEM_TO_RECIPE, DIC_ISLAND_SHOP_RECIPE +from module.island.ui import IslandUI, NestedNavbar +from module.island_handler.assets import * +from module.logger import logger +from module.ocr.ocr import Digit, Ocr +from module.ui.assets import ISLAND_GOTO_ISLAND_SHOP +from module.ui.navbar import Navbar +from module.ui.page import page_island, page_island_shop +from module.ui_white.assets import BACK_ARROW_WHITE + + +SHOP_ITEM_NAME_AREA = (12, 142, 134, 163) + + +class IslandShop(IslandUI): + has_shop_banner = False + + def ui_goto_island_shop(self): + self.ui_goto(page_island) + for _ in self.loop(): + if self.ui_page_appear(page_island_shop, offset=(0, 20)): + return True + elif self.appear(ISLAND_SHOP_MILL_CHECK, offset=(20, 20)): + return True + elif self.appear(ISLAND_SHOP_RECOMMEND, offset=(0, 20)): + self.has_shop_banner = True + return True + if self.appear_then_click(ISLAND_GOTO_ISLAND_SHOP, offset=(20, 20), interval=1): + continue + + @cached_property + def _island_shop_side_navbar(self): + if not self.has_shop_banner: + return NestedNavbar( + grids=ButtonGrid(origin=(12, 80), delta=(0, 70), button_shape=(127, 70), grid_shape=(1, 6), name='ISLAND_SHOP_NAVBAR'), + subgrid_delta=(0, 58), subgrid_button_shape=(127, 58), + subgrid_shapes=[(1, 1), (1, 2), (1, 1), (1, 1), (1, 2), (1, 0)], + direction='vertical', + ) + else: + return NestedNavbar( + grids=ButtonGrid(origin=(12, 80), delta=(0, 70), button_shape=(127, 70), grid_shape=(1, 7), name='ISLAND_SHOP_NAVBAR'), + subgrid_delta=(0, 58), subgrid_button_shape=(127, 58), + subgrid_shapes=[(1, 0), (1, 1), (1, 2), (1, 1), (1, 1), (1, 2), (1, 0)], + direction='vertical', + ) + + def island_shop_side_navbar_ensure(self, main_index, sub_index=None, skip_first_screenshot=True): + if not self.has_shop_banner: + return self._island_shop_side_navbar.set(self, main_index=main_index, sub_index=sub_index, skip_first_screenshot=skip_first_screenshot) + else: + return self._island_shop_side_navbar.set(self, main_index=main_index+1, sub_index=sub_index, skip_first_screenshot=skip_first_screenshot) + + @property + def _island_shop_item_grid(self): + return ButtonGrid( + origin=(224, 184), + delta=(163, 226), + button_shape=(147, 201), + grid_shape=(6, 2), + name="island_shop_item_grid", + ) + + @property + def _island_shop_item_name_grid(self): + return self._island_shop_item_grid.crop( + SHOP_ITEM_NAME_AREA, + name="island_shop_item_name_grid", + ) + + @property + def _island_shop_item_name_ocr(self): + if server.server == 'jp': + lang = 'jp' + elif server.server == 'cn': + lang = 'cnocr' + else: + lang = 'azur_lane' + return Ocr( + self._island_shop_item_name_grid.buttons, + lang=lang, + letter=(66, 66, 66), + threshold=200, + name="island_shop_item_name_ocr", + ) + + def item_name_to_recipe_id(self, name): + if name == '': + return None + min_distance = float('inf') + min_real_distance = float('inf') + corrected_name = None + corrected_id = None + for recipe_id, recipe in DIC_ISLAND_SHOP_RECIPE.items(): + recipe_name = recipe['name'][server.server] + if isinstance(name, str) and isinstance(recipe_name, str) and len(recipe_name) >= len(name) and len(name) > 0: + best_sub_distance = float('inf') + L = len(name) + for i in range(len(recipe_name) - L + 1): + sub = recipe_name[i:i+L] + d = levenshtein_distance(name, sub) + if d < best_sub_distance: + best_sub_distance = d + distance = best_sub_distance + real_distance = levenshtein_distance(name, recipe_name) + else: + distance = levenshtein_distance(name, recipe_name) + real_distance = distance + if distance < min_distance or (distance == min_distance and real_distance < min_real_distance): + min_distance = distance + min_real_distance = real_distance + corrected_name = recipe_name + corrected_id = recipe_id + if min_real_distance > 0: + logger.info(f"Corrected '{name}' to '{corrected_name}' with distance {min_distance}") + return corrected_id + + @cached_property + def island_shop_item_ids(self): + return self.island_shop_get_item_ids() + + def island_shop_get_item_ids(self): + names = self._island_shop_item_name_ocr.ocr(self.device.image) + ids = [self.item_name_to_recipe_id(name) for name in names if name != ''] + return ids + + @property + def _island_currency_grid(self): + return ButtonGrid( + origin=(688, 18), + delta=(171, 0), + button_shape=(126, 29), + grid_shape=(3, 1), + name="island_currency_grid", + ) + + def island_shop_get_currency(self): + for _ in self.loop(timeout=5): + if self.ui_page_appear(page_island_shop): + # coin is currency + price_button = self._island_currency_grid.buttons[-1] + price_ocr = Digit([price_button], letter=(255, 245, 118), threshold=160, name="island_currency_ocr") + price = price_ocr.ocr(self.device.image) + if price != 0: + return {1: price} + else: + # wheat, corn and grass are currency + price_ocr = Digit(self._island_currency_grid.buttons, letter=(255, 245, 118), threshold=160, name="island_currency_ocr") + price = price_ocr.ocr(self.device.image) + if any(p != 0 for p in price): + return {2000: price[0], 2001: price[1], 2008: price[2]} + else: + logger.warning("Failed to get currency amount, return empty dict") + return {} + + def island_shop_buy_ensure_amount(self, amount): + # having button of -10, -1, +1, +10, and the amount to buy, we can ensure the amount to buy by clicking these buttons + # try using least number of clicks + def middle_integer(N): + d = N - 1 + q = d // 10 + r = d % 10 + if r <= 5: + middle = 10*q + 1 + else: + middle = 10*(q + 1) + 1 + return middle + + ocr = Digit([ISLAND_SHOP_BUY_AMOUNT], letter=(57, 58, 60), threshold=160, name="island_shop_buy_amount_ocr") + def one_tenth_ocr(image): + amount = ocr.ocr(image) + return amount // 10 + + self.ui_ensure_index(middle_integer(amount) // 10, one_tenth_ocr, ISLAND_SHOP_BUY_AMOUNT_PLUS_TEN, ISLAND_SHOP_BUY_AMOUNT_MINUS_TEN) + self.ui_ensure_index(amount, ocr, ISLAND_SHOP_BUY_AMOUNT_PLUS_ONE, ISLAND_SHOP_BUY_AMOUNT_MINUS_ONE) + + def island_shop_buy_execute(self, button, amount): + for _ in self.loop(): + if self.appear(ISLAND_SHOP_BUY_CHECK, offset=(20, 20)): + break + if self.appear(BACK_ARROW_WHITE, offset=(20, 20), interval=2): + self.device.click(button) + self.island_shop_buy_ensure_amount(amount) + for _ in self.loop(): + if self.handle_island_additional(): + self.interval_reset(BACK_ARROW_WHITE) + continue + if self.appear_then_click(ISLAND_SHOP_BUY_CONFIRM, offset=(20, 20), interval=3): + self.interval_reset(BACK_ARROW_WHITE) + continue + if self.appear(BACK_ARROW_WHITE, offset=(20, 20), interval=2): + break + + def island_shop_buy_in_page(self, recipe_id, amount=1): + currency_id = list(DIC_ISLAND_SHOP_RECIPE[recipe_id]['resource_consume'].keys())[0] + currency_amount = self.island_shop_get_currency().get(currency_id, 0) + logger.info(f"Current currency amount: {currency_amount}, required: {DIC_ISLAND_SHOP_RECIPE[recipe_id]['resource_consume'][currency_id] * amount}") + if currency_amount < DIC_ISLAND_SHOP_RECIPE[recipe_id]['resource_consume'][currency_id] * amount: + logger.warning(f"Not enough currency to buy {amount} of recipe {recipe_id}") + return False + for id, button in zip(self.island_shop_item_ids, self._island_shop_item_grid.buttons): + if id == recipe_id: + self.island_shop_buy_execute(button, amount) + return True + logger.warning(f"Recipe {recipe_id} not found in shop") + return False + + @cached_property + def _island_shop_tab(self): + return Navbar( + grids=ButtonGrid( + origin=(184, 82), + delta=(176, 0), + button_shape=(176, 42), + grid_shape=(3, 1), + name="island_shop_tab_grid", + ), + active_color=(249, 181, 76), + inactive_color=(235, 235, 235), + active_threshold=221, + inactive_threshold=240, + active_count=5000, + inactive_count=5000, + name="island_shop_tab", + ) + + def island_shop_set_navbar_and_tab(self, recipe_id, isolated=True): + # only works for general items (skins and items are not included) + del_cached_property(self, 'island_shop_item_ids') + success = True + if isolated: + search_range = range(10019, 10038) + else: + search_range = [*range(10019, 10022), *range(10031, 10038), *range(10109, 10114)] + target_shop_id = None + for shop_id in search_range: + if recipe_id in DIC_ISLAND_SHOP[shop_id]['goods']: + target_shop_id = shop_id + logger.info(f"Recipe {recipe_id} is in shop {shop_id}, name {DIC_ISLAND_SHOP[shop_id]['name'][server.server]}") + break + order = [0, 0, 0] + for index in range(3): + order[2 - index] = DIC_ISLAND_SHOP[target_shop_id]['order'] + target_shop_id = DIC_ISLAND_SHOP[target_shop_id]['parent_id'] + if target_shop_id == 0: + break + if not isolated or recipe_id == 103004: + success = self.island_shop_side_navbar_ensure(main_index=order[0]-1, sub_index=order[1]-1) + if success and 111101 <= recipe_id < 111300: + # fishery bug makes freshwater fish, saltwater fish and other products not distinguishable + # after jumping from details page, so we need to set top tab to specific tab according to the recipe_id + for index, shop_id in enumerate([10033, 10034, 10035]): + if recipe_id in DIC_ISLAND_SHOP[shop_id]['goods']: + success = self._island_shop_tab.set(main=self, left=index+1) + return True + elif not isolated and success and recipe_id >= 411000: + for index, shop_id in enumerate([10111, 10112, 10113]): + if recipe_id in DIC_ISLAND_SHOP[shop_id]['goods']: + success = self._island_shop_tab.set(main=self, left=index+1) + return True + return success + + def wait_island_shop_loading(self): + for _ in self.loop(timeout=5): + if self.ui_page_appear(page_island_shop, offset=(0, 20)): + return True + if self.appear(ISLAND_SHOP_MILL_CHECK, offset=(20, 20)): + return True + if self.appear(ISLAND_SHOP_RECOMMEND, offset=(0, 20)): + self.has_shop_banner = True + return True + if self.appear(ISLAND_SHOP_LOADING, offset=(20, 20)): + continue + + def island_shop_buy(self, shopping_dict={}, isolated=True): + """ + Parameters: + shopping_dict (dict): {item_id: amount} of items to buy. item_id is the id of the item in DIC_ISLAND_SHOP_RECIPE. amount is the amount to buy. + isolated (bool): whether to only buy from isolated shop. If False, will buy from general shop. + notice that the behaviour of multiple items to buy and isolated=True is undefined. + """ + self.wait_island_shop_loading() + success = True + for item_id, amount in shopping_dict.items(): + if item_id not in DIC_ISLAND_SHOP_ITEM_TO_RECIPE: + logger.warning(f"Item {item_id} not found in data, cannot buy") + continue + recipe_id = DIC_ISLAND_SHOP_ITEM_TO_RECIPE[item_id] + if recipe_id not in DIC_ISLAND_SHOP_RECIPE: + logger.warning(f"Recipe {recipe_id} not found in data, cannot buy") + continue + buy_count = (amount - 1) // DIC_ISLAND_SHOP_RECIPE[recipe_id]['items'][item_id] + 1 + logger.info(f"Buying {amount} of item {item_id} requires recipe {recipe_id} with buy count {buy_count}") + if self.island_shop_set_navbar_and_tab(recipe_id, isolated=isolated): + if not self.island_shop_buy_in_page(recipe_id, buy_count): + logger.warning(f"Failed to buy recipe {recipe_id}") + success = False + else: + logger.warning(f"Failed to set tabs for recipe {recipe_id}, cannot buy") + success = False + return success \ No newline at end of file diff --git a/module/island_handler/technology_scanner.py b/module/island_handler/technology_scanner.py new file mode 100644 index 0000000000..2fe5e087d0 --- /dev/null +++ b/module/island_handler/technology_scanner.py @@ -0,0 +1,176 @@ +import cv2 +import numpy as np +from yaml import safe_dump + +from module.base.button import ButtonGrid +from module.base.decorator import cached_property +from module.base.mask import Mask +from module.base.utils import color_similarity_2d, rgb2luma, load_image, random_rectangle_vector, area_offset, crop +from module.island_handler.assets import * +from module.island.data import DIC_ISLAND_TECHNOLOGY +from module.island.ui import IslandUI +from module.ui.navbar import Navbar +from module.ui.page import page_island_technology + +DELTA_X = 136 + 2/3 +DELTA_Y = 60 +ORIGIN_X = -5/3 +ORIGIN_Y = 46 +LEFT_STRIP = 167 +MASK_ISLAND_TECHNOLOGY = Mask('./assets/mask/MASK_ISLAND_TECHNOLOGY.png') +TECHNOLOGY_LENGTH = { + '2': 3139 - 1280 + LEFT_STRIP, + '3': 4231 - 1280 + LEFT_STRIP, + '4': 3003 - 1280 + LEFT_STRIP, + '5': 5462 - 1280 + LEFT_STRIP, + '6': 4233 - 1280 + LEFT_STRIP, +} +DETECTION_AREA = (167, 54, 1280, 720) +DETECTION_AREA_MASK = (1098, 646, 1280, 720) +BUTTON_AREA = (-110, -26, 110, 26) + + +def extract_flowchart(image): + brightness = rgb2luma(image) + black = color_similarity_2d(image, (7, 10, 17)) + brightness_mask = cv2.inRange(brightness, 160, 255) + black_mask = cv2.inRange(black, 245, 255) + mask = cv2.bitwise_or(brightness_mask, black_mask) + contours, _ = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + filled_mask = np.zeros_like(mask) + cv2.drawContours(filled_mask, contours, -1, 255, thickness=cv2.FILLED) + filled_mask = MASK_ISLAND_TECHNOLOGY.apply(filled_mask) + filled_mask = filled_mask[:, LEFT_STRIP:] + return filled_mask + +def get_technology_tab_and_position(index): + tech_info = DIC_ISLAND_TECHNOLOGY[index] + tab = tech_info['tech_belong'] + axis_x, axis_y = tech_info['axis'] + position_x = ORIGIN_X + DELTA_X * axis_x + position_y = ORIGIN_Y + DELTA_Y * axis_y + return tab, (position_x, position_y) + + +class IslandTechnologyScanner(IslandUI): + """ + Currently only supports checking tab 2,3,4,5,6. + """ + @cached_property + def _island_technology_side_navbar(self): + island_technology_side_navbar = ButtonGrid( + origin=(13, 107), delta=(0, 196/3), + button_shape=(128, 43), grid_shape=(1, 5) + ) + return Navbar(grids=island_technology_side_navbar, + active_color=(30, 143, 255), + inactive_color=(50, 52, 55), + active_count=500, + inactive_count=500) + + def _island_technology_side_navbar_get_active(self): + active, _, _ = self._island_technology_side_navbar.get_info(main=self) + if active is None: + return 1 + return active + 2 + + def island_technology_side_navbar_ensure(self, tab=1, skip_first_screenshot=True): + """ + Tab 2, 3, 4, 5, 6 corresponds to _island_technology_side_navbar 1, 2, 3, 4, 5 + Tab 1 is a special situation where the botton icon is chosen, + and all the navbar icons are inactive. + """ + for _ in self.loop(skip_first=skip_first_screenshot): + active = self._island_technology_side_navbar_get_active() + if active == tab: + return True + if tab == 1: + self.device.click(ISLAND_TECHNOLOGY_TAB1) + continue + else: + if active == 1: + self.device.click(self._island_technology_side_navbar.grids.buttons[tab-2]) + continue + else: + self._island_technology_side_navbar.set(self, upper=tab-1) + return True + return False + + def get_technology_view_position(self, tab): + globe_view = load_image(f'./assets/island/technology/technology_chart_{tab}.png') + extracted_flowchart = extract_flowchart(self.device.image) + result = cv2.matchTemplate(globe_view, extracted_flowchart, cv2.TM_CCOEFF_NORMED) + _, similarity, _, loca = cv2.minMaxLoc(result) + # print(similarity) + return loca[0] + + def _island_technology_swipe(self, forward=True): + detection_area = DETECTION_AREA + direction_vector = (-600, 0) if forward else (600, 0) + p1, p2 = random_rectangle_vector( + direction_vector, box=detection_area, random_range=(-50, -50, 50, 50), padding=20 + ) + self.device.drag(p1, p2, segments=2, shake=(0, 25), point_random=(0, 0, 0, 0), shake_random=(0, -5, 0, 5)) + + def technology_reset_view(self, skip_first_screenshot=True): + active = self._island_technology_side_navbar_get_active() + for _ in range(10): # tab 5 has 4400 length, so 5 swipes are not enough + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + position_x = self.get_technology_view_position(tab=active) + if position_x < 3: + break + self._island_technology_swipe(forward=False) + self.device.click_record_remove('DRAG') + + def scan_all(self): + all_technology = {} + for index in DIC_ISLAND_TECHNOLOGY.keys(): + if DIC_ISLAND_TECHNOLOGY[index]['tech_belong'] not in [2, 3, 4, 5, 6]: + continue + tab, position = get_technology_tab_and_position(index) + all_technology[index] = { + 'tab': tab, + 'position': position, + 'active': False, + } + technology_by_tab = [{} for _ in range(5)] + for index, info in all_technology.items(): + technology_by_tab[info['tab'] - 2][index] = info['position'] + for tab in [2, 3, 4, 5, 6]: + self.island_technology_side_navbar_ensure(tab=tab) + self.technology_reset_view() + position_x_old = None + for _ in self.loop(): + position_x = self.get_technology_view_position(tab=tab) + if position_x_old is not None: + if position_x - position_x_old < 5: + break + position_x_old = position_x + for index, (tech_pos_x, tech_pos_y) in technology_by_tab[tab - 2].items(): + tech_pos_x_in_view = tech_pos_x - position_x + if (DETECTION_AREA[0] - BUTTON_AREA[0] <= LEFT_STRIP + tech_pos_x_in_view <= DETECTION_AREA[2] - BUTTON_AREA[2] + and not ( + tech_pos_y > DETECTION_AREA_MASK[1] + BUTTON_AREA[1] + and LEFT_STRIP + tech_pos_x_in_view >= DETECTION_AREA_MASK[0] + )): + tech_button = crop(self.device.image, area=area_offset(BUTTON_AREA, (LEFT_STRIP + tech_pos_x_in_view, tech_pos_y))) + luma = rgb2luma(tech_button) + color = np.mean(luma.flatten()) + if color > 160: + all_technology[index]['active'] = True + self._island_technology_swipe(forward=True) + self.device.click_record_remove('DRAG') + return {index: info['active'] for index, info in all_technology.items()} + + def get_technology_status(self, dump_key=None): + self.ui_ensure(page_island_technology) + result = self.scan_all() + if dump_key is not None: + value = safe_dump(result) + self.config.cross_set(keys=dump_key, value=value) + return result + + diff --git a/module/submodule/utils.py b/module/submodule/utils.py index 5fdda4c932..e6c133f60c 100644 --- a/module/submodule/utils.py +++ b/module/submodule/utils.py @@ -18,6 +18,7 @@ def get_available_func(): 'Daemon', 'OpsiDaemon', 'EventStory', + 'IslandProductionPlanner', 'AzurLaneUncensored', 'Benchmark', 'GameManager', diff --git a/module/ui/assets.py b/module/ui/assets.py index f12bbf4163..f8ca5287f4 100644 --- a/module/ui/assets.py +++ b/module/ui/assets.py @@ -29,6 +29,7 @@ DORMMENU_CHECK = Button(area={'cn': (261, 487, 334, 587), 'en': (261, 487, 334, 587), 'jp': (261, 487, 334, 587), 'tw': (261, 487, 334, 587)}, color={'cn': (181, 172, 178), 'en': (181, 172, 178), 'jp': (181, 172, 178), 'tw': (181, 172, 178)}, button={'cn': (261, 487, 334, 587), 'en': (261, 487, 334, 587), 'jp': (261, 487, 334, 587), 'tw': (261, 487, 334, 587)}, file={'cn': './assets/cn/ui/DORMMENU_CHECK.png', 'en': './assets/en/ui/DORMMENU_CHECK.png', 'jp': './assets/jp/ui/DORMMENU_CHECK.png', 'tw': './assets/tw/ui/DORMMENU_CHECK.png'}) DORMMENU_GOTO_ACADEMY = Button(area={'cn': (261, 487, 334, 587), 'en': (261, 487, 334, 587), 'jp': (261, 487, 334, 587), 'tw': (261, 487, 334, 587)}, color={'cn': (181, 172, 178), 'en': (181, 172, 178), 'jp': (181, 172, 178), 'tw': (181, 172, 178)}, button={'cn': (261, 487, 334, 587), 'en': (261, 487, 334, 587), 'jp': (261, 487, 334, 587), 'tw': (261, 487, 334, 587)}, file={'cn': './assets/cn/ui/DORMMENU_GOTO_ACADEMY.png', 'en': './assets/en/ui/DORMMENU_GOTO_ACADEMY.png', 'jp': './assets/jp/ui/DORMMENU_GOTO_ACADEMY.png', 'tw': './assets/tw/ui/DORMMENU_GOTO_ACADEMY.png'}) DORMMENU_GOTO_DORM = Button(area={'cn': (408, 494, 541, 587), 'en': (408, 494, 541, 587), 'jp': (408, 494, 541, 587), 'tw': (408, 494, 541, 587)}, color={'cn': (167, 148, 137), 'en': (167, 148, 137), 'jp': (167, 148, 137), 'tw': (167, 148, 137)}, button={'cn': (408, 494, 541, 587), 'en': (408, 494, 541, 587), 'jp': (408, 494, 541, 587), 'tw': (408, 494, 541, 587)}, file={'cn': './assets/cn/ui/DORMMENU_GOTO_DORM.png', 'en': './assets/en/ui/DORMMENU_GOTO_DORM.png', 'jp': './assets/jp/ui/DORMMENU_GOTO_DORM.png', 'tw': './assets/tw/ui/DORMMENU_GOTO_DORM.png'}) +DORMMENU_GOTO_ISLAND = Button(area={'cn': (771, 400, 950, 508), 'en': (771, 400, 950, 508), 'jp': (771, 400, 950, 508), 'tw': (771, 400, 950, 508)}, color={'cn': (177, 180, 191), 'en': (177, 180, 191), 'jp': (177, 180, 191), 'tw': (177, 180, 191)}, button={'cn': (771, 400, 950, 508), 'en': (771, 400, 950, 508), 'jp': (771, 400, 950, 508), 'tw': (771, 400, 950, 508)}, file={'cn': './assets/cn/ui/DORMMENU_GOTO_ISLAND.png', 'en': './assets/cn/ui/DORMMENU_GOTO_ISLAND.png', 'jp': './assets/cn/ui/DORMMENU_GOTO_ISLAND.png', 'tw': './assets/cn/ui/DORMMENU_GOTO_ISLAND.png'}) DORMMENU_GOTO_MAIN = Button(area={'cn': (261, 487, 334, 587), 'en': (261, 487, 334, 587), 'jp': (261, 487, 334, 587), 'tw': (261, 487, 334, 587)}, color={'cn': (181, 172, 178), 'en': (181, 172, 178), 'jp': (181, 172, 178), 'tw': (181, 172, 178)}, button={'cn': (150, 153, 437, 279), 'en': (150, 153, 437, 279), 'jp': (150, 153, 437, 279), 'tw': (150, 153, 437, 279)}, file={'cn': './assets/cn/ui/DORMMENU_GOTO_MAIN.png', 'en': './assets/en/ui/DORMMENU_GOTO_MAIN.png', 'jp': './assets/jp/ui/DORMMENU_GOTO_MAIN.png', 'tw': './assets/tw/ui/DORMMENU_GOTO_MAIN.png'}) DORMMENU_GOTO_MEOWFFICER = Button(area={'cn': (634, 512, 749, 569), 'en': (634, 512, 749, 569), 'jp': (634, 512, 749, 569), 'tw': (634, 512, 749, 569)}, color={'cn': (144, 153, 176), 'en': (144, 153, 176), 'jp': (144, 153, 176), 'tw': (144, 153, 176)}, button={'cn': (634, 512, 749, 569), 'en': (634, 512, 749, 569), 'jp': (634, 512, 749, 569), 'tw': (634, 512, 749, 569)}, file={'cn': './assets/cn/ui/DORMMENU_GOTO_MEOWFFICER.png', 'en': './assets/en/ui/DORMMENU_GOTO_MEOWFFICER.png', 'jp': './assets/jp/ui/DORMMENU_GOTO_MEOWFFICER.png', 'tw': './assets/tw/ui/DORMMENU_GOTO_MEOWFFICER.png'}) DORMMENU_GOTO_PRIVATE_QUARTERS = Button(area={'cn': (998, 581, 1148, 601), 'en': (998, 581, 1148, 601), 'jp': (998, 581, 1148, 601), 'tw': (998, 581, 1148, 601)}, color={'cn': (140, 140, 139), 'en': (140, 140, 139), 'jp': (140, 140, 139), 'tw': (140, 140, 139)}, button={'cn': (1010, 336, 1140, 596), 'en': (1010, 336, 1140, 596), 'jp': (1010, 336, 1140, 596), 'tw': (1010, 336, 1140, 596)}, file={'cn': './assets/cn/ui/DORMMENU_GOTO_PRIVATE_QUARTERS.png', 'en': './assets/en/ui/DORMMENU_GOTO_PRIVATE_QUARTERS.png', 'jp': './assets/jp/ui/DORMMENU_GOTO_PRIVATE_QUARTERS.png', 'tw': './assets/tw/ui/DORMMENU_GOTO_PRIVATE_QUARTERS.png'}) @@ -48,6 +49,28 @@ IDLE = Button(area={'cn': (864, 672, 873, 688), 'en': (864, 672, 873, 688), 'jp': (864, 672, 873, 688), 'tw': (864, 672, 873, 688)}, color={'cn': (158, 159, 167), 'en': (158, 159, 167), 'jp': (158, 159, 167), 'tw': (158, 159, 167)}, button={'cn': (864, 672, 873, 688), 'en': (864, 672, 873, 688), 'jp': (864, 672, 873, 688), 'tw': (864, 672, 873, 688)}, file={'cn': './assets/cn/ui/IDLE.png', 'en': './assets/en/ui/IDLE.png', 'jp': './assets/jp/ui/IDLE.png', 'tw': './assets/tw/ui/IDLE.png'}) IDLE_2 = Button(area={'cn': (864, 672, 873, 688), 'en': (864, 672, 873, 688), 'jp': (864, 672, 873, 688), 'tw': (864, 672, 873, 688)}, color={'cn': (173, 178, 186), 'en': (173, 178, 186), 'jp': (173, 178, 186), 'tw': (173, 178, 186)}, button={'cn': (864, 672, 873, 688), 'en': (864, 672, 873, 688), 'jp': (864, 672, 873, 688), 'tw': (864, 672, 873, 688)}, file={'cn': './assets/cn/ui/IDLE_2.png', 'en': './assets/cn/ui/IDLE_2.png', 'jp': './assets/cn/ui/IDLE_2.png', 'tw': './assets/cn/ui/IDLE_2.png'}) IDLE_3 = Button(area={'cn': (864, 672, 873, 688), 'en': (864, 672, 873, 688), 'jp': (864, 672, 873, 688), 'tw': (864, 672, 873, 688)}, color={'cn': (185, 183, 182), 'en': (185, 183, 182), 'jp': (185, 183, 182), 'tw': (185, 183, 182)}, button={'cn': (864, 672, 873, 688), 'en': (864, 672, 873, 688), 'jp': (864, 672, 873, 688), 'tw': (864, 672, 873, 688)}, file={'cn': './assets/cn/ui/IDLE_3.png', 'en': './assets/cn/ui/IDLE_3.png', 'jp': './assets/cn/ui/IDLE_3.png', 'tw': './assets/cn/ui/IDLE_3.png'}) +ISLAND_CHECK = Button(area={'cn': (1208, 24, 1248, 64), 'en': (1208, 24, 1248, 64), 'jp': (1208, 24, 1248, 64), 'tw': (1208, 24, 1248, 64)}, color={'cn': (209, 210, 206), 'en': (209, 210, 206), 'jp': (209, 210, 206), 'tw': (209, 210, 206)}, button={'cn': (1208, 24, 1248, 64), 'en': (1208, 24, 1248, 64), 'jp': (1208, 24, 1248, 64), 'tw': (1208, 24, 1248, 64)}, file={'cn': './assets/cn/ui/ISLAND_CHECK.png', 'en': './assets/cn/ui/ISLAND_CHECK.png', 'jp': './assets/cn/ui/ISLAND_CHECK.png', 'tw': './assets/cn/ui/ISLAND_CHECK.png'}) +ISLAND_COMMISSION_CHECK = Button(area={'cn': (199, 134, 373, 184), 'en': (199, 134, 373, 184), 'jp': (199, 134, 373, 184), 'tw': (199, 134, 373, 184)}, color={'cn': (195, 194, 192), 'en': (195, 194, 192), 'jp': (195, 194, 192), 'tw': (195, 194, 192)}, button={'cn': (199, 134, 373, 184), 'en': (199, 134, 373, 184), 'jp': (199, 134, 373, 184), 'tw': (199, 134, 373, 184)}, file={'cn': './assets/cn/ui/ISLAND_COMMISSION_CHECK.png', 'en': './assets/cn/ui/ISLAND_COMMISSION_CHECK.png', 'jp': './assets/jp/ui/ISLAND_COMMISSION_CHECK.png', 'tw': './assets/cn/ui/ISLAND_COMMISSION_CHECK.png'}) +ISLAND_GOTO_ISLAND_MAP = Button(area={'cn': (840, 23, 880, 63), 'en': (840, 23, 880, 63), 'jp': (840, 23, 880, 63), 'tw': (840, 23, 880, 63)}, color={'cn': (197, 199, 196), 'en': (197, 199, 196), 'jp': (197, 199, 196), 'tw': (197, 199, 196)}, button={'cn': (840, 23, 880, 63), 'en': (840, 23, 880, 63), 'jp': (840, 23, 880, 63), 'tw': (840, 23, 880, 63)}, file={'cn': './assets/cn/ui/ISLAND_GOTO_ISLAND_MAP.png', 'en': './assets/cn/ui/ISLAND_GOTO_ISLAND_MAP.png', 'jp': './assets/cn/ui/ISLAND_GOTO_ISLAND_MAP.png', 'tw': './assets/cn/ui/ISLAND_GOTO_ISLAND_MAP.png'}) +ISLAND_GOTO_ISLAND_PHONE = Button(area={'cn': (1208, 24, 1248, 64), 'en': (1208, 24, 1248, 64), 'jp': (1208, 24, 1248, 64), 'tw': (1208, 24, 1248, 64)}, color={'cn': (209, 210, 206), 'en': (209, 210, 206), 'jp': (209, 210, 206), 'tw': (209, 210, 206)}, button={'cn': (1208, 24, 1248, 64), 'en': (1208, 24, 1248, 64), 'jp': (1208, 24, 1248, 64), 'tw': (1208, 24, 1248, 64)}, file={'cn': './assets/cn/ui/ISLAND_GOTO_ISLAND_PHONE.png', 'en': './assets/cn/ui/ISLAND_GOTO_ISLAND_PHONE.png', 'jp': './assets/cn/ui/ISLAND_GOTO_ISLAND_PHONE.png', 'tw': './assets/cn/ui/ISLAND_GOTO_ISLAND_PHONE.png'}) +ISLAND_GOTO_ISLAND_SEASON = Button(area={'cn': (931, 24, 971, 64), 'en': (931, 24, 971, 64), 'jp': (931, 24, 971, 64), 'tw': (931, 24, 971, 64)}, color={'cn': (202, 204, 200), 'en': (202, 204, 200), 'jp': (202, 204, 200), 'tw': (202, 204, 200)}, button={'cn': (931, 24, 971, 64), 'en': (931, 24, 971, 64), 'jp': (931, 24, 971, 64), 'tw': (931, 24, 971, 64)}, file={'cn': './assets/cn/ui/ISLAND_GOTO_ISLAND_SEASON.png', 'en': './assets/cn/ui/ISLAND_GOTO_ISLAND_SEASON.png', 'jp': './assets/cn/ui/ISLAND_GOTO_ISLAND_SEASON.png', 'tw': './assets/cn/ui/ISLAND_GOTO_ISLAND_SEASON.png'}) +ISLAND_GOTO_ISLAND_SHOP = Button(area={'cn': (747, 25, 787, 65), 'en': (747, 25, 787, 65), 'jp': (747, 25, 787, 65), 'tw': (747, 25, 787, 65)}, color={'cn': (195, 196, 194), 'en': (195, 196, 194), 'jp': (195, 196, 194), 'tw': (195, 196, 194)}, button={'cn': (747, 25, 787, 65), 'en': (747, 25, 787, 65), 'jp': (747, 25, 787, 65), 'tw': (747, 25, 787, 65)}, file={'cn': './assets/cn/ui/ISLAND_GOTO_ISLAND_SHOP.png', 'en': './assets/cn/ui/ISLAND_GOTO_ISLAND_SHOP.png', 'jp': './assets/cn/ui/ISLAND_GOTO_ISLAND_SHOP.png', 'tw': './assets/cn/ui/ISLAND_GOTO_ISLAND_SHOP.png'}) +ISLAND_GOTO_ISLAND_TECHNOLOGY = Button(area={'cn': (1025, 26, 1068, 65), 'en': (1025, 26, 1068, 65), 'jp': (1025, 26, 1068, 65), 'tw': (1025, 26, 1068, 65)}, color={'cn': (181, 182, 179), 'en': (181, 182, 179), 'jp': (181, 182, 179), 'tw': (181, 182, 179)}, button={'cn': (1025, 26, 1068, 65), 'en': (1025, 26, 1068, 65), 'jp': (1025, 26, 1068, 65), 'tw': (1025, 26, 1068, 65)}, file={'cn': './assets/cn/ui/ISLAND_GOTO_ISLAND_TECHNOLOGY.png', 'en': './assets/cn/ui/ISLAND_GOTO_ISLAND_TECHNOLOGY.png', 'jp': './assets/cn/ui/ISLAND_GOTO_ISLAND_TECHNOLOGY.png', 'tw': './assets/cn/ui/ISLAND_GOTO_ISLAND_TECHNOLOGY.png'}) +ISLAND_MANAGE_CHECK = Button(area={'cn': (123, 19, 219, 45), 'en': (123, 19, 219, 45), 'jp': (123, 19, 219, 45), 'tw': (123, 19, 219, 45)}, color={'cn': (85, 90, 102), 'en': (85, 90, 102), 'jp': (85, 90, 102), 'tw': (85, 90, 102)}, button={'cn': (123, 19, 219, 45), 'en': (123, 19, 219, 45), 'jp': (123, 19, 219, 45), 'tw': (123, 19, 219, 45)}, file={'cn': './assets/cn/ui/ISLAND_MANAGE_CHECK.png', 'en': './assets/cn/ui/ISLAND_MANAGE_CHECK.png', 'jp': './assets/jp/ui/ISLAND_MANAGE_CHECK.png', 'tw': './assets/cn/ui/ISLAND_MANAGE_CHECK.png'}) +ISLAND_MAP_CHECK = Button(area={'cn': (122, 19, 217, 47), 'en': (122, 19, 217, 47), 'jp': (122, 19, 217, 47), 'tw': (122, 19, 217, 47)}, color={'cn': (93, 102, 112), 'en': (93, 102, 112), 'jp': (93, 102, 112), 'tw': (93, 102, 112)}, button={'cn': (122, 19, 217, 47), 'en': (122, 19, 217, 47), 'jp': (122, 19, 217, 47), 'tw': (122, 19, 217, 47)}, file={'cn': './assets/cn/ui/ISLAND_MAP_CHECK.png', 'en': './assets/cn/ui/ISLAND_MAP_CHECK.png', 'jp': './assets/jp/ui/ISLAND_MAP_CHECK.png', 'tw': './assets/cn/ui/ISLAND_MAP_CHECK.png'}) +ISLAND_ORDER_CHECK = Button(area={'cn': (125, 19, 220, 46), 'en': (125, 19, 220, 46), 'jp': (125, 19, 220, 46), 'tw': (125, 19, 220, 46)}, color={'cn': (79, 88, 100), 'en': (79, 88, 100), 'jp': (79, 88, 100), 'tw': (79, 88, 100)}, button={'cn': (125, 19, 220, 46), 'en': (125, 19, 220, 46), 'jp': (125, 19, 220, 46), 'tw': (125, 19, 220, 46)}, file={'cn': './assets/cn/ui/ISLAND_ORDER_CHECK.png', 'en': './assets/cn/ui/ISLAND_ORDER_CHECK.png', 'jp': './assets/jp/ui/ISLAND_ORDER_CHECK.png', 'tw': './assets/cn/ui/ISLAND_ORDER_CHECK.png'}) +ISLAND_PHONE_CHECK = Button(area={'cn': (1033, 638, 1080, 684), 'en': (1033, 638, 1080, 684), 'jp': (1033, 638, 1080, 684), 'tw': (1033, 638, 1080, 684)}, color={'cn': (113, 126, 143), 'en': (113, 126, 143), 'jp': (113, 126, 143), 'tw': (113, 126, 143)}, button={'cn': (1033, 638, 1080, 684), 'en': (1033, 638, 1080, 684), 'jp': (1033, 638, 1080, 684), 'tw': (1033, 638, 1080, 684)}, file={'cn': './assets/cn/ui/ISLAND_PHONE_CHECK.png', 'en': './assets/cn/ui/ISLAND_PHONE_CHECK.png', 'jp': './assets/cn/ui/ISLAND_PHONE_CHECK.png', 'tw': './assets/cn/ui/ISLAND_PHONE_CHECK.png'}) +ISLAND_PHONE_GOTO_ISLAND = Button(area={'cn': (1033, 638, 1080, 684), 'en': (1033, 638, 1080, 684), 'jp': (1033, 638, 1080, 684), 'tw': (1033, 638, 1080, 684)}, color={'cn': (113, 126, 143), 'en': (113, 126, 143), 'jp': (113, 126, 143), 'tw': (113, 126, 143)}, button={'cn': (0, 0, 829, 720), 'en': (0, 0, 829, 720), 'jp': (0, 0, 829, 720), 'tw': (0, 0, 829, 720)}, file={'cn': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.png', 'en': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.png', 'jp': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.png', 'tw': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND.png'}) +ISLAND_PHONE_GOTO_ISLAND_COMMISSION = Button(area={'cn': (1033, 638, 1080, 684), 'en': (1033, 638, 1080, 684), 'jp': (1033, 638, 1080, 684), 'tw': (1033, 638, 1080, 684)}, color={'cn': (113, 126, 143), 'en': (113, 126, 143), 'jp': (113, 126, 143), 'tw': (113, 126, 143)}, button={'cn': (897, 329, 1032, 458), 'en': (897, 329, 1032, 458), 'jp': (897, 329, 1032, 458), 'tw': (897, 329, 1032, 458)}, file={'cn': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.png', 'en': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.png', 'jp': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.png', 'tw': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_COMMISSION.png'}) +ISLAND_PHONE_GOTO_ISLAND_MANAGE = Button(area={'cn': (1072, 485, 1125, 538), 'en': (1072, 485, 1125, 538), 'jp': (1072, 485, 1125, 538), 'tw': (1072, 485, 1125, 538)}, color={'cn': (180, 221, 124), 'en': (180, 221, 124), 'jp': (180, 221, 124), 'tw': (180, 221, 124)}, button={'cn': (1072, 485, 1125, 538), 'en': (1072, 485, 1125, 538), 'jp': (1072, 485, 1125, 538), 'tw': (1072, 485, 1125, 538)}, file={'cn': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_MANAGE.png', 'en': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_MANAGE.png', 'jp': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_MANAGE.png', 'tw': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_MANAGE.png'}) +ISLAND_PHONE_GOTO_ISLAND_ORDER = Button(area={'cn': (1033, 638, 1080, 684), 'en': (1033, 638, 1080, 684), 'jp': (1033, 638, 1080, 684), 'tw': (1033, 638, 1080, 684)}, color={'cn': (113, 126, 143), 'en': (113, 126, 143), 'jp': (113, 126, 143), 'tw': (113, 126, 143)}, button={'cn': (900, 200, 1212, 300), 'en': (900, 200, 1212, 300), 'jp': (900, 200, 1212, 300), 'tw': (900, 200, 1212, 300)}, file={'cn': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.png', 'en': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.png', 'jp': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.png', 'tw': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_ORDER.png'}) +ISLAND_PHONE_GOTO_ISLAND_STORAGE = Button(area={'cn': (899, 484, 955, 539), 'en': (899, 484, 955, 539), 'jp': (899, 484, 955, 539), 'tw': (899, 484, 955, 539)}, color={'cn': (138, 168, 215), 'en': (138, 168, 215), 'jp': (138, 168, 215), 'tw': (138, 168, 215)}, button={'cn': (899, 484, 955, 539), 'en': (899, 484, 955, 539), 'jp': (899, 484, 955, 539), 'tw': (899, 484, 955, 539)}, file={'cn': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_STORAGE.png', 'en': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_STORAGE.png', 'jp': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_STORAGE.png', 'tw': './assets/cn/ui/ISLAND_PHONE_GOTO_ISLAND_STORAGE.png'}) +ISLAND_PHONE_GOTO_MAIN = Button(area={'cn': (1033, 638, 1080, 684), 'en': (1033, 638, 1080, 684), 'jp': (1033, 638, 1080, 684), 'tw': (1033, 638, 1080, 684)}, color={'cn': (113, 126, 143), 'en': (113, 126, 143), 'jp': (113, 126, 143), 'tw': (113, 126, 143)}, button={'cn': (1033, 638, 1080, 684), 'en': (1033, 638, 1080, 684), 'jp': (1033, 638, 1080, 684), 'tw': (1033, 638, 1080, 684)}, file={'cn': './assets/cn/ui/ISLAND_PHONE_GOTO_MAIN.png', 'en': './assets/cn/ui/ISLAND_PHONE_GOTO_MAIN.png', 'jp': './assets/cn/ui/ISLAND_PHONE_GOTO_MAIN.png', 'tw': './assets/cn/ui/ISLAND_PHONE_GOTO_MAIN.png'}) +ISLAND_SEASON_CHECK = Button(area={'cn': (125, 21, 218, 46), 'en': (125, 21, 218, 46), 'jp': (125, 21, 218, 46), 'tw': (125, 21, 218, 46)}, color={'cn': (60, 68, 81), 'en': (60, 68, 81), 'jp': (60, 68, 81), 'tw': (60, 68, 81)}, button={'cn': (125, 21, 218, 46), 'en': (125, 21, 218, 46), 'jp': (125, 21, 218, 46), 'tw': (125, 21, 218, 46)}, file={'cn': './assets/cn/ui/ISLAND_SEASON_CHECK.png', 'en': './assets/cn/ui/ISLAND_SEASON_CHECK.png', 'jp': './assets/jp/ui/ISLAND_SEASON_CHECK.png', 'tw': './assets/cn/ui/ISLAND_SEASON_CHECK.png'}) +ISLAND_SHOP_CHECK = Button(area={'cn': (994, 25, 1016, 46), 'en': (994, 25, 1016, 46), 'jp': (994, 25, 1016, 46), 'tw': (994, 25, 1016, 46)}, color={'cn': (220, 188, 75), 'en': (220, 188, 75), 'jp': (220, 188, 75), 'tw': (220, 188, 75)}, button={'cn': (994, 25, 1016, 46), 'en': (994, 25, 1016, 46), 'jp': (994, 25, 1016, 46), 'tw': (994, 25, 1016, 46)}, file={'cn': './assets/cn/ui/ISLAND_SHOP_CHECK.png', 'en': './assets/cn/ui/ISLAND_SHOP_CHECK.png', 'jp': './assets/cn/ui/ISLAND_SHOP_CHECK.png', 'tw': './assets/cn/ui/ISLAND_SHOP_CHECK.png'}) +ISLAND_STORAGE_CHECK = Button(area={'cn': (159, 99, 195, 117), 'en': (159, 99, 195, 117), 'jp': (159, 99, 195, 117), 'tw': (159, 99, 195, 117)}, color={'cn': (104, 105, 108), 'en': (104, 105, 108), 'jp': (104, 105, 108), 'tw': (104, 105, 108)}, button={'cn': (159, 99, 195, 117), 'en': (159, 99, 195, 117), 'jp': (159, 99, 195, 117), 'tw': (159, 99, 195, 117)}, file={'cn': './assets/cn/ui/ISLAND_STORAGE_CHECK.png', 'en': './assets/cn/ui/ISLAND_STORAGE_CHECK.png', 'jp': './assets/jp/ui/ISLAND_STORAGE_CHECK.png', 'tw': './assets/cn/ui/ISLAND_STORAGE_CHECK.png'}) +ISLAND_STORAGE_EXIT = Button(area={'cn': (1124, 86, 1163, 123), 'en': (1124, 86, 1163, 123), 'jp': (1124, 86, 1163, 123), 'tw': (1124, 86, 1163, 123)}, color={'cn': (228, 228, 228), 'en': (228, 228, 228), 'jp': (228, 228, 228), 'tw': (228, 228, 228)}, button={'cn': (1124, 86, 1163, 123), 'en': (1124, 86, 1163, 123), 'jp': (1124, 86, 1163, 123), 'tw': (1124, 86, 1163, 123)}, file={'cn': './assets/cn/ui/ISLAND_STORAGE_EXIT.png', 'en': './assets/cn/ui/ISLAND_STORAGE_EXIT.png', 'jp': './assets/cn/ui/ISLAND_STORAGE_EXIT.png', 'tw': './assets/cn/ui/ISLAND_STORAGE_EXIT.png'}) +ISLAND_TECHNOLOGY_CHECK = Button(area={'cn': (123, 19, 218, 46), 'en': (123, 19, 218, 46), 'jp': (123, 19, 218, 46), 'tw': (123, 19, 218, 46)}, color={'cn': (86, 90, 102), 'en': (86, 90, 102), 'jp': (86, 90, 102), 'tw': (86, 90, 102)}, button={'cn': (123, 19, 218, 46), 'en': (123, 19, 218, 46), 'jp': (123, 19, 218, 46), 'tw': (123, 19, 218, 46)}, file={'cn': './assets/cn/ui/ISLAND_TECHNOLOGY_CHECK.png', 'en': './assets/cn/ui/ISLAND_TECHNOLOGY_CHECK.png', 'jp': './assets/jp/ui/ISLAND_TECHNOLOGY_CHECK.png', 'tw': './assets/cn/ui/ISLAND_TECHNOLOGY_CHECK.png'}) MAIN_GOTO_BUILD = Button(area={'cn': (958, 665, 1113, 714), 'en': (962, 688, 1109, 711), 'jp': (1035, 674, 1088, 702), 'tw': (963, 671, 1091, 709)}, color={'cn': (129, 83, 76), 'en': (145, 88, 79), 'jp': (187, 131, 125), 'tw': (145, 93, 85)}, button={'cn': (958, 665, 1113, 714), 'en': (962, 688, 1109, 711), 'jp': (1035, 674, 1088, 702), 'tw': (963, 671, 1091, 709)}, file={'cn': './assets/cn/ui/MAIN_GOTO_BUILD.png', 'en': './assets/en/ui/MAIN_GOTO_BUILD.png', 'jp': './assets/jp/ui/MAIN_GOTO_BUILD.png', 'tw': './assets/tw/ui/MAIN_GOTO_BUILD.png'}) MAIN_GOTO_CAMPAIGN = Button(area={'cn': (1029, 304, 1102, 342), 'en': (1027, 307, 1118, 333), 'jp': (1019, 305, 1096, 343), 'tw': (1026, 303, 1104, 344)}, color={'cn': (240, 213, 157), 'en': (243, 219, 165), 'jp': (239, 210, 155), 'tw': (240, 213, 162)}, button={'cn': (1021, 292, 1160, 432), 'en': (1018, 293, 1156, 431), 'jp': (1006, 288, 1155, 436), 'tw': (1014, 288, 1159, 433)}, file={'cn': './assets/cn/ui/MAIN_GOTO_CAMPAIGN.png', 'en': './assets/en/ui/MAIN_GOTO_CAMPAIGN.png', 'jp': './assets/jp/ui/MAIN_GOTO_CAMPAIGN.png', 'tw': './assets/tw/ui/MAIN_GOTO_CAMPAIGN.png'}) MAIN_GOTO_DOCK = Button(area={'cn': (172, 668, 326, 715), 'en': (172, 668, 326, 715), 'jp': (172, 668, 326, 715), 'tw': (172, 668, 326, 715)}, color={'cn': (72, 90, 122), 'en': (72, 90, 122), 'jp': (72, 90, 122), 'tw': (72, 90, 122)}, button={'cn': (172, 668, 326, 715), 'en': (172, 668, 326, 715), 'jp': (172, 668, 326, 715), 'tw': (172, 668, 326, 715)}, file={'cn': './assets/cn/ui/MAIN_GOTO_DOCK.png', 'en': './assets/en/ui/MAIN_GOTO_DOCK.png', 'jp': './assets/jp/ui/MAIN_GOTO_DOCK.png', 'tw': './assets/tw/ui/MAIN_GOTO_DOCK.png'}) diff --git a/module/ui/page.py b/module/ui/page.py index 2705a53a43..1a780e9646 100644 --- a/module/ui/page.py +++ b/module/ui/page.py @@ -70,6 +70,9 @@ def __hash__(self): def __str__(self): return self.name + def is_island(self): + return self.name == 'page_island' or self.name.startswith('page_island_') + def link(self, button, destination): self.links[destination] = button @@ -287,6 +290,47 @@ def link(self, button, destination): page_dormmenu.link(button=DORMMENU_GOTO_PRIVATE_QUARTERS, destination=page_private_quarters) page_private_quarters.link(button=PQ_GOTO_MAIN, destination=page_main) +# Island +page_island = Page(ISLAND_CHECK) +page_dormmenu.link(button=DORMMENU_GOTO_ISLAND, destination=page_island) + +page_island_shop = Page(ISLAND_SHOP_CHECK) # use coin icon as check +page_island.link(button=ISLAND_GOTO_ISLAND_SHOP, destination=page_island_shop) +page_island_shop.link(button=BACK_ARROW_WHITE, destination=page_island) + +page_island_map = Page(ISLAND_MAP_CHECK) +page_island.link(button=ISLAND_GOTO_ISLAND_MAP, destination=page_island_map) +page_island_map.link(button=BACK_ARROW_WHITE, destination=page_island) + +page_island_season = Page(ISLAND_SEASON_CHECK) +page_island.link(button=ISLAND_GOTO_ISLAND_SEASON, destination=page_island_season) +page_island_season.link(button=BACK_ARROW_WHITE, destination=page_island) + +page_island_technology = Page(ISLAND_TECHNOLOGY_CHECK) +page_island.link(button=ISLAND_GOTO_ISLAND_TECHNOLOGY, destination=page_island_technology) +page_island_technology.link(button=BACK_ARROW_WHITE, destination=page_island) + +page_island_phone = Page(ISLAND_PHONE_CHECK) +page_island.link(button=ISLAND_GOTO_ISLAND_PHONE, destination=page_island_phone) +page_island_phone.link(button=ISLAND_PHONE_GOTO_ISLAND, destination=page_island) +page_island_phone.link(button=ISLAND_PHONE_GOTO_MAIN, destination=page_main) + +page_island_order = Page(ISLAND_ORDER_CHECK) +page_island_phone.link(button=ISLAND_PHONE_GOTO_ISLAND_ORDER, destination=page_island_order) +page_island_order.link(button=BACK_ARROW_WHITE, destination=page_island_phone) + +page_island_commission = Page(ISLAND_COMMISSION_CHECK) # IslandShipOrder +page_island_phone.link(button=ISLAND_PHONE_GOTO_ISLAND_COMMISSION, destination=page_island_commission) +page_island_commission.link(button=BACK_ARROW_WHITE, destination=page_island_phone) + +page_island_storage = Page(ISLAND_STORAGE_CHECK) +page_island_phone.link(button=ISLAND_PHONE_GOTO_ISLAND_STORAGE, destination=page_island_storage) +page_island_storage.link(button=ISLAND_STORAGE_EXIT, destination=page_island_phone) + +page_island_manage = Page(ISLAND_MANAGE_CHECK) +page_island_phone.link(button=ISLAND_PHONE_GOTO_ISLAND_MANAGE, destination=page_island_manage) +page_island_manage.link(button=BACK_ARROW_WHITE, destination=page_island_phone) + # Game room & choose game page_game_room = Page(GAME_ROOM_CHECK) page_academy.link(button=ACADEMY_GOTO_GAME_ROOM, destination=page_game_room) diff --git a/module/ui/ui.py b/module/ui/ui.py index 92ec1a8255..0a92ba8a98 100644 --- a/module/ui/ui.py +++ b/module/ui/ui.py @@ -239,6 +239,7 @@ def ui_goto(self, destination, get_ship=True, offset=(30, 30), skip_first_screen self.interval_clear(list(Page.iter_check_buttons())) logger.hr(f"UI goto {destination}") + island_page_detected = False while 1: GOTO_MAIN.clear_offset() if skip_first_screenshot: @@ -249,6 +250,7 @@ def ui_goto(self, destination, get_ship=True, offset=(30, 30), skip_first_screen # Destination page if self.ui_page_appear(page=destination, offset=offset): logger.info(f'Page arrive: {destination}') + self.ui_current = destination break # Other pages @@ -257,6 +259,8 @@ def ui_goto(self, destination, get_ship=True, offset=(30, 30), skip_first_screen if page.parent is None or page.check_button is None: continue if self.appear(page.check_button, offset=offset, interval=5): + self.ui_current = page + island_page_detected = page.is_island() or page.parent.is_island() logger.info(f'Page switch: {page} -> {page.parent}') button = page.links[page.parent] self.device.click(button) @@ -267,7 +271,7 @@ def ui_goto(self, destination, get_ship=True, offset=(30, 30), skip_first_screen continue # Additional - if self.ui_additional(get_ship=get_ship): + if self.ui_additional(get_ship=get_ship and not island_page_detected): continue # Reset connection