diff --git a/assets/gui/css/alas-pc.css b/assets/gui/css/alas-pc.css index 0adfa2591..a3873f49e 100644 --- a/assets/gui/css/alas-pc.css +++ b/assets/gui/css/alas-pc.css @@ -26,6 +26,16 @@ overflow-y: auto; } +#pywebio-scope-log-bar { + display: flex; + flex-direction: column; + height: 11.5rem; +} + +#pywebio-scope-dashboard { + flex: 1; +} + #pywebio-scope-log-bar, #pywebio-scope-log, #pywebio-scope-daemon-overview #pywebio-scope-groups { diff --git a/assets/gui/css/alas.css b/assets/gui/css/alas.css index 5fc57f5d4..b7128f4ae 100644 --- a/assets/gui/css/alas.css +++ b/assets/gui/css/alas.css @@ -394,15 +394,25 @@ pre.rich-traceback-code { grid-auto-flow: column; } -#pywebio-scope-log-bar { - height: 11.5rem; -} - #pywebio-scope-dashboard { display: flex; - align-content: flex-start; + align-content: space-between; + justify-content: flex-start; flex-flow: row wrap; overflow: auto; + margin-top: .5rem; +} + +#pywebio-scope-dashboard > i { + flex-grow: 1; + align-self: flex-end; + width: 10rem; +} + +[id^="pywebio-scope-dashboard-row-"] { + display: flex; + flex-grow: 1; + width: 10rem; } .dashboard-icon { @@ -412,13 +422,6 @@ pre.rich-traceback-code { border-radius: 50%; } -[id^="pywebio-scope-dashboard-row-"] { - display: flex; - flex-grow: 1; - min-width: 4rem; - max-width: 10rem; -} - *[style*="--dashboard-value--"] { font-size: 1rem; font-weight: 500; diff --git a/assets/share/dungeon/ui/OCR_SIMUNI_POINT.png b/assets/share/dungeon/ui/OCR_SIMUNI_POINT.png new file mode 100644 index 000000000..f67fa87cb Binary files /dev/null and b/assets/share/dungeon/ui/OCR_SIMUNI_POINT.png differ diff --git a/config/template.json b/config/template.json index 6ec1415f7..c05b72df5 100644 --- a/config/template.json +++ b/config/template.json @@ -57,7 +57,8 @@ }, "DungeonStorage": { "TrailblazePower": {}, - "DungeonDouble": {} + "DungeonDouble": {}, + "SimulatedUniverse": {} } }, "DailyQuest": { @@ -119,7 +120,8 @@ "Name_2": "Akashic_Records", "Name_3": "The_Invisible_Hand", "Name_4": "Nine_Billion_Names", - "Duration": 20 + "Duration": 20, + "Assignment": {} } } } \ No newline at end of file diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 97694e31c..4d3f4208e 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -373,6 +373,14 @@ "value": {}, "display": "hide", "stored": "StoredDungeonDouble" + }, + "SimulatedUniverse": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredSimulatedUniverse", + "order": 5, + "color": "#8fb5fe" } } }, @@ -935,6 +943,14 @@ 12, 20 ] + }, + "Assignment": { + "type": "stored", + "value": {}, + "display": "hide", + "stored": "StoredAssignment", + "order": 3, + "color": "#deba95" } } } diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 7d0c54b74..383436c54 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -115,6 +115,10 @@ DungeonStorage: color: "#eb8efe" DungeonDouble: stored: StoredDungeonDouble + SimulatedUniverse: + stored: StoredSimulatedUniverse + order: 5 + color: "#8fb5fe" AchievableQuest: # Quests will be injected in config updater @@ -147,4 +151,8 @@ Assignment: option: [ Nameless_Land_Nameless_People, ] Duration: value: 20 - option: [ 4, 8, 12, 20 ] \ No newline at end of file + option: [ 4, 8, 12, 20 ] + Assignment: + stored: StoredAssignment + order: 3 + color: "#deba95" diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 34647ddbe..bdba746c7 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -57,6 +57,7 @@ class GeneratedConfig: # Group `DungeonStorage` DungeonStorage_TrailblazePower = {} DungeonStorage_DungeonDouble = {} + DungeonStorage_SimulatedUniverse = {} # Group `AchievableQuest` AchievableQuest_Complete_1_Daily_Mission = 'achievable' # achievable, not_set, not_supported @@ -95,3 +96,4 @@ class GeneratedConfig: Assignment_Name_3 = 'The_Invisible_Hand' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm Assignment_Name_4 = 'Nine_Billion_Names' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm Assignment_Duration = 20 # 4, 8, 12, 20 + Assignment_Assignment = {} diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index c3eba38d1..31ba90310 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -364,6 +364,10 @@ "DungeonDouble": { "name": "Dungeon Double", "help": "" + }, + "SimulatedUniverse": { + "name": "Sim.Uni.", + "help": "" } }, "AchievableQuest": { @@ -641,6 +645,10 @@ "8": "8 Hours", "12": "12 Hours", "20": "20 Hours" + }, + "Assignment": { + "name": "Assignment", + "help": "" } }, "Gui": { diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 7e6199cc9..e5d789860 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -364,6 +364,10 @@ "DungeonDouble": { "name": "DungeonStorage.DungeonDouble.name", "help": "DungeonStorage.DungeonDouble.help" + }, + "SimulatedUniverse": { + "name": "DungeonStorage.SimulatedUniverse.name", + "help": "DungeonStorage.SimulatedUniverse.help" } }, "AchievableQuest": { @@ -641,6 +645,10 @@ "8": "8", "12": "12", "20": "20" + }, + "Assignment": { + "name": "Assignment.Assignment.name", + "help": "Assignment.Assignment.help" } }, "Gui": { diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index c7ecf1396..ddcf77838 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -364,6 +364,10 @@ "DungeonDouble": { "name": "副本双倍", "help": "" + }, + "SimulatedUniverse": { + "name": "模拟宇宙", + "help": "" } }, "AchievableQuest": { @@ -641,6 +645,10 @@ "8": "8小时", "12": "12小时", "20": "20小时" + }, + "Assignment": { + "name": "委托", + "help": "" } }, "Gui": { diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index aa2a45450..49272650a 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -364,6 +364,10 @@ "DungeonDouble": { "name": "副本雙倍", "help": "" + }, + "SimulatedUniverse": { + "name": "模擬宇宙", + "help": "" } }, "AchievableQuest": { @@ -641,6 +645,10 @@ "8": "8小時", "12": "12小時", "20": "20小時" + }, + "Assignment": { + "name": "委託", + "help": "" } }, "Gui": { diff --git a/module/config/stored/classes.py b/module/config/stored/classes.py index c1df02fb9..a56e86a49 100644 --- a/module/config/stored/classes.py +++ b/module/config/stored/classes.py @@ -169,6 +169,14 @@ class StoredTrailblazePower(StoredCounter): FIXED_TOTAL = 180 +class StoredSimulatedUniverse(StoredCounter, StoredExpiredAt0400): + pass + + +class StoredAssignment(StoredCounter): + pass + + class StoredDaily(StoredExpiredAt0400): quest1 = '' quest2 = '' diff --git a/module/config/stored/stored_generated.py b/module/config/stored/stored_generated.py index 95462b4aa..18f8fe087 100644 --- a/module/config/stored/stored_generated.py +++ b/module/config/stored/stored_generated.py @@ -1,4 +1,5 @@ from module.config.stored.classes import ( + StoredAssignment, StoredBase, StoredCounter, StoredDaily, @@ -6,6 +7,7 @@ from module.config.stored.classes import ( StoredDungeonDouble, StoredExpiredAt0400, StoredInt, + StoredSimulatedUniverse, StoredTrailblazePower, ) @@ -16,5 +18,7 @@ from module.config.stored.classes import ( class StoredGenerated: TrailblazePower = StoredTrailblazePower("Dungeon.DungeonStorage.TrailblazePower") DungeonDouble = StoredDungeonDouble("Dungeon.DungeonStorage.DungeonDouble") + SimulatedUniverse = StoredSimulatedUniverse("Dungeon.DungeonStorage.SimulatedUniverse") DailyActivity = StoredDailyActivity("DailyQuest.DailyStorage.DailyActivity") DailyQuest = StoredDaily("DailyQuest.DailyStorage.DailyQuest") + Assignment = StoredAssignment("Assignment.Assignment.Assignment") diff --git a/module/webui/app.py b/module/webui/app.py index 5c25cdd05..d3d07d68f 100644 --- a/module/webui/app.py +++ b/module/webui/app.py @@ -416,7 +416,8 @@ class AlasGUI(Frame): put_scope("dashboard", [ # Empty dashboard, values will be updated in alas_update_overview_task() put_scope(f"dashboard-row-{arg}", []) for arg in self.ALAS_STORED.keys() - ]) + # Empty content to left-align last row + ] + [put_html("")] * len(self.ALAS_STORED)) ]) put_scope("log", [put_html("")]) diff --git a/tasks/assignment/ui.py b/tasks/assignment/ui.py index ae57bc05e..cadd5d19a 100644 --- a/tasks/assignment/ui.py +++ b/tasks/assignment/ui.py @@ -145,7 +145,14 @@ class AssignmentUI(UI): @property def _limit_status(self) -> tuple[int, int, int]: self.device.screenshot() - return DigitCounter(OCR_ASSIGNMENT_LIMIT).ocr_single_line(self.device.image) + current, remain, total = DigitCounter(OCR_ASSIGNMENT_LIMIT).ocr_single_line(self.device.image) + if total and current <= total: + logger.attr('Assignment', f'{current}/{total}') + self.config.stored.Assignment.set(current, total) + else: + logger.warning(f'Invalid assignment limit: {current}/{total}') + self.config.stored.Assignment.set(0, 0) + return current, remain, total def _iter_groups(self) -> Iterator[AssignmentGroup]: for state in ASSIGNMENT_TOP_SWITCH.state_list: diff --git a/tasks/dungeon/assets/assets_dungeon_ui.py b/tasks/dungeon/assets/assets_dungeon_ui.py index 1a1d5adfe..5cf5b0695 100644 --- a/tasks/dungeon/assets/assets_dungeon_ui.py +++ b/tasks/dungeon/assets/assets_dungeon_ui.py @@ -53,6 +53,16 @@ OCR_DUNGEON_NAV = ButtonWrapper( button=(108, 132, 428, 613), ), ) +OCR_SIMUNI_POINT = ButtonWrapper( + name='OCR_SIMUNI_POINT', + share=Button( + file='./assets/share/dungeon/ui/OCR_SIMUNI_POINT.png', + area=(580, 190, 880, 235), + search=(560, 170, 900, 255), + color=(158, 164, 252), + button=(580, 190, 880, 235), + ), +) OPERATION_BRIEFING_CHECK = ButtonWrapper( name='OPERATION_BRIEFING_CHECK', share=Button( diff --git a/tasks/dungeon/dungeon.py b/tasks/dungeon/dungeon.py index 0463f464e..7185ba774 100644 --- a/tasks/dungeon/dungeon.py +++ b/tasks/dungeon/dungeon.py @@ -143,6 +143,9 @@ class Dungeon(DungeonUI, DungeonEvent, Combat): with self.config.multi_set(): self.config.stored.DungeonDouble.calyx = calyx self.config.stored.DungeonDouble.relic = relic + # Update SimulatedUniverse points + logger.info('Get simulated universe points') + self.dungeon_get_simuni_point() # Run double events ran_calyx_golden = False diff --git a/tasks/dungeon/ui.py b/tasks/dungeon/ui.py index ab4c8e388..f06481bd7 100644 --- a/tasks/dungeon/ui.py +++ b/tasks/dungeon/ui.py @@ -5,7 +5,7 @@ from module.base.button import ClickButton from module.base.timer import Timer from module.base.utils import get_color from module.logger import logger -from module.ocr.ocr import Ocr, OcrResultButton +from module.ocr.ocr import DigitCounter, Ocr, OcrResultButton from module.ocr.utils import split_and_pair_button_attr from module.ui.draggable_list import DraggableList from module.ui.switch import Switch @@ -74,6 +74,10 @@ class OcrDungeonList(Ocr): return result +class OcrSimUniPoint(DigitCounter): + merge_thres_x = 50 + + class OcrDungeonListLimitEntrance(OcrDungeonList): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -219,6 +223,22 @@ class DungeonUI(UI): logger.info('DungeonNav row Forgotten_Hall stabled') return True + def dungeon_get_simuni_point(self) -> int: + """ + Page: + in: page_guide, Survival_Index, Simulated_Universe + """ + ocr = OcrSimUniPoint(OCR_SIMUNI_POINT) + value, _, total = ocr.ocr_single_line(self.device.image) + if total and value <= total: + logger.attr('SimulatedUniverse', f'{value}/{total}') + self.config.stored.SimulatedUniverse.set(value, total) + return value + else: + logger.warning(f'Invalid SimulatedUniverse points: {value}/{total}') + self.config.stored.SimulatedUniverse.set(0, 0) + return 0 + def _dungeon_nav_goto(self, dungeon: DungeonList, skip_first_screenshot=True): """ Equivalent to `DUNGEON_NAV_LIST.select_row(dungeon.dungeon_nav, main=self)`