mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-22 00:35:34 +00:00
commit
fc8ce808ca
@ -47,6 +47,7 @@
|
||||
"ServerUpdate": "04:00"
|
||||
},
|
||||
"Planner": {
|
||||
"PlannerOverall": {},
|
||||
"Item_Credit": {},
|
||||
"Item_Trailblaze_EXP": {},
|
||||
"Item_Traveler_Guide": {},
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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:
|
||||
|
@ -61,6 +61,8 @@ Dashboard:
|
||||
HoursAgo:
|
||||
DaysAgo:
|
||||
LongTimeAgo:
|
||||
# Planner
|
||||
EtaDays:
|
||||
|
||||
AddAlas:
|
||||
PopupTitle:
|
||||
|
@ -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",
|
||||
|
@ -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 = {}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "新しいコンフィグを追加",
|
||||
|
@ -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": "添加新配置",
|
||||
|
@ -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": "添加新的設定",
|
||||
|
@ -404,3 +404,8 @@ class StoredPlanner(StoredBase):
|
||||
value: int
|
||||
total: int
|
||||
synthesize: int
|
||||
|
||||
|
||||
class StoredPlannerOverall(StoredBase):
|
||||
value: str = '??%'
|
||||
comment: str = '<??d'
|
||||
|
@ -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")
|
||||
|
@ -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--"),
|
||||
|
@ -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--"),
|
||||
])
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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,46 +261,47 @@ 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.time = now()
|
||||
_ = 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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user