From f81b40963d68f39db675f49ce3699e11110dd2ec Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 10 Sep 2024 22:41:00 +0800 Subject: [PATCH] Fix: Dungeon list swiping in 2.5 --- assets/share/dungeon/ui/CALYX_WORLD_3.png | Bin 6900 -> 0 bytes .../LIST_ASCENDING.png} | Bin 6798 -> 6177 bytes .../LIST_DESCENDING.png} | Bin 6999 -> 6073 bytes .../OCR_DUNGEON_LIST.BUTTON.png | Bin .../{ui => ui_list}/OCR_DUNGEON_LIST.png | Bin .../dungeon/ui_list/OCR_DUNGEON_NAME.png | Bin 0 -> 43374 bytes .../ui_list/OCR_DUNGEON_NAME_ROGUE.png | Bin 0 -> 19772 bytes .../dungeon/ui_list/OCR_DUNGEON_TELEPORT.png | Bin 0 -> 47126 bytes dev_tools/keywords/dungeon_list.py | 31 +- tasks/daily/daily_quest.py | 2 +- tasks/dungeon/assets/assets_dungeon_ui.py | 40 - .../dungeon/assets/assets_dungeon_ui_list.py | 65 ++ tasks/dungeon/dungeon.py | 6 +- tasks/dungeon/keywords/dungeon.py | 346 ++++---- tasks/dungeon/stamina.py | 2 +- tasks/dungeon/ui.py | 770 ------------------ tasks/dungeon/ui/interact.py | 85 ++ tasks/dungeon/ui/llist.py | 385 +++++++++ tasks/dungeon/ui/nav.py | 351 ++++++++ tasks/dungeon/{ => ui}/state.py | 0 tasks/dungeon/ui/ui.py | 41 + tasks/dungeon/{ => ui}/ui_rogue.py | 8 +- tasks/dungeon/weekly.py | 8 +- tasks/forgotten_hall/ui.py | 4 +- tasks/ornament/combat.py | 2 +- tasks/rogue/entry/entry.py | 4 +- tasks/rogue/event/reward.py | 2 +- 27 files changed, 1140 insertions(+), 1012 deletions(-) delete mode 100644 assets/share/dungeon/ui/CALYX_WORLD_3.png rename assets/share/dungeon/{ui/CALYX_WORLD_1.png => ui_list/LIST_ASCENDING.png} (75%) rename assets/share/dungeon/{ui/CALYX_WORLD_2.png => ui_list/LIST_DESCENDING.png} (73%) rename assets/share/dungeon/{ui => ui_list}/OCR_DUNGEON_LIST.BUTTON.png (100%) rename assets/share/dungeon/{ui => ui_list}/OCR_DUNGEON_LIST.png (100%) create mode 100644 assets/share/dungeon/ui_list/OCR_DUNGEON_NAME.png create mode 100644 assets/share/dungeon/ui_list/OCR_DUNGEON_NAME_ROGUE.png create mode 100644 assets/share/dungeon/ui_list/OCR_DUNGEON_TELEPORT.png create mode 100644 tasks/dungeon/assets/assets_dungeon_ui_list.py delete mode 100644 tasks/dungeon/ui.py create mode 100644 tasks/dungeon/ui/interact.py create mode 100644 tasks/dungeon/ui/llist.py create mode 100644 tasks/dungeon/ui/nav.py rename tasks/dungeon/{ => ui}/state.py (100%) create mode 100644 tasks/dungeon/ui/ui.py rename tasks/dungeon/{ => ui}/ui_rogue.py (93%) diff --git a/assets/share/dungeon/ui/CALYX_WORLD_3.png b/assets/share/dungeon/ui/CALYX_WORLD_3.png deleted file mode 100644 index eeb73049b481bbfb72978d365a87ea12522ed2b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6900 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 ZB>@h&{<71dVV<+Y+v+-Mr7E^h{sZOLLNEXT diff --git a/assets/share/dungeon/ui/CALYX_WORLD_1.png b/assets/share/dungeon/ui_list/LIST_ASCENDING.png similarity index 75% rename from assets/share/dungeon/ui/CALYX_WORLD_1.png rename to assets/share/dungeon/ui_list/LIST_ASCENDING.png index 5457292d261d6d6497a8b955c490411ba6d68717..f6f6a8d8ddc740f525e8651de56e2b16071cbb73 100644 GIT binary patch delta 846 zcmV-U1F`&$HK8!Dy$V|mPDw;TRCwC#-M?;BR}cr_JLm37fB`8K+xRUgf)FV~K}5|n zVE&{bAr^58@&YD34Soxt1iK+MCM{Kv3q~xOiD4C$ziC_Z6#qcDI_XewsP6 zO#`!06CVe&Fbg>Xe=xH`2><{94z=}mE28wznKd)}=Th2QyIZ$zt*<}3dGp6}=f3IR zueB?sojZO^>84Xr0ssJje@jgfnKhzB#NOWC`uci_h=`|8pFV!{=*6EeK7F&aC3G1D zU;qGsS(aIMcX#jKzyJLC^O~8N-F}yuyLT3W*>LE+GW;0A*#0000Emo+ywHdY_6 zzWM9TXf!%~fBJN0?!ABCf7;cqIcO1&^RS``zyJUMv*+cjmuqWlo12^S^YdrVo*fJa zHFGc=G))r`ZD}VHZ~yEjumB7I05E%Mt-CwB^YimxpE)yk{P>_9O!j*tqLdO5%|SP~ z-;Bl7at9ay0N`+=(dgpEi${(eDQ(HDU9F{*Zog|we_J!VS|g&XwcFp{@4A(hd#jIE zZ~uB*-2`9&0Du`25l4?69Umvhn-Bi_DEsvL+nqbh%gfvEwl92lLEWa75oiDafWu{G zl!y}job$(HL_N6fTWihCm6dzT%gbG@OFu7Nx^zk1rj`yc006+@V&cYcf6SWW#aC@< z2ZQ0=f4g^|{qgMW*4xFO7O!5ts%Qc*006*@X5i9H3_nk{qXbLvANM`bn2^95m5&1wQJYUpZ|8UC*0Let&<@O+aKV6>~8=70RR63 Y0BuiN6aIe*-v9sr07*qoM6N<$f(GHA`~Uy| delta 1566 zcmZ8heKga182_@$G+laeQ>3`}EYT4u)4c5trE%MI%&Y$tL1zb~9^MW;S+z)*ttr`~B;CzRz=>&+~mgpXZZrlfJ*Id2O&h z_ptjfz6m2Uqf10ze^Z_D=rcyR^Q0sE+J+bo#ESi9ir8Ym$TnA+tyNrp$ zQS@@XWT|2D+4bexmaq7Nr4UO;l=wmUVa67wuvlh~`8#Rj>sv{m1O~Ncs48t}Ty3yR z3%9>BJ>74d7~4?R#nA$met2B~fWb8_0B~RpeAP0#cFq=qDMo^8Ko57f>qJB3<;`muvg>b zgx|3S}XmP(!9~?#XWNx^f^ooyc(7f)6-xqFX34OA+(yG5hve?9hOU{QUfuZTJ1r z)N~yC=tyw}G#}{Ylk%T}f|PQJzc9dKDNwYCN|;AxJn?%7#L`qRV**BYY&c%kB1}|!|xVYq*TEe{&K$B&RH-x zH3WdR4TvxU`k{M;N+pjQWg@)P$d%6;a(jAuP$-mH3XMh+_A%8?4Zj^C5{c&0F*oy? z_cPZKIypHx@a*$S53EAv6+vBx`d`0(orp;*aJ%)xw{P=5a@)Qd;{%A#N82-)*x1s2 z&{A)Aw}1;l{JQWo-4#lFD~5)KgwfHw7IjOEO%{P6k?ffbw;ts2xo7%v-`>y8&MxCs zc=-5ky{)0y@<@DqJn~~l=Fa5gWaFa6K86=M^YiF)hBKW|NZ2ee+SZ&XK90;6-r3{3 zVMSBy7)YU#8MMxIcH+>Gtk24yP~1EJ&CobF*ClA{&4|&atj5TYQ>V;R-VDE~iWc@d zq#xS=*l|C+)MgXNPOHwx#l^{IM`beESWwFeY%WYpOzeJpd-R6e9UUE4mGe_Fg-9fV z#u|K@E~wkLZ$EJ93CW_I!wFAtQQa}Qv3li~st;NXB#Y2?=r1%HJtCiVPW*Zn8FKdQ z^x(wArz{48F*!L&Z-4G|wW*{e?A*D3f?ZVxcnjxWkHhOw>ciLo-aT; z*5cx#T`;t*T{=8G+^m%I9xf>chy$XSFpyrruB@-GsIaE@9P#$vM2VnM6?0R5A5vDW zNbC#5?fkv=v)Cd1A&s_XZ+1`DTTzVpeNXrP#5Z>*7bKhaT%;UOiR;Oik$Qks4L47s z6Sm7}O1pcWDF!Z!(=5*JUZHF)O2gbe<>>*5GCaKPApi5hq@<+ksG8#9ZkN zNd;Ozw6aT@8Lnm`BC$aiW=5c=Wy1jp(t>WC;Ad9?EjB&atf2^*iVxvj@D8F-tnKY3 zS8|zjs}{@x&ne*xE>*K_X(Sg`-P5O>=lz&8`xd>*>gwvk!ou$MLtec@+AAdz=$&WY zjdD00p*6!4GMR6~Wi?)owp^nr?+#7R?6z}MeicE9%CN&+_EfWcf+ulX^bk2G`5FQC z(GLhc(s``rq=^*@1kyHT3Wy5;L#>NRZ?_t^au{Y^>|x pW&Q6Jwq$c1)03!W1ORF^0`xJvjaRS7)xas>u*VVi3jE0%{{nZk^+5mt diff --git a/assets/share/dungeon/ui/CALYX_WORLD_2.png b/assets/share/dungeon/ui_list/LIST_DESCENDING.png similarity index 73% rename from assets/share/dungeon/ui/CALYX_WORLD_2.png rename to assets/share/dungeon/ui_list/LIST_DESCENDING.png index 3b3e3edb76e15028fa4e91d86798437e14a3c5fe..3d34041c786265c9110b588e7ef5e624b2bf4a24 100644 GIT binary patch delta 740 zcmVnRTTlHb%h3G(k$w7UxEmN`$Ez zrB9KHXGqW7U?VkF3V8$$&1-~2j(SVqA%UEM2Ag=NhXo6@7QM33_Z=*I?9LX27UGNrPdl*8fhhxPRk!rQlR&d&bscDwiP z-HkEJVk}b%A++Y3qPv|k5dZ)H{I8VC@%W;6Rus>QYUSkQb{C1sDJT zpq8rHZ%SoZPEtzGpa1r9|79vuwHQK(F<$>9x7;@(0001gn5k;HA%yXGJQxg$qL`GE zlu}A%j9D`BtsB2m0002oT!YhKFxcPQFUzu#HL4Y{D8K*!0AH2y_@XF^ot>Q+BAQK79S@_*1jh%JW=q3NQeF06@K6zx^myvUqxSdU*Ke=;P7i z;$nZZKi`@clmZL@08mR=jAcqy0Z!rHyP)5{|Lgqx^YYU2=8v20c3WHuFaQ8RU0t=` z8UOG+`7*gkUsgIRyJz1=sZ^1sbCr4(bl`|V1v*K5yfzX~t_ z0KnH(BE~p<3?Y=0va{0J*x1PPT>Djk0RRAM-FUO>PqIc9LyTFhX1}YetM|XVpXYg& zHD+#*Mgax@0H~Sjf3TL8mLIM^Tw7bK=Dpo+H_!9(@;)TvUy~sV+a2H^00030{{sLY Wz41u?XT!(<0000ewvS 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 literal 0 HcmV?d00001 diff --git a/assets/share/dungeon/ui_list/OCR_DUNGEON_NAME_ROGUE.png b/assets/share/dungeon/ui_list/OCR_DUNGEON_NAME_ROGUE.png new file mode 100644 index 0000000000000000000000000000000000000000..3dd619f0037218537f2c698c021a1e1771715b0c GIT binary patch literal 19772 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 literal 0 HcmV?d00001 diff --git a/assets/share/dungeon/ui_list/OCR_DUNGEON_TELEPORT.png b/assets/share/dungeon/ui_list/OCR_DUNGEON_TELEPORT.png new file mode 100644 index 0000000000000000000000000000000000000000..5c1c74d8aa6f5a4dbfbda13a2eb81d40782d0394 GIT binary patch literal 47126 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 literal 0 HcmV?d00001 diff --git a/dev_tools/keywords/dungeon_list.py b/dev_tools/keywords/dungeon_list.py index f9f67a902..93403a078 100644 --- a/dev_tools/keywords/dungeon_list.py +++ b/dev_tools/keywords/dungeon_list.py @@ -122,16 +122,27 @@ class GenerateDungeonList(GenerateKeyword): dungeon['name'] = 'Divergent_Universe_' + dungeon['name'] if 100 < dungeon['dungeon_id'] < 200: dungeon['name'] = 'Simulated_Universe_' + dungeon['name'] - # Reverse Divergent_Universe - start = 0 - end = 0 - for index, dungeon in enumerate(dungeons): - if dungeon['name'].startswith('Divergent_Universe'): - if start == 0: - start = index - end = index + 1 - if start > 0 and end > 0: - dungeons = dungeons[:start] + dungeons[start:end][::-1] + dungeons[end:] + + # Reverse dungeon list, latest at top + def reverse_on_name(d, prefix): + start = 0 + end = 0 + for index, dungeon in enumerate(d): + if dungeon['name'].startswith(prefix): + if start == 0: + start = index + end = index + 1 + if start > 0 and end > 0: + d = d[:start] + d[start:end][::-1] + d[end:] + return d + + dungeons = reverse_on_name(dungeons, 'Divergent_Universe') + dungeons = reverse_on_name(dungeons, 'Cavern_of_Corrosion') + dungeons = reverse_on_name(dungeons, 'Echo_of_War') + + # Reverse Calyx_Golden, sort by world + # Poor sort + dungeons[0:3], dungeons[6:9] = dungeons[6:9], dungeons[0:3] # Re-sort ID self.keyword_index = 0 diff --git a/tasks/daily/daily_quest.py b/tasks/daily/daily_quest.py index a5d7a9d7b..d60d2ad5a 100644 --- a/tasks/daily/daily_quest.py +++ b/tasks/daily/daily_quest.py @@ -21,7 +21,7 @@ from tasks.daily.synthesize import SynthesizeMaterialUI from tasks.daily.use_technique import UseTechniqueUI from tasks.dungeon.assets.assets_dungeon_ui import DAILY_TRAINING_CHECK from tasks.dungeon.keywords import KEYWORDS_DUNGEON_TAB -from tasks.dungeon.ui import DungeonUI +from tasks.dungeon.ui.ui import DungeonUI from tasks.item.consumable_usage import ConsumableUsageUI from tasks.item.relics import RelicsUI from tasks.map.route.loader import RouteLoader diff --git a/tasks/dungeon/assets/assets_dungeon_ui.py b/tasks/dungeon/assets/assets_dungeon_ui.py index ba77ca705..ba4730031 100644 --- a/tasks/dungeon/assets/assets_dungeon_ui.py +++ b/tasks/dungeon/assets/assets_dungeon_ui.py @@ -3,36 +3,6 @@ from module.base.button import Button, ButtonWrapper # This file was auto-generated, do not modify it manually. To generate: # ``` python -m dev_tools.button_extract ``` -CALYX_WORLD_1 = ButtonWrapper( - name='CALYX_WORLD_1', - share=Button( - file='./assets/share/dungeon/ui/CALYX_WORLD_1.png', - area=(490, 185, 540, 230), - search=(470, 165, 560, 250), - color=(197, 196, 196), - button=(490, 185, 540, 230), - ), -) -CALYX_WORLD_2 = ButtonWrapper( - name='CALYX_WORLD_2', - share=Button( - file='./assets/share/dungeon/ui/CALYX_WORLD_2.png', - area=(590, 185, 640, 230), - search=(570, 165, 660, 250), - color=(199, 198, 198), - button=(590, 185, 640, 230), - ), -) -CALYX_WORLD_3 = ButtonWrapper( - name='CALYX_WORLD_3', - share=Button( - file='./assets/share/dungeon/ui/CALYX_WORLD_3.png', - area=(689, 186, 739, 231), - search=(669, 166, 759, 251), - color=(158, 158, 158), - button=(689, 186, 739, 231), - ), -) DAILY_TRAINING_CHECK = ButtonWrapper( name='DAILY_TRAINING_CHECK', share=Button( @@ -73,16 +43,6 @@ LIST_LOADED_CHECK = ButtonWrapper( button=(576, 606, 951, 664), ), ) -OCR_DUNGEON_LIST = ButtonWrapper( - name='OCR_DUNGEON_LIST', - share=Button( - file='./assets/share/dungeon/ui/OCR_DUNGEON_LIST.png', - area=(581, 176, 1165, 661), - search=(561, 156, 1185, 681), - color=(212, 214, 220), - button=(440, 176, 588, 656), - ), -) OCR_DUNGEON_NAV = ButtonWrapper( name='OCR_DUNGEON_NAV', share=Button( diff --git a/tasks/dungeon/assets/assets_dungeon_ui_list.py b/tasks/dungeon/assets/assets_dungeon_ui_list.py new file mode 100644 index 000000000..275e63cb8 --- /dev/null +++ b/tasks/dungeon/assets/assets_dungeon_ui_list.py @@ -0,0 +1,65 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +LIST_ASCENDING = ButtonWrapper( + name='LIST_ASCENDING', + share=Button( + file='./assets/share/dungeon/ui_list/LIST_ASCENDING.png', + area=(1125, 643, 1143, 661), + search=(1105, 623, 1163, 681), + color=(195, 194, 196), + button=(1125, 643, 1143, 661), + ), +) +LIST_DESCENDING = ButtonWrapper( + name='LIST_DESCENDING', + share=Button( + file='./assets/share/dungeon/ui_list/LIST_DESCENDING.png', + area=(1125, 643, 1143, 661), + search=(1105, 623, 1163, 681), + color=(195, 194, 196), + button=(1125, 643, 1143, 661), + ), +) +OCR_DUNGEON_LIST = ButtonWrapper( + name='OCR_DUNGEON_LIST', + share=Button( + file='./assets/share/dungeon/ui_list/OCR_DUNGEON_LIST.png', + area=(581, 176, 1165, 661), + search=(561, 156, 1185, 681), + color=(212, 214, 220), + button=(440, 176, 588, 656), + ), +) +OCR_DUNGEON_NAME = ButtonWrapper( + name='OCR_DUNGEON_NAME', + share=Button( + file='./assets/share/dungeon/ui_list/OCR_DUNGEON_NAME.png', + area=(563, 172, 788, 624), + search=(543, 152, 808, 644), + color=(245, 243, 245), + button=(563, 172, 788, 624), + ), +) +OCR_DUNGEON_NAME_ROGUE = ButtonWrapper( + name='OCR_DUNGEON_NAME_ROGUE', + share=Button( + file='./assets/share/dungeon/ui_list/OCR_DUNGEON_NAME_ROGUE.png', + area=(563, 292, 788, 624), + search=(543, 272, 808, 644), + color=(249, 247, 249), + button=(563, 292, 788, 624), + ), +) +OCR_DUNGEON_TELEPORT = ButtonWrapper( + name='OCR_DUNGEON_TELEPORT', + share=Button( + file='./assets/share/dungeon/ui_list/OCR_DUNGEON_TELEPORT.png', + area=(1013, 172, 1163, 624), + search=(993, 152, 1183, 644), + color=(231, 234, 230), + button=(1013, 172, 1163, 624), + ), +) diff --git a/tasks/dungeon/dungeon.py b/tasks/dungeon/dungeon.py index c628cd15e..0da8d343b 100644 --- a/tasks/dungeon/dungeon.py +++ b/tasks/dungeon/dungeon.py @@ -213,7 +213,7 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): elif require and not self.support_once: # Run with support all the way return self._dungeon_run(dungeon=dungeon, team=team, wave_limit=0, - support_character=self.config.DungeonSupport_Character) + support_character=self.config.DungeonSupport_Character) else: # Normal run @@ -250,10 +250,10 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat): if self.has_double_rogue_event(): rogue = self.get_double_rogue_remain() if self.has_double_calyx_event(): - self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Calyx_Golden) + self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Calyx_Golden) calyx = self.get_double_event_remain() if self.has_double_relic_event(): - self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion) + self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion) relic = self.get_double_rogue_remain() with self.config.multi_set(): self.config.stored.DungeonDouble.calyx = calyx diff --git a/tasks/dungeon/keywords/dungeon.py b/tasks/dungeon/keywords/dungeon.py index 48e14fa7e..8a8648b64 100644 --- a/tasks/dungeon/keywords/dungeon.py +++ b/tasks/dungeon/keywords/dungeon.py @@ -3,38 +3,38 @@ from .classes import DungeonList # This file was auto-generated, do not modify it manually. To generate: # ``` python -m dev_tools.keyword_extract ``` -Calyx_Golden_Memories_Jarilo_VI = DungeonList( +Calyx_Golden_Memories_Penacony = DungeonList( id=1, - name='Calyx_Golden_Memories_Jarilo_VI', + name='Calyx_Golden_Memories_Penacony', cn='回忆之蕾', cht='回憶之蕾', en='Bud of Memories', jp='回憶の蕾', es='Flor de los recuerdos', - dungeon_id=1001, - plane_id=2010101, + dungeon_id=1014, + plane_id=2031301, ) -Calyx_Golden_Aether_Jarilo_VI = DungeonList( +Calyx_Golden_Aether_Penacony = DungeonList( id=2, - name='Calyx_Golden_Aether_Jarilo_VI', + name='Calyx_Golden_Aether_Penacony', cn='以太之蕾', cht='乙太之蕾', en='Bud of Aether', jp='エーテルの蕾', es='Flor de éter', - dungeon_id=1002, - plane_id=2011101, + dungeon_id=1015, + plane_id=2031201, ) -Calyx_Golden_Treasures_Jarilo_VI = DungeonList( +Calyx_Golden_Treasures_Penacony = DungeonList( id=3, - name='Calyx_Golden_Treasures_Jarilo_VI', + name='Calyx_Golden_Treasures_Penacony', cn='藏珍之蕾', cht='藏珍之蕾', en='Bud of Treasures', jp='秘蔵の蕾', es='Flor de tesoros', - dungeon_id=1003, - plane_id=2012101, + dungeon_id=1016, + plane_id=2031101, ) Calyx_Golden_Memories_The_Xianzhou_Luofu = DungeonList( id=4, @@ -69,38 +69,38 @@ Calyx_Golden_Treasures_The_Xianzhou_Luofu = DungeonList( dungeon_id=1013, plane_id=2022201, ) -Calyx_Golden_Memories_Penacony = DungeonList( +Calyx_Golden_Memories_Jarilo_VI = DungeonList( id=7, - name='Calyx_Golden_Memories_Penacony', + name='Calyx_Golden_Memories_Jarilo_VI', cn='回忆之蕾', cht='回憶之蕾', en='Bud of Memories', jp='回憶の蕾', es='Flor de los recuerdos', - dungeon_id=1014, - plane_id=2031301, + dungeon_id=1001, + plane_id=2010101, ) -Calyx_Golden_Aether_Penacony = DungeonList( +Calyx_Golden_Aether_Jarilo_VI = DungeonList( id=8, - name='Calyx_Golden_Aether_Penacony', + name='Calyx_Golden_Aether_Jarilo_VI', cn='以太之蕾', cht='乙太之蕾', en='Bud of Aether', jp='エーテルの蕾', es='Flor de éter', - dungeon_id=1015, - plane_id=2031201, + dungeon_id=1002, + plane_id=2011101, ) -Calyx_Golden_Treasures_Penacony = DungeonList( +Calyx_Golden_Treasures_Jarilo_VI = DungeonList( id=9, - name='Calyx_Golden_Treasures_Penacony', + name='Calyx_Golden_Treasures_Jarilo_VI', cn='藏珍之蕾', cht='藏珍之蕾', en='Bud of Treasures', jp='秘蔵の蕾', es='Flor de tesoros', - dungeon_id=1016, - plane_id=2031101, + dungeon_id=1003, + plane_id=2012101, ) Calyx_Crimson_Destruction_Herta_StorageZone = DungeonList( id=10, @@ -476,107 +476,8 @@ Stagnant_Shadow_Gloam = DungeonList( dungeon_id=1121, plane_id=2033201, ) -Cavern_of_Corrosion_Path_of_Gelid_Wind = DungeonList( - id=44, - name='Cavern_of_Corrosion_Path_of_Gelid_Wind', - cn='霜风之径', - cht='霜風之徑', - en='Path of Gelid Wind', - jp='霜風の路', - es='Senda del viento gélido', - dungeon_id=1201, - plane_id=2000201, -) -Cavern_of_Corrosion_Path_of_Jabbing_Punch = DungeonList( - id=45, - name='Cavern_of_Corrosion_Path_of_Jabbing_Punch', - cn='迅拳之径', - cht='迅拳之徑', - en='Path of Jabbing Punch', - jp='迅拳の路', - es='Senda de los puños rápidos', - dungeon_id=1202, - plane_id=2013101, -) -Cavern_of_Corrosion_Path_of_Drifting = DungeonList( - id=46, - name='Cavern_of_Corrosion_Path_of_Drifting', - cn='漂泊之径', - cht='漂泊之徑', - en='Path of Drifting', - jp='漂泊の路', - es='Senda de la deriva', - dungeon_id=1203, - plane_id=2013201, -) -Cavern_of_Corrosion_Path_of_Providence = DungeonList( - id=47, - name='Cavern_of_Corrosion_Path_of_Providence', - cn='睿治之径', - cht='睿治之徑', - en='Path of Providence', - jp='睿治の路', - es='Senda de la providencia', - dungeon_id=1204, - plane_id=2013401, -) -Cavern_of_Corrosion_Path_of_Holy_Hymn = DungeonList( - id=48, - name='Cavern_of_Corrosion_Path_of_Holy_Hymn', - cn='圣颂之径', - cht='聖頌之徑', - en='Path of Holy Hymn', - jp='聖頌の路', - es='Senda del himno sagrado', - dungeon_id=1205, - plane_id=2021101, -) -Cavern_of_Corrosion_Path_of_Conflagration = DungeonList( - id=49, - name='Cavern_of_Corrosion_Path_of_Conflagration', - cn='野焰之径', - cht='野焰之徑', - en='Path of Conflagration', - jp='野焔の路', - es='Senda de la conflagración', - dungeon_id=1206, - plane_id=2021201, -) -Cavern_of_Corrosion_Path_of_Elixir_Seekers = DungeonList( - id=50, - name='Cavern_of_Corrosion_Path_of_Elixir_Seekers', - cn='药使之径', - cht='藥使之徑', - en='Path of Elixir Seekers', - jp='薬使の路', - es='Senda de los elixires', - dungeon_id=1207, - plane_id=2023101, -) -Cavern_of_Corrosion_Path_of_Darkness = DungeonList( - id=51, - name='Cavern_of_Corrosion_Path_of_Darkness', - cn='幽冥之径', - cht='幽冥之徑', - en='Path of Darkness', - jp='幽冥の路', - es='Senda de la oscuridad', - dungeon_id=1208, - plane_id=2022301, -) -Cavern_of_Corrosion_Path_of_Dreamdive = DungeonList( - id=52, - name='Cavern_of_Corrosion_Path_of_Dreamdive', - cn='梦潜之径', - cht='夢潛之徑', - en='Path of Dreamdive', - jp='夢潜の路', - es='Senda de los sueños', - dungeon_id=1209, - plane_id=2031101, -) Cavern_of_Corrosion_Path_of_Cavalier = DungeonList( - id=53, + id=44, name='Cavern_of_Corrosion_Path_of_Cavalier', cn='勇骑之径', cht='勇騎之徑', @@ -586,52 +487,118 @@ Cavern_of_Corrosion_Path_of_Cavalier = DungeonList( dungeon_id=1210, plane_id=2033201, ) -Echo_of_War_Destruction_Beginning = DungeonList( - id=54, - name='Echo_of_War_Destruction_Beginning', - cn='毁灭的开端•历战余响', - cht='毀滅的開端•歷戰餘響', - en="Echo of War: Destruction's Beginning", - jp='歴戦余韻・壊滅の始まり', - es='El principio de la Destrucción', - dungeon_id=1301, - plane_id=2000301, +Cavern_of_Corrosion_Path_of_Dreamdive = DungeonList( + id=45, + name='Cavern_of_Corrosion_Path_of_Dreamdive', + cn='梦潜之径', + cht='夢潛之徑', + en='Path of Dreamdive', + jp='夢潜の路', + es='Senda de los sueños', + dungeon_id=1209, + plane_id=2031101, ) -Echo_of_War_End_of_the_Eternal_Freeze = DungeonList( - id=55, - name='Echo_of_War_End_of_the_Eternal_Freeze', - cn='寒潮的落幕•历战余响', - cht='寒潮的落幕•歷戰餘響', - en='Echo of War: End of the Eternal Freeze', - jp='歴戦余韻・寒波の幕切れ', - es='El fin del Hielo Eterno', - dungeon_id=1302, +Cavern_of_Corrosion_Path_of_Darkness = DungeonList( + id=46, + name='Cavern_of_Corrosion_Path_of_Darkness', + cn='幽冥之径', + cht='幽冥之徑', + en='Path of Darkness', + jp='幽冥の路', + es='Senda de la oscuridad', + dungeon_id=1208, + plane_id=2022301, +) +Cavern_of_Corrosion_Path_of_Elixir_Seekers = DungeonList( + id=47, + name='Cavern_of_Corrosion_Path_of_Elixir_Seekers', + cn='药使之径', + cht='藥使之徑', + en='Path of Elixir Seekers', + jp='薬使の路', + es='Senda de los elixires', + dungeon_id=1207, + plane_id=2023101, +) +Cavern_of_Corrosion_Path_of_Conflagration = DungeonList( + id=48, + name='Cavern_of_Corrosion_Path_of_Conflagration', + cn='野焰之径', + cht='野焰之徑', + en='Path of Conflagration', + jp='野焔の路', + es='Senda de la conflagración', + dungeon_id=1206, + plane_id=2021201, +) +Cavern_of_Corrosion_Path_of_Holy_Hymn = DungeonList( + id=49, + name='Cavern_of_Corrosion_Path_of_Holy_Hymn', + cn='圣颂之径', + cht='聖頌之徑', + en='Path of Holy Hymn', + jp='聖頌の路', + es='Senda del himno sagrado', + dungeon_id=1205, + plane_id=2021101, +) +Cavern_of_Corrosion_Path_of_Providence = DungeonList( + id=50, + name='Cavern_of_Corrosion_Path_of_Providence', + cn='睿治之径', + cht='睿治之徑', + en='Path of Providence', + jp='睿治の路', + es='Senda de la providencia', + dungeon_id=1204, plane_id=2013401, ) -Echo_of_War_Divine_Seed = DungeonList( - id=56, - name='Echo_of_War_Divine_Seed', - cn='不死的神实•历战余响', - cht='不死的神實•歷戰餘響', - en='Echo of War: Divine Seed', - jp='歴戦余韻・不死の神実', - es='Semilla divina', - dungeon_id=1303, - plane_id=2023201, +Cavern_of_Corrosion_Path_of_Drifting = DungeonList( + id=51, + name='Cavern_of_Corrosion_Path_of_Drifting', + cn='漂泊之径', + cht='漂泊之徑', + en='Path of Drifting', + jp='漂泊の路', + es='Senda de la deriva', + dungeon_id=1203, + plane_id=2013201, ) -Echo_of_War_Borehole_Planet_Old_Crater = DungeonList( - id=57, - name='Echo_of_War_Borehole_Planet_Old_Crater', - cn='蛀星的旧靥•历战余响', - cht='蛀星的舊靨•歷戰餘響', - en="Echo of War: Borehole Planet's Old Crater", - jp='歴戦余韻・星を蝕む往日の面影', - es='Cráter del planeta devorado', - dungeon_id=1304, - plane_id=2000401, +Cavern_of_Corrosion_Path_of_Jabbing_Punch = DungeonList( + id=52, + name='Cavern_of_Corrosion_Path_of_Jabbing_Punch', + cn='迅拳之径', + cht='迅拳之徑', + en='Path of Jabbing Punch', + jp='迅拳の路', + es='Senda de los puños rápidos', + dungeon_id=1202, + plane_id=2013101, +) +Cavern_of_Corrosion_Path_of_Gelid_Wind = DungeonList( + id=53, + name='Cavern_of_Corrosion_Path_of_Gelid_Wind', + cn='霜风之径', + cht='霜風之徑', + en='Path of Gelid Wind', + jp='霜風の路', + es='Senda del viento gélido', + dungeon_id=1201, + plane_id=2000201, +) +Echo_of_War_Inner_Beast_Battlefield = DungeonList( + id=54, + name='Echo_of_War_Inner_Beast_Battlefield', + cn='心兽的战场•历战余响', + cht='心獸的戰場•歷戰餘響', + en="Echo of War: Inner Beast's Battlefield", + jp='歴戦余韻・心獣の戦場', + es='Campo de batalla de la bestia interior', + dungeon_id=1306, + plane_id=2024201, ) Echo_of_War_Salutations_of_Ashen_Dreams = DungeonList( - id=58, + id=55, name='Echo_of_War_Salutations_of_Ashen_Dreams', cn='尘梦的赞礼•历战余响', cht='塵夢的讚禮•歷戰餘響', @@ -641,16 +608,49 @@ Echo_of_War_Salutations_of_Ashen_Dreams = DungeonList( dungeon_id=1305, plane_id=2033201, ) -Echo_of_War_Inner_Beast_Battlefield = DungeonList( +Echo_of_War_Borehole_Planet_Old_Crater = DungeonList( + id=56, + name='Echo_of_War_Borehole_Planet_Old_Crater', + cn='蛀星的旧靥•历战余响', + cht='蛀星的舊靨•歷戰餘響', + en="Echo of War: Borehole Planet's Old Crater", + jp='歴戦余韻・星を蝕む往日の面影', + es='Cráter del planeta devorado', + dungeon_id=1304, + plane_id=2000401, +) +Echo_of_War_Divine_Seed = DungeonList( + id=57, + name='Echo_of_War_Divine_Seed', + cn='不死的神实•历战余响', + cht='不死的神實•歷戰餘響', + en='Echo of War: Divine Seed', + jp='歴戦余韻・不死の神実', + es='Semilla divina', + dungeon_id=1303, + plane_id=2023201, +) +Echo_of_War_End_of_the_Eternal_Freeze = DungeonList( + id=58, + name='Echo_of_War_End_of_the_Eternal_Freeze', + cn='寒潮的落幕•历战余响', + cht='寒潮的落幕•歷戰餘響', + en='Echo of War: End of the Eternal Freeze', + jp='歴戦余韻・寒波の幕切れ', + es='El fin del Hielo Eterno', + dungeon_id=1302, + plane_id=2013401, +) +Echo_of_War_Destruction_Beginning = DungeonList( id=59, - name='Echo_of_War_Inner_Beast_Battlefield', - cn='心兽的战场•历战余响', - cht='心獸的戰場•歷戰餘響', - en="Echo of War: Inner Beast's Battlefield", - jp='歴戦余韻・心獣の戦場', - es='Campo de batalla de la bestia interior', - dungeon_id=1306, - plane_id=2024201, + name='Echo_of_War_Destruction_Beginning', + cn='毁灭的开端•历战余响', + cht='毀滅的開端•歷戰餘響', + en="Echo of War: Destruction's Beginning", + jp='歴戦余韻・壊滅の始まり', + es='El principio de la Destrucción', + dungeon_id=1301, + plane_id=2000301, ) Simulated_Universe_World_1 = DungeonList( id=60, diff --git a/tasks/dungeon/stamina.py b/tasks/dungeon/stamina.py index 41fd56354..092cd3bd7 100644 --- a/tasks/dungeon/stamina.py +++ b/tasks/dungeon/stamina.py @@ -6,7 +6,7 @@ from tasks.base.page import page_guide from tasks.combat.assets.assets_combat_stamina_status import ICON_SEARCH, IMMERSIFIER_ICON from tasks.dungeon.assets.assets_dungeon_stamina import * from tasks.dungeon.keywords import KEYWORDS_DUNGEON_TAB -from tasks.dungeon.ui import DungeonUI +from tasks.dungeon.ui.ui import DungeonUI class DungeonStamina(DungeonUI): diff --git a/tasks/dungeon/ui.py b/tasks/dungeon/ui.py deleted file mode 100644 index e558d038d..000000000 --- a/tasks/dungeon/ui.py +++ /dev/null @@ -1,770 +0,0 @@ -import re - -import cv2 -import numpy as np - -from module.base.base import ModuleBase -from module.base.button import ClickButton -from module.base.decorator import run_once -from module.base.timer import Timer -from module.base.utils import get_color -from module.exception import ScriptError -from module.logger import logger -from module.ocr.ocr import Ocr, OcrResultButton -from module.ocr.utils import split_and_pair_button_attr, split_and_pair_buttons -from module.ui.draggable_list import DraggableList -from module.ui.switch import Switch -from tasks.base.page import page_guide -from tasks.combat.assets.assets_combat_interact import DUNGEON_COMBAT_INTERACT, DUNGEON_COMBAT_INTERACT_TEXT -from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE -from tasks.dungeon.assets.assets_dungeon_ui import * -from tasks.dungeon.assets.assets_dungeon_ui_rogue import * -from tasks.dungeon.keywords import ( - DungeonList, - DungeonNav, - DungeonTab, - KEYWORDS_DUNGEON_ENTRANCE, - KEYWORDS_DUNGEON_LIST, - KEYWORDS_DUNGEON_NAV, - KEYWORDS_DUNGEON_TAB -) -from tasks.dungeon.keywords.classes import DungeonEntrance -from tasks.dungeon.state import DungeonState -from tasks.map.interact.aim import inrange -from tasks.map.keywords import KEYWORDS_MAP_WORLD, MapPlane - - -class DungeonTabSwitch(Switch): - SEARCH_BUTTON = TAB_SEARCH - - def add_state(self, state, check_button, click_button=None): - # Load search - if check_button is not None: - check_button.load_search(self.__class__.SEARCH_BUTTON.area) - if click_button is not None: - click_button.load_search(self.__class__.SEARCH_BUTTON.area) - return super().add_state(state, check_button, click_button) - - def click(self, state, main): - """ - Args: - state (str): - main (ModuleBase): - """ - button = self.get_data(state)['click_button'] - _ = main.appear(button) # Search button to load offset - main.device.click(button) - - -SWITCH_DUNGEON_TAB = DungeonTabSwitch('DungeonTab', is_selector=True) -SWITCH_DUNGEON_TAB.add_state( - KEYWORDS_DUNGEON_TAB.Operation_Briefing, - check_button=OPERATION_BRIEFING_CHECK, - click_button=OPERATION_BRIEFING_CLICK -) -SWITCH_DUNGEON_TAB.add_state( - KEYWORDS_DUNGEON_TAB.Daily_Training, - check_button=DAILY_TRAINING_CHECK, - click_button=DAILY_TRAINING_CLICK -) -SWITCH_DUNGEON_TAB.add_state( - KEYWORDS_DUNGEON_TAB.Survival_Index, - check_button=SURVIVAL_INDEX_CHECK, - click_button=SURVIVAL_INDEX_CLICK -) -SWITCH_DUNGEON_TAB.add_state( - KEYWORDS_DUNGEON_TAB.Simulated_Universe, - check_button=SIMULATED_UNIVERSE_CHECK, - click_button=SIMULATED_UNIVERSE_CLICK -) -SWITCH_DUNGEON_TAB.add_state( - KEYWORDS_DUNGEON_TAB.Treasures_Lightward, - check_button=TREASURES_LIGHTWARD_CHECK, - click_button=TREASURES_LIGHTWARD_CLICK -) - - -class OcrDungeonNav(Ocr): - def after_process(self, result): - result = super().after_process(result) - result = result.replace('#', '') - if self.lang == 'cn': - result = result.replace('萼喜', '萼') - result = result.replace('带', '滞') # 凝带虚影 - return result - - -class OcrDungeonList(Ocr): - def after_process(self, result): - # 乙太之蕾•雅利洛-Ⅵ - result = re.sub(r'-[VⅤ][IⅠ]', '-Ⅵ', result) - - # 苏乐达™热砂海选会场 - result = re.sub(r'(苏乐达|蘇樂達|SoulGlad|スラーダ|FelizAlma)[rtT]*M', r'\1', result) - - result = super().after_process(result) - - if self.lang == 'cn': - result = result.replace('翼', '巽') # 巽风之形 - result = result.replace('皖A0', '50').replace('皖', '') - # 燔灼之形•凝滞虚影 - result = result.replace('熠', '燔') - result = re.sub('^灼之形', '燔灼之形', result) - # 偃偶之形•凝滞虚影 - result = re.sub('^偶之形', '偃偶之形', result) - # 嗔怒之形•凝滞虚影 - result = re.sub('^怒之形', '嗔怒之形', result) - # 蛀星的旧·历战余响 - result = re.sub(r'蛀星的旧.*?历战', '蛀星的旧靥•历战', result) - - # 9支援仓段 - for word in 'Q9α': - result = result.removeprefix(word) - return result - - -class OcrDungeonListCalyxCrimson(OcrDungeonList): - def _match_result(self, *args, **kwargs): - """ - Convert MapPlane object to their corresponding DungeonList object - """ - plane = super()._match_result(*args, **kwargs) - if plane is not None: - for dungeon in DungeonList.instances.values(): - if dungeon.is_Calyx_Crimson and dungeon.plane == plane: - return dungeon - return plane - - -class OcrDungeonListLimitEntrance(OcrDungeonList): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.button = ClickButton((*self.button.area[:3], self.button.area[3] - 70)) - - -class OcrDungeonListCalyxCrimsonLimitEntrance(OcrDungeonListCalyxCrimson, OcrDungeonListLimitEntrance): - pass - - -class DraggableDungeonNav(DraggableList): - # 0.5 is the magic number to reach bottom in 1 swipe - # but relax we still have retires when magic doesn't work - drag_vector = (0.50, 0.52) - - -class DraggableDungeonList(DraggableList): - teleports: list[OcrResultButton] = [] - navigates: list[OcrResultButton] = [] - - # use_plane: True to use map planes to predict dungeons only. - # Can only be True in Calyx Crimson - use_plane = False - # limit_entrance: True to ensure the teleport button is insight - limit_entrance = False - - def load_rows(self, main: ModuleBase, allow_early_access=False): - """ - Args: - main: - allow_early_access: True to allow dungeons that are in temporarily early access during events - """ - relative_area = (0, 0, 1280, 120) - if self.use_plane: - self.keyword_class = [MapPlane, DungeonEntrance] - if self.limit_entrance: - self.ocr_class = OcrDungeonListCalyxCrimsonLimitEntrance - else: - self.ocr_class = OcrDungeonListCalyxCrimson - else: - self.keyword_class = [DungeonList, DungeonEntrance] - if self.limit_entrance: - self.ocr_class = OcrDungeonListLimitEntrance - else: - self.ocr_class = OcrDungeonList - super().load_rows(main=main) - - # Check early access dungeons - buttons = DUNGEON_LIST.cur_buttons.copy() - for name, button in split_and_pair_buttons( - DUNGEON_LIST.cur_buttons, - split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Enter, - relative_area=relative_area - ): - logger.warning(f'Early access dungeon: {name}') - buttons.remove(name) - buttons.remove(button) - - # Remove early access dungeons - if not allow_early_access: - DUNGEON_LIST.cur_buttons = buttons - # From super.load_rows(), re-calculate indexes - indexes = [self.keyword2index(row.matched_keyword) - for row in self.cur_buttons] - indexes = [index for index in indexes if index] - - if not indexes: - logger.warning(f'No valid rows loaded into {self}') - return - - self.cur_min = min(indexes) - self.cur_max = max(indexes) - logger.attr(self.name, f'{self.cur_min} - {self.cur_max}') - - # Replace dungeon.button with teleport - self.teleports = list(split_and_pair_button_attr( - DUNGEON_LIST.cur_buttons, - split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Teleport and x != KEYWORDS_DUNGEON_ENTRANCE.Enter, - relative_area=relative_area - )) - self.navigates = list(split_and_pair_button_attr( - DUNGEON_LIST.cur_buttons, - split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Navigate, - relative_area=relative_area - )) - - -DUNGEON_NAV_LIST = DraggableDungeonNav( - 'DungeonNavList', keyword_class=DungeonNav, ocr_class=OcrDungeonNav, search_button=OCR_DUNGEON_NAV) -DUNGEON_LIST = DraggableDungeonList( - 'DungeonList', keyword_class=[DungeonList, DungeonEntrance, MapPlane], - ocr_class=OcrDungeonList, search_button=OCR_DUNGEON_LIST) - - -class DungeonUI(DungeonState): - def dungeon_tab_goto(self, state: DungeonTab): - """ - Args: - state: - - Returns: - bool: If UI switched - - Examples: - self = DungeonUI('alas') - self.device.screenshot() - self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Operation_Briefing) - self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Daily_Training) - self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index) - """ - logger.hr('Dungeon tab goto', level=2) - ui_switched = self.ui_ensure(page_guide) - tab_switched = SWITCH_DUNGEON_TAB.set(state, main=self) - - if ui_switched or tab_switched: - if state == KEYWORDS_DUNGEON_TAB.Daily_Training: - logger.info(f'Tab goto {state}, wait until loaded') - self._dungeon_wait_daily_training_loaded() - elif state == KEYWORDS_DUNGEON_TAB.Survival_Index: - logger.info(f'Tab goto {state}, wait until loaded') - self._dungeon_wait_survival_index_loaded() - elif state == KEYWORDS_DUNGEON_TAB.Treasures_Lightward: - logger.info(f'Tab goto {state}, wait until loaded') - self._dungeon_wait_treasures_lightward_loaded() - return True - else: - return False - - def _dungeon_wait_daily_training_loaded(self, skip_first_screenshot=True): - """ - Returns: - bool: True if wait success, False if wait timeout. - - Pages: - in: page_guide, Daily_Training - """ - timeout = Timer(2, count=4).start() - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - - if timeout.reached(): - logger.warning('Wait daily training loaded timeout') - return False - color = get_color(self.device.image, DAILY_TRAINING_LOADED.area) - if np.mean(color) < 128: - logger.info('Daily training loaded') - return True - - def _dungeon_wait_survival_index_loaded(self, skip_first_screenshot=True): - """ - Returns: - bool: True if wait success, False if wait timeout. - - Pages: - in: page_guide, Survival_Index - """ - timeout = Timer(2, count=4).start() - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - - if timeout.reached(): - logger.warning('Wait survival index loaded timeout') - return False - if self.appear(SURVIVAL_INDEX_SU_LOADED): - logger.info('Survival index loaded, SURVIVAL_INDEX_SU_LOADED') - return True - if self.appear(SURVIVAL_INDEX_OE_LOADED): - logger.info('Survival index loaded, SURVIVAL_INDEX_OE_LOADED') - return True - - def _dungeon_survival_index_top_appear(self): - if self.appear(SURVIVAL_INDEX_SU_LOADED): - return True - if self.appear(SURVIVAL_INDEX_OE_LOADED): - return True - return False - - def _dungeon_wait_treasures_lightward_loaded(self, skip_first_screenshot=True): - """ - Returns: - bool: True if wait success, False if wait timeout. - - Pages: - in: page_guide, Survival_Index - """ - timeout = Timer(2, count=4).start() - TREASURES_LIGHTWARD_LOADED.set_search_offset((5, 5)) - TREASURES_LIGHTWARD_LOCKED.set_search_offset((5, 5)) - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - - if timeout.reached(): - logger.warning('Wait treasures lightward loaded timeout') - return False - if self.appear(TREASURES_LIGHTWARD_LOADED): - logger.info('Treasures lightward loaded (event unlocked)') - return True - if self.appear(TREASURES_LIGHTWARD_LOCKED): - logger.info('Treasures lightward loaded (event locked)') - return True - - def _dungeon_list_button_has_content(self): - # Check if having any content - # List background: 254, guild border: 225 - r, g, b = cv2.split(self.image_crop(LIST_LOADED_CHECK, copy=False)) - minimum = cv2.min(cv2.min(r, g), b) - minimum = inrange(minimum, lower=0, upper=180) - if minimum.size > 100: - return True - else: - return False - - def _dungeon_wait_until_dungeon_list_loaded(self, skip_first_screenshot=True): - timeout = Timer(1, count=3).start() - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - - # End - if timeout.reached(): - logger.warning('Wait until dungeon list loaded timeout') - return False - - if self._dungeon_list_button_has_content(): - logger.info('Dungeon list loaded') - return True - - def _dungeon_wait_until_echo_or_war_stabled(self, skip_first_screenshot=True): - """ - Returns: - bool: True if wait success, False if wait timeout. - - Pages: - in: page_guide, Survival_Index - """ - # Wait until Forgotten_Hall stabled - timeout = Timer(2, count=4).start() - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - - # End - if timeout.reached(): - logger.warning('Wait until Echo_of_War stabled timeout') - return False - - DUNGEON_NAV_LIST.load_rows(main=self) - - # End - button = DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Echo_of_War, show_warning=False) - if button: - # 513 is the top of the last row of DungeonNav - if button.area[1] > 513: - logger.info('DungeonNav row Echo_of_War stabled') - return True - else: - logger.info('No Echo_of_War in list skip waiting') - return False - - def _dungeon_nav_goto(self, nav: DungeonNav, skip_first_screenshot=True): - """ - Equivalent to `DUNGEON_NAV_LIST.select_row(dungeon.dungeon_nav, main=self)` - but with tricks to be faster - - Args: - nav: - skip_first_screenshot: - """ - logger.hr('Dungeon nav goto', level=2) - logger.info(f'Dungeon nav goto {nav}') - - # Wait rows - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - DUNGEON_NAV_LIST.load_rows(main=self) - if DUNGEON_NAV_LIST.cur_buttons: - break - - # Wait first row selected - timeout = Timer(0.5, count=2).start() - skip_first_screenshot = True - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - if timeout.reached(): - logger.info('DUNGEON_NAV_LIST not selected') - break - if button := DUNGEON_NAV_LIST.get_selected_row(main=self): - logger.info(f'DUNGEON_NAV_LIST selected at {button}') - break - - # Check if it's at the first page. - if DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Simulated_Universe, show_warning=False) \ - or DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Ornament_Extraction, show_warning=False): - # Going to use a faster method to navigate but can only start from list top - logger.info('DUNGEON_NAV_LIST at top') - # Update points if possible - # 2.3, No longer weekly points after Divergent Universe unlocked - # if DUNGEON_NAV_LIST.is_row_selected(button, main=self): - # self.dungeon_update_simuni() - # Treasures lightward is always at top - elif DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Forgotten_Hall, show_warning=False) \ - or DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Pure_Fiction, show_warning=False): - logger.info('DUNGEON_NAV_LIST at top') - else: - # To start from any list states. - logger.info('DUNGEON_NAV_LIST not at top') - DUNGEON_NAV_LIST.select_row(nav, main=self) - return True - - # Check the first page - if nav in [ - KEYWORDS_DUNGEON_NAV.Simulated_Universe, - KEYWORDS_DUNGEON_NAV.Divergent_Universe, - KEYWORDS_DUNGEON_NAV.Ornament_Extraction, - KEYWORDS_DUNGEON_NAV.Calyx_Golden, - KEYWORDS_DUNGEON_NAV.Calyx_Crimson, - KEYWORDS_DUNGEON_NAV.Stagnant_Shadow, - KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion, - KEYWORDS_DUNGEON_NAV.Forgotten_Hall, - KEYWORDS_DUNGEON_NAV.Pure_Fiction, - ]: - button = DUNGEON_NAV_LIST.keyword2button(nav) - if button: - DUNGEON_NAV_LIST.select_row(nav, main=self, insight=False) - return True - - # Check the second page - while 1: - DUNGEON_NAV_LIST.drag_page('down', main=self) - # No skip_first_screenshot since drag_page is just called - if self._dungeon_wait_until_echo_or_war_stabled(skip_first_screenshot=False): - DUNGEON_NAV_LIST.select_row(nav, main=self, insight=False) - return True - - def _dungeon_world_set(self, dungeon: DungeonList, skip_first_screenshot=True): - """ - Switch worlds in Calyx_Golden - - Returns: - bool: True if success to set - """ - logger.hr('Dungeon world set', level=2) - if not dungeon.is_Calyx_Golden: - logger.warning(f'Dungeon {dungeon} is not Calyx Golden, no need to set world') - return False - if dungeon.world is None: - logger.error(f'Dungeon {dungeon} does not belongs to any world') - return False - dic_world_button = { - KEYWORDS_MAP_WORLD.Jarilo_VI: CALYX_WORLD_1, - KEYWORDS_MAP_WORLD.The_Xianzhou_Luofu: CALYX_WORLD_2, - KEYWORDS_MAP_WORLD.Penacony: CALYX_WORLD_3, - } - button = dic_world_button.get(dungeon.world) - if button is None: - logger.error(f'Dungeon {dungeon} with world {dungeon.world} has no corresponding world button') - return False - - logger.info(f'Dungeon world set {dungeon.world}') - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - - # End - if self.image_color_count(button, color=(18, 18, 18), threshold=180, count=50): - logger.info(f'Dungeon world at {dungeon.world}') - return True - # Click - if self.ui_page_appear(page_guide, interval=2): - self.device.click(button) - continue - - def _dungeon_world_set_wrapper(self, dungeon: DungeonList, skip_first_screenshot=True): - """ - Switch worlds in Calyx_Golden with error handling - If world tab is not unlocked, fallback to Jarilo dungeons - """ - # Wait world tab - button = CALYX_WORLD_1 - tab = False - timeout = Timer(0.6, count=3).start() - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - # End - if timeout.reached(): - break - # Selected tab - if self.image_color_count(button, color=(18, 18, 18), threshold=180, count=50): - tab = True - break - # Unselected tab - if self.image_color_count(button, color=(134, 134, 134), threshold=180, count=50): - tab = True - break - - logger.attr('WorldTab', tab) - if not tab: - logger.warning('World tab is not unlocked, fallback to Jarilo dungeons') - if dungeon.is_Calyx_Golden_Memories: - dungeon = KEYWORDS_DUNGEON_LIST.Calyx_Golden_Treasures_Jarilo_VI - if dungeon.is_Calyx_Golden_Aether: - dungeon = KEYWORDS_DUNGEON_LIST.Calyx_Golden_Aether_Jarilo_VI - if dungeon.is_Calyx_Golden_Treasures: - dungeon = KEYWORDS_DUNGEON_LIST.Calyx_Golden_Treasures_Jarilo_VI - - self._dungeon_world_set(dungeon, skip_first_screenshot=skip_first_screenshot) - return dungeon - - def _dungeon_insight(self, dungeon: DungeonList): - """ - Pages: - in: page_guide, Survival_Index, nav including dungeon - out: page_guide, Survival_Index, nav including dungeon, dungeon insight - """ - logger.hr('Dungeon insight', level=2) - DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx_Crimson) - # Insight dungeon - DUNGEON_LIST.insight_row(dungeon, main=self) - self.device.click_record_clear() - # Check if dungeon unlocked - for entrance in DUNGEON_LIST.navigates: - entrance: OcrResultButton = entrance - logger.warning(f'Teleport {entrance.matched_keyword} is not unlocked') - if entrance == dungeon: - logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked') - return False - - # Find teleport button - if dungeon not in [tp.matched_keyword for tp in DUNGEON_LIST.teleports]: - # Dungeon name is insight but teleport button is not - logger.info('Dungeon name is insight, swipe down a little bit to find the teleport button') - if dungeon.is_Forgotten_Hall: - DUNGEON_LIST.drag_vector = (-0.4, -0.2) # Keyword loaded is reversed - else: - DUNGEON_LIST.drag_vector = (0.2, 0.4) - DUNGEON_LIST.limit_entrance = True - DUNGEON_LIST.insight_row(dungeon, main=self) - self.device.click_record_clear() - DUNGEON_LIST.drag_vector = DraggableList.drag_vector - DUNGEON_LIST.limit_entrance = False - DUNGEON_LIST.load_rows(main=self) - # Check if dungeon unlocked - for entrance in DUNGEON_LIST.navigates: - if entrance == dungeon: - logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked') - return False - - return True - - def _dungeon_enter(self, dungeon, enter_check_button=COMBAT_PREPARE, skip_first_screenshot=True): - """ - Pages: - in: page_guide, Survival_Index, nav including dungeon - out: COMBAT_PREPARE, FORGOTTEN_HALL_CHECK - """ - logger.hr('Dungeon enter', level=2) - DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx_Crimson) - skip_first_load = skip_first_screenshot - - @run_once - def screenshot_interval_set(): - self.device.screenshot_interval_set('combat') - - while 1: - if skip_first_screenshot: - skip_first_screenshot = False - else: - self.device.screenshot() - - # End - if self.appear(enter_check_button): - logger.info(f'Arrive {enter_check_button.name}') - self.device.screenshot_interval_set() - break - - # Additional - # Popup that confirm character switch - if self.handle_popup_confirm(): - self.interval_reset(page_guide.check_button) - continue - - # Click teleport - if self.appear(page_guide.check_button, interval=1): - if skip_first_load: - skip_first_load = False - else: - DUNGEON_LIST.load_rows(main=self) - entrance = DUNGEON_LIST.keyword2button(dungeon) - if entrance is not None: - self.device.click(entrance) - screenshot_interval_set() - self.interval_reset(page_guide.check_button) - continue - else: - logger.warning(f'Cannot find dungeon entrance of {dungeon}') - continue - - def get_dungeon_interact(self) -> DungeonList | None: - """ - Pages: - in: page_main - """ - if not self.appear(DUNGEON_COMBAT_INTERACT): - logger.info('No dungeon interact') - return None - - self.acquire_lang_checked() - - ocr = OcrDungeonList(DUNGEON_COMBAT_INTERACT_TEXT) - result = ocr.detect_and_ocr(self.device.image) - - dungeon = None - # Special match names in English - # Second row must have at least 3 characters which is the shortest name "Ire" - # Stangnant Shadow: Shape of - # Quanta - if len(result) == 2 and len(result[1].ocr_text) >= 3: - first, second = result[0].ocr_text, result[1].ocr_text - if re.search(r'Stagnant\s*Shadow', first): - dungeon = DungeonList.find_dungeon_by_string(en=second, is_Stagnant_Shadow=True) - elif re.search(r'Cavern\s*of\s*Corrosion', first): - dungeon = DungeonList.find_dungeon_by_string(en=second, is_Cavern_of_Corrosion=True) - elif re.search(r'Echo\s*of\s*War', first): - dungeon = DungeonList.find_dungeon_by_string(en=second, is_Echo_of_War=True) - elif re.search(r'Calyx[\s(]+Golden', first): - dungeon = DungeonList.find_dungeon_by_string(en=second, is_Calyx_Golden=True, world=self.plane.world) - elif re.search(r'Calyx[\s(]+Crimson', first): - dungeon = DungeonList.find_dungeon_by_string(en=second, is_Calyx_Crimson=True, plane=self.plane) - if dungeon is not None: - logger.attr('DungeonInteract', dungeon) - return dungeon - - # Join - result = ' '.join([row.ocr_text for row in result]) - - # Special match names in Chinese - # Only calyxes need spacial match - if res := re.search(r'(^.+之蕾)', result): - dungeon = DungeonList.find_dungeon_by_string(cn=res.group(1), is_Calyx_Crimson=True, plane=self.plane) - if dungeon is not None: - logger.attr('DungeonInteract', dungeon) - return dungeon - dungeon = DungeonList.find_dungeon_by_string(cn=res.group(1), is_Calyx_Golden=True, world=self.plane.world) - if dungeon is not None: - logger.attr('DungeonInteract', dungeon) - return dungeon - - # Dungeons - try: - dungeon = DungeonList.find(result) - logger.attr('DungeonInteract', dungeon) - return dungeon - except ScriptError: - pass - # Simulated Universe returns Simulated_Universe_World_1 - try: - dungeon = DungeonNav.find(result) - if dungeon == KEYWORDS_DUNGEON_NAV.Simulated_Universe: - dungeon = KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_1 - logger.attr('DungeonInteract', dungeon) - return dungeon - except ScriptError: - pass - # Unknown - logger.attr('DungeonInteract', None) - return None - - def dungeon_goto(self, dungeon: DungeonList): - """ - Returns: - bool: If success - - Pages: - in: page_guide, Survival_Index - out: COMBAT_PREPARE if success - page_guide if failed - - Examples: - from tasks.dungeon.keywords import KEYWORDS_DUNGEON_LIST - self = DungeonUI('src') - self.device.screenshot() - self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index) - self.dungeon_goto(KEYWORDS_DUNGEON_LIST.Calyx_Crimson_Harmony) - """ - # Reset search button - DUNGEON_LIST.search_button = OCR_DUNGEON_LIST - - if dungeon.is_Calyx_Crimson \ - or dungeon.is_Stagnant_Shadow \ - or dungeon.is_Cavern_of_Corrosion \ - or dungeon.is_Echo_of_War \ - or dungeon.is_Ornament_Extraction: - self._dungeon_nav_goto(dungeon.dungeon_nav) - self._dungeon_wait_until_dungeon_list_loaded() - self._dungeon_insight(dungeon) - self._dungeon_enter(dungeon) - return True - if dungeon.is_Calyx_Golden: - self._dungeon_nav_goto(dungeon.dungeon_nav) - self._dungeon_wait_until_dungeon_list_loaded() - dungeon = self._dungeon_world_set_wrapper(dungeon) - self._dungeon_wait_until_dungeon_list_loaded() - self._dungeon_insight(dungeon) - self._dungeon_enter(dungeon) - return True - - logger.error(f'Goto dungeon {dungeon} is not supported') - return False diff --git a/tasks/dungeon/ui/interact.py b/tasks/dungeon/ui/interact.py new file mode 100644 index 000000000..89be8ba23 --- /dev/null +++ b/tasks/dungeon/ui/interact.py @@ -0,0 +1,85 @@ +import re + +from module.exception import ScriptError +from module.logger import logger +from tasks.base.ui import UI +from tasks.combat.assets.assets_combat_interact import DUNGEON_COMBAT_INTERACT, DUNGEON_COMBAT_INTERACT_TEXT +from tasks.dungeon.keywords import ( + DungeonList, + DungeonNav, + KEYWORDS_DUNGEON_LIST, + KEYWORDS_DUNGEON_NAV +) +from tasks.dungeon.ui.llist import OcrDungeonName + + +class DungeonUIInteract(UI): + def get_dungeon_interact(self) -> DungeonList | None: + """ + Pages: + in: page_main + """ + if not self.appear(DUNGEON_COMBAT_INTERACT): + logger.info('No dungeon interact') + return None + + self.acquire_lang_checked() + + ocr = OcrDungeonName(DUNGEON_COMBAT_INTERACT_TEXT) + result = ocr.detect_and_ocr(self.device.image) + + dungeon = None + # Special match names in English + # Second row must have at least 3 characters which is the shortest name "Ire" + # Stangnant Shadow: Shape of + # Quanta + if len(result) == 2 and len(result[1].ocr_text) >= 3: + first, second = result[0].ocr_text, result[1].ocr_text + if re.search(r'Stagnant\s*Shadow', first): + dungeon = DungeonList.find_dungeon_by_string(en=second, is_Stagnant_Shadow=True) + elif re.search(r'Cavern\s*of\s*Corrosion', first): + dungeon = DungeonList.find_dungeon_by_string(en=second, is_Cavern_of_Corrosion=True) + elif re.search(r'Echo\s*of\s*War', first): + dungeon = DungeonList.find_dungeon_by_string(en=second, is_Echo_of_War=True) + elif re.search(r'Calyx[\s(]+Golden', first): + dungeon = DungeonList.find_dungeon_by_string(en=second, is_Calyx_Golden=True, world=self.plane.world) + elif re.search(r'Calyx[\s(]+Crimson', first): + dungeon = DungeonList.find_dungeon_by_string(en=second, is_Calyx_Crimson=True, plane=self.plane) + if dungeon is not None: + logger.attr('DungeonInteract', dungeon) + return dungeon + + # Join + result = ' '.join([row.ocr_text for row in result]) + + # Special match names in Chinese + # Only calyxes need spacial match + if res := re.search(r'(^.+之蕾)', result): + dungeon = DungeonList.find_dungeon_by_string(cn=res.group(1), is_Calyx_Crimson=True, plane=self.plane) + if dungeon is not None: + logger.attr('DungeonInteract', dungeon) + return dungeon + dungeon = DungeonList.find_dungeon_by_string(cn=res.group(1), is_Calyx_Golden=True, world=self.plane.world) + if dungeon is not None: + logger.attr('DungeonInteract', dungeon) + return dungeon + + # Dungeons + try: + dungeon = DungeonList.find(result) + logger.attr('DungeonInteract', dungeon) + return dungeon + except ScriptError: + pass + # Simulated Universe returns Simulated_Universe_World_1 + try: + dungeon = DungeonNav.find(result) + if dungeon == KEYWORDS_DUNGEON_NAV.Simulated_Universe: + dungeon = KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_1 + logger.attr('DungeonInteract', dungeon) + return dungeon + except ScriptError: + pass + # Unknown + logger.attr('DungeonInteract', None) + return None diff --git a/tasks/dungeon/ui/llist.py b/tasks/dungeon/ui/llist.py new file mode 100644 index 000000000..b47671c16 --- /dev/null +++ b/tasks/dungeon/ui/llist.py @@ -0,0 +1,385 @@ +import re + +import cv2 +from pponnxcr.predict_system import BoxedResult + +from module.base.base import ModuleBase +from module.base.decorator import run_once +from module.base.timer import Timer +from module.base.utils import area_center, area_offset, crop, image_size +from module.logger import logger +from module.ocr.ocr import Ocr, OcrResultButton +from module.ocr.utils import split_and_pair_button_attr, split_and_pair_buttons +from module.ui.draggable_list import DraggableList +from module.ui.switch import Switch +from tasks.base.page import page_guide +from tasks.base.ui import UI +from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE +from tasks.dungeon.assets.assets_dungeon_ui_list import * +from tasks.dungeon.keywords import ( + DungeonList, + KEYWORDS_DUNGEON_ENTRANCE, + KEYWORDS_DUNGEON_LIST +) +from tasks.dungeon.keywords.classes import DungeonEntrance +from tasks.map.keywords import MapPlane + +LIST_SORTING = Switch('DUNGEON_LIST_SORTING', is_selector=True) +LIST_SORTING.add_state('Ascending', check_button=LIST_ASCENDING) +LIST_SORTING.add_state('Descending', check_button=LIST_DESCENDING) +LIST_SORTING.Ascending = 'Ascending' +LIST_SORTING.Descending = 'Descending' + + +class OcrDungeonName(Ocr): + def after_process(self, result): + # 乙太之蕾•雅利洛-Ⅵ + result = re.sub(r'-[VⅤ][IⅠ]', '-Ⅵ', result) + + # 苏乐达™热砂海选会场 + result = re.sub(r'(苏乐达|蘇樂達|SoulGlad|スラーダ|FelizAlma)[rtT]*M', r'\1', result) + + result = super().after_process(result) + + if self.lang == 'cn': + result = result.replace('翼', '巽') # 巽风之形 + result = result.replace('皖A0', '50').replace('皖', '') + # 燔灼之形•凝滞虚影 + result = result.replace('熠', '燔') + result = re.sub('^灼之形', '燔灼之形', result) + # 偃偶之形•凝滞虚影 + result = re.sub('^偶之形', '偃偶之形', result) + # 嗔怒之形•凝滞虚影 + result = re.sub('^怒之形', '嗔怒之形', result) + # 蛀星的旧·历战余响 + result = re.sub(r'蛀星的旧.*?历战', '蛀星的旧靥•历战', result) + + # 9支援仓段 + for word in 'Q9α': + result = result.removeprefix(word) + return result + + +class OcrDungeonList(OcrDungeonName): + # Keep __init__ parameter unused + def __init__(self, button: ButtonWrapper = None, lang=None, name=None): + super().__init__(button=button, lang=lang, name='OcrDungeonList') + self.limit_entrance = False + + def detect_and_ocr(self, image, direct_ocr=False) -> list[BoxedResult]: + if self.button != OCR_DUNGEON_NAME: + return super().detect_and_ocr(image, direct_ocr=direct_ocr) + + # Concat OCR_DUNGEON_NAME and OCR_DUNGEON_TELEPORT + # so they can be OCRed at one time + left = crop(image, OCR_DUNGEON_NAME.area, copy=False) + right = crop(image, OCR_DUNGEON_TELEPORT.area, copy=False) + lw, lh = image_size(left) + rw, rh = image_size(right) + if lh != rh: + logger.error('OCR_DUNGEON_NAME and OCR_DUNGEON_TELEPORT does not have same height, image cannot concat') + image = cv2.hconcat([left, right]) + + if self.limit_entrance: + w, h = image_size(image) + image = crop(image, (0, 0, w, h - 70), copy=False) + + results = super().detect_and_ocr(image, direct_ocr=True) + + # Move box + for result in results: + x, _ = area_center(result.box) + # Belongs to right image + if x >= lw: + result.box = area_offset(result.box, offset=(-lw, 0)) + result.box = area_offset(result.box, offset=OCR_DUNGEON_TELEPORT.area[:2]) + # Belongs to left image + else: + result.box = area_offset(result.box, offset=OCR_DUNGEON_NAME.area[:2]) + + return results + + +class OcrDungeonListUsingPlane(OcrDungeonList): + def _match_result(self, *args, **kwargs): + """ + Convert MapPlane object to their corresponding DungeonList object + """ + plane = super()._match_result(*args, **kwargs) + if plane is not None: + for dungeon in DungeonList.instances.values(): + if dungeon.is_Calyx_Golden and dungeon.plane == plane: + return dungeon + return plane + + +class OcrDungeonListLimitEntrance(OcrDungeonList): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.limit_entrance = True + + +class OcrDungeonListUsingPlaneLimitEntrance(OcrDungeonListUsingPlane, OcrDungeonListLimitEntrance): + pass + + +class DraggableDungeonList(DraggableList): + teleports: list[OcrResultButton] = [] + navigates: list[OcrResultButton] = [] + + # use_plane: True to use map planes to predict dungeons only. + # Can only be True in Calyx Crimson + use_plane = False + # limit_entrance: True to ensure the teleport button is insight + limit_entrance = False + + def load_rows(self, main: ModuleBase, allow_early_access=False): + """ + Args: + main: + allow_early_access: True to allow dungeons that are in temporarily early access during events + """ + relative_area = (0, 0, 1280, 120) + if self.use_plane: + self.keyword_class = [MapPlane, DungeonEntrance] + if self.limit_entrance: + self.ocr_class = OcrDungeonListUsingPlaneLimitEntrance + else: + self.ocr_class = OcrDungeonListUsingPlane + else: + self.keyword_class = [DungeonList, DungeonEntrance] + if self.limit_entrance: + self.ocr_class = OcrDungeonListLimitEntrance + else: + self.ocr_class = OcrDungeonList + super().load_rows(main=main) + + # Check early access dungeons + buttons = DUNGEON_LIST.cur_buttons.copy() + for name, button in split_and_pair_buttons( + DUNGEON_LIST.cur_buttons, + split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Enter, + relative_area=relative_area + ): + logger.warning(f'Early access dungeon: {name}') + buttons.remove(name) + buttons.remove(button) + + # Remove early access dungeons + if not allow_early_access: + DUNGEON_LIST.cur_buttons = buttons + # From super.load_rows(), re-calculate indexes + indexes = [self.keyword2index(row.matched_keyword) + for row in self.cur_buttons] + indexes = [index for index in indexes if index] + + if not indexes: + logger.warning(f'No valid rows loaded into {self}') + return + + self.cur_min = min(indexes) + self.cur_max = max(indexes) + logger.attr(self.name, f'{self.cur_min} - {self.cur_max}') + + # Replace dungeon.button with teleport + self.teleports = list(split_and_pair_button_attr( + self.cur_buttons, + split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Teleport and x != KEYWORDS_DUNGEON_ENTRANCE.Enter, + relative_area=relative_area + )) + self.navigates = list(split_and_pair_button_attr( + self.cur_buttons, + split_func=lambda x: x != KEYWORDS_DUNGEON_ENTRANCE.Navigate, + relative_area=relative_area + )) + + +DUNGEON_LIST = DraggableDungeonList( + 'DungeonList', keyword_class=[DungeonList, DungeonEntrance, MapPlane], + ocr_class=OcrDungeonList, search_button=OCR_DUNGEON_NAME) + + +class DungeonUIList(UI): + def _dungeon_list_reset(self): + """ + Reset list to top + + Returns: + bool: If success + """ + logger.info('Dungeon list reset') + current = LIST_SORTING.get(main=self) + if current == LIST_SORTING.Descending: + another = LIST_SORTING.Ascending + elif current == LIST_SORTING.Ascending: + another = LIST_SORTING.Descending + else: + logger.warning('Unknown dungeon LIST_SORTING') + return False + + LIST_SORTING.set(another, main=self) + LIST_SORTING.set(current, main=self) + return True + + def _dungeon_insight_index(self, dungeon: DungeonList): + """ + Insight a dungeon using pre-defined dungeon indexes from DUNGEON_LIST + + Pages: + in: page_guide, Survival_Index, nav including dungeon + out: page_guide, Survival_Index, nav including dungeon, dungeon insight + """ + logger.hr('Dungeon insight (index)', level=2) + if dungeon.is_Ornament_Extraction: + # Limit drag area in iOrnament_Extraction + DUNGEON_LIST.search_button = OCR_DUNGEON_NAME_ROGUE + elif dungeon.is_Echo_of_War: + DUNGEON_LIST.search_button = OCR_DUNGEON_LIST + else: + DUNGEON_LIST.search_button = OCR_DUNGEON_NAME + # Predict dungeon by plane name in calyxes where dungeons share the same names + DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx) + DUNGEON_LIST.check_row_order = True + + # Insight dungeon + DUNGEON_LIST.insight_row(dungeon, main=self) + self.device.click_record_clear() + # Check if dungeon unlocked + for entrance in DUNGEON_LIST.navigates: + entrance: OcrResultButton = entrance + logger.warning(f'Teleport {entrance.matched_keyword} is not unlocked') + if entrance == dungeon: + logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked') + return False + + # Find teleport button + if dungeon not in [tp.matched_keyword for tp in DUNGEON_LIST.teleports]: + # Dungeon name is insight but teleport button is not + logger.info('Dungeon name is insight, swipe down a little bit to find the teleport button') + if dungeon.is_Forgotten_Hall: + DUNGEON_LIST.drag_vector = (-0.4, -0.2) # Keyword loaded is reversed + else: + DUNGEON_LIST.drag_vector = (0.2, 0.4) + DUNGEON_LIST.limit_entrance = True + DUNGEON_LIST.insight_row(dungeon, main=self) + self.device.click_record_clear() + DUNGEON_LIST.drag_vector = DraggableList.drag_vector + DUNGEON_LIST.limit_entrance = False + DUNGEON_LIST.load_rows(main=self) + # Check if dungeon unlocked + for entrance in DUNGEON_LIST.navigates: + if entrance.matched_keyword == dungeon: + logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked') + return False + + return True + + def _dungeon_insight_sort(self, dungeon: DungeonList): + """ + Insight a dungeon using sorter and plain drag, reset list on error + """ + logger.hr('Dungeon insight (sort)', level=2) + logger.info(f'Dungeon insight: {dungeon}') + DUNGEON_LIST.search_button = OCR_DUNGEON_NAME + DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx_Golden) + DUNGEON_LIST.check_row_order = False + + for _ in range(3): + visited = set() + end_count = 0 + self.device.click_record_clear() + while 1: + visited_count = len(visited) + # Load + DUNGEON_LIST.load_rows(main=self, allow_early_access=True) + for entrance in DUNGEON_LIST.teleports: + if entrance.matched_keyword == dungeon: + logger.info(f'Found dungeon {dungeon}') + return True + for entrance in DUNGEON_LIST.navigates: + if entrance.matched_keyword == dungeon: + logger.error(f'Trying to enter dungeon {dungeon}, but teleport is not unlocked') + return False + + # Check end + for entrance in DUNGEON_LIST.cur_buttons: + visited.add(entrance.matched_keyword.name) + if len(visited) <= visited_count: + logger.warning('No more rows loaded') + end_count += 1 + if end_count >= 3: + logger.error('Dungeon list reached end but target dungeon not found') + break + + # Drag down + DUNGEON_LIST.drag_page('down', main=self) + self.wait_until_stable(DUNGEON_LIST.search_button, timer=Timer( + 0, count=0), timeout=Timer(1.5, count=5)) + + self._dungeon_list_reset() + + logger.error('Failed to insight dungeon after 3 trial') + return False + + def dungeon_insight(self, dungeon: DungeonList): + """ + Insight a dungeon + + Pages: + in: page_guide, Survival_Index, nav including dungeon + out: page_guide, Survival_Index, nav including dungeon, dungeon insight + """ + if dungeon.is_Calyx_Crimson or dungeon.is_Stagnant_Shadow: + # Having dungeon sorting and early access + self._dungeon_insight_sort(dungeon) + else: + self._dungeon_insight_index(dungeon) + + def _dungeon_enter(self, dungeon, enter_check_button=COMBAT_PREPARE, skip_first_screenshot=True): + """ + Pages: + in: page_guide, Survival_Index, nav including dungeon + out: COMBAT_PREPARE, FORGOTTEN_HALL_CHECK + """ + logger.hr('Dungeon enter', level=2) + DUNGEON_LIST.use_plane = bool(dungeon.is_Calyx_Crimson) + skip_first_load = skip_first_screenshot + + @run_once + def screenshot_interval_set(): + self.device.screenshot_interval_set('combat') + + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + if self.appear(enter_check_button): + logger.info(f'Arrive {enter_check_button.name}') + break + + # Additional + # Popup that confirm character switch + if self.handle_popup_confirm(): + self.interval_reset(page_guide.check_button) + continue + + # Click teleport + if self.appear(page_guide.check_button, interval=1): + if skip_first_load: + skip_first_load = False + else: + DUNGEON_LIST.load_rows(main=self) + entrance = DUNGEON_LIST.keyword2button(dungeon) + if entrance is not None: + self.device.click(entrance) + screenshot_interval_set() + self.interval_reset(page_guide.check_button) + continue + else: + logger.warning(f'Cannot find dungeon entrance of {dungeon}') + continue + + self.device.screenshot_interval_set() diff --git a/tasks/dungeon/ui/nav.py b/tasks/dungeon/ui/nav.py new file mode 100644 index 000000000..c173bb883 --- /dev/null +++ b/tasks/dungeon/ui/nav.py @@ -0,0 +1,351 @@ +import cv2 +import numpy as np + +from module.base.base import ModuleBase +from module.base.timer import Timer +from module.base.utils import get_color +from module.logger import logger +from module.ocr.ocr import Ocr +from module.ui.draggable_list import DraggableList +from module.ui.switch import Switch +from tasks.base.page import page_guide +from tasks.base.ui import UI +from tasks.dungeon.assets.assets_dungeon_ui import * +from tasks.dungeon.assets.assets_dungeon_ui_rogue import * +from tasks.dungeon.keywords import ( + DungeonNav, + DungeonTab, + KEYWORDS_DUNGEON_NAV, + KEYWORDS_DUNGEON_TAB +) +from tasks.map.interact.aim import inrange + + +class DungeonTabSwitch(Switch): + SEARCH_BUTTON = TAB_SEARCH + + def add_state(self, state, check_button, click_button=None): + # Load search + if check_button is not None: + check_button.load_search(self.__class__.SEARCH_BUTTON.area) + if click_button is not None: + click_button.load_search(self.__class__.SEARCH_BUTTON.area) + return super().add_state(state, check_button, click_button) + + def click(self, state, main): + """ + Args: + state (str): + main (ModuleBase): + """ + button = self.get_data(state)['click_button'] + _ = main.appear(button) # Search button to load offset + main.device.click(button) + + +SWITCH_DUNGEON_TAB = DungeonTabSwitch('DungeonTab', is_selector=True) +SWITCH_DUNGEON_TAB.add_state( + KEYWORDS_DUNGEON_TAB.Operation_Briefing, + check_button=OPERATION_BRIEFING_CHECK, + click_button=OPERATION_BRIEFING_CLICK +) +SWITCH_DUNGEON_TAB.add_state( + KEYWORDS_DUNGEON_TAB.Daily_Training, + check_button=DAILY_TRAINING_CHECK, + click_button=DAILY_TRAINING_CLICK +) +SWITCH_DUNGEON_TAB.add_state( + KEYWORDS_DUNGEON_TAB.Survival_Index, + check_button=SURVIVAL_INDEX_CHECK, + click_button=SURVIVAL_INDEX_CLICK +) +SWITCH_DUNGEON_TAB.add_state( + KEYWORDS_DUNGEON_TAB.Simulated_Universe, + check_button=SIMULATED_UNIVERSE_CHECK, + click_button=SIMULATED_UNIVERSE_CLICK +) +SWITCH_DUNGEON_TAB.add_state( + KEYWORDS_DUNGEON_TAB.Treasures_Lightward, + check_button=TREASURES_LIGHTWARD_CHECK, + click_button=TREASURES_LIGHTWARD_CLICK +) + + +class OcrDungeonNav(Ocr): + def after_process(self, result): + result = super().after_process(result) + result = result.replace('#', '') + if self.lang == 'cn': + result = result.replace('萼喜', '萼') + result = result.replace('带', '滞') # 凝带虚影 + return result + + +class DraggableDungeonNav(DraggableList): + # 0.5 is the magic number to reach bottom in 1 swipe + # but relax we still have retires when magic doesn't work + drag_vector = (0.50, 0.52) + + +DUNGEON_NAV_LIST = DraggableDungeonNav( + 'DungeonNavList', keyword_class=DungeonNav, ocr_class=OcrDungeonNav, search_button=OCR_DUNGEON_NAV) + + +class DungeonUINav(UI): + def dungeon_tab_goto(self, state: DungeonTab): + """ + Args: + state: + + Returns: + bool: If UI switched + + Examples: + self = DungeonUI('alas') + self.device.screenshot() + self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Operation_Briefing) + self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Daily_Training) + self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index) + """ + logger.hr('Dungeon tab goto', level=2) + ui_switched = self.ui_ensure(page_guide) + tab_switched = SWITCH_DUNGEON_TAB.set(state, main=self) + + if ui_switched or tab_switched: + if state == KEYWORDS_DUNGEON_TAB.Daily_Training: + logger.info(f'Tab goto {state}, wait until loaded') + self._dungeon_wait_daily_training_loaded() + elif state == KEYWORDS_DUNGEON_TAB.Survival_Index: + logger.info(f'Tab goto {state}, wait until loaded') + self._dungeon_wait_survival_index_loaded() + elif state == KEYWORDS_DUNGEON_TAB.Treasures_Lightward: + logger.info(f'Tab goto {state}, wait until loaded') + self._dungeon_wait_treasures_lightward_loaded() + return True + else: + return False + + def _dungeon_wait_daily_training_loaded(self, skip_first_screenshot=True): + """ + Returns: + bool: True if wait success, False if wait timeout. + + Pages: + in: page_guide, Daily_Training + """ + timeout = Timer(2, count=4).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if timeout.reached(): + logger.warning('Wait daily training loaded timeout') + return False + color = get_color(self.device.image, DAILY_TRAINING_LOADED.area) + if np.mean(color) < 128: + logger.info('Daily training loaded') + return True + + def _dungeon_wait_survival_index_loaded(self, skip_first_screenshot=True): + """ + Returns: + bool: True if wait success, False if wait timeout. + + Pages: + in: page_guide, Survival_Index + """ + timeout = Timer(2, count=4).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if timeout.reached(): + logger.warning('Wait survival index loaded timeout') + return False + if self.appear(SURVIVAL_INDEX_SU_LOADED): + logger.info('Survival index loaded, SURVIVAL_INDEX_SU_LOADED') + return True + if self.appear(SURVIVAL_INDEX_OE_LOADED): + logger.info('Survival index loaded, SURVIVAL_INDEX_OE_LOADED') + return True + + def _dungeon_survival_index_top_appear(self): + if self.appear(SURVIVAL_INDEX_SU_LOADED): + return True + if self.appear(SURVIVAL_INDEX_OE_LOADED): + return True + return False + + def _dungeon_wait_treasures_lightward_loaded(self, skip_first_screenshot=True): + """ + Returns: + bool: True if wait success, False if wait timeout. + + Pages: + in: page_guide, Survival_Index + """ + timeout = Timer(2, count=4).start() + TREASURES_LIGHTWARD_LOADED.set_search_offset((5, 5)) + TREASURES_LIGHTWARD_LOCKED.set_search_offset((5, 5)) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if timeout.reached(): + logger.warning('Wait treasures lightward loaded timeout') + return False + if self.appear(TREASURES_LIGHTWARD_LOADED): + logger.info('Treasures lightward loaded (event unlocked)') + return True + if self.appear(TREASURES_LIGHTWARD_LOCKED): + logger.info('Treasures lightward loaded (event locked)') + return True + + def _dungeon_list_button_has_content(self): + # Check if having any content + # List background: 254, guild border: 225 + r, g, b = cv2.split(self.image_crop(LIST_LOADED_CHECK, copy=False)) + minimum = cv2.min(cv2.min(r, g), b) + minimum = inrange(minimum, lower=0, upper=180) + if minimum.size > 100: + return True + else: + return False + + def _dungeon_wait_until_dungeon_list_loaded(self, skip_first_screenshot=True): + timeout = Timer(1, count=3).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + if timeout.reached(): + logger.warning('Wait until dungeon list loaded timeout') + return False + + if self._dungeon_list_button_has_content(): + logger.info('Dungeon list loaded') + return True + + def _dungeon_wait_until_echo_or_war_stabled(self, skip_first_screenshot=True): + """ + Returns: + bool: True if wait success, False if wait timeout. + + Pages: + in: page_guide, Survival_Index + """ + # Wait until Forgotten_Hall stabled + timeout = Timer(2, count=4).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + if timeout.reached(): + logger.warning('Wait until Echo_of_War stabled timeout') + return False + + DUNGEON_NAV_LIST.load_rows(main=self) + + # End + button = DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Echo_of_War, show_warning=False) + if button: + # 513 is the top of the last row of DungeonNav + if button.area[1] > 513: + logger.info('DungeonNav row Echo_of_War stabled') + return True + else: + logger.info('No Echo_of_War in list skip waiting') + return False + + def dungeon_nav_goto(self, nav: DungeonNav, skip_first_screenshot=True): + """ + Equivalent to `DUNGEON_NAV_LIST.select_row(dungeon.dungeon_nav, main=self)` + but with tricks to be faster + + Args: + nav: + skip_first_screenshot: + """ + logger.hr('Dungeon nav goto', level=2) + logger.info(f'Dungeon nav goto {nav}') + + # Wait rows + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + DUNGEON_NAV_LIST.load_rows(main=self) + if DUNGEON_NAV_LIST.cur_buttons: + break + + # Wait first row selected + timeout = Timer(0.5, count=2).start() + skip_first_screenshot = True + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + if timeout.reached(): + logger.info('DUNGEON_NAV_LIST not selected') + break + if button := DUNGEON_NAV_LIST.get_selected_row(main=self): + logger.info(f'DUNGEON_NAV_LIST selected at {button}') + break + + # Check if it's at the first page. + if DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Simulated_Universe, show_warning=False) \ + or DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Ornament_Extraction, show_warning=False): + # Going to use a faster method to navigate but can only start from list top + logger.info('DUNGEON_NAV_LIST at top') + # Update points if possible + # 2.3, No longer weekly points after Divergent Universe unlocked + # if DUNGEON_NAV_LIST.is_row_selected(button, main=self): + # self.dungeon_update_simuni() + # Treasures lightward is always at top + elif DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Forgotten_Hall, show_warning=False) \ + or DUNGEON_NAV_LIST.keyword2button(KEYWORDS_DUNGEON_NAV.Pure_Fiction, show_warning=False): + logger.info('DUNGEON_NAV_LIST at top') + else: + # To start from any list states. + logger.info('DUNGEON_NAV_LIST not at top') + DUNGEON_NAV_LIST.select_row(nav, main=self) + return True + + # Check the first page + if nav in [ + KEYWORDS_DUNGEON_NAV.Simulated_Universe, + KEYWORDS_DUNGEON_NAV.Divergent_Universe, + KEYWORDS_DUNGEON_NAV.Ornament_Extraction, + KEYWORDS_DUNGEON_NAV.Calyx_Golden, + KEYWORDS_DUNGEON_NAV.Calyx_Crimson, + KEYWORDS_DUNGEON_NAV.Stagnant_Shadow, + KEYWORDS_DUNGEON_NAV.Cavern_of_Corrosion, + KEYWORDS_DUNGEON_NAV.Forgotten_Hall, + KEYWORDS_DUNGEON_NAV.Pure_Fiction, + ]: + button = DUNGEON_NAV_LIST.keyword2button(nav) + if button: + DUNGEON_NAV_LIST.select_row(nav, main=self, insight=False) + return True + + # Check the second page + while 1: + DUNGEON_NAV_LIST.drag_page('down', main=self) + # No skip_first_screenshot since drag_page is just called + if self._dungeon_wait_until_echo_or_war_stabled(skip_first_screenshot=False): + DUNGEON_NAV_LIST.select_row(nav, main=self, insight=False) + return True diff --git a/tasks/dungeon/state.py b/tasks/dungeon/ui/state.py similarity index 100% rename from tasks/dungeon/state.py rename to tasks/dungeon/ui/state.py diff --git a/tasks/dungeon/ui/ui.py b/tasks/dungeon/ui/ui.py new file mode 100644 index 000000000..ad9c4042a --- /dev/null +++ b/tasks/dungeon/ui/ui.py @@ -0,0 +1,41 @@ +from module.logger import logger +from tasks.dungeon.keywords import DungeonList +from tasks.dungeon.ui.interact import DungeonUIInteract +from tasks.dungeon.ui.llist import DungeonUIList +from tasks.dungeon.ui.nav import DungeonUINav +from tasks.dungeon.ui.state import DungeonState + + +class DungeonUI(DungeonState, DungeonUINav, DungeonUIList, DungeonUIInteract): + def dungeon_goto(self, dungeon: DungeonList): + """ + Returns: + bool: If success + + Pages: + in: page_guide, Survival_Index + out: COMBAT_PREPARE if success + page_guide if failed + + Examples: + from tasks.dungeon.keywords import KEYWORDS_DUNGEON_LIST + self = DungeonUI('src') + self.device.screenshot() + self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index) + self.dungeon_goto(KEYWORDS_DUNGEON_LIST.Calyx_Crimson_Harmony) + """ + # Reset search button + if dungeon.is_Calyx_Golden \ + or dungeon.is_Calyx_Crimson \ + or dungeon.is_Stagnant_Shadow \ + or dungeon.is_Cavern_of_Corrosion \ + or dungeon.is_Echo_of_War \ + or dungeon.is_Ornament_Extraction: + self.dungeon_nav_goto(dungeon.dungeon_nav) + self._dungeon_wait_until_dungeon_list_loaded() + self.dungeon_insight(dungeon) + self._dungeon_enter(dungeon) + return True + + logger.error(f'Goto dungeon {dungeon} is not supported') + return False diff --git a/tasks/dungeon/ui_rogue.py b/tasks/dungeon/ui/ui_rogue.py similarity index 93% rename from tasks/dungeon/ui_rogue.py rename to tasks/dungeon/ui/ui_rogue.py index 1d2304115..a73968cf5 100644 --- a/tasks/dungeon/ui_rogue.py +++ b/tasks/dungeon/ui/ui_rogue.py @@ -3,9 +3,11 @@ from module.base.utils import random_rectangle_vector from module.logger import logger from tasks.base.page import page_guide from tasks.dungeon.assets.assets_dungeon_ui import * +from tasks.dungeon.assets.assets_dungeon_ui_list import OCR_DUNGEON_LIST from tasks.dungeon.assets.assets_dungeon_ui_rogue import * from tasks.dungeon.keywords import KEYWORDS_DUNGEON_NAV, KEYWORDS_DUNGEON_TAB -from tasks.dungeon.ui import DungeonUI, SWITCH_DUNGEON_TAB +from tasks.dungeon.ui.nav import SWITCH_DUNGEON_TAB +from tasks.dungeon.ui.ui import DungeonUI from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import TELEPORT @@ -41,7 +43,7 @@ class DungeonRogueUI(DungeonUI): logger.info(f'Tab goto {state}, wait until loaded') self._dungeon_wait_until_rogue_loaded() # Switch nav - self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Simulated_Universe) + self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Simulated_Universe) # No idea how to wait list loaded # List is not able to swipe without fully loaded self.wait_until_stable(LIST_LOADED_CHECK) @@ -59,7 +61,7 @@ class DungeonRogueUI(DungeonUI): if self.appear(SURVIVAL_INDEX_SU_LOADED): logger.info('Already at nav Simulated_Universe') else: - self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Simulated_Universe) + self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Simulated_Universe) def _dungeon_wait_until_rogue_loaded(self, skip_first_screenshot=True): """ diff --git a/tasks/dungeon/weekly.py b/tasks/dungeon/weekly.py index 1fb29992e..616035a23 100644 --- a/tasks/dungeon/weekly.py +++ b/tasks/dungeon/weekly.py @@ -2,10 +2,9 @@ from module.config.utils import get_server_next_monday_update from module.logger import logger from module.ocr.ocr import DigitCounter from tasks.daily.keywords import KEYWORDS_DAILY_QUEST -from tasks.dungeon.assets.assets_dungeon_ui import OCR_DUNGEON_LIST, OCR_WEEKLY_LIMIT +from tasks.dungeon.assets.assets_dungeon_ui import OCR_WEEKLY_LIMIT from tasks.dungeon.dungeon import Dungeon from tasks.dungeon.keywords import DungeonList, KEYWORDS_DUNGEON_NAV, KEYWORDS_DUNGEON_TAB -from tasks.dungeon.ui import DUNGEON_LIST class OcrWeeklyLimit(DigitCounter): @@ -78,8 +77,7 @@ class WeeklyDungeon(Dungeon): # UI switches self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index) # Equivalent to self.dungeon_goto(dungeon), but check limit remains - DUNGEON_LIST.search_button = OCR_DUNGEON_LIST - self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Echo_of_War) + self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Echo_of_War) self._dungeon_wait_until_dungeon_list_loaded() monday = get_server_next_monday_update(self.config.Scheduler_ServerUpdate) @@ -94,7 +92,7 @@ class WeeklyDungeon(Dungeon): self.config.task_delay(target=monday) self.config.task_stop() - self._dungeon_insight(dungeon) + self.dungeon_insight(dungeon) self._dungeon_enter(dungeon) # Combat diff --git a/tasks/forgotten_hall/ui.py b/tasks/forgotten_hall/ui.py index 08b1aa7a8..98dfc1a91 100644 --- a/tasks/forgotten_hall/ui.py +++ b/tasks/forgotten_hall/ui.py @@ -10,8 +10,8 @@ from module.ocr.keyword import Keyword from module.ocr.ocr import Ocr, OcrResultButton from module.ui.draggable_list import DraggableList from tasks.base.assets.assets_base_page import FORGOTTEN_HALL_CHECK, MAP_EXIT +from tasks.dungeon.ui.ui import DungeonUI from tasks.dungeon.keywords import DungeonList, KEYWORDS_DUNGEON_LIST, KEYWORDS_DUNGEON_NAV, KEYWORDS_DUNGEON_TAB -from tasks.dungeon.ui import DungeonUI from tasks.forgotten_hall.assets.assets_forgotten_hall_nav import * from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import * from tasks.forgotten_hall.keywords import ForgottenHallStage, KEYWORDS_FORGOTTEN_HALL_STAGE @@ -187,7 +187,7 @@ class ForgottenHallUI(DungeonUI, ForgottenHallTeam): logger.info('Already in forgotten hall') else: self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Treasures_Lightward) - self._dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Forgotten_Hall) + self.dungeon_nav_goto(KEYWORDS_DUNGEON_NAV.Forgotten_Hall) self.stage_choose(dungeon) logger.info(f'Stage list select: {stage_keyword}') diff --git a/tasks/ornament/combat.py b/tasks/ornament/combat.py index c1c6c730c..5d880ea2d 100644 --- a/tasks/ornament/combat.py +++ b/tasks/ornament/combat.py @@ -6,7 +6,7 @@ from tasks.base.assets.assets_base_popup import POPUP_CANCEL from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE from tasks.combat.assets.assets_combat_support import COMBAT_SUPPORT_LIST from tasks.dungeon.dungeon import Dungeon -from tasks.dungeon.state import DungeonState +from tasks.dungeon.ui.state import DungeonState from tasks.map.route.loader import RouteLoader from tasks.map.route.route.daily import OrnamentExtraction__route from tasks.ornament.assets.assets_ornament_combat import * diff --git a/tasks/rogue/entry/entry.py b/tasks/rogue/entry/entry.py index 6a94b51b9..f18019b24 100644 --- a/tasks/rogue/entry/entry.py +++ b/tasks/rogue/entry/entry.py @@ -14,8 +14,8 @@ from tasks.base.assets.assets_base_page import MAP_EXIT from tasks.base.page import page_guide, page_item, page_main, page_rogue from tasks.dungeon.keywords import DungeonList from tasks.dungeon.keywords.dungeon import Simulated_Universe_World_1 -from tasks.dungeon.state import OcrSimUniPoint -from tasks.dungeon.ui_rogue import DungeonRogueUI +from tasks.dungeon.ui.state import OcrSimUniPoint +from tasks.dungeon.ui.ui_rogue import DungeonRogueUI from tasks.forgotten_hall.assets.assets_forgotten_hall_ui import TELEPORT from tasks.rogue.assets.assets_rogue_entry import ( LEVEL_CONFIRM, diff --git a/tasks/rogue/event/reward.py b/tasks/rogue/event/reward.py index a280c24ad..b7937005f 100644 --- a/tasks/rogue/event/reward.py +++ b/tasks/rogue/event/reward.py @@ -4,7 +4,7 @@ from module.base.timer import Timer from module.logger import logger from tasks.base.assets.assets_base_popup import GET_REWARD from tasks.combat.interact import CombatInteract -from tasks.dungeon.state import DungeonState +from tasks.dungeon.ui.state import DungeonState from tasks.rogue.assets.assets_rogue_reward import REWARD_CLOSE, USE_IMMERSIFIER, USE_STAMINA from tasks.rogue.blessing.ui import RogueUI