Merge pull request #508 from LmeSzinc/dev

Dev
This commit is contained in:
LmeSzinc 2024-06-12 12:30:44 +08:00 committed by GitHub
commit fc8ce808ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 315 additions and 82 deletions

View File

@ -47,6 +47,7 @@
"ServerUpdate": "04:00"
},
"Planner": {
"PlannerOverall": {},
"Item_Credit": {},
"Item_Trailblaze_EXP": {},
"Item_Traveler_Guide": {},

View File

@ -66,6 +66,8 @@ class GenerateItemBase(GenerateKeyword):
continue
dic.setdefault(item_id, dungeon_id)
# Credict
dic.setdefault(2, 1003)
return dic
@ -79,6 +81,7 @@ class GenerateItemCurrency(GenerateItemBase):
if data['subtype'] == 'Virtual' and data['item_id'] < 100:
if data['item_id'] not in self.whitelist:
continue
data['dungeon_id'] = self.dict_itemid_to_dungeonid.get(data['item_id'], -1)
yield data

View File

@ -101,7 +101,7 @@ class AzurLaneAutoScript:
self.device.sleep(10)
return False
except GamePageUnknownError:
logger.info('Game server may be under maintenance or network may be broken, check server status now')
# logger.info('Game server may be under maintenance or network may be broken, check server status now')
self.checker.check_now()
if self.checker.is_available():
logger.critical('Game page unknown')

View File

@ -57,12 +57,12 @@ class Button(Resource):
return load_image(self.file, self.area)
@cached_property
def image_binary(self):
return rgb2gray(self.image)
def image_luma(self):
return rgb2luma(self.image)
def resource_release(self):
del_cached_property(self, 'image')
del_cached_property(self, 'image_binary')
del_cached_property(self, 'image_luma')
self.clear_offset()
def __str__(self):
@ -119,7 +119,7 @@ class Button(Resource):
self._button_offset = np.array(point) + self.search[:2] - self.area[:2]
return sim > similarity
def match_template_binary(self, image, similarity=0.85, direct_match=False) -> bool:
def match_template_luma(self, image, similarity=0.85, direct_match=False) -> bool:
"""
Detects assets by template matching.
@ -135,8 +135,8 @@ class Button(Resource):
"""
if not direct_match:
image = crop(image, self.search, copy=False)
image = rgb2gray(image)
res = cv2.matchTemplate(self.image_binary, image, cv2.TM_CCOEFF_NORMED)
image = rgb2luma(image)
res = cv2.matchTemplate(self.image_luma, image, cv2.TM_CCOEFF_NORMED)
_, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = np.array(point) + self.search[:2] - self.area[:2]
@ -254,9 +254,9 @@ class ButtonWrapper(Resource):
return True
return False
def match_template_binary(self, image, similarity=0.85, direct_match=False) -> bool:
def match_template_luma(self, image, similarity=0.85, direct_match=False) -> bool:
for assets in self.buttons:
if assets.match_template_binary(image, similarity=similarity, direct_match=direct_match):
if assets.match_template_luma(image, similarity=similarity, direct_match=direct_match):
self._matched_button = assets
return True
return False

View File

@ -220,6 +220,14 @@
}
},
"Planner": {
"PlannerOverall": {
"type": "stored",
"value": {},
"display": true,
"stored": "StoredPlannerOverall",
"order": 4,
"color": "#85e7f2"
},
"Item_Credit": {
"type": "planner",
"value": {},
@ -729,9 +737,7 @@
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredEchoOfWar",
"order": 4,
"color": "#85e7f2"
"stored": "StoredEchoOfWar"
},
"SimulatedUniverse": {
"type": "stored",

View File

@ -129,15 +129,18 @@ DungeonStorage:
stored: StoredDungeonDouble
EchoOfWar:
stored: StoredEchoOfWar
order: 4
color: "#85e7f2"
SimulatedUniverse:
stored: StoredSimulatedUniverse
order: 6
color: "#8fb5fe"
SupportReward:
Collect: true
Planner: {}
Planner:
PlannerOverall:
stored: StoredPlannerOverall
display: true
order: 4
color: "#85e7f2"
# Items will be injected in config updater
Weekly:

View File

@ -61,6 +61,8 @@ Dashboard:
HoursAgo:
DaysAgo:
LongTimeAgo:
# Planner
EtaDays:
AddAlas:
PopupTitle:

View File

@ -38,15 +38,15 @@
"order": 3,
"color": "#79dbc4"
},
"EchoOfWar": {
"name": "EchoOfWar",
"path": "Dungeon.DungeonStorage.EchoOfWar",
"i18n": "DungeonStorage.EchoOfWar.name",
"stored": "StoredEchoOfWar",
"PlannerOverall": {
"name": "PlannerOverall",
"path": "Dungeon.Planner.PlannerOverall",
"i18n": "Planner.PlannerOverall.name",
"stored": "StoredPlannerOverall",
"attrs": {
"time": "2020-01-01 00:00:00",
"total": 3,
"value": 0
"comment": "<??d",
"value": "??%"
},
"order": 4,
"color": "#85e7f2"
@ -714,6 +714,19 @@
"order": 0,
"color": "#777777"
},
"EchoOfWar": {
"name": "EchoOfWar",
"path": "Dungeon.DungeonStorage.EchoOfWar",
"i18n": "DungeonStorage.EchoOfWar.name",
"stored": "StoredEchoOfWar",
"attrs": {
"time": "2020-01-01 00:00:00",
"total": 3,
"value": 0
},
"order": 0,
"color": "#777777"
},
"DailyQuest": {
"name": "DailyQuest",
"path": "DailyQuest.DailyStorage.DailyQuest",

View File

@ -53,7 +53,7 @@ class GeneratedConfig:
# Group `DungeonSupport`
DungeonSupport_Use = 'when_daily' # always_use, when_daily, do_not_use
DungeonSupport_Character = 'FirstCharacter' # FirstCharacter, Acheron, Argenti, Arlan, Asta, Aventurine, Bailu, BlackSwan, Blade, Boothill, Bronya, Clara, DanHeng, DanHengImbibitorLunae, DrRatio, FuXuan, Gallagher, Gepard, Guinaifen, Hanya, Herta, Himeko, Hook, Huohuo, JingYuan, Jingliu, Kafka, Luka, Luocha, Lynx, March7th, Misha, Natasha, Pela, Qingque, Robin, RuanMei, Sampo, Seele, Serval, SilverWolf, Sparkle, Sushang, Tingyun, TopazNumby, TrailblazerDestruction, TrailblazerPreservation, Welt, Xueyi, Yanqing, Yukong
DungeonSupport_Character = 'FirstCharacter' # FirstCharacter, Acheron, Argenti, Arlan, Asta, Aventurine, Bailu, BlackSwan, Blade, Boothill, Bronya, Clara, DanHeng, DanHengImbibitorLunae, DrRatio, FuXuan, Gallagher, Gepard, Guinaifen, Hanya, Herta, Himeko, Hook, Huohuo, JingYuan, Jingliu, Kafka, Luka, Luocha, Lynx, March7th, Misha, Natasha, Pela, Qingque, Robin, RuanMei, Sampo, Seele, Serval, SilverWolf, Sparkle, Sushang, Tingyun, TopazNumby, TrailblazerDestruction, TrailblazerHarmony, TrailblazerPreservation, Welt, Xueyi, Yanqing, Yukong
# Group `DungeonStorage`
DungeonStorage_TrailblazePower = {}
@ -66,6 +66,7 @@ class GeneratedConfig:
SupportReward_Collect = True
# Group `Planner`
Planner_PlannerOverall = {}
Planner_Item_Credit = {}
Planner_Item_Trailblaze_EXP = {}
Planner_Item_Traveler_Guide = {}

View File

@ -465,6 +465,10 @@
"name": "Character Planner Progress",
"help": "Character planner is prioritized. After completed, \"Dungeon Settings\" will be executed."
},
"PlannerOverall": {
"name": "Planner",
"help": "Overall progress of character planner"
},
"Item_Credit": {
"name": "Credit",
"help": ""
@ -1269,7 +1273,8 @@
"MinutesAgo": "{time}min ago",
"HoursAgo": "{time}h ago",
"DaysAgo": "{time}d ago",
"LongTimeAgo": "long time ago"
"LongTimeAgo": "long time ago",
"EtaDays": "ETA {time}d"
},
"AddAlas": {
"PopupTitle": "Add new config",

View File

@ -465,6 +465,10 @@
"name": "Progreso del planificador de personajes",
"help": "Se prioriza el planificador de personajes. Una vez completado, se ejecutará la \"Ajustes de Mazmorra\"."
},
"PlannerOverall": {
"name": "Plan.",
"help": "Progreso general del planificador de personajes"
},
"Item_Credit": {
"name": "Crédito",
"help": ""
@ -1269,7 +1273,8 @@
"MinutesAgo": "hace {time}m",
"HoursAgo": "hace {time}h",
"DaysAgo": "hace {time}d",
"LongTimeAgo": "hace mucho tiempo"
"LongTimeAgo": "hace mucho tiempo",
"EtaDays": "ETA {time}d"
},
"AddAlas": {
"PopupTitle": "Añadir nueva configuración",

View File

@ -465,6 +465,10 @@
"name": "Planner._info.name",
"help": "Planner._info.help"
},
"PlannerOverall": {
"name": "Planner.PlannerOverall.name",
"help": "Planner.PlannerOverall.help"
},
"Item_Credit": {
"name": "信用ポイント",
"help": ""
@ -1269,7 +1273,8 @@
"MinutesAgo": "Gui.Dashboard.MinutesAgo",
"HoursAgo": "Gui.Dashboard.HoursAgo",
"DaysAgo": "Gui.Dashboard.DaysAgo",
"LongTimeAgo": "Gui.Dashboard.LongTimeAgo"
"LongTimeAgo": "Gui.Dashboard.LongTimeAgo",
"EtaDays": "Gui.Dashboard.EtaDays"
},
"AddAlas": {
"PopupTitle": "新しいコンフィグを追加",

View File

@ -465,6 +465,10 @@
"name": "养成规划进度",
"help": "优先执行养成规划,养成规划完成后,执行 \"每日副本设置\""
},
"PlannerOverall": {
"name": "养成规划",
"help": "角色养成规划总体进度"
},
"Item_Credit": {
"name": "信用点",
"help": ""
@ -1269,7 +1273,8 @@
"MinutesAgo": "{time}分钟前",
"HoursAgo": "{time}小时前",
"DaysAgo": "{time}天前",
"LongTimeAgo": "很久以前"
"LongTimeAgo": "很久以前",
"EtaDays": "剩余{time}天"
},
"AddAlas": {
"PopupTitle": "添加新配置",

View File

@ -465,6 +465,10 @@
"name": "養成規劃進度",
"help": "優先執行養成規劃,養成規劃完成後,執行 \"每日副本設定\""
},
"PlannerOverall": {
"name": "養成規劃",
"help": "角色養成規劃總體進度"
},
"Item_Credit": {
"name": "信用點",
"help": ""
@ -1269,7 +1273,8 @@
"MinutesAgo": "{time}分鐘前",
"HoursAgo": "{time}小時前",
"DaysAgo": "{time}天前",
"LongTimeAgo": "很久以前"
"LongTimeAgo": "很久以前",
"EtaDays": "剩餘{time}日"
},
"AddAlas": {
"PopupTitle": "添加新的設定",

View File

@ -404,3 +404,8 @@ class StoredPlanner(StoredBase):
value: int
total: int
synthesize: int
class StoredPlannerOverall(StoredBase):
value: str = '??%'
comment: str = '<??d'

View File

@ -20,6 +20,7 @@ from module.config.stored.classes import (
StoredImmersifier,
StoredInt,
StoredPlanner,
StoredPlannerOverall,
StoredSimulatedUniverse,
StoredSimulatedUniverseElite,
StoredTrailblazePower,
@ -33,6 +34,7 @@ class StoredGenerated:
CloudRemainSeasonPass = StoredInt("Alas.CloudStorage.CloudRemainSeasonPass")
CloudRemainPaid = StoredInt("Alas.CloudStorage.CloudRemainPaid")
CloudRemainFree = StoredInt("Alas.CloudStorage.CloudRemainFree")
PlannerOverall = StoredPlannerOverall("Dungeon.Planner.PlannerOverall")
Item_Credit = StoredPlanner("Dungeon.Planner.Item_Credit")
Item_Trailblaze_EXP = StoredPlanner("Dungeon.Planner.Item_Trailblaze_EXP")
Item_Traveler_Guide = StoredPlanner("Dungeon.Planner.Item_Traveler_Guide")

View File

@ -341,6 +341,11 @@ class AlasGUI(Frame):
put_text(config.get("value", nodata)).style("--dashboard-value--"),
put_text(f' / {config.get("total", "")}').style("--dashboard-time--"),
]
elif "comment" in dic.get("attrs", []) and config.get("comment") is not None:
return [
put_text(config.get("value", nodata)).style("--dashboard-value--"),
put_text(f' {config.get("comment", "")}').style("--dashboard-time--"),
]
else:
return [
put_text(config.get("value", nodata)).style("--dashboard-value--"),

View File

@ -346,6 +346,7 @@ def put_arg_stored(kwargs: T_Output_Kwargs) -> Output:
value = values.pop("value", "")
total = values.pop("total", "")
time_ = values.pop("time", "")
comment = values.pop("comment", "")
if value != "" and total != "":
# 0 / 100
@ -353,6 +354,12 @@ def put_arg_stored(kwargs: T_Output_Kwargs) -> Output:
put_text(value).style("--dashboard-value--"),
put_text(f" / {total}").style("--dashboard-time--"),
])]
elif value != "" and comment != "":
# 88% <1.2d
rows = [put_scope(f"dashboard-value-{name}", [
put_text(value).style("--dashboard-value--"),
put_text(f" {comment}").style("--dashboard-time--"),
])]
elif value != "":
# 100
rows = [put_scope(f"dashboard-value-{name}", [
@ -393,6 +400,11 @@ def put_arg_planner(kwargs: T_Output_Kwargs) -> Output | None:
except KeyError:
# Hide items not needed by the planner
return None
eta = values.get("eta", 0)
if eta > 0:
eta = f" - {t('Gui.Dashboard.EtaDays', time=eta)}"
else:
eta = ""
value = values.pop('value', 0)
if isinstance(value, dict):
@ -402,7 +414,7 @@ def put_arg_planner(kwargs: T_Output_Kwargs) -> Output | None:
total = tuple(total.values())
row = put_scope(f"arg_stored-stored-value-{name}", [
put_text(f"{progress:.2f}%").style("--dashboard-bold--"),
put_text(f"{progress:.2f}%{eta}").style("--dashboard-bold--"),
put_text(f"{value} / {total}").style("--dashboard-time--"),
])

View File

@ -96,7 +96,8 @@ class UI(MainPage):
if self.handle_popup_confirm():
timeout.reset()
continue
if self.appear_then_click(LOGIN_CONFIRM, interval=5):
if self.is_in_login_confirm(interval=5):
self.device.click(LOGIN_CONFIRM)
timeout.reset()
continue
if self.appear(MAP_LOADING, interval=5):
@ -169,7 +170,8 @@ class UI(MainPage):
continue
if self.handle_popup_confirm():
continue
if self.appear_then_click(LOGIN_CONFIRM, interval=5):
if self.is_in_login_confirm(interval=5):
self.device.click(LOGIN_CONFIRM)
continue
# Reset connection
@ -314,11 +316,11 @@ class UI(MainPage):
return False
appear = False
if MAIN_GOTO_CHARACTER.match_template_binary(self.device.image):
if MAIN_GOTO_CHARACTER.match_template_luma(self.device.image):
if self.image_color_count(MAIN_GOTO_CHARACTER, color=(235, 235, 235), threshold=234, count=400):
appear = True
if not appear:
if MAP_EXIT.match_template_binary(self.device.image):
if MAP_EXIT.match_template_luma(self.device.image):
if self.image_color_count(MAP_EXIT, color=(235, 235, 235), threshold=221, count=50):
appear = True
@ -327,6 +329,20 @@ class UI(MainPage):
return appear
def is_in_login_confirm(self, interval=0):
self.device.stuck_record_add(LOGIN_CONFIRM)
if interval and not self.interval_is_reached(LOGIN_CONFIRM, interval=interval):
return False
appear = LOGIN_CONFIRM.match_template_luma(self.device.image)
if appear and interval:
self.interval_reset(LOGIN_CONFIRM, interval=interval)
return appear
def is_in_map_exit(self, interval=0):
self.device.stuck_record_add(MAP_EXIT)
@ -334,7 +350,7 @@ class UI(MainPage):
return False
appear = False
if MAP_EXIT.match_template_binary(self.device.image):
if MAP_EXIT.match_template_luma(self.device.image):
if self.image_color_count(MAP_EXIT, color=(235, 235, 235), threshold=221, count=50):
appear = True

View File

@ -56,7 +56,8 @@ class Login(UI, LoginAndroidCloud):
orientation_timer.reset()
# Login
if self.appear_then_click(LOGIN_CONFIRM):
if self.is_in_login_confirm(interval=5):
self.device.click(LOGIN_CONFIRM)
login_success = True
continue
if self.appear_then_click(USER_AGREEMENT_ACCEPT):

View File

@ -14,7 +14,7 @@ Credit = ItemCurrency(
rarity='Rare',
item_id=2,
item_group=0,
dungeon_id=-1,
dungeon_id=1003,
)
Trailblaze_EXP = ItemCurrency(
id=2,

View File

@ -1,8 +1,9 @@
import math
import typing as t
from datetime import datetime
from functools import partial
from functools import cached_property as functools_cached_property, partial
from pydantic import BaseModel, ValidationError, WrapValidator, field_validator, model_validator
from pydantic import BaseModel, ValidationError, WrapValidator, computed_field, field_validator, model_validator
from module.base.decorator import cached_property, del_cached_property
from module.config.stored.classes import now
@ -74,16 +75,37 @@ class MultiValue(BaseModelWithFallback):
self.blue += other.blue
self.purple += other.purple
def __sub__(self, other):
green = max(self.green - other.green, 0)
blue = max(self.blue - other.blue, 0)
purple = max(self.purple - other.purple, 0)
return MultiValue(green=green, blue=blue, purple=purple)
def equivalent_green(self):
return self.green + self.blue * 3 + self.purple * 9
def clear(self):
self.green = 0
self.blue = 0
self.purple = 0
SET_ROW_EXCLUDE = {
'drop_equivalent_green',
'combat_cost',
'progress_remain',
'progress_total',
'progress_current',
}
class StoredPlannerProxy(BaseModelWithFallback):
item: ITEM_TYPES
value: int | MultiValue = 0
total: int | MultiValue = 0
synthesize: int | MultiValue = 0
progress: float = 0.
# progress: float = 0.
# eta: float = 0.
time: datetime = DEFAULT_TIME
@field_validator('item', mode='before')
@ -110,6 +132,12 @@ class StoredPlannerProxy(BaseModelWithFallback):
self.synthesize = 0
return self
def clear(self):
if self.item.has_group_base:
self.value.clear()
else:
self.value = 0
def update_synthesize(self):
if self.item.has_group_base:
green = self.value.green - self.total.green
@ -147,6 +175,82 @@ class StoredPlannerProxy(BaseModelWithFallback):
else:
self.value.blue += self.synthesize.purple * 3
@computed_field(repr=False)
@functools_cached_property
def drop_equivalent_green(self) -> float:
# Tracks_of_Destiny
if self.item.dungeon is None:
return 1
if self.item.dungeon.is_Calyx_Golden_Treasures:
return 24000
if self.item.dungeon.is_Calyx_Golden_Memories:
# purple, blue, green = 5, 1, 0
return 48
if self.item.dungeon.is_Calyx_Golden_Aether:
# purple, blue, green = 1, 2, 2.5
return 17.5
if self.item.is_ItemAscension:
return 3
if self.item.is_ItemTrace:
# purple, blue, green = 0.155, 1, 1.25
return 5.645
if self.item.is_ItemWeekly:
return 3
raise ScriptError(f'{self} has no drop_equivalent_green defined')
@computed_field(repr=False)
@functools_cached_property
def combat_cost(self) -> int:
# Tracks_of_Destiny
if self.item.dungeon is None:
return 30
if self.item.dungeon.is_Calyx_Golden:
return 10
if self.item.is_ItemAscension:
return 30
if self.item.is_ItemTrace:
return 10
if self.item.is_ItemWeekly:
return 30
raise ScriptError(f'{self} has no stamina_pre_combat defined')
@computed_field(repr=False)
@functools_cached_property
def progress_remain(self) -> float:
if self.item.has_group_base:
remain = self.total - self.value - self.synthesize
return remain.equivalent_green()
else:
remain = max(self.total - self.value, 0)
return remain
@computed_field(repr=False)
@functools_cached_property
def progress_total(self) -> float:
if self.item.has_group_base:
return self.total.equivalent_green()
else:
return self.total
@computed_field(repr=False)
@functools_cached_property
def progress_current(self) -> float:
if self.item.has_group_base:
current = self.progress_total - self.progress_remain
current = min(max(current, 0), self.progress_total)
return current
else:
current = self.value
current = min(max(current, 0), self.total)
return current
@computed_field
@functools_cached_property
def progress(self) -> float:
# 0 to 100
progress = self.progress_current / self.progress_total * 100
return round(min(max(progress, 0), 100), 2)
def is_approaching_total(self, wave_done: int = 0):
"""
Args:
@ -157,45 +261,46 @@ class StoredPlannerProxy(BaseModelWithFallback):
"""
wave_done = max(wave_done, 0)
# Items with a static drop rate will have `AVG * (wave_done + 1)
if self.item.dungeon.is_Calyx_Golden_Treasures:
return self.value + 24000 * (wave_done + 12) >= self.total
if self.item.dungeon.is_Calyx_Golden_Memories:
# purple, blue, green = 5, 1, 0
value = self.value.equivalent_green()
total = self.total.equivalent_green()
return value + 48 * (wave_done + 12) >= total
if self.item.dungeon.is_Calyx_Golden_Aether:
# purple, blue, green = 1, 2, 2.5
value = self.value.equivalent_green()
total = self.total.equivalent_green()
return value + 17.5 * (wave_done + 12) >= total
if self.item.is_ItemAscension:
return self.value + 3 * (wave_done + 1) >= self.total
if self.item.is_ItemTrace:
# purple, blue, green = 0.155, 1, 1.25
value = self.value.equivalent_green()
total = self.total.equivalent_green()
return value + 5.645 * (wave_done + 12) >= total
if self.item.is_ItemWeekly:
return self.value + 3 * (wave_done + 1) >= self.total
return False
def update_progress(self):
if self.item.has_group_base:
total = self.total.equivalent_green()
green = min(self.value.green, self.total.green)
blue = min(self.value.blue + self.synthesize.blue, self.total.blue)
purple = min(self.value.purple + self.synthesize.purple, self.total.purple)
value = green + blue * 3 + purple * 9
progress = value / total * 100
self.progress = round(min(max(progress, 0), 100), 2)
remain = self.progress_remain
cost = self.combat_cost
drop = self.drop_equivalent_green
if cost == 10:
return remain <= drop * (wave_done + 12)
else:
progress = self.value / self.total * 100
self.progress = round(min(max(progress, 0), 100), 2)
return remain <= drop * (wave_done + 1)
@computed_field
@functools_cached_property
def eta(self) -> float:
"""
Estimate remaining days to farm
"""
if not self.need_farm():
return 0.
if self.item.dungeon is None:
return 0.
remain = self.progress_remain
cost = self.combat_cost
drop = self.drop_equivalent_green
if self.item.is_ItemWeekly:
weeks = math.ceil(remain / drop / 3)
return weeks * 7
else:
stamina = math.ceil(remain / drop) * cost
return round(stamina / 240, 1)
def update(self, time=False):
for attr in SET_ROW_EXCLUDE:
del_cached_property(self, attr)
del_cached_property(self, 'progress')
del_cached_property(self, 'eta')
def update(self):
self.update_synthesize()
self.update_progress()
_ = self.progress
_ = self.eta
if time:
self.time = now()
def load_value_total(self, item: ItemBase, value=None, total=None, synthesize=None):
@ -344,9 +449,11 @@ class PlannerProgressParser:
def from_config(self, data):
self.rows = {}
for row in data.values():
for name, row in data.items():
if not row:
continue
if name == 'PlannerOverall':
continue
try:
row = StoredPlannerProxy(**row)
except (ScriptError, ValidationError) as e:
@ -355,8 +462,7 @@ class PlannerProgressParser:
if not row.item.is_group_base:
logger.error(f'from_config: item is not group base {row}')
continue
row.update_synthesize()
row.update_progress()
row.update(time=False)
self.rows[row.item.name] = row
return self
@ -372,17 +478,37 @@ class PlannerProgressParser:
self.rows[name] = row
for row in self.rows.values():
row.update()
row.update(time=True)
def to_config(self) -> dict:
data = {}
for row in self.rows.values():
name = f'Item_{row.item.name}'
dic = row.model_dump()
dic = row.model_dump(exclude=SET_ROW_EXCLUDE)
dic['item'] = row.item.name
data[name] = dic
return data
def get_overall(self):
"""
Calculate overall progress
Note that this method will clear all values
Returns:
float: Progress percentage
float: ETA in days
"""
eta = 0.
progress_current = 0.
progress_total = 0.
for row in self.rows.values():
eta += row.eta
progress_current += row.progress_current
progress_total += row.progress_total
progress = round(progress_current / progress_total * 100, 2)
return progress, eta
def iter_row_to_farm(self, need_farm=True) -> t.Iterable[StoredPlannerProxy]:
"""
Args:
@ -494,6 +620,7 @@ class PlannerMixin(UI):
planner = self.planner
data = planner.to_config()
progress, eta = planner.get_overall()
with self.config.multi_set():
# Set value
@ -506,5 +633,14 @@ class PlannerMixin(UI):
remove.append(key)
for key in remove:
self.config.cross_set(f'Dungeon.Planner.{key}', {})
print(progress, eta)
# Set overall
self.config.stored.PlannerOverall.value = f'{progress:.2f}%'
self.config.stored.PlannerOverall.comment = f'<{eta:.1f}d'
del_cached_property(self, 'planner')
if __name__ == '__main__':
self = PlannerMixin('src')
self.planner_write(self.planner)

View File

@ -4,6 +4,7 @@ import cv2
from pponnxcr.predict_system import BoxedResult
from module.base.utils import area_center, area_in_area
from module.exception import GamePageUnknownError
from module.logger import logger
from module.ocr.ocr import Ocr, OcrWhiteLetterOnComplexBackground
from module.ui.scroll import AdaptiveScroll
@ -22,6 +23,7 @@ DETAIL_TITLE.load_search(RESULT_CHECK.search)
class OcrItemName(Ocr):
def after_process(self, result):
result = result.replace('念火之心', '忿火之心')
result = re.sub('^火之心', '忿火之心', result)
result = re.sub('工造机$', '工造机杼', result)
result = re.sub('工造迥?轮', '工造迴轮', result)
result = re.sub('月狂[療撩]?牙', '月狂獠牙', result)
@ -172,7 +174,7 @@ class PlannerScan(SynthesizeUI, PlannerMixin):
logger.hr('Parse planner result', level=2)
if not self.ui_page_appear(page_planner):
logger.error('Not in page_planner, game must in the planner result page before scanning')
return []
raise GamePageUnknownError
scroll = AdaptiveScroll(RESULT_SCROLL.button, name=RESULT_SCROLL.name)
scroll.drag_threshold = 0.1