diff --git a/config/template.json b/config/template.json index 7dac4f56c..43698ad48 100644 --- a/config/template.json +++ b/config/template.json @@ -272,5 +272,10 @@ "Enable": true, "AimClicker": "do_not_click" } + }, + "PlannerScan": { + "PlannerScan": { + "ResultAdd": false + } } } \ No newline at end of file diff --git a/module/config/argument/args.json b/module/config/argument/args.json index a08473dd0..08f603e6d 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -1865,5 +1865,13 @@ ] } } + }, + "PlannerScan": { + "PlannerScan": { + "ResultAdd": { + "type": "checkbox", + "value": false + } + } } } \ No newline at end of file diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index bb96f750d..dbfaf0725 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -307,3 +307,5 @@ Daemon: AimClicker: value: do_not_click option: [ item_enemy, item, enemy, do_not_click ] +PlannerScan: + ResultAdd: false diff --git a/module/config/argument/menu.json b/module/config/argument/menu.json index 1d71e0235..43ff195a6 100644 --- a/module/config/argument/menu.json +++ b/module/config/argument/menu.json @@ -31,7 +31,8 @@ "menu": "list", "page": "tool", "tasks": [ - "Daemon" + "Daemon", + "PlannerScan" ] } } \ No newline at end of file diff --git a/module/config/argument/task.yaml b/module/config/argument/task.yaml index f8adc6a7b..326bbe993 100644 --- a/module/config/argument/task.yaml +++ b/module/config/argument/task.yaml @@ -71,3 +71,5 @@ Tool: tasks: Daemon: - Daemon + PlannerScan: + - PlannerScan diff --git a/module/config/config_generated.py b/module/config/config_generated.py index aa7408d3f..14a1910c3 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -208,3 +208,6 @@ class GeneratedConfig: # Group `Daemon` Daemon_Enable = True # True Daemon_AimClicker = 'do_not_click' # item_enemy, item, enemy, do_not_click + + # Group `PlannerScan` + PlannerScan_ResultAdd = False diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 7b5ded006..d5c2b7e5e 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -61,6 +61,10 @@ "Daemon": { "name": "Dialogue Clicker", "help": "" + }, + "PlannerScan": { + "name": "Character Planner", + "help": "" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "Don't click" } }, + "PlannerScan": { + "_info": { + "name": "Scan Character Planner Results", + "help": "Tools need to stop the scheduler and then run independently\nBefore use, set planner goal in the in-game planner, calculate the results, and start scanning from the result page. Detailed usage see: https://github.com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + }, + "ResultAdd": { + "name": "Accumulate multiple scan results", + "help": "Turn on when planning multiple characters, will raise multiple characters all together\nTurn off when planning one character, planning goal will be refreshed at every scan" + } + }, "Gui": { "Aside": { "Install": "Install", diff --git a/module/config/i18n/es-ES.json b/module/config/i18n/es-ES.json index 8216819e8..37d09e63d 100644 --- a/module/config/i18n/es-ES.json +++ b/module/config/i18n/es-ES.json @@ -61,6 +61,10 @@ "Daemon": { "name": "Clic de diálogo", "help": "" + }, + "PlannerScan": { + "name": "Planificador de personajes", + "help": "" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "No hacer clic" } }, + "PlannerScan": { + "_info": { + "name": "Escanear resultados del planificador de caracteres", + "help": "Las herramientas deben detener el programador y luego ejecutarse de forma independiente\nAntes de usarlo, establezca el objetivo del planificador en el planificador del juego, calcule los resultados y comience a escanear desde la página de resultados. Para uso detallado, consulte: https://github .com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + }, + "ResultAdd": { + "name": "Acumular múltiples resultados de escaneo", + "help": "Activar cuando se planifican varios personajes, generará varios personajes todos juntos\nDesactivar cuando se planifica un personaje, el objetivo de planificación se actualizará en cada escaneo" + } + }, "Gui": { "Aside": { "Install": "Instalar", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 3448cfdaa..bc67d3ae8 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -61,6 +61,10 @@ "Daemon": { "name": "Task.Daemon.name", "help": "Task.Daemon.help" + }, + "PlannerScan": { + "name": "Task.PlannerScan.name", + "help": "Task.PlannerScan.help" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "do_not_click" } }, + "PlannerScan": { + "_info": { + "name": "PlannerScan._info.name", + "help": "PlannerScan._info.help" + }, + "ResultAdd": { + "name": "PlannerScan.ResultAdd.name", + "help": "PlannerScan.ResultAdd.help" + } + }, "Gui": { "Aside": { "Install": "インストール", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 2e4fbaaca..d42da45f6 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -61,6 +61,10 @@ "Daemon": { "name": "剧情连点器", "help": "" + }, + "PlannerScan": { + "name": "角色养成规划", + "help": "" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "不点击" } }, + "PlannerScan": { + "_info": { + "name": "识别角色养成规划计算结果", + "help": "工具需要停止调度器再单独运行\n使用前需要在游戏内养成计算器中设定养成目标,并计算出结果,在计算结果页面启动养成规划识别。详细使用教程见:\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + }, + "ResultAdd": { + "name": "累加多次扫描结果", + "help": "需要养成多个角色时勾选,同时养成多个角色\n仅养成一个角色不勾选,每次扫描时刷新养成目标" + } + }, "Gui": { "Aside": { "Install": "安装", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 0230d865d..740d642b7 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -61,6 +61,10 @@ "Daemon": { "name": "劇情連點器", "help": "" + }, + "PlannerScan": { + "name": "角色養成規劃", + "help": "" } }, "Scheduler": { @@ -1265,6 +1269,16 @@ "do_not_click": "不點擊" } }, + "PlannerScan": { + "_info": { + "name": "辨識角色養成規劃計算結果", + "help": "工具需要停止調度器再單獨運行\n使用前需要在遊戲內養成計算器中設定養成目標,併計算出結果,在計算結果頁面啟動養成規劃識別。詳細使用教程 請參閱:\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/planner_cn" + }, + "ResultAdd": { + "name": "累加多次掃描結果", + "help": "需要養成多個角色時勾選,同時養成多個角色\n只養成一個角色不勾選,每次掃描時刷新養成目標" + } + }, "Gui": { "Aside": { "Install": "安裝", diff --git a/module/webui/process_manager.py b/module/webui/process_manager.py index f414654f8..ca94c289c 100644 --- a/module/webui/process_manager.py +++ b/module/webui/process_manager.py @@ -151,6 +151,10 @@ class ProcessManager: from tasks.base.daemon import Daemon Daemon(config=config_name, task="Daemon").run() + elif func == "PlannerScan": + from tasks.planner.scan import PlannerScan + + PlannerScan(config=config_name, task="PlannerScan").run() else: logger.critical(f"No function matched: {func}") logger.info(f"[{config_name}] exited. Reason: Finish\n") diff --git a/tasks/planner/model.py b/tasks/planner/model.py index 09a53e70f..fda50e8c7 100644 --- a/tasks/planner/model.py +++ b/tasks/planner/model.py @@ -69,6 +69,11 @@ class MultiValue(BaseModelWithFallback): blue: int = 0 purple: int = 0 + def add(self, other: "MultiValue"): + self.green += other.green + self.blue += other.blue + self.purple += other.purple + class StoredPlannerProxy(BaseModelWithFallback): item: ITEM_TYPES @@ -178,8 +183,10 @@ class StoredPlannerProxy(BaseModelWithFallback): self.value.green = value if total is not None: self.total.green = total + # Cannot synthesize green # if synthesize is not None: # self.synthesize.green = synthesize + self.synthesize.green = 0 elif item.is_rarity_blue: if value is not None: self.value.blue = value @@ -198,11 +205,39 @@ class StoredPlannerProxy(BaseModelWithFallback): raise ScriptError( f'load_value_total: Trying to load {item} in to {self} but item is in invalid rarity') else: + # Cannot synthesize if item doesn't have multiple rarity + self.synthesize = 0 if value is not None: self.value = value if total is not None: self.total = total + def add_planner_result(self, row: "StoredPlannerProxy"): + """ + Add data from another StoredPlannerProxy to self + """ + item = row.item + if self.item.has_group_base: + if item.group_base != self.item: + raise ScriptError( + f'load_value_total: Trying to load {item} into {self} but they are different items') + else: + if item != self.item: + raise ScriptError( + f'load_value_total: Trying to load {item} into {self} but they are different items') + if self.item.has_group_base: + if not self.item.is_rarity_purple: + raise ScriptError( + f'load_value_total: Trying to load {item} into {self} but self is not in rarity purple') + # Add `total` only + # `synthesize` will be updated later + # `value` remains unchanged since you still having that many items + self.total.add(row.total) + else: + self.value += row.value + self.total += row.total + self.synthesize += row.synthesize + def need_farm(self): return self.progress < 100 @@ -289,6 +324,20 @@ class PlannerProgressParser: self.rows[row.item.name] = row return self + def add_planner_result(self, planner: "PlannerProgressParser"): + """ + Add another planner result to self + """ + for name, row in planner.rows.items(): + if name in self.rows: + self_row = self.rows[name] + self_row.add_planner_result(row) + else: + self.rows[name] = row + + for row in self.rows.values(): + row.update() + def to_config(self) -> dict: data = {} for row in self.rows.values(): @@ -383,7 +432,13 @@ class PlannerMixin(UI): """ Write planner detection results info user config """ + add = self.config.PlannerScan_ResultAdd + logger.attr('ResultAdd', add) + planner = PlannerProgressParser().from_planner_results(results) + if add: + planner.add_planner_result(self.planner) + self.planner_write(planner) @cached_property @@ -405,6 +460,15 @@ class PlannerMixin(UI): data = planner.to_config() with self.config.multi_set(): + # Set value for key, value in data.items(): self.config.cross_set(f'Dungeon.Planner.{key}', value) + # Remove other value + remove = [] + for key, value in self.config.cross_get('Dungeon.Planner', default={}).items(): + if value != {} and key not in data: + remove.append(key) + for key in remove: + self.config.cross_set(f'Dungeon.Planner.{key}', {}) + del_cached_property(self, 'planner') diff --git a/tasks/planner/result.py b/tasks/planner/scan.py similarity index 97% rename from tasks/planner/result.py rename to tasks/planner/scan.py index 91ad258c8..a57d54eb8 100644 --- a/tasks/planner/result.py +++ b/tasks/planner/scan.py @@ -18,9 +18,6 @@ MATERIAL_TITLE.load_search(RESULT_CHECK.search) DETAIL_TITLE.load_search(RESULT_CHECK.search) - - - class OcrItemName(Ocr): def after_process(self, result): result = result.replace('念火之心', '忿火之心') @@ -75,7 +72,7 @@ class OcrPlannerResult(OcrWhiteLetterOnComplexBackground, OcrItemName): return image -class PlannerResult(SynthesizeUI, PlannerMixin): +class PlannerScan(SynthesizeUI, PlannerMixin): def is_in_planner_result(self): if self.appear(RESULT_CHECK): return True @@ -216,8 +213,12 @@ class PlannerResult(SynthesizeUI, PlannerMixin): self.planner_write_results(out) return out + def run(self): + self.device.screenshot() + self.parse_planner_result() + if __name__ == '__main__': - self = PlannerResult('src') + self = PlannerScan('src', task='PlannerScan') self.device.screenshot() self.parse_planner_result()