From 4245109ed243cd8f152d0a88b0ed8f8aabdf2605 Mon Sep 17 00:00:00 2001 From: guoh064 <50830808+guoh064@users.noreply.github.com> Date: Sat, 30 Nov 2024 03:00:31 +0800 Subject: [PATCH 01/30] Upd: [JP] commission suffix ocr pre_process (#4403) --- module/commission/project.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/module/commission/project.py b/module/commission/project.py index babd46897..ea2557e9d 100644 --- a/module/commission/project.py +++ b/module/commission/project.py @@ -36,6 +36,13 @@ class SuffixOcr(Ocr): if len(left): image = image[:, left[-1] - look_back:] + if server.server in ['jp']: + # slice top and bottom part to get clearer roman digits + # will need to pad white background for better recognization + image = image[8:-10, :] + cv2.normalize(image, image, -55, 255, cv2.NORM_MINMAX) + image = (image > 128).astype(np.uint8) * 255 + image = np.pad(image, ((4, 4), (0, 0)), mode='constant', constant_values=255) return image @@ -167,7 +174,7 @@ class Commission: self.genre = self.commission_name_parse(self.name) # Suffix - ocr = SuffixOcr(button, lang='azur_lane', letter=(255, 255, 255), threshold=128, alphabet='IV') + ocr = SuffixOcr(button, lang='azur_lane', letter=(201, 201, 201), threshold=128, alphabet='IV') self.suffix = self.beautify_name(ocr.ocr(self.image)) # Duration time From 4eef4050efdd369445911574893b2160cd777e7e Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 30 Nov 2024 03:02:36 +0800 Subject: [PATCH 02/30] Upd: [TW] Event event_20241121_cn --- campaign/Readme.md | 1 + module/config/argument/args.json | 24 ++++++++---------------- module/config/i18n/zh-TW.json | 2 +- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/campaign/Readme.md b/campaign/Readme.md index a8868c653..8c5b6b371 100644 --- a/campaign/Readme.md +++ b/campaign/Readme.md @@ -222,3 +222,4 @@ To add a new event, add a new row in here, and run `python -m module.config.conf | 20241114 | event 20220915 cn | Violet Tempest Blooming Lycoris Rerun | 复刻紫绛槿岚 | Violet Tempest Blooming Lycoris Rerun | 赫の涙月 菫の暁風(復刻) | - | | 20241114 | event 20240229 cn | Snowrealm Peregrination | - | - | - | 雪境迷蹤 | | 20241121 | event 20241121 cn | Dangerous Inventions Incoming | 危险发明迫近中 | Dangerous Inventions Incoming | 危険発明接近中 | - | +| 20241128 | event 20241121 cn | - | - | - | - | 危險發明逼近中 | diff --git a/module/config/argument/args.json b/module/config/argument/args.json index ad2ba7282..1773dc192 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -1715,13 +1715,12 @@ ], "display": "hide", "option_bold": [ - "event_20240229_cn", "event_20241121_cn" ], "cn": "event_20241121_cn", "en": "event_20241121_cn", "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "tw": "event_20241121_cn" }, "Mode": { "type": "select", @@ -2056,13 +2055,12 @@ "event_20241121_cn" ], "option_bold": [ - "event_20240229_cn", "event_20241121_cn" ], "cn": "event_20241121_cn", "en": "event_20241121_cn", "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "tw": "event_20241121_cn" }, "Mode": { "type": "select", @@ -2512,13 +2510,12 @@ "event_20241121_cn" ], "option_bold": [ - "event_20240229_cn", "event_20241121_cn" ], "cn": "event_20241121_cn", "en": "event_20241121_cn", "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "tw": "event_20241121_cn" }, "Mode": { "type": "select", @@ -3930,13 +3927,12 @@ "event_20241121_cn" ], "option_bold": [ - "event_20240229_cn", "event_20241121_cn" ], "cn": "event_20241121_cn", "en": "event_20241121_cn", "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "tw": "event_20241121_cn" }, "Mode": { "type": "select", @@ -4403,13 +4399,12 @@ "event_20241121_cn" ], "option_bold": [ - "event_20240229_cn", "event_20241121_cn" ], "cn": "event_20241121_cn", "en": "event_20241121_cn", "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "tw": "event_20241121_cn" }, "Mode": { "type": "select", @@ -4876,13 +4871,12 @@ "event_20241121_cn" ], "option_bold": [ - "event_20240229_cn", "event_20241121_cn" ], "cn": "event_20241121_cn", "en": "event_20241121_cn", "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "tw": "event_20241121_cn" }, "Mode": { "type": "select", @@ -5349,13 +5343,12 @@ "event_20241121_cn" ], "option_bold": [ - "event_20240229_cn", "event_20241121_cn" ], "cn": "event_20241121_cn", "en": "event_20241121_cn", "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "tw": "event_20241121_cn" }, "Mode": { "type": "select", @@ -5812,13 +5805,12 @@ "event_20241121_cn" ], "option_bold": [ - "event_20240229_cn", "event_20241121_cn" ], "cn": "event_20241121_cn", "en": "event_20241121_cn", "jp": "event_20241121_cn", - "tw": "event_20240229_cn" + "tw": "event_20241121_cn" }, "Mode": { "type": "select", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index a03a9eda7..87f452bc9 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -734,7 +734,7 @@ "event_20240829_cn": "埋葬於彼岸之花", "event_20240912_cn": "Ode of Everblooming Crimson", "event_20241024_cn": "Tempesta and the Sleeping Sea", - "event_20241121_cn": "Dangerous Inventions Incoming", + "event_20241121_cn": "危險發明逼近中", "raid_20200624": "特別演習埃塞克斯級(復刻)", "raid_20210708": "復刻穿越彼方的水線", "raid_20220127": "演習神秘事件調查", From 9efe5907760c091e2410c1504964c9ac004bbfd6 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:56:54 +0800 Subject: [PATCH 03/30] Fix: [CN] POPUP_CONFIRM_WHITE_BATTLEPASS as popup confirm is different in battle pass --- .../ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png | Bin 0 -> 6607 bytes module/freebies/battle_pass.py | 5 +++++ module/ui_white/assets.py | 1 + 3 files changed, 6 insertions(+) create mode 100644 assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png diff --git a/assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png b/assets/cn/ui_white/POPUP_CONFIRM_WHITE_BATTLEPASS.png new file mode 100644 index 0000000000000000000000000000000000000000..477c9a064ef992f2b06b560ebf824ba7d4560e81 GIT binary patch literal 6607 zcmeI0^-~nw+r|$iAtgu%(qYhLptQh(lytW&y)-N>i1>)qQc?>761sG)w9+jg-MJ#M zgs{?I9-p7yzv1s<4>=ggcr|7d9_Q;;!{0RSjeAWyUbAiC`0e(wCpuDNk{J0XVr(Bwf@-In)SX^^%%KwmsrCnf`UUYZ(x|wy;q$NapLM z?AvIl+?&_NQF^8iw%KM1s)PKoTU#gNCk>bk%;^ApkSg>V4R8Efjw5)e9r0Llf-P1P z($?J=icbewAY`Xr>-`EPY6oD40PYv;RVH(aR|*68z-?5Jo2GN|BD_wE*8s4`g9T0+ z%vHQNZO|pC+jSjuF@iFkN?S6J2!Jy@P@WTfqyU@0RHTW(L{Y*F1DMFUSwsS&@j&Wr zWo~@gdSI#dLYp7IfwIH#-%XfGK@oKYwH` zM@e6ZEy5O}f)6iO&!_!&fP0#3YH>?$_?f$vt51n`45pV*Uc?oL;X3b(2sw+nGqfaL zZpu&hX4mi^v=*m&C;YQ^{1cAuq$Qg55Yb(>=A&3yK0MH$lD7;%!w*K&!bAqO4iW&KWWTsuCPz&E zK*Eq3L&#|b5l4jPv9G_r{*1G| z%OelJNZv8t48pZ1;Boz7w5jZI+;kVa=$q3w4ay>kFDjl9e8=~e{anb#^rj=*bI41a z$}BWF$7zVEid8DKCZ}NN=?`x&5cDR-CUi)I((dh~mFe3^MazE-nRq+tZB`8>^kXG# zSPbv`&@s0lt*|8A_)(#>n_)6;ka6(DiK<*xae_=k1oCw9q=%^; zGSqlC;#|7tR!AyBQS2%4wQ*E&nuwF`)|J}plI^dT)JSuG4l<7>$*V-=TdA1xuB$Rb*C?=+tNbb5u}Jb~QNZ)pxt2b{gox>p_PY&ff8 z%D0qZrys=YnDy{qzT_0ID8f4-*0*ms-3Yi*z*u`jC{-<=Xe1@IOILVYlsOgpj_Msu z&+zWy-9Wnd9-1V{B>kk|B+N%$Jx@KEQTWHJx}Wr@KAGzW^z*zP#xeHpcn`cAP|rdC?9hy zwkr-UzCYzZ)ih03_HYjUs#;vUfd05kq0(AeQ`EFoZdO0hjv~r7GCSMuZwT9 z&mB_9y@lr8j)X2t_%TS^p9*$->A+4Gw8>UaABR(i*w5vwli|S0CN)GHLN>)$Cq2B3B>%;XA^v6*u zW2PJY8!lr8&RWa{>E!8>>Af|D#p%UuKIty=*O#ux-Wk4gpyN6vvU$2mwwbnhW$WRg z)gjNJ%i$nP-%T2pu}5By0zJIE`+S_;8aDD4#aoNoidxqVpv=(so|*d+4EayvdTCO8 zQW$hx3f>gBX8HFftuk%b4OfpGg_ZlYgEL%m z?_16+NH6%hABPb#_c1pzKWSvutjyp=jtRSTO}CjWH1-BqN=i#TKOR^oknWJi&+^ZT zY|3xqoR@0i3LXni4JJNCo;*74J&XO_ya?Z;-_;J@GNlX<`{GX}ZZ? zHYKYwIiDHU)(3i#zK@M0jN$?xM7*O(sgPL>XP4I!V$@tMa8+la-FulHp{dxE6P@#{ zycVtc7_pYApLFc>E2-XRdv9{U>8{sKH71Nr8un8HpLW z+7u4SRea;ZwYO_E?fCc-C|2NWE*)U&uDjnvstm8o-0a+!B8x8aJy88w%xHag0UetWe;QOvv> zy~=FiqB`gG-rw;croXDcr=LK=N@B~TuLd=}_x@h)p?_wGS+D+C@u%9RN#3ck=2_o> zeadhax<_-^#Lb3kK0gDeM#Z25*WvB3*;poP=DOy!;90b?uVy1}`^VB{qmMW1_ z4t6_Rgp``VHlE{pw+tSac>Pk}cr=6^2|0Fb8TB0hJuYaL1#iT5Y%t) zXx{_-si;)4ldF@hVE`_*n}1q7Fdc+NW9#NS8`)BCpUR$V?GGK>bQXNm#7ScU&rgML zFZWM)hO@*jUz_YU5N&k;{CNNf4F}-p;xexT;4KWmPfGwK-vPh?Ls+yc0YG`G@{&3-N+Q51E}60iTu?(OrAFE4_u(3=EtKLXuPWG4w1r`*~D)kgjV_e^a~i z0q^W)uE%Z2tK`CO+N3mw3PkmmFX%bH$ubOwkC{K!=bo%a+&*g>*$kO$Fq$)6>rB6& zaOv%D{3Y<0z+VD?3H&ASm%#re&~lLwOO6NJjQUE>-2B6b9Ff6kqpry{(tS5f`m1>N z+kKEe8csDL7$n&OO(7#bSg3dhTN|4oJ-GGAXoI`^^a;Xw3tA;PQ$MbGzK?{OPOCeK z7I#0M4Z6?()S-A_&Zru}eZZ+WEMP&Gz@0lflL|o)>vVtLi7P&8nB>jSXTDFHCi zi1a&uStfMvI`rsq8!(*d{v?SLt3tkm>o_=H2X2zA30D}RNmeTnG@8aZ+&*KMk^Imd* z@bdF2%Do_-d!*Vbdv>D#EX3CJl;EJ*y`kc;AhEl+nK9sxy8==3{$eEr%BQj+fjbr& zBX0Pv=Zh`kExpR#Xmp;;DO`{X8X`veA58%@ihDi5lJmE^zYJZ;^e9X8twZ?pY@H>Z zEe`G*Hl(H7q=p~|UhH1^x7fkF{l@nK!Ux$i?cva}D}IZrX|zYy0q85}hwddhrk_4Q zB+r%)hJKPp<6heS(dXpH+6LX{%R-iI?2PuWUW|o!b4PQUoOEV|l^E>(%1f9#osz)& zS6i^9r#ZxHe35pbmMQ#baorzL#Y;R0$r5!q&2P<}GZ!-D9jt@tod}YI0&=jEz$}DV z8Jmb-!q8&Jx)&Gwjn8+c1NZl$!OY-rvdDjS@zNCi&jJqvclNh+x5Yh9juvE_KR}@Re zD?PYsL!fzh!BTyfwNDwWbY%^CG*O~|Io}w3&qwoVZ$V*og-WTY%txK++<*$uwFUUmUL}lV9{nM&PlBs1j)}9&=Er|Z^q6Q$PeRGNm#?UD< z0F7RJxg%0+rxdk$P+6WHvXwbwce*^2+1I+YoOKaYEQzoaI8asMyQRikimNzQ#_9g? xL3H`D4~u?yIr Date: Mon, 9 Dec 2024 12:44:54 +0800 Subject: [PATCH 04/30] upd: [JP] asset COMMISSION_START (#4353) * upd: [JP] asset COMMISSION_START * Fix: check color for COMMISSION_START --- .../jp/commission/COMMISSION_START.BUTTON.png | Bin 16080 -> 0 bytes assets/jp/commission/COMMISSION_START.png | Bin 10506 -> 12264 bytes module/commission/assets.py | 2 +- module/commission/commission.py | 4 +++- 4 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 assets/jp/commission/COMMISSION_START.BUTTON.png diff --git a/assets/jp/commission/COMMISSION_START.BUTTON.png b/assets/jp/commission/COMMISSION_START.BUTTON.png deleted file mode 100644 index 40f01bb2242a76afe3bef004d163a3bfe8a8b96d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16080 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 Jf4cMNe*xkGx%B`5 delta 4421 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 diff --git a/module/commission/assets.py b/module/commission/assets.py index 282e65e95..703630296 100644 --- a/module/commission/assets.py +++ b/module/commission/assets.py @@ -8,7 +8,7 @@ COMMISSION_ADVICE = Button(area={'cn': (871, 322, 999, 383), 'en': (871, 328, 10 COMMISSION_DAILY = Button(area={'cn': (35, 132, 67, 186), 'en': (30, 126, 75, 188), 'jp': (17, 168, 82, 185), 'tw': (35, 132, 67, 186)}, color={'cn': (208, 172, 118), 'en': (170, 132, 92), 'jp': (148, 115, 76), 'tw': (208, 171, 119)}, button={'cn': (35, 132, 67, 186), 'en': (30, 126, 75, 188), 'jp': (17, 168, 82, 185), 'tw': (35, 132, 67, 186)}, file={'cn': './assets/cn/commission/COMMISSION_DAILY.png', 'en': './assets/en/commission/COMMISSION_DAILY.png', 'jp': './assets/jp/commission/COMMISSION_DAILY.png', 'tw': './assets/tw/commission/COMMISSION_DAILY.png'}) COMMISSION_HAS_PENDING = Button(area={'cn': (320, 288, 380, 338), 'en': (320, 288, 380, 338), 'jp': (320, 288, 380, 338), 'tw': (320, 288, 380, 338)}, color={'cn': (121, 113, 152), 'en': (121, 113, 152), 'jp': (121, 113, 152), 'tw': (121, 113, 152)}, button={'cn': (320, 288, 380, 338), 'en': (320, 288, 380, 338), 'jp': (320, 288, 380, 338), 'tw': (320, 288, 380, 338)}, file={'cn': './assets/cn/commission/COMMISSION_HAS_PENDING.png', 'en': './assets/en/commission/COMMISSION_HAS_PENDING.png', 'jp': './assets/jp/commission/COMMISSION_HAS_PENDING.png', 'tw': './assets/tw/commission/COMMISSION_HAS_PENDING.png'}) COMMISSION_SCROLL_AREA = Button(area={'cn': (1254, 77, 1261, 676), 'en': (1254, 77, 1261, 676), 'jp': (1254, 77, 1261, 676), 'tw': (1254, 77, 1261, 676)}, color={'cn': (213, 183, 66), 'en': (213, 183, 66), 'jp': (213, 183, 66), 'tw': (213, 183, 66)}, button={'cn': (1254, 77, 1261, 676), 'en': (1254, 77, 1261, 676), 'jp': (1254, 77, 1261, 676), 'tw': (1254, 77, 1261, 676)}, file={'cn': './assets/cn/commission/COMMISSION_SCROLL_AREA.png', 'en': './assets/en/commission/COMMISSION_SCROLL_AREA.png', 'jp': './assets/jp/commission/COMMISSION_SCROLL_AREA.png', 'tw': './assets/tw/commission/COMMISSION_SCROLL_AREA.png'}) -COMMISSION_START = Button(area={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1062, 342, 1125, 374), 'tw': (1027, 326, 1157, 389)}, color={'cn': (229, 175, 113), 'en': (236, 197, 150), 'jp': (237, 201, 153), 'tw': (231, 180, 120)}, button={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1028, 336, 1157, 398), 'tw': (1027, 326, 1157, 389)}, file={'cn': './assets/cn/commission/COMMISSION_START.png', 'en': './assets/en/commission/COMMISSION_START.png', 'jp': './assets/jp/commission/COMMISSION_START.png', 'tw': './assets/tw/commission/COMMISSION_START.png'}) +COMMISSION_START = Button(area={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1033, 340, 1153, 376), 'tw': (1027, 326, 1157, 389)}, color={'cn': (229, 175, 113), 'en': (236, 197, 150), 'jp': (231, 184, 121), 'tw': (231, 180, 120)}, button={'cn': (1028, 322, 1156, 383), 'en': (1066, 342, 1117, 358), 'jp': (1033, 340, 1153, 376), 'tw': (1027, 326, 1157, 389)}, file={'cn': './assets/cn/commission/COMMISSION_START.png', 'en': './assets/en/commission/COMMISSION_START.png', 'jp': './assets/jp/commission/COMMISSION_START.png', 'tw': './assets/tw/commission/COMMISSION_START.png'}) COMMISSION_URGENT = Button(area={'cn': (35, 231, 68, 281), 'en': (28, 221, 76, 283), 'jp': (34, 266, 68, 279), 'tw': (35, 229, 69, 280)}, color={'cn': (215, 188, 124), 'en': (169, 138, 95), 'jp': (216, 190, 111), 'tw': (213, 186, 123)}, button={'cn': (35, 231, 68, 281), 'en': (28, 221, 76, 283), 'jp': (34, 266, 68, 279), 'tw': (35, 229, 69, 280)}, file={'cn': './assets/cn/commission/COMMISSION_URGENT.png', 'en': './assets/en/commission/COMMISSION_URGENT.png', 'jp': './assets/jp/commission/COMMISSION_URGENT.png', 'tw': './assets/tw/commission/COMMISSION_URGENT.png'}) EXP_INFO_S_REWARD = Button(area={'cn': (498, 140, 557, 154), 'en': (1138, 40, 1266, 145), 'jp': (498, 140, 557, 154), 'tw': (498, 140, 557, 154)}, color={'cn': (233, 241, 127), 'en': (89, 115, 159), 'jp': (233, 241, 127), 'tw': (233, 241, 127)}, button={'cn': (498, 140, 557, 154), 'en': (1138, 40, 1266, 145), 'jp': (498, 140, 557, 154), 'tw': (498, 140, 557, 154)}, file={'cn': './assets/cn/commission/EXP_INFO_S_REWARD.png', 'en': './assets/en/commission/EXP_INFO_S_REWARD.png', 'jp': './assets/jp/commission/EXP_INFO_S_REWARD.png', 'tw': './assets/tw/commission/EXP_INFO_S_REWARD.png'}) REWARD_1 = Button(area={'cn': (383, 285, 503, 297), 'en': (403, 274, 504, 290), 'jp': (432, 273, 476, 294), 'tw': (383, 285, 503, 297)}, color={'cn': (238, 168, 81), 'en': (241, 198, 145), 'jp': (241, 188, 122), 'tw': (238, 168, 81)}, button={'cn': (383, 285, 503, 297), 'en': (392, 262, 515, 303), 'jp': (393, 262, 514, 303), 'tw': (383, 285, 503, 297)}, file={'cn': './assets/cn/commission/REWARD_1.png', 'en': './assets/en/commission/REWARD_1.png', 'jp': './assets/jp/commission/REWARD_1.png', 'tw': './assets/tw/commission/REWARD_1.png'}) diff --git a/module/commission/commission.py b/module/commission/commission.py index 390e3ff90..edaf58505 100644 --- a/module/commission/commission.py +++ b/module/commission/commission.py @@ -363,7 +363,9 @@ class RewardCommission(UI, InfoHandler): raise GameStuckError('Triggered commission list flashing bug') # Click - if self.appear_then_click(COMMISSION_START, offset=(5, 20), interval=7): + if (self.appear(COMMISSION_START, offset=(5, 20), interval=7) + and COMMISSION_START.match_appear_on(self.device.image)): + self.device.click(COMMISSION_START) self.interval_reset(COMMISSION_ADVICE) comm_timer.reset() continue From 40b3c252dc41bc20a03207c57c065543bc2f7743 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:45:38 +0800 Subject: [PATCH 05/30] Fix: Remove sleep in _tactical_book_select() --- module/tactical/tactical_class.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/module/tactical/tactical_class.py b/module/tactical/tactical_class.py index 0a8eb5e57..d9829a04b 100644 --- a/module/tactical/tactical_class.py +++ b/module/tactical/tactical_class.py @@ -243,18 +243,23 @@ class RewardTacticalClass(Dock): book (Book): skip_first_screenshot (bool): """ + logger.info(f'Book select {book}') + interval = Timer(2, count=6) while 1: if skip_first_screenshot: skip_first_screenshot = False else: self.device.screenshot() - if not book.check_selected(self.device.image): - self.device.click(book.button) - self.device.sleep((0.3, 0.5)) - else: + # End + if book.check_selected(self.device.image): break + if interval.reached(): + self.device.click(book.button) + interval.reset() + continue + def _tactical_books_filter_exp(self): """ Complex filter to remove specific grade From 164db6f4da605c4b20ceb80de9cfdd41b05c1b74 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:20:34 +0800 Subject: [PATCH 06/30] Chore: Rename "threshold" in template matching to "similarity" to be differ from the "threshold" in color matching --- module/base/base.py | 17 ++++++------ module/base/button.py | 36 +++++++++++++------------- module/config/config_manual.py | 2 -- module/map_detection/grid_predictor.py | 8 +++--- module/meta_reward/meta_reward.py | 2 +- module/os/map_fleet_selector.py | 2 +- module/os_handler/strategic.py | 4 +-- module/research/project.py | 15 ++++++----- module/retire/retirement.py | 2 +- module/ui/ui.py | 2 +- 10 files changed, 44 insertions(+), 46 deletions(-) diff --git a/module/base/base.py b/module/base/base.py index 7e9e0b674..85563306a 100644 --- a/module/base/base.py +++ b/module/base/base.py @@ -124,14 +124,14 @@ class ModuleBase: return button - def appear(self, button, offset=0, interval=0, threshold=None): + def appear(self, button, offset=0, interval=0, similarity=0.85, threshold=30): """ Args: button (Button, Template, HierarchyButton, str): offset (bool, int): interval (int, float): interval between two active events. - threshold (int, float): 0 to 1 if use offset, bigger means more similar, - 0 to 255 if not use offset, smaller means more similar + similarity (int, float): 0 to 1. + threshold (int, float): 0 to 255 if not use offset, smaller means more similar Returns: bool: @@ -167,20 +167,19 @@ class ModuleBase: elif offset: if isinstance(offset, bool): offset = self.config.BUTTON_OFFSET - appear = button.match(self.device.image, offset=offset, - threshold=self.config.BUTTON_MATCH_SIMILARITY if threshold is None else threshold) + appear = button.match(self.device.image, offset=offset, similarity=similarity) else: - appear = button.appear_on(self.device.image, - threshold=self.config.COLOR_SIMILAR_THRESHOLD if threshold is None else threshold) + appear = button.appear_on(self.device.image, threshold=threshold) if appear and interval: self.interval_timer[button.name].reset() return appear - def appear_then_click(self, button, screenshot=False, genre='items', offset=0, interval=0, threshold=None): + def appear_then_click(self, button, screenshot=False, genre='items', offset=0, interval=0, similarity=0.85, + threshold=30): button = self.ensure_button(button) - appear = self.appear(button, offset=offset, interval=interval, threshold=threshold) + appear = self.appear(button, offset=offset, interval=interval, similarity=similarity, threshold=threshold) if appear: if screenshot: self.device.sleep(self.config.WAIT_BEFORE_SAVING_SCREEN_SHOT) diff --git a/module/base/button.py b/module/base/button.py index c4fd82f3d..0344dc266 100644 --- a/module/base/button.py +++ b/module/base/button.py @@ -198,13 +198,13 @@ class Button(Resource): self._match_binary_init = False self._match_luma_init = False - def match(self, image, offset=30, threshold=0.85): + def match(self, image, offset=30, similarity=0.85): """Detects button by template matching. To Some button, its location may not be static. Args: image: Screenshot. offset (int, tuple): Detection area offset. - threshold (float): 0-1. Similarity. + similarity (float): 0-1. Similarity. Returns: bool. @@ -223,25 +223,25 @@ class Button(Resource): if self.is_gif: for template in self.image: res = cv2.matchTemplate(template, image, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - if similarity > threshold: + if sim > similarity: return True return False else: res = cv2.matchTemplate(self.image, image, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - return similarity > threshold + return sim > similarity - def match_binary(self, image, offset=30, threshold=0.85): + def match_binary(self, image, offset=30, similarity=0.85): """Detects button by template matching. To Some button, its location may not be static. This method will apply template matching under binarization. Args: image: Screenshot. offset (int, tuple): Detection area offset. - threshold (float): 0-1. Similarity. + similarity (float): 0-1. Similarity. Returns: bool. @@ -266,9 +266,9 @@ class Button(Resource): _, image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # template matching res = cv2.matchTemplate(template, image_binary, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - if similarity > threshold: + if sim > similarity: return True return False else: @@ -278,18 +278,18 @@ class Button(Resource): _, image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # template matching res = cv2.matchTemplate(self.image_binary, image_binary, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - return similarity > threshold + return sim > similarity - def match_luma(self, image, offset=30, threshold=0.85): + def match_luma(self, image, offset=30, similarity=0.85): """ Detects button by template matching under Y channel (Luminance) Args: image: Screenshot. offset (int, tuple): Detection area offset. - threshold (float): 0-1. Similarity. + similarity (float): 0-1. Similarity. Returns: bool. @@ -310,16 +310,16 @@ class Button(Resource): image_luma = rgb2luma(image) for template in self.image_luma: res = cv2.matchTemplate(template, image_luma, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - if similarity > threshold: + if sim > similarity: return True else: image_luma = rgb2luma(image) res = cv2.matchTemplate(self.image_luma, image_luma, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) + _, sim, _, point = cv2.minMaxLoc(res) self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) - return similarity > threshold + return sim > similarity def match_appear_on(self, image, threshold=30): """ diff --git a/module/config/config_manual.py b/module/config/config_manual.py index 46746053f..140f53a57 100644 --- a/module/config/config_manual.py +++ b/module/config/config_manual.py @@ -39,9 +39,7 @@ class ManualConfig: """ module.base """ - COLOR_SIMILAR_THRESHOLD = 10 BUTTON_OFFSET = 30 - BUTTON_MATCH_SIMILARITY = 0.85 WAIT_BEFORE_SAVING_SCREEN_SHOT = 1 """ diff --git a/module/map_detection/grid_predictor.py b/module/map_detection/grid_predictor.py index 4e220f4cb..b58034a02 100644 --- a/module/map_detection/grid_predictor.py +++ b/module/map_detection/grid_predictor.py @@ -332,11 +332,11 @@ class GridPredictor: color = cv2.mean(crop(mask.image, area=np.rint(area).astype(int), copy=False)) return color[0] > 235 - def is_similar_to(self, grid, threshold=0.9): + def is_similar_to(self, grid, similarity=0.9): """ Args: grid (GridPredictor): Another Grid instance. - threshold (float): 0 to 1. + similarity (float): 0 to 1. Returns: bool: If current grid is similar to another. @@ -346,5 +346,5 @@ class GridPredictor: piece_1 = self._image_similar_piece piece_2 = grid._image_similar_full res = cv2.matchTemplate(piece_2, piece_1, cv2.TM_CCOEFF_NORMED) - _, similarity, _, point = cv2.minMaxLoc(res) - return similarity > threshold + _, sim, _, point = cv2.minMaxLoc(res) + return sim > similarity diff --git a/module/meta_reward/meta_reward.py b/module/meta_reward/meta_reward.py index 16bc07b8b..b701e930b 100644 --- a/module/meta_reward/meta_reward.py +++ b/module/meta_reward/meta_reward.py @@ -117,7 +117,7 @@ class DossierReward(Combat, UI): in: dossier meta page """ self.device.screenshot() - if self.appear(DOSSIER_REWARD_RECEIVE, offset=(-40, 10, -10, 40), threshold=0.7): + if self.appear(DOSSIER_REWARD_RECEIVE, offset=(-40, 10, -10, 40), similarity=0.7): logger.info('Found dossier reward red dot') return True else: diff --git a/module/os/map_fleet_selector.py b/module/os/map_fleet_selector.py index a4e24bf74..0a50f5876 100644 --- a/module/os/map_fleet_selector.py +++ b/module/os/map_fleet_selector.py @@ -29,7 +29,7 @@ class FleetSelector: int: Index of current fleet, 1 to 4. return 0 if unrecognized. """ for index, button in enumerate([FLEET_1, FLEET_2, FLEET_3, FLEET_4]): - if self.main.appear(button, offset=(20, 20), threshold=0.75): + if self.main.appear(button, offset=(20, 20), similarity=0.75): return index + 1 logger.info('Unknown OpSi fleet') diff --git a/module/os_handler/strategic.py b/module/os_handler/strategic.py index 0c1fc39a9..a808f3fdf 100644 --- a/module/os_handler/strategic.py +++ b/module/os_handler/strategic.py @@ -104,7 +104,7 @@ class StrategicSearchHandler(MapEventHandler): else: self.device.screenshot() - self.appear(STRATEGIC_SEARCH_DEVICE_CHECK, offset=(20, 200), threshold=0.7) + self.appear(STRATEGIC_SEARCH_DEVICE_CHECK, offset=(20, 200), similarity=0.7) STRATEGIC_SEARCH_DEVICE_STOP.load_offset(STRATEGIC_SEARCH_DEVICE_CHECK) STRATEGIC_SEARCH_DEVICE_CONTINUE.load_offset(STRATEGIC_SEARCH_DEVICE_CHECK) @@ -129,7 +129,7 @@ class StrategicSearchHandler(MapEventHandler): else: self.device.screenshot() - self.appear(STRATEGIC_SEARCH_SUBMIT_CHECK, offset=(20, 20), threshold=0.7) + self.appear(STRATEGIC_SEARCH_SUBMIT_CHECK, offset=(20, 20), similarity=0.7) STRATEGIC_SEARCH_SUBMIT_OFF.load_offset(STRATEGIC_SEARCH_SUBMIT_CHECK) STRATEGIC_SEARCH_SUBMIT_ON.load_offset(STRATEGIC_SEARCH_SUBMIT_CHECK) diff --git a/module/research/project.py b/module/research/project.py index 277081aa4..7b11291ed 100644 --- a/module/research/project.py +++ b/module/research/project.py @@ -180,14 +180,14 @@ def parse_time(string): return timedelta(hours=result[0], minutes=result[1], seconds=result[2]) -def match_template(image, template, area, offset=30, threshold=0.85): +def match_template(image, template, area, offset=30, similarity=0.85): """ Args: image (np.ndarray): Screenshot template (np.ndarray): area (tuple): Crop area of image. offset (int, tuple): Detection area offset. - threshold (float): 0-1. Similarity. Lower than this value will return float(0). + similarity (float): 0-1. Similarity. Lower than this value will return float(0). Returns: similarity (float): """ @@ -198,8 +198,9 @@ def match_template(image, template, area, offset=30, threshold=0.85): image = crop(image, offset + area, copy=False) res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) _, sim, _, point = cv2.minMaxLoc(res) - similarity = sim if sim >= threshold else 0.0 - return similarity + if sim < similarity: + sim = 0.0 + return sim def get_research_series_jp_old(image): @@ -274,7 +275,7 @@ def get_research_genre_jp(image): """ genre = '' for button in RESEARCH_DETAIL_GENRE: - if button.match(image, offset=(30, 20), threshold=0.9): + if button.match(image, offset=(30, 20), similarity=0.9): # DETAIL_GENRE_H_0.name.split("_")[2] == 'H' genre = button.name.split("_")[2] break @@ -308,7 +309,7 @@ def get_research_cost_jp(image): template=template, area=DETAIL_COST.area, offset=(10, 10), - threshold=0.8) + similarity=0.8) if not sim: continue for cost in costs: @@ -343,7 +344,7 @@ def get_research_ship_jp(image): template=load_image(template), area=DETAIL_BLUEPRINT.area, offset=(10, 10), - threshold=0.9) + similarity=0.9) if sim > similarity: similarity = sim ship = name diff --git a/module/retire/retirement.py b/module/retire/retirement.py index cd1b58a82..1059c3bce 100644 --- a/module/retire/retirement.py +++ b/module/retire/retirement.py @@ -462,7 +462,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler): self.device.screenshot() # End - if not self.appear(RETIRE_COIN, threshold=0.97): + if not self.appear(RETIRE_COIN, similarity=0.97): return True if count > 3: logger.warning('_retire_select_one failed after 3 trial') diff --git a/module/ui/ui.py b/module/ui/ui.py index d41fdc298..993dad04d 100644 --- a/module/ui/ui.py +++ b/module/ui/ui.py @@ -477,7 +477,7 @@ class UI(InfoHandler): return True # Dorm popup - if self.appear(DORM_INFO, offset=(30, 30), threshold=0.75, interval=3): + if self.appear(DORM_INFO, offset=(30, 30), similarity=0.75, interval=3): self.device.click(DORM_INFO) return True if self.appear_then_click(DORM_FEED_CANCEL, offset=(30, 30), interval=3): From ba5b853cb9dfe63a037c89c429f6a6f21c27deef Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:09:34 +0800 Subject: [PATCH 07/30] Refactor: Standardize the use of match_template_color() --- module/base/base.py | 32 ++++++++++++++++++++++++++++ module/base/button.py | 20 +++++++++++------ module/commission/commission.py | 3 +-- module/dorm/buy_furniture.py | 3 +-- module/freebies/battle_pass.py | 3 +-- module/handler/login.py | 2 +- module/handler/strategy.py | 3 +-- module/map/map_operation.py | 6 ++---- module/meowfficer/base.py | 3 +-- module/meowfficer/buy.py | 3 +-- module/meowfficer/collect.py | 3 +-- module/meta_reward/meta_reward.py | 6 ++---- module/os/fleet.py | 3 ++- module/os_ash/meta.py | 6 ++---- module/os_handler/action_point.py | 3 +-- module/os_handler/enemy_searching.py | 2 +- module/os_handler/map_event.py | 15 +++++-------- module/os_handler/mission.py | 9 +++----- module/os_handler/strategic.py | 3 +-- module/retire/retirement.py | 11 ++++------ module/reward/reward.py | 3 +-- module/storage/ui.py | 3 +-- 22 files changed, 79 insertions(+), 66 deletions(-) diff --git a/module/base/base.py b/module/base/base.py index 85563306a..4646baee5 100644 --- a/module/base/base.py +++ b/module/base/base.py @@ -176,6 +176,38 @@ class ModuleBase: return appear + def match_template_color(self, button, offset=(20, 20), interval=0, similarity=0.85, threshold=30): + """ + Args: + button (Button): + offset (bool, int): + interval (int, float): interval between two active events. + similarity (int, float): 0 to 1. + threshold (int, float): 0 to 255 if not use offset, smaller means more similar + + Returns: + bool: + """ + button = self.ensure_button(button) + self.device.stuck_record_add(button) + + if interval: + if button.name in self.interval_timer: + if self.interval_timer[button.name].limit != interval: + self.interval_timer[button.name] = Timer(interval) + else: + self.interval_timer[button.name] = Timer(interval) + if not self.interval_timer[button.name].reached(): + return False + + appear = button.match_template_color( + self.device.image, offset=offset, similarity=similarity, threshold=threshold) + + if appear and interval: + self.interval_timer[button.name].reset() + + return appear + def appear_then_click(self, button, screenshot=False, genre='items', offset=0, interval=0, similarity=0.85, threshold=30): button = self.ensure_button(button) diff --git a/module/base/button.py b/module/base/button.py index 0344dc266..6c550b4de 100644 --- a/module/base/button.py +++ b/module/base/button.py @@ -321,18 +321,26 @@ class Button(Resource): self._button_offset = area_offset(self._button, offset[:2] + np.array(point)) return sim > similarity - def match_appear_on(self, image, threshold=30): + def match_template_color(self, image, offset=(20, 20), similarity=0.85, threshold=30): """ + Template match first, color match then + Args: image: Screenshot. - threshold: Default to 10. + offset (int, tuple): Detection area offset. + similarity (float): 0-1. + threshold (int): Default to 30. Returns: - bool: + bool. """ - diff = np.subtract(self.button, self._button)[:2] - area = area_offset(self.area, offset=diff) - return color_similar(color1=get_color(image, area), color2=self.color, threshold=threshold) + if self.match(image, offset=offset, similarity=similarity): + diff = np.subtract(self.button, self._button)[:2] + area = area_offset(self.area, offset=diff) + color = get_color(image, area) + return color_similar(color1=color, color2=self.color, threshold=threshold) + else: + return False def crop(self, area, image=None, name=None): """ diff --git a/module/commission/commission.py b/module/commission/commission.py index edaf58505..727b51779 100644 --- a/module/commission/commission.py +++ b/module/commission/commission.py @@ -363,8 +363,7 @@ class RewardCommission(UI, InfoHandler): raise GameStuckError('Triggered commission list flashing bug') # Click - if (self.appear(COMMISSION_START, offset=(5, 20), interval=7) - and COMMISSION_START.match_appear_on(self.device.image)): + if self.match_template_color(COMMISSION_START, offset=(5, 20), interval=7): self.device.click(COMMISSION_START) self.interval_reset(COMMISSION_ADVICE) comm_timer.reset() diff --git a/module/dorm/buy_furniture.py b/module/dorm/buy_furniture.py index 4d2061d38..2f9e62579 100644 --- a/module/dorm/buy_furniture.py +++ b/module/dorm/buy_furniture.py @@ -183,8 +183,7 @@ class BuyFurniture(UI): False if Failed buy """ self.enter_first_furniture_details_page() - if self.appear(DORM_FURNITURE_COUNTDOWN, offset=(20, 20)) \ - and DORM_FURNITURE_COUNTDOWN.match_appear_on(self.device.image): + if self.match_template_color(DORM_FURNITURE_COUNTDOWN, offset=(20, 20)): logger.info("There is a time-limited furniture available for buy") if self.buy_furniture_once(self.config.BuyFurniture_BuyOption): diff --git a/module/freebies/battle_pass.py b/module/freebies/battle_pass.py index a5d977e56..75dadff99 100644 --- a/module/freebies/battle_pass.py +++ b/module/freebies/battle_pass.py @@ -73,8 +73,7 @@ class BattlePass(Combat, UI): if self.appear_then_click(REWARD_RECEIVE, offset=(20, 20), interval=3): confirm_timer.reset() continue - if self.appear(REWARD_RECEIVE_SP, offset=(20, 20), interval=3) \ - and REWARD_RECEIVE_SP.match_appear_on(self.device.image, threshold=15): + if self.match_template_color(REWARD_RECEIVE_SP, offset=(20, 20), interval=3, threshold=15): self.device.click(REWARD_RECEIVE_SP) confirm_timer.reset() continue diff --git a/module/handler/login.py b/module/handler/login.py index bb4b0ed1c..bce990522 100644 --- a/module/handler/login.py +++ b/module/handler/login.py @@ -50,7 +50,7 @@ class LoginHandler(UI): confirm_timer.reset() # Login - if self.appear(LOGIN_CHECK, offset=(30, 30), interval=5) and LOGIN_CHECK.match_appear_on(self.device.image): + if self.match_template_color(LOGIN_CHECK, offset=(30, 30), interval=5): self.device.click(LOGIN_CHECK) if not login_success: logger.info('Login success') diff --git a/module/handler/strategy.py b/module/handler/strategy.py index 7b78fd1e3..b3ff703c5 100644 --- a/module/handler/strategy.py +++ b/module/handler/strategy.py @@ -217,8 +217,7 @@ class StrategyHandler(InfoHandler): in: STRATEGY_OPENED out: STRATEGY_OPENED """ - if (self.appear(MOB_MOVE_ENTER, offset=MOB_MOVE_OFFSET) - and MOB_MOVE_ENTER.match_appear_on(self.device.image)): + if self.match_template_color(MOB_MOVE_ENTER, offset=MOB_MOVE_OFFSET): return True else: return False diff --git a/module/map/map_operation.py b/module/map/map_operation.py index e7fff3314..4a9de599a 100644 --- a/module/map/map_operation.py +++ b/module/map/map_operation.py @@ -269,8 +269,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand return True if mode == 'normal': - if self.appear(MAP_MODE_SWITCH_NORMAL, offset=(20, 20)) \ - and MAP_MODE_SWITCH_NORMAL.match_appear_on(self.device.image): + if self.match_template_color(MAP_MODE_SWITCH_NORMAL, offset=(20, 20)): logger.attr('MAP_MODE_SWITCH', 'normal') return True elif self.appear(MAP_MODE_SWITCH_HARD, offset=(20, 20), interval=2): @@ -281,8 +280,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand else: return False elif mode == 'hard': - if self.appear(MAP_MODE_SWITCH_HARD, offset=(20, 20)) \ - and MAP_MODE_SWITCH_HARD.match_appear_on(self.device.image): + if self.match_template_color(MAP_MODE_SWITCH_HARD, offset=(20, 20)): logger.attr('MAP_MODE_SWITCH', 'hard') return True if self.appear(MAP_MODE_SWITCH_NORMAL, offset=(20, 20), interval=2): diff --git a/module/meowfficer/base.py b/module/meowfficer/base.py index 02f98159c..0c5fcdc1a 100644 --- a/module/meowfficer/base.py +++ b/module/meowfficer/base.py @@ -69,8 +69,7 @@ class MeowfficerBase(UI): self.device.screenshot() # End - if self.appear(MEOWFFICER_CHECK, offset=(20, 20)) \ - and MEOWFFICER_CHECK.match_appear_on(self.device.image): + if self.match_template_color(MEOWFFICER_CHECK, offset=(20, 20)): break else: if click_timer.reached(): diff --git a/module/meowfficer/buy.py b/module/meowfficer/buy.py index 213841480..c76abdba1 100644 --- a/module/meowfficer/buy.py +++ b/module/meowfficer/buy.py @@ -99,8 +99,7 @@ class MeowfficerBuy(MeowfficerBase): continue # End - if self.appear(MEOWFFICER_BUY_ENTER, offset=(20, 20)) \ - and MEOWFFICER_BUY_ENTER.match_appear_on(self.device.image): + if self.match_template_color(MEOWFFICER_BUY_ENTER, offset=(20, 20)): break def meow_buy(self) -> bool: diff --git a/module/meowfficer/collect.py b/module/meowfficer/collect.py index 673aa4a2c..1ff9e10f0 100644 --- a/module/meowfficer/collect.py +++ b/module/meowfficer/collect.py @@ -75,8 +75,7 @@ class MeowfficerCollect(MeowfficerBase): Returns: bool """ - if self.appear(MEOWFFICER_GET_CHECK, offset=(40, 40)) and MEOWFFICER_GET_CHECK.match_appear_on( - self.device.image): + if self.match_template_color(MEOWFFICER_GET_CHECK, offset=(40, 40)): return True if self.appear(MEOWFFICER_TRAIN_START, offset=(20, 20)): diff --git a/module/meta_reward/meta_reward.py b/module/meta_reward/meta_reward.py index b701e930b..66512e98b 100644 --- a/module/meta_reward/meta_reward.py +++ b/module/meta_reward/meta_reward.py @@ -64,8 +64,7 @@ class BeaconReward(Combat, UI): else: self.device.screenshot() - if self.appear(REWARD_RECEIVE, offset=(20, 20), interval=3) and REWARD_RECEIVE.match_appear_on( - self.device.image): + if self.match_template_color(REWARD_RECEIVE, offset=(20, 20), interval=3): self.device.click(REWARD_RECEIVE) confirm_timer.reset() continue @@ -166,8 +165,7 @@ class DossierReward(Combat, UI): else: self.device.screenshot() - if self.appear(DOSSIER_REWARD_RECEIVE, offset=(20, 20), interval=3) and DOSSIER_REWARD_RECEIVE.match_appear_on( - self.device.image): + if self.match_template_color(DOSSIER_REWARD_RECEIVE, offset=(20, 20), interval=3): self.device.click(DOSSIER_REWARD_RECEIVE) confirm_timer.reset() continue diff --git a/module/os/fleet.py b/module/os/fleet.py index aff0ecef0..77d63638a 100644 --- a/module/os/fleet.py +++ b/module/os/fleet.py @@ -369,7 +369,8 @@ class OSFleet(OSCamera, Combat, Fleet, OSAsh): # Arrive # Check colors, because screen goes black when something is unlocking. - if self.is_in_map() and IN_MAP.match_appear_on(self.device.image): + # A direct use of IN_MAP, basically `self.is_in_map() and IN_MAP.match_template_color()` + if self.match_template_color(IN_MAP, offset=(200, 5)): self.update_os() current = self.view.backend.homo_loca logger.attr('homo_loca', current) diff --git a/module/os_ash/meta.py b/module/os_ash/meta.py index b1312d4da..3920b86a6 100644 --- a/module/os_ash/meta.py +++ b/module/os_ash/meta.py @@ -231,12 +231,10 @@ class OpsiAshBeacon(Meta): else: self.device.screenshot() - if self.appear(META_INNER_PAGE_DAMAGE, offset=(20, 20)) \ - and META_INNER_PAGE_DAMAGE.match_appear_on(self.device.image): + if self.match_template_color(META_INNER_PAGE_DAMAGE, offset=(20, 20)): logger.info('Already in meta damage page') break - if self.appear(META_INNER_PAGE_NOT_DAMAGE, offset=(20, 20)) \ - and META_INNER_PAGE_NOT_DAMAGE.match_appear_on(self.device.image): + if self.match_template_color(META_INNER_PAGE_NOT_DAMAGE, offset=(20, 20)): logger.info('In meta details page, should switch to damage page') self.appear_then_click(META_INNER_PAGE_NOT_DAMAGE, offset=(20, 20), interval=2) continue diff --git a/module/os_handler/action_point.py b/module/os_handler/action_point.py index 17084dd65..0bc485188 100644 --- a/module/os_handler/action_point.py +++ b/module/os_handler/action_point.py @@ -106,8 +106,7 @@ class ActionPointHandler(UI, MapEventHandler): return self.appear(ACTION_POINT_USE, offset=(20, 20)) def is_current_ap_visible(self): - return self.appear(CURRENT_AP_CHECK, offset=(40, 5)) \ - and CURRENT_AP_CHECK.match_appear_on(self.device.image, threshold=15) + return self.match_template_color(CURRENT_AP_CHECK, offset=(40, 5), threshold=15) def action_point_use(self, skip_first_screenshot=True): prev = self._action_point_current diff --git a/module/os_handler/enemy_searching.py b/module/os_handler/enemy_searching.py index 383e61a31..2394a0cd1 100644 --- a/module/os_handler/enemy_searching.py +++ b/module/os_handler/enemy_searching.py @@ -10,7 +10,7 @@ class EnemySearchingHandler(EnemySearchingHandler_): def is_in_map(self): if IN_MAP.match_luma(self.device.image, offset=(200, 5)): return True - if self.appear(MAP_GOTO_GLOBE_FOG, offset=(5, 5)) and MAP_GOTO_GLOBE_FOG.match_appear_on(self.device.image): + if self.match_template_color(MAP_GOTO_GLOBE_FOG, offset=(5, 5)): return True return False diff --git a/module/os_handler/map_event.py b/module/os_handler/map_event.py index 653ed46a9..ee3244460 100644 --- a/module/os_handler/map_event.py +++ b/module/os_handler/map_event.py @@ -244,14 +244,12 @@ class MapEventHandler(EnemySearchingHandler): Returns: bool: If clicked. """ - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120)) \ - and AUTO_SEARCH_OS_MAP_OPTION_OFF.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120)): if self.info_bar_count() >= 2: self.device.screenshot_interval_set() self.os_auto_search_quit(drop=drop) raise CampaignEnd - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120)) \ - and AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120)): if self.info_bar_count() >= 2: self.device.screenshot_interval_set() self.os_auto_search_quit(drop=drop) @@ -268,20 +266,17 @@ class MapEventHandler(EnemySearchingHandler): if enable is None: pass elif enable: - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120), interval=3) \ - and AUTO_SEARCH_OS_MAP_OPTION_OFF.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 120), interval=3): self.device.click(AUTO_SEARCH_OS_MAP_OPTION_OFF) self.interval_reset(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED) return True # Game client bugged sometimes, AUTO_SEARCH_OS_MAP_OPTION_OFF grayed out but still functional - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120), interval=3) \ - and AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED, offset=(5, 120), interval=3): self.device.click(AUTO_SEARCH_OS_MAP_OPTION_OFF_DISABLED) self.interval_reset(AUTO_SEARCH_OS_MAP_OPTION_OFF) return True else: - if self.appear(AUTO_SEARCH_OS_MAP_OPTION_ON, offset=(5, 120), interval=3) \ - and AUTO_SEARCH_OS_MAP_OPTION_ON.match_appear_on(self.device.image): + if self.match_template_color(AUTO_SEARCH_OS_MAP_OPTION_ON, offset=(5, 120), interval=3): self.device.click(AUTO_SEARCH_OS_MAP_OPTION_ON) return True diff --git a/module/os_handler/mission.py b/module/os_handler/mission.py index 51a0697f3..081ac2de4 100644 --- a/module/os_handler/mission.py +++ b/module/os_handler/mission.py @@ -81,15 +81,13 @@ class MissionHandler(GlobeOperation, ZoneManager): # End if self.is_in_os_mission() \ and not self.appear(MISSION_FINISH, offset=(20, 20)) \ - and not (self.appear(MISSION_CHECKOUT, offset=(20, 20)) - and MISSION_CHECKOUT.match_appear_on(self.device.image)): + and not self.match_template_color(MISSION_CHECKOUT, offset=(20, 20)): # No mission found, wait to confirm. Missions might not be loaded so fast. if confirm_timer.reached(): logger.info('No OS mission found.') break elif self.is_in_os_mission() \ - and (self.appear(MISSION_CHECKOUT, offset=(20, 20)) - and MISSION_CHECKOUT.match_appear_on(self.device.image)): + and self.match_template_color(MISSION_CHECKOUT, offset=(20, 20)): # Found one mission. logger.info('Found at least one OS missions.') break @@ -119,8 +117,7 @@ class MissionHandler(GlobeOperation, ZoneManager): logger.info('Monthly BOSS mission found, checking missions bellow it') checkout_offset = (-20, 100, 20, 150) - if not (self.appear(MISSION_CHECKOUT, offset=checkout_offset) - and MISSION_CHECKOUT.match_appear_on(self.device.image)): + if not self.match_template_color(MISSION_CHECKOUT, offset=checkout_offset): # If not having enough items to claim a mission, # there will still be MISSION_CHECKOUT, but button is transparent. # So here needs to use both template matching and color detection. diff --git a/module/os_handler/strategic.py b/module/os_handler/strategic.py index a808f3fdf..07eb97aa1 100644 --- a/module/os_handler/strategic.py +++ b/module/os_handler/strategic.py @@ -25,8 +25,7 @@ class StrategicSearchHandler(MapEventHandler): continue if self.appear(AUTO_SEARCH_REWARD, offset=(50, 50)): continue - if self.appear(STRATEGIC_SEARCH_MAP_OPTION_OFF, offset=(20, 20), interval=2) \ - and STRATEGIC_SEARCH_MAP_OPTION_OFF.match_appear_on(self.device.image): + if self.match_template_color(STRATEGIC_SEARCH_MAP_OPTION_OFF, offset=(20, 20), interval=2): self.device.click(STRATEGIC_SEARCH_MAP_OPTION_OFF) continue diff --git a/module/retire/retirement.py b/module/retire/retirement.py index 1059c3bce..622cc735c 100644 --- a/module/retire/retirement.py +++ b/module/retire/retirement.py @@ -110,12 +110,9 @@ class Retirement(Enhancement, QuickRetireSettingHandler): timeout.reset() # Click - if self.appear(SHIP_CONFIRM, offset=(30, 30), interval=2): - if SHIP_CONFIRM.match_appear_on(self.device.image): - self.device.click(SHIP_CONFIRM) - continue - else: - self.interval_clear(SHIP_CONFIRM) + if self.match_template_color(SHIP_CONFIRM, offset=(30, 30), interval=2): + self.device.click(SHIP_CONFIRM) + continue if self.appear(SHIP_CONFIRM_2, offset=(30, 30), interval=2): if self.retire_keep_common_cv and not self._have_kept_cv: self.keep_one_common_cv() @@ -259,7 +256,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler): if selected == 0: break self.device.screenshot() - if not (self.appear(SHIP_CONFIRM, offset=(30, 30)) and SHIP_CONFIRM.match_appear_on(self.device.image)): + if not self.match_template_color(SHIP_CONFIRM, offset=(30, 30)): logger.warning('No ship selected, retrying') continue diff --git a/module/reward/reward.py b/module/reward/reward.py index 21f660778..4d790ba4b 100644 --- a/module/reward/reward.py +++ b/module/reward/reward.py @@ -103,8 +103,7 @@ class Reward(UI): for button in [MISSION_MULTI, MISSION_SINGLE]: if not click_timer.reached(): continue - if self.appear(button, offset=(20, 200), interval=interval) \ - and button.match_appear_on(self.device.image): + if self.match_template_color(button, offset=(20, 200), interval=interval): self.device.click(button) exit_timer.reset() click_timer.reset() diff --git a/module/storage/ui.py b/module/storage/ui.py index 06e53df1f..9ba518954 100644 --- a/module/storage/ui.py +++ b/module/storage/ui.py @@ -40,8 +40,7 @@ class StorageUI(UI): Returns: bool, if in MATERIAL_CHECK, appear and match_appear_on """ - return self.appear(MATERIAL_CHECK, offset=(20, 20), interval=interval) \ - and MATERIAL_CHECK.match_appear_on(self.device.image) + return self.match_template_color(MATERIAL_CHECK, offset=(20, 20), interval=interval) def _storage_enter_material(self, skip_first_screenshot=True): """ From 97f8367a0019bd827086b0e016e44a1c27fce05e Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:55:23 +0800 Subject: [PATCH 08/30] Fix: Handle storage page in os_auto_search_quit() --- module/os_handler/map_event.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/module/os_handler/map_event.py b/module/os_handler/map_event.py index ee3244460..7c33989d0 100644 --- a/module/os_handler/map_event.py +++ b/module/os_handler/map_event.py @@ -1,12 +1,13 @@ from module.base.timer import Timer from module.combat.assets import * from module.exception import CampaignEnd -from module.handler.assets import * +from module.handler.assets import POPUP_CANCEL, POPUP_CONFIRM from module.logger import logger from module.os.assets import GLOBE_GOTO_MAP from module.os_handler.assets import * from module.os_handler.enemy_searching import EnemySearchingHandler from module.statistics.azurstats import DropImage +from module.ui.assets import BACK_ARROW from module.ui.switch import Switch @@ -208,7 +209,6 @@ class MapEventHandler(EnemySearchingHandler): if drop: drop.handle_add(main=self, before=4) self.device.click(AUTO_SEARCH_REWARD) - self.clicked = True self.interval_reset([ AUTO_SEARCH_REWARD, AUTO_SEARCH_OS_MAP_OPTION_ON, @@ -225,6 +225,14 @@ class MapEventHandler(EnemySearchingHandler): # because of duplicated clicks and clicks to places outside the map confirm_timer.reset() continue + # Donno why but it just entered storage, exit it anyway + # Equivalent to is_in_storage, but can't inherit StorageHandler here + # STORAGE_CHECK is a duplicate name, this is the os_handler/STORAGE_CHECK, not handler/STORAGE_CHECK + if self.appear(STORAGE_CHECK, offset=(20, 20), interval=5): + logger.info(f'{STORAGE_CHECK} -> {BACK_ARROW}') + self.device.click(BACK_ARROW) + confirm_timer.reset() + continue # End if self.is_in_map(): From 26943697cb6e2487c85f302dc622843b27ec4b60 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 9 Dec 2024 23:25:45 +0800 Subject: [PATCH 09/30] Fix: Wait disappearing scroll in retirement_get_common_rarity_cv() --- module/retire/retirement.py | 50 ++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/module/retire/retirement.py b/module/retire/retirement.py index 622cc735c..f08ca8295 100644 --- a/module/retire/retirement.py +++ b/module/retire/retirement.py @@ -451,6 +451,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler): """ count = 0 RETIRE_COIN.load_color(self.device.image) + RETIRE_COIN._match_init = True while 1: if skip_first_screenshot: @@ -459,7 +460,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler): self.device.screenshot() # End - if not self.appear(RETIRE_COIN, similarity=0.97): + if not RETIRE_COIN.match(self.device.image, offset=(20, 20), similarity=0.97): return True if count > 3: logger.warning('_retire_select_one failed after 3 trial') @@ -501,25 +502,56 @@ class Retirement(Enhancement, QuickRetireSettingHandler): return None def retirement_get_common_rarity_cv(self, skip_first_screenshot=False): - button = self.retirement_get_common_rarity_cv_in_page() - if button is not None: - return button + """ + Args: + skip_first_screenshot: - for _ in range(7): - if not RETIRE_CONFIRM_SCROLL.appear(main=self): - logger.info('Scroll bar disappeared, stop') - break - RETIRE_CONFIRM_SCROLL.next_page(main=self) + Returns: + Button: Button to click to remove ship from retire list + """ + swipe_count = 0 + disappear_confirm = Timer(2, count=6) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # Try to get CV button = self.retirement_get_common_rarity_cv_in_page() if button is not None: return button + + # Wait scroll bar + if RETIRE_CONFIRM_SCROLL.appear(main=self): + disappear_confirm.clear() + else: + disappear_confirm.start() + if disappear_confirm.reached(): + logger.warning('Scroll bar disappeared, stop') + break + else: + continue + if RETIRE_CONFIRM_SCROLL.at_bottom(main=self): logger.info('Scroll bar reached end, stop') break + # Swipe next page + if swipe_count >= 7: + logger.info('Reached maximum swipes to find common CV') + break + RETIRE_CONFIRM_SCROLL.next_page(main=self) + swipe_count += 1 + return button def keep_one_common_cv(self): + """ + Returns: + + """ + logger.info('Keep one common CV') button = self.retirement_get_common_rarity_cv() if button is not None: self._retire_select_one(button) From 386a2482adbda5f4701c88982364a29bae1c46ef Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Mon, 9 Dec 2024 23:37:07 +0800 Subject: [PATCH 10/30] Fix: No drop image caching if no drop image save since stats service is offline now --- module/statistics/azurstats.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/module/statistics/azurstats.py b/module/statistics/azurstats.py index 45fbfa200..c815479dd 100644 --- a/module/statistics/azurstats.py +++ b/module/statistics/azurstats.py @@ -67,7 +67,10 @@ class DropImage: return len(self.images) def __bool__(self): - return self.save or self.upload + # Uncomment these if stats service re-run in the future + # return self.save or self.upload + + return self.save def __enter__(self): return self @@ -217,6 +220,8 @@ class AzurStats: save_thread = threading.Thread( target=self._save, args=(image, genre, filename)) save_thread.start() + + # Uncomment these if stats service re-run in the future # if upload: # upload_thread = threading.Thread( # target=self._upload, args=(image, genre, filename)) From 4ab8337324557215c87cd3080f7590e6d1c678e4 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Tue, 10 Dec 2024 03:28:20 +0800 Subject: [PATCH 11/30] Sync: [ALAS] Refactored Switch.set() --- campaign/campaign_main/campaign_15_base.py | 4 +- .../campaign_war_archives/campaign_base.py | 4 +- campaign/event_20221124_cn/campaign_base.py | 4 +- campaign/event_20240725_cn/campaign_base.py | 4 +- campaign/event_20240829_cn/campaign_base.py | 4 +- campaign/event_20240912_cn/campaign_base.py | 4 +- campaign/event_20241024_cn/campaign_base.py | 12 +- module/campaign/campaign_ui.py | 8 +- module/coalition/ui.py | 16 +- module/commission/commission.py | 4 +- module/equipment/equipment_change.py | 4 +- module/handler/fast_forward.py | 42 ++--- module/handler/strategy.py | 40 ++--- module/meowfficer/collect.py | 6 +- module/os_handler/map_event.py | 8 +- module/retire/dock.py | 8 +- module/ui/switch.py | 152 ++++++++++++------ 17 files changed, 191 insertions(+), 133 deletions(-) diff --git a/campaign/campaign_main/campaign_15_base.py b/campaign/campaign_main/campaign_15_base.py index 6e723b7bb..af7a5b841 100644 --- a/campaign/campaign_main/campaign_15_base.py +++ b/campaign/campaign_main/campaign_15_base.py @@ -53,9 +53,9 @@ class CampaignBase(CampaignBase_): map_has_mob_move = True - def strategy_set_execute(self, formation_index=None, sub_view=None, sub_hunt=None): + def strategy_set_execute(self, formation=None, sub_view=None, sub_hunt=None): super().strategy_set_execute( - formation_index=formation_index, + formation=formation, sub_view=sub_view, sub_hunt=sub_hunt, ) diff --git a/campaign/campaign_war_archives/campaign_base.py b/campaign/campaign_war_archives/campaign_base.py index ecfac7b81..76e0bac68 100644 --- a/campaign/campaign_war_archives/campaign_base.py +++ b/campaign/campaign_war_archives/campaign_base.py @@ -12,8 +12,8 @@ from module.war_archives.assets import (WAR_ARCHIVES_CAMPAIGN_CHECK, from module.war_archives.dictionary import dic_archives_template WAR_ARCHIVES_SWITCH = Switch('War_Archives_switch', is_selector=True) -WAR_ARCHIVES_SWITCH.add_status('ex', WAR_ARCHIVES_EX_ON) -WAR_ARCHIVES_SWITCH.add_status('sp', WAR_ARCHIVES_SP_ON) +WAR_ARCHIVES_SWITCH.add_state('ex', WAR_ARCHIVES_EX_ON) +WAR_ARCHIVES_SWITCH.add_state('sp', WAR_ARCHIVES_SP_ON) WAR_ARCHIVES_SCROLL = Scroll(WAR_ARCHIVES_SCROLL, color=(247, 211, 66), name='WAR_ARCHIVES_SCROLL') diff --git a/campaign/event_20221124_cn/campaign_base.py b/campaign/event_20221124_cn/campaign_base.py index 676c7c97b..29fd7794c 100644 --- a/campaign/event_20221124_cn/campaign_base.py +++ b/campaign/event_20221124_cn/campaign_base.py @@ -1,6 +1,6 @@ from module.campaign.campaign_base import CampaignBase as CampaignBase_ from module.combat.assets import GET_ITEMS_1_RYZA -from module.handler.fast_forward import auto_search +from module.handler.fast_forward import AUTO_SEARCH from module.handler.assets import MYSTERY_ITEM from module.logger import logger from module.map.map_grids import SelectedGrids @@ -62,7 +62,7 @@ class CampaignBase(CampaignBase_): # Chapter TH has no map_percentage and no 3_stars if name.startswith('th') or name.startswith('ht'): - appear = auto_search.appear(main=self) + appear = AUTO_SEARCH.appear(main=self) self.map_is_100_percent_clear = self.map_is_3_stars = self.map_is_threat_safe = appear self.map_has_clear_mode = appear self.map_show_info() diff --git a/campaign/event_20240725_cn/campaign_base.py b/campaign/event_20240725_cn/campaign_base.py index f4ca6680f..98087ca16 100644 --- a/campaign/event_20240725_cn/campaign_base.py +++ b/campaign/event_20240725_cn/campaign_base.py @@ -4,8 +4,8 @@ from module.campaign.campaign_ui import ModeSwitch from module.logger import logger MODE_SWITCH_20240725 = ModeSwitch('Mode_switch_20240725', offset=(30, 30)) -MODE_SWITCH_20240725.add_status('combat', SWITCH_20240725_COMBAT) -MODE_SWITCH_20240725.add_status('story', SWITCH_20240725_STORY) +MODE_SWITCH_20240725.add_state('combat', SWITCH_20240725_COMBAT) +MODE_SWITCH_20240725.add_state('story', SWITCH_20240725_STORY) class CampaignBase(CampaignBase_): diff --git a/campaign/event_20240829_cn/campaign_base.py b/campaign/event_20240829_cn/campaign_base.py index 608acabc7..3e03f23c4 100644 --- a/campaign/event_20240829_cn/campaign_base.py +++ b/campaign/event_20240829_cn/campaign_base.py @@ -4,8 +4,8 @@ from module.campaign.campaign_ui import ModeSwitch from module.logger import logger MODE_SWITCH_20240725 = ModeSwitch('Mode_switch_20240725', offset=(30, 30)) -MODE_SWITCH_20240725.add_status('combat', SWITCH_20240725_COMBAT) -MODE_SWITCH_20240725.add_status('story', SWITCH_20240725_STORY) +MODE_SWITCH_20240725.add_state('combat', SWITCH_20240725_COMBAT) +MODE_SWITCH_20240725.add_state('story', SWITCH_20240725_STORY) class CampaignBase(CampaignBase_): diff --git a/campaign/event_20240912_cn/campaign_base.py b/campaign/event_20240912_cn/campaign_base.py index 9a81e471c..20f534cd7 100644 --- a/campaign/event_20240912_cn/campaign_base.py +++ b/campaign/event_20240912_cn/campaign_base.py @@ -5,8 +5,8 @@ from module.ui.ui import page_event MODE_SWITCH_20240912 = ModeSwitch('Mode_switch_20240912', is_selector=True, offset=(30, 30)) -MODE_SWITCH_20240912.add_status('combat', SWITCH_20240725_COMBAT, offset=(444, 4)) -MODE_SWITCH_20240912.add_status('story', SWITCH_20240725_STORY, offset=(444, 4)) +MODE_SWITCH_20240912.add_state('combat', SWITCH_20240725_COMBAT, offset=(444, 4)) +MODE_SWITCH_20240912.add_state('story', SWITCH_20240725_STORY, offset=(444, 4)) class CampaignBase(CampaignBase_): diff --git a/campaign/event_20241024_cn/campaign_base.py b/campaign/event_20241024_cn/campaign_base.py index 849294076..bf995a47f 100644 --- a/campaign/event_20241024_cn/campaign_base.py +++ b/campaign/event_20241024_cn/campaign_base.py @@ -7,14 +7,14 @@ from module.map_detection.grid import Grid from module.template.assets import TEMPLATE_ENEMY_BOSS MODE_SWITCH_20240725 = ModeSwitch('Mode_switch_20240725', is_selector=True, offset=(30, 30)) -MODE_SWITCH_20240725.add_status('combat', SWITCH_20240725_COMBAT, offset=(444, 4)) -MODE_SWITCH_20240725.add_status('story', SWITCH_20240725_STORY, offset=(444, 4)) +MODE_SWITCH_20240725.add_state('combat', SWITCH_20240725_COMBAT, offset=(444, 4)) +MODE_SWITCH_20240725.add_state('story', SWITCH_20240725_STORY, offset=(444, 4)) CHAPTER_SWITCH_20241024 = ModeSwitch('Chapter_switch_20241024', is_selector=True, offset=(30, 30)) -CHAPTER_SWITCH_20241024.add_status('ab', CHAPTER_20241024_AB) -CHAPTER_SWITCH_20241024.add_status('cd', CHAPTER_20241024_CD) -CHAPTER_SWITCH_20241024.add_status('sp', CHAPTER_20241024_SP) -CHAPTER_SWITCH_20241024.add_status('ex', CHAPTER_20241024_EX) +CHAPTER_SWITCH_20241024.add_state('ab', CHAPTER_20241024_AB) +CHAPTER_SWITCH_20241024.add_state('cd', CHAPTER_20241024_CD) +CHAPTER_SWITCH_20241024.add_state('sp', CHAPTER_20241024_SP) +CHAPTER_SWITCH_20241024.add_state('ex', CHAPTER_20241024_EX) class EventGrid(Grid): diff --git a/module/campaign/campaign_ui.py b/module/campaign/campaign_ui.py index 8da38f93a..e8cd110ad 100644 --- a/module/campaign/campaign_ui.py +++ b/module/campaign/campaign_ui.py @@ -18,11 +18,11 @@ class ModeSwitch(Switch): MODE_SWITCH_1 = ModeSwitch('Mode_switch_1', offset=(30, 10)) -MODE_SWITCH_1.add_status('normal', SWITCH_1_NORMAL) -MODE_SWITCH_1.add_status('hard', SWITCH_1_HARD) +MODE_SWITCH_1.add_state('normal', SWITCH_1_NORMAL) +MODE_SWITCH_1.add_state('hard', SWITCH_1_HARD) MODE_SWITCH_2 = ModeSwitch('Mode_switch_2', offset=(30, 10)) -MODE_SWITCH_2.add_status('hard', SWITCH_2_HARD) -MODE_SWITCH_2.add_status('ex', SWITCH_2_EX) +MODE_SWITCH_2.add_state('hard', SWITCH_2_HARD) +MODE_SWITCH_2.add_state('ex', SWITCH_2_EX) class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): diff --git a/module/coalition/ui.py b/module/coalition/ui.py index 7fd2ee01a..df7490f41 100644 --- a/module/coalition/ui.py +++ b/module/coalition/ui.py @@ -24,12 +24,12 @@ class CoalitionUI(Combat): """ MODE_SWITCH = Switch('CoalitionMode', offset=(20, 20)) if event == 'coalition_20230323': - MODE_SWITCH.add_status('story', FROSTFALL_MODE_STORY) - MODE_SWITCH.add_status('battle', FROSTFALL_MODE_BATTLE) + MODE_SWITCH.add_state('story', FROSTFALL_MODE_STORY) + MODE_SWITCH.add_state('battle', FROSTFALL_MODE_BATTLE) elif event == 'coalition_20240627': # Note that switch button are reversed - MODE_SWITCH.add_status('story', ACADEMY_MODE_BATTLE) - MODE_SWITCH.add_status('battle', ACADEMY_MODE_STORY) + MODE_SWITCH.add_state('story', ACADEMY_MODE_BATTLE) + MODE_SWITCH.add_state('battle', ACADEMY_MODE_STORY) else: logger.error(f'MODE_SWITCH is not defined in event {event}') raise ScriptError @@ -52,11 +52,11 @@ class CoalitionUI(Combat): """ FLEET_SWITCH = Switch('FleetMode', is_selector=True, offset=0) # No offset for color match if event == 'coalition_20230323': - FLEET_SWITCH.add_status('single', FROSTFALL_SWITCH_SINGLE) - FLEET_SWITCH.add_status('multi', FROSTFALL_SWITCH_MULTI) + FLEET_SWITCH.add_state('single', FROSTFALL_SWITCH_SINGLE) + FLEET_SWITCH.add_state('multi', FROSTFALL_SWITCH_MULTI) elif event == 'coalition_20240627': - FLEET_SWITCH.add_status('single', ACADEMY_SWITCH_SINGLE) - FLEET_SWITCH.add_status('multi', ACADEMY_SWITCH_MULTI) + FLEET_SWITCH.add_state('single', ACADEMY_SWITCH_SINGLE) + FLEET_SWITCH.add_state('multi', ACADEMY_SWITCH_MULTI) else: logger.error(f'FLEET_SWITCH is not defined in event {event}') raise ScriptError diff --git a/module/commission/commission.py b/module/commission/commission.py index 727b51779..c1f657d56 100644 --- a/module/commission/commission.py +++ b/module/commission/commission.py @@ -24,8 +24,8 @@ from module.ui.ui import UI from module.ui_white.assets import REWARD_1_WHITE, REWARD_GOTO_COMMISSION_WHITE COMMISSION_SWITCH = Switch('Commission_switch', is_selector=True) -COMMISSION_SWITCH.add_status('daily', COMMISSION_DAILY) -COMMISSION_SWITCH.add_status('urgent', COMMISSION_URGENT) +COMMISSION_SWITCH.add_state('daily', COMMISSION_DAILY) +COMMISSION_SWITCH.add_state('urgent', COMMISSION_URGENT) COMMISSION_SCROLL = Scroll(COMMISSION_SCROLL_AREA, color=(247, 211, 66), name='COMMISSION_SCROLL') diff --git a/module/equipment/equipment_change.py b/module/equipment/equipment_change.py index 941d2d3c7..360db0f8c 100644 --- a/module/equipment/equipment_change.py +++ b/module/equipment/equipment_change.py @@ -18,8 +18,8 @@ EQUIPMENT_SCROLL = Scroll(EQUIP_SCROLL, color=(247, 211, 66), name='EQUIP_SCROLL SIM_VALUE = 0.90 equipping_filter = Switch('Equipping_filter') -equipping_filter.add_status('on', check_button=EQUIPPING_ON) -equipping_filter.add_status('off', check_button=EQUIPPING_OFF) +equipping_filter.add_state('on', check_button=EQUIPPING_ON) +equipping_filter.add_state('off', check_button=EQUIPPING_OFF) class EquipmentChange(Equipment): diff --git a/module/handler/fast_forward.py b/module/handler/fast_forward.py index 7f9f6cf99..5a342b07f 100644 --- a/module/handler/fast_forward.py +++ b/module/handler/fast_forward.py @@ -7,15 +7,15 @@ from module.handler.auto_search import AutoSearchHandler from module.logger import logger from module.ui.switch import Switch -fast_forward = Switch('Fast_Forward') -fast_forward.add_status('on', check_button=FAST_FORWARD_ON) -fast_forward.add_status('off', check_button=FAST_FORWARD_OFF) -fleet_lock = Switch('Fleet_Lock', offset=(5, 20)) -fleet_lock.add_status('on', check_button=FLEET_LOCKED) -fleet_lock.add_status('off', check_button=FLEET_UNLOCKED) -auto_search = Switch('Auto_Search', offset=(20, 20)) -auto_search.add_status('on', check_button=AUTO_SEARCH_ON) -auto_search.add_status('off', check_button=AUTO_SEARCH_OFF) +FAST_FORWARD = Switch('Fast_Forward') +FAST_FORWARD.add_state('on', check_button=FAST_FORWARD_ON) +FAST_FORWARD.add_state('off', check_button=FAST_FORWARD_OFF) +FLEET_LOCK = Switch('Fleet_Lock', offset=(5, 20)) +FLEET_LOCK.add_state('on', check_button=FLEET_LOCKED) +FLEET_LOCK.add_state('off', check_button=FLEET_UNLOCKED) +AUTO_SEARCH = Switch('Auto_Search', offset=(20, 20)) +AUTO_SEARCH.add_state('on', check_button=AUTO_SEARCH_ON) +AUTO_SEARCH.add_state('off', check_button=AUTO_SEARCH_OFF) def map_files(event): @@ -127,9 +127,9 @@ class FastForwardHandler(AutoSearchHandler): # Minor issue here # Using auto_search option because clear mode cannot be detected whether on SP # If user manually turn off auto search, alas can't enable it again - self.map_has_clear_mode = auto_search.appear(main=self) + self.map_has_clear_mode = AUTO_SEARCH.appear(main=self) else: - self.map_has_clear_mode = self.map_is_100_percent_clear and fast_forward.appear(main=self) + self.map_has_clear_mode = self.map_is_100_percent_clear and FAST_FORWARD.appear(main=self) # Override config if self.map_achieved_star_1: @@ -187,8 +187,8 @@ class FastForwardHandler(AutoSearchHandler): self.map_is_2x_book = False pass - status = 'on' if self.config.Campaign_UseClearMode else 'off' - changed = fast_forward.set(status=status, main=self) + state = 'on' if self.config.Campaign_UseClearMode else 'off' + changed = FAST_FORWARD.set(state, main=self) return changed def handle_map_fleet_lock(self, enable=None): @@ -201,14 +201,14 @@ class FastForwardHandler(AutoSearchHandler): """ # Fleet lock depends on if it appear on map, not depends on map status. # Because if already in map, there's no map status, - if not fleet_lock.appear(main=self): + if not FLEET_LOCK.appear(main=self): logger.info('No fleet lock option.') return False if enable is None: enable = self.config.Campaign_UseFleetLock - status = 'on' if enable else 'off' - changed = fleet_lock.set(status=status, main=self) + state = 'on' if enable else 'off' + changed = FLEET_LOCK.set(state, main=self) return changed @@ -223,13 +223,13 @@ class FastForwardHandler(AutoSearchHandler): # if not self.map_is_clear_mode: # return False - if not auto_search.appear(main=self): + if not AUTO_SEARCH.appear(main=self): logger.info('No auto search option.') self.map_is_auto_search = False return False - status = 'on' if self.map_is_auto_search else 'off' - changed = auto_search.set(status=status, main=self) + state = 'on' if self.map_is_auto_search else 'off' + changed = AUTO_SEARCH.set(state, main=self) return changed @@ -461,8 +461,8 @@ class FastForwardHandler(AutoSearchHandler): book_check = BOOK_CHECK_AUTO book_box = BOOK_BOX_AUTO - status = 'on' if self.map_is_2x_book else 'off' - if self._set_2x_book_status(status, book_check, book_box): + state = 'on' if self.map_is_2x_book else 'off' + if self._set_2x_book_status(state, book_check, book_box): self.emotion.map_is_2x_book = self.map_is_2x_book else: self.map_is_2x_book = False diff --git a/module/handler/strategy.py b/module/handler/strategy.py index b3ff703c5..65d45576c 100644 --- a/module/handler/strategy.py +++ b/module/handler/strategy.py @@ -7,18 +7,18 @@ from module.template.assets import (TEMPLATE_FORMATION_1, TEMPLATE_FORMATION_2, from module.ui.switch import Switch # 2023.10.19, icons on one row increased from 2 to 3 -formation = Switch('Formation', offset=(100, 200)) -formation.add_status('line_ahead', check_button=FORMATION_1) -formation.add_status('double_line', check_button=FORMATION_2) -formation.add_status('diamond', check_button=FORMATION_3) +FORMATION = Switch('Formation', offset=(100, 200)) +FORMATION.add_state('line_ahead', check_button=FORMATION_1) +FORMATION.add_state('double_line', check_button=FORMATION_2) +FORMATION.add_state('diamond', check_button=SUBMARINE_HUNT_ON) -submarine_hunt = Switch('Submarine_hunt', offset=(200, 200)) -submarine_hunt.add_status('on', check_button=SUBMARINE_HUNT_ON) -submarine_hunt.add_status('off', check_button=SUBMARINE_HUNT_OFF) +SUBMARINE_HUNT = Switch('Submarine_hunt', offset=(200, 200)) +SUBMARINE_HUNT.add_state('on', check_button=SUBMARINE_HUNT_ON) +SUBMARINE_HUNT.add_state('off', check_button=SUBMARINE_HUNT_OFF) -submarine_view = Switch('Submarine_view', offset=(100, 200)) -submarine_view.add_status('on', check_button=SUBMARINE_VIEW_ON) -submarine_view.add_status('off', check_button=SUBMARINE_VIEW_OFF) +SUBMARINE_VIEW = Switch('Submarine_view', offset=(100, 200)) +SUBMARINE_VIEW.add_state('on', check_button=SUBMARINE_VIEW_ON) +SUBMARINE_VIEW.add_state('off', check_button=SUBMARINE_VIEW_OFF) MOB_MOVE_OFFSET = (120, 200) @@ -60,20 +60,20 @@ class StrategyHandler(InfoHandler): if not self.appear(STRATEGY_OPENED, offset=200): break - def strategy_set_execute(self, formation_index=None, sub_view=None, sub_hunt=None): + def strategy_set_execute(self, formation=None, sub_view=None, sub_hunt=None): """ Args: - formation_index (int): 1-3, or None for don't change + formation (str): 'line_ahead', 'double_line', 'diamond', or None for don't change sub_view (bool): sub_hunt (bool): Pages: in: STRATEGY_OPENED """ - logger.info(f'Strategy set: formation={formation_index}, submarine_view={sub_view}, submarine_hunt={sub_hunt}') + logger.info(f'Strategy set: formation={formation}, submarine_view={sub_view}, submarine_hunt={sub_hunt}') - if formation_index is not None: - formation.set(str(formation_index), main=self) + if formation is not None: + FORMATION.set(formation, main=self) # Disable this until the icon bug of submarine zone is fixed # And don't enable MAP_HAS_DYNAMIC_RED_BORDER when using submarine @@ -81,13 +81,13 @@ class StrategyHandler(InfoHandler): # Don't know when but the game bug was fixed, remove the use of SwitchWithHandler if sub_view is not None: - if submarine_view.appear(main=self): - submarine_view.set('on' if sub_view else 'off', main=self) + if SUBMARINE_VIEW.appear(main=self): + SUBMARINE_VIEW.set('on' if sub_view else 'off', main=self) else: logger.warning('Setting up submarine_view but no icon appears') if sub_hunt is not None: - if submarine_hunt.appear(main=self): - submarine_hunt.set('on' if sub_hunt else 'off', main=self) + if SUBMARINE_HUNT.appear(main=self): + SUBMARINE_HUNT.set('on' if sub_hunt else 'off', main=self) else: logger.warning('Setting up submarine_hunt but no icon appears') @@ -110,7 +110,7 @@ class StrategyHandler(InfoHandler): self.strategy_open() self.strategy_set_execute( - formation_index=expected_formation, + formation=expected_formation, sub_view=False, sub_hunt=bool(self.config.Submarine_Fleet) and self.config.Submarine_Mode in ['hunt_only', 'hunt_and_boss'] ) diff --git a/module/meowfficer/collect.py b/module/meowfficer/collect.py index 1ff9e10f0..5ce6a0373 100644 --- a/module/meowfficer/collect.py +++ b/module/meowfficer/collect.py @@ -15,12 +15,12 @@ MEOWFFICER_SHIFT_DETECT = Button( name='MEOWFFICER_SHIFT_DETECT') SWITCH_LOCK = Switch(name='Meowfficer_Lock', offset=(40, 40)) -SWITCH_LOCK.add_status( +SWITCH_LOCK.add_state( 'lock', check_button=MEOWFFICER_APPLY_UNLOCK, click_button=MEOWFFICER_APPLY_LOCK ) -SWITCH_LOCK.add_status( +SWITCH_LOCK.add_state( 'unlock', check_button=MEOWFFICER_APPLY_LOCK, click_button=MEOWFFICER_APPLY_UNLOCK @@ -175,7 +175,7 @@ class MeowfficerCollect(MeowfficerBase): lock (bool): """ # Apply designated lock status - SWITCH_LOCK.set(status='lock' if lock else 'unlock', main=self) + SWITCH_LOCK.set('lock' if lock else 'unlock', main=self) # Wait until info bar disappears self.ensure_no_info_bar(timeout=1) diff --git a/module/os_handler/map_event.py b/module/os_handler/map_event.py index 7c33989d0..7a640a3be 100644 --- a/module/os_handler/map_event.py +++ b/module/os_handler/map_event.py @@ -20,8 +20,8 @@ class FleetLockSwitch(Switch): fleet_lock = FleetLockSwitch('Fleet_Lock', offset=(10, 120)) -fleet_lock.add_status('on', check_button=OS_FLEET_LOCKED) -fleet_lock.add_status('off', check_button=OS_FLEET_UNLOCKED) +fleet_lock.add_state('on', check_button=OS_FLEET_LOCKED) +fleet_lock.add_state('off', check_button=OS_FLEET_UNLOCKED) class MapEventHandler(EnemySearchingHandler): @@ -306,7 +306,7 @@ class MapEventHandler(EnemySearchingHandler): if enable is None: enable = self.config.Campaign_UseFleetLock - status = 'on' if enable else 'off' - changed = fleet_lock.set(status=status, main=self) + state = 'on' if enable else 'off' + changed = fleet_lock.set(state, main=self) return changed diff --git a/module/retire/dock.py b/module/retire/dock.py index 4e6fe5905..e98550d7c 100644 --- a/module/retire/dock.py +++ b/module/retire/dock.py @@ -12,12 +12,12 @@ from module.ui.setting import Setting from module.ui.switch import Switch DOCK_SORTING = Switch('Dork_sorting') -DOCK_SORTING.add_status('Ascending', check_button=SORT_ASC, click_button=SORTING_CLICK) -DOCK_SORTING.add_status('Descending', check_button=SORT_DESC, click_button=SORTING_CLICK) +DOCK_SORTING.add_state('Ascending', check_button=SORT_ASC, click_button=SORTING_CLICK) +DOCK_SORTING.add_state('Descending', check_button=SORT_DESC, click_button=SORTING_CLICK) DOCK_FAVOURITE = Switch('Favourite_filter') -DOCK_FAVOURITE.add_status('on', check_button=COMMON_SHIP_FILTER_ENABLE) -DOCK_FAVOURITE.add_status('off', check_button=COMMON_SHIP_FILTER_DISABLE) +DOCK_FAVOURITE.add_state('on', check_button=COMMON_SHIP_FILTER_ENABLE) +DOCK_FAVOURITE.add_state('off', check_button=COMMON_SHIP_FILTER_DISABLE) CARD_GRIDS = ButtonGrid( origin=(93, 76), delta=(164 + 2 / 3, 227), button_shape=(138, 204), grid_shape=(7, 2), name='CARD') diff --git a/module/ui/switch.py b/module/ui/switch.py index a72b9f366..c222bee17 100644 --- a/module/ui/switch.py +++ b/module/ui/switch.py @@ -1,5 +1,4 @@ from module.base.base import ModuleBase -from module.base.button import Button from module.base.timer import Timer from module.exception import ScriptError from module.logger import logger @@ -7,16 +6,15 @@ from module.logger import logger class Switch: """ - A wrapper to handle switches in game. - Set switch status with reties. + A wrapper to handle switches in game, switch among states with retries. Examples: # Definitions submarine_hunt = Switch('Submarine_hunt', offset=120) - submarine_hunt.add_status('on', check_button=SUBMARINE_HUNT_ON) - submarine_hunt.add_status('off', check_button=SUBMARINE_HUNT_OFF) + submarine_hunt.add_state('on', check_button=SUBMARINE_HUNT_ON) + submarine_hunt.add_state('off', check_button=SUBMARINE_HUNT_OFF) - # Change status to ON + # Change state to ON submarine_view.set('on', main=self) """ @@ -28,23 +26,24 @@ class Switch: For example: | [Daily] | Urgent | -> click -> | Daily | [Urgent] | False if this is a switch, click the switch itself, and it changed in the same position. For example: | [ON] | -> click -> | [OFF] | - offset (bool, int, tuple): Global offset in current switch """ self.name = name - self.is_choice = is_selector + self.is_selector = is_selector self.offset = offset - self.status_list = [] + self.state_list = [] - def add_status(self, status, check_button, click_button=None, offset=0): + def add_state(self, state, check_button, click_button=None, offset=0): """ Args: - status (str): + state (str): State name but cannot use 'unknown' as state name check_button (Button): click_button (Button): offset (bool, int, tuple): """ - self.status_list.append({ - 'status': status, + if state == 'unknown': + raise ScriptError(f'Cannot use "unknown" as state name') + self.state_list.append({ + 'state': state, 'check_button': check_button, 'click_button': click_button if click_button is not None else check_button, 'offset': offset if offset else self.offset @@ -58,7 +57,7 @@ class Switch: Returns: bool """ - for data in self.status_list: + for data in self.state_list: if main.appear(data['check_button'], offset=data['offset']): return True @@ -70,31 +69,39 @@ class Switch: main (ModuleBase): Returns: - str: Status name or 'unknown'. + str: state name or 'unknown'. """ - for data in self.status_list: + for data in self.state_list: if main.appear(data['check_button'], offset=data['offset']): - return data['status'] + return data['state'] return 'unknown' - def get_data(self, status): + def click(self, state, main): """ Args: - status (str): + state (str): + main (ModuleBase): + """ + button = self.get_data(state)['click_button'] + main.device.click(button) + + def get_data(self, state): + """ + Args: + state (str): Returns: - dict: Dictionary in add_status + dict: Dictionary in add_state Raises: - ScriptError: If status invalid + ScriptError: If state invalid """ - for row in self.status_list: - if row['status'] == status: + for row in self.state_list: + if row['state'] == state: return row - logger.warning(f'Switch {self.name} received an invalid status {status}') - raise ScriptError(f'Switch {self.name} received an invalid status {status}') + raise ScriptError(f'Switch {self.name} received an invalid state: {state}') def handle_additional(self, main): """ @@ -106,21 +113,22 @@ class Switch: """ return False - def set(self, status, main, skip_first_screenshot=True): + def set(self, state, main, skip_first_screenshot=True): """ Args: - status (str): + state: main (ModuleBase): skip_first_screenshot (bool): Returns: - bool: + bool: If clicked """ - self.get_data(status) + logger.info(f'{self.name} set to {state}') + self.get_data(state) - counter = 0 changed = False - warning_show_timer = Timer(5, count=10).start() + has_unknown = False + unknown_timer = Timer(5, count=10).start() click_timer = Timer(1, count=3) while 1: if skip_first_screenshot: @@ -132,31 +140,81 @@ class Switch: current = self.get(main=main) logger.attr(self.name, current) + # End + if current == state: + return changed + # Handle additional popups if self.handle_additional(main=main): continue - # End - if current == status: - return changed - # Warning if current == 'unknown': - if warning_show_timer.reached(): - logger.warning(f'Unknown {self.name} switch') - warning_show_timer.reset() - if counter >= 1: - logger.warning(f'{self.name} switch {status} asset has evaluated to unknown too many times, ' - f'asset should be re-verified') - return False - counter += 1 - continue + if unknown_timer.reached(): + logger.warning(f'Switch {self.name} has states evaluated to unknown, ' + f'asset should be re-verified') + has_unknown = True + unknown_timer.reset() + # If unknown_timer never reached, don't click when having an unknown state, + # the unknown state is probably the switching animation. + # If unknown_timer reached once, click target state ignoring whether state is unknown or not, + # the unknown state is probably a new state not yet added. + # By ignoring new states, Switch.set() can still switch among known states. + if not has_unknown: + continue + else: + # Known state, reset timer + unknown_timer.reset() # Click if click_timer.reached(): - click_status = status if self.is_choice else current - main.device.click(self.get_data(click_status)['click_button']) - click_timer.reset() + if self.is_selector: + # Click target state to switch + click_state = state + else: + # If this is a selector, click on current state to switch to another + # But 'unknown' is not clickable, if it is, click target state instead + # assuming all selector states share the same position. + if current == 'unknown': + click_state = state + else: + click_state = current + self.click(click_state, main=main) changed = True + click_timer.reset() + unknown_timer.reset() return changed + + def wait(self, main, skip_first_screenshot=True): + """ + Wait until any state activated + + Args: + main (ModuleBase): + skip_first_screenshot: + + Returns: + bool: If success + """ + timeout = Timer(2, count=6).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + main.device.screenshot() + + # Detect + current = self.get(main=main) + logger.attr(self.name, current) + + # End + if current != 'unknown': + return True + if timeout.reached(): + logger.warning(f'{self.name} wait activated timeout') + return False + + # Handle additional popups + if self.handle_additional(main=main): + continue From a67bbba736bc5e9ae2109d705e38f21b6e7968ae Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:22:11 +0800 Subject: [PATCH 12/30] Fix: [EN] Strict color match threshold on RESET_AVAILABLE --- module/research/research.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/research/research.py b/module/research/research.py index 4328c056a..62b35b042 100644 --- a/module/research/research.py +++ b/module/research/research.py @@ -50,7 +50,7 @@ class RewardResearch(ResearchSelector, ResearchQueue, StorageHandler): Returns: bool: If reset success. """ - if not self.appear(RESET_AVAILABLE): + if not self.appear(RESET_AVAILABLE, threshold=10): logger.info('Research reset unavailable') return False @@ -63,7 +63,7 @@ class RewardResearch(ResearchSelector, ResearchQueue, StorageHandler): else: self.device.screenshot() - if self.appear_then_click(RESET_AVAILABLE, interval=10): + if self.appear_then_click(RESET_AVAILABLE, interval=10, threshold=10): continue if self.handle_popup_confirm('RESEARCH_RESET'): executed = True From 48e4c78a43f3f83a2bdf9a2a314486b10c96eba3 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:22:54 +0800 Subject: [PATCH 13/30] Fix: Default color match threshold should be the original value COLOR_SIMILAR_THRESHOLD --- module/base/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/base/base.py b/module/base/base.py index 4646baee5..b671fda64 100644 --- a/module/base/base.py +++ b/module/base/base.py @@ -124,7 +124,7 @@ class ModuleBase: return button - def appear(self, button, offset=0, interval=0, similarity=0.85, threshold=30): + def appear(self, button, offset=0, interval=0, similarity=0.85, threshold=10): """ Args: button (Button, Template, HierarchyButton, str): From bfdf6e4179ac55a9e377b50a6d4e6eb97ff0f52f Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:50:53 +0800 Subject: [PATCH 14/30] Fix: Redirect stage names like "B-1" to be foolproof --- module/campaign/run.py | 2 -- module/handler/fast_forward.py | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/module/campaign/run.py b/module/campaign/run.py index a3b284157..37cdc6dbd 100644 --- a/module/campaign/run.py +++ b/module/campaign/run.py @@ -2,7 +2,6 @@ import copy import importlib import os import random -import re from module.campaign.campaign_base import CampaignBase from module.campaign.campaign_event import CampaignEvent @@ -164,7 +163,6 @@ class CampaignRun(CampaignEvent): Returns: str, str: name, folder """ - name = re.sub('[ \t\n]', '', str(name)).lower() name = to_map_file_name(name) # For GemsFarming, auto choose events or main chapters if self.config.task.command == 'GemsFarming': diff --git a/module/handler/fast_forward.py b/module/handler/fast_forward.py index 5a342b07f..7a49bb21f 100644 --- a/module/handler/fast_forward.py +++ b/module/handler/fast_forward.py @@ -1,4 +1,5 @@ import os +import re from module.base.timer import Timer from module.base.utils import color_bar_percentage @@ -51,7 +52,14 @@ def to_map_input_name(name: str) -> str: campaign_7_2 -> 7-2 d3 -> D3 """ - name = name.upper() + name = str(name).upper() + # Remove whitespaces + name = re.sub('[ \t\n]', '', name).lower() + # B-1 -> B1 + res = re.match(r'([a-zA-Z])+[- ]+(\d+)', name) + if res: + name = f'{res.group(1)}{res.group(2)}' + # campaign_7_2 -> 7-2 name = name.replace('CAMPAIGN_', '').replace('_', '-') return name @@ -64,7 +72,14 @@ def to_map_file_name(name: str) -> str: campaign_7_2 -> campaign_7_2 D3 -> d3 """ - name = name.lower() + name = str(name).lower() + # Remove whitespaces + name = re.sub('[ \t\n]', '', name).lower() + # B-1 -> B1 + res = re.match(r'([a-zA-Z])+[- ]+(\d+)', name) + if res: + name = f'{res.group(1)}{res.group(2)}' + # 7-2 to campaign_7_2 if name and name[0].isdigit(): name = 'campaign_' + name.replace('-', '_') return name From 8dbc3acc56157ea5b96945734d07cf8dec469e96 Mon Sep 17 00:00:00 2001 From: guoh064 <50830808+guoh064@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:52:28 +0800 Subject: [PATCH 15/30] upd: [JP] yellow_coin color (#4427) --- module/os_handler/os_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/os_handler/os_status.py b/module/os_handler/os_status.py index 4e199813a..d0ad00f3a 100644 --- a/module/os_handler/os_status.py +++ b/module/os_handler/os_status.py @@ -16,7 +16,7 @@ from module.ui.ui import UI if server.server != 'jp': OCR_SHOP_YELLOW_COINS = Digit(SHOP_YELLOW_COINS, letter=(239, 239, 239), threshold=160, name='OCR_SHOP_YELLOW_COINS') else: - OCR_SHOP_YELLOW_COINS = Digit(SHOP_YELLOW_COINS, letter=(193, 193, 193), threshold=200, name='OCR_SHOP_YELLOW_COINS') + OCR_SHOP_YELLOW_COINS = Digit(SHOP_YELLOW_COINS, letter=(201, 201, 201), threshold=200, name='OCR_SHOP_YELLOW_COINS') OCR_SHOP_PURPLE_COINS = Digit(SHOP_PURPLE_COINS, letter=(255, 255, 255), name='OCR_SHOP_PURPLE_COINS') OCR_OS_SHOP_PURPLE_COINS = Digit(OS_SHOP_PURPLE_COINS, letter=(255, 255, 255), name='OCR_OS_SHOP_PURPLE_COINS') From eb836d1b4f6daf3f9ed77f5b311832c62a96aea0 Mon Sep 17 00:00:00 2001 From: sui_feng <115386623+sui-feng-cb@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:52:45 +0800 Subject: [PATCH 16/30] Fix: correct check_button in add_state() (#4429) --- module/handler/strategy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/handler/strategy.py b/module/handler/strategy.py index 65d45576c..4cf9a4956 100644 --- a/module/handler/strategy.py +++ b/module/handler/strategy.py @@ -10,7 +10,7 @@ from module.ui.switch import Switch FORMATION = Switch('Formation', offset=(100, 200)) FORMATION.add_state('line_ahead', check_button=FORMATION_1) FORMATION.add_state('double_line', check_button=FORMATION_2) -FORMATION.add_state('diamond', check_button=SUBMARINE_HUNT_ON) +FORMATION.add_state('diamond', check_button=FORMATION_3) SUBMARINE_HUNT = Switch('Submarine_hunt', offset=(200, 200)) SUBMARINE_HUNT.add_state('on', check_button=SUBMARINE_HUNT_ON) From 27b92c9b58c82311df7cb2115573b72a9acd9a7b Mon Sep 17 00:00:00 2001 From: "Shane (Treasure) Xue" Date: Mon, 16 Dec 2024 00:05:50 +0800 Subject: [PATCH 17/30] Bugfix: to_map_input_name case issue (#4433) After bugfixes #4423 logic in the function changed and on the line where CAMPAIGN_ is removed the name is actually in lower case. Moved upper case manupulation in order to ensure correct case. Upper was not removed considering the function should turn d3 into D3. Before this bugfix incrementing to the next stage was unusable (eg. 12-2 would be incremented to a strange "n2") Co-authored-by: LmeSzinc --- module/handler/fast_forward.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/handler/fast_forward.py b/module/handler/fast_forward.py index 7a49bb21f..4c2d3cbf3 100644 --- a/module/handler/fast_forward.py +++ b/module/handler/fast_forward.py @@ -52,13 +52,14 @@ def to_map_input_name(name: str) -> str: campaign_7_2 -> 7-2 d3 -> D3 """ - name = str(name).upper() # Remove whitespaces name = re.sub('[ \t\n]', '', name).lower() # B-1 -> B1 res = re.match(r'([a-zA-Z])+[- ]+(\d+)', name) if res: name = f'{res.group(1)}{res.group(2)}' + # Change back to upper case for campaign removal + name = str(name).upper() # campaign_7_2 -> 7-2 name = name.replace('CAMPAIGN_', '').replace('_', '-') return name From 52a29aa288b13d26f9a49c21ec9adc425be3c165 Mon Sep 17 00:00:00 2001 From: guoh064 <50830808+guoh064@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:06:17 +0800 Subject: [PATCH 18/30] Fix: jp commission suffix pre_process (#4435) --- module/commission/project.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/module/commission/project.py b/module/commission/project.py index ea2557e9d..41fff2c42 100644 --- a/module/commission/project.py +++ b/module/commission/project.py @@ -41,8 +41,9 @@ class SuffixOcr(Ocr): # will need to pad white background for better recognization image = image[8:-10, :] cv2.normalize(image, image, -55, 255, cv2.NORM_MINMAX) - image = (image > 128).astype(np.uint8) * 255 - image = np.pad(image, ((4, 4), (0, 0)), mode='constant', constant_values=255) + image = (image > 80).astype(np.uint8) * 255 + image = np.pad(image, ((0, 1), (0, 0)), mode='edge') + image = np.pad(image, ((4, 3), (0, 0)), mode='constant', constant_values=255) return image From 256e069dd73b1ef45cb56e35e7f7764f3ef36aba Mon Sep 17 00:00:00 2001 From: guoh064 <50830808+guoh064@users.noreply.github.com> Date: Mon, 16 Dec 2024 07:42:36 +0800 Subject: [PATCH 19/30] Tmp: resume low hp detection after false BATTLE_STATUS --- module/exercise/combat.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module/exercise/combat.py b/module/exercise/combat.py index e6735a10d..c191552ec 100644 --- a/module/exercise/combat.py +++ b/module/exercise/combat.py @@ -53,6 +53,8 @@ class ExerciseCombat(HpDaemon, OpponentChoose, ExerciseEquipment, Combat): p = self.is_combat_executing() if p: + if end: + end = False if pause is None: pause = p else: From fa78a951c21140e262d977d13280021fa3fa9a8d Mon Sep 17 00:00:00 2001 From: guoh064 <50830808+guoh064@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:02:12 +0800 Subject: [PATCH 20/30] Add: event entrance of event_20241219_cn --- campaign/Readme.md | 3 +- module/config/argument/args.json | 96 +++++++++++++++++++------------- module/config/i18n/en-US.json | 1 + module/config/i18n/ja-JP.json | 1 + module/config/i18n/zh-CN.json | 1 + module/config/i18n/zh-TW.json | 1 + 6 files changed, 62 insertions(+), 41 deletions(-) diff --git a/campaign/Readme.md b/campaign/Readme.md index 8c5b6b371..3a0c0d468 100644 --- a/campaign/Readme.md +++ b/campaign/Readme.md @@ -222,4 +222,5 @@ To add a new event, add a new row in here, and run `python -m module.config.conf | 20241114 | event 20220915 cn | Violet Tempest Blooming Lycoris Rerun | 复刻紫绛槿岚 | Violet Tempest Blooming Lycoris Rerun | 赫の涙月 菫の暁風(復刻) | - | | 20241114 | event 20240229 cn | Snowrealm Peregrination | - | - | - | 雪境迷蹤 | | 20241121 | event 20241121 cn | Dangerous Inventions Incoming | 危险发明迫近中 | Dangerous Inventions Incoming | 危険発明接近中 | - | -| 20241128 | event 20241121 cn | - | - | - | - | 危險發明逼近中 | +| 20241128 | event 20241121 cn | Dangerous Inventions | - | - | - | 危險發明逼近中 | +| 20241219 | event 20241219 cn | Substellar Crepuscule | 星光下的余晖 | Substellar Crepuscule | 星降る夕影の残光 | - | diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 1773dc192..615c6bb24 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -1711,15 +1711,17 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "display": "hide", "option_bold": [ - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", "tw": "event_20241121_cn" }, "Mode": { @@ -2052,14 +2054,16 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", "tw": "event_20241121_cn" }, "Mode": { @@ -2507,14 +2511,16 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", "tw": "event_20241121_cn" }, "Mode": { @@ -3924,14 +3930,16 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", "tw": "event_20241121_cn" }, "Mode": { @@ -4396,14 +4404,16 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", "tw": "event_20241121_cn" }, "Mode": { @@ -4868,14 +4878,16 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", "tw": "event_20241121_cn" }, "Mode": { @@ -5340,14 +5352,16 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", "tw": "event_20241121_cn" }, "Mode": { @@ -5802,14 +5816,16 @@ "event_20240829_cn", "event_20240912_cn", "event_20241024_cn", - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn" + "event_20241121_cn", + "event_20241219_cn" ], - "cn": "event_20241121_cn", - "en": "event_20241121_cn", - "jp": "event_20241121_cn", + "cn": "event_20241219_cn", + "en": "event_20241219_cn", + "jp": "event_20241219_cn", "tw": "event_20241121_cn" }, "Mode": { diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index ac6160ec4..27cd5ef02 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -735,6 +735,7 @@ "event_20240912_cn": "Ode of Everblooming Crimson", "event_20241024_cn": "Tempesta and the Sleeping Sea", "event_20241121_cn": "Dangerous Inventions Incoming", + "event_20241219_cn": "Substellar Crepuscule", "raid_20200624": "Air Raid Drills with Essex Rerun", "raid_20210708": "Cross Wave rerun", "raid_20220127": "Mystery Investigation", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index bf0dfe3a6..118882807 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -735,6 +735,7 @@ "event_20240912_cn": "絳染む丹華の詠歌", "event_20241024_cn": "テンペスタと眠りし海", "event_20241121_cn": "危険発明接近中", + "event_20241219_cn": "星降る夕影の残光", "raid_20200624": "特別演習超空強襲波(復刻)", "raid_20210708": "交錯する新たな波 (復刻)", "raid_20220127": "秘密事件調査", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index eefe4bec8..4b191f233 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -735,6 +735,7 @@ "event_20240912_cn": "唤醒苍红之炎", "event_20241024_cn": "飓风与沉眠之海", "event_20241121_cn": "危险发明迫近中", + "event_20241219_cn": "星光下的余晖", "raid_20200624": "复刻特别演习埃塞克斯级", "raid_20210708": "复刻穿越彼方的水线", "raid_20220127": "演习神秘事件调查", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 87f452bc9..2ddc6ae65 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -735,6 +735,7 @@ "event_20240912_cn": "Ode of Everblooming Crimson", "event_20241024_cn": "Tempesta and the Sleeping Sea", "event_20241121_cn": "危險發明逼近中", + "event_20241219_cn": "Substellar Crepuscule", "raid_20200624": "特別演習埃塞克斯級(復刻)", "raid_20210708": "復刻穿越彼方的水線", "raid_20220127": "演習神秘事件調查", From a1d8e62b47a032df0c3df288e57e479a194ac78c Mon Sep 17 00:00:00 2001 From: guoh064 <50830808+guoh064@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:03:11 +0800 Subject: [PATCH 21/30] Add: event entrance of event_20231221_cn (tw event) --- campaign/Readme.md | 1 + module/config/argument/args.json | 32 ++++++++++++++++---------------- module/config/i18n/zh-TW.json | 2 +- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/campaign/Readme.md b/campaign/Readme.md index 3a0c0d468..8a49eb0b6 100644 --- a/campaign/Readme.md +++ b/campaign/Readme.md @@ -224,3 +224,4 @@ To add a new event, add a new row in here, and run `python -m module.config.conf | 20241121 | event 20241121 cn | Dangerous Inventions Incoming | 危险发明迫近中 | Dangerous Inventions Incoming | 危険発明接近中 | - | | 20241128 | event 20241121 cn | Dangerous Inventions | - | - | - | 危險發明逼近中 | | 20241219 | event 20241219 cn | Substellar Crepuscule | 星光下的余晖 | Substellar Crepuscule | 星降る夕影の残光 | - | +| 20241219 | event 20231221 cn | Light-Chasing Sea of Stars | - | - | - | 星海逐光 | diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 615c6bb24..e62b90f71 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -1716,13 +1716,13 @@ ], "display": "hide", "option_bold": [ - "event_20241121_cn", + "event_20231221_cn", "event_20241219_cn" ], "cn": "event_20241219_cn", "en": "event_20241219_cn", "jp": "event_20241219_cn", - "tw": "event_20241121_cn" + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -2058,13 +2058,13 @@ "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn", + "event_20231221_cn", "event_20241219_cn" ], "cn": "event_20241219_cn", "en": "event_20241219_cn", "jp": "event_20241219_cn", - "tw": "event_20241121_cn" + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -2515,13 +2515,13 @@ "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn", + "event_20231221_cn", "event_20241219_cn" ], "cn": "event_20241219_cn", "en": "event_20241219_cn", "jp": "event_20241219_cn", - "tw": "event_20241121_cn" + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -3934,13 +3934,13 @@ "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn", + "event_20231221_cn", "event_20241219_cn" ], "cn": "event_20241219_cn", "en": "event_20241219_cn", "jp": "event_20241219_cn", - "tw": "event_20241121_cn" + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -4408,13 +4408,13 @@ "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn", + "event_20231221_cn", "event_20241219_cn" ], "cn": "event_20241219_cn", "en": "event_20241219_cn", "jp": "event_20241219_cn", - "tw": "event_20241121_cn" + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -4882,13 +4882,13 @@ "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn", + "event_20231221_cn", "event_20241219_cn" ], "cn": "event_20241219_cn", "en": "event_20241219_cn", "jp": "event_20241219_cn", - "tw": "event_20241121_cn" + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -5356,13 +5356,13 @@ "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn", + "event_20231221_cn", "event_20241219_cn" ], "cn": "event_20241219_cn", "en": "event_20241219_cn", "jp": "event_20241219_cn", - "tw": "event_20241121_cn" + "tw": "event_20231221_cn" }, "Mode": { "type": "select", @@ -5820,13 +5820,13 @@ "event_20241219_cn" ], "option_bold": [ - "event_20241121_cn", + "event_20231221_cn", "event_20241219_cn" ], "cn": "event_20241219_cn", "en": "event_20241219_cn", "jp": "event_20241219_cn", - "tw": "event_20241121_cn" + "tw": "event_20231221_cn" }, "Mode": { "type": "select", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 2ddc6ae65..56035f02b 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -725,7 +725,7 @@ "event_20230914_cn": "須臾望月抄", "event_20231026_cn": "飓風與青春之泉", "event_20231123_cn": "蒼閃忍法帖", - "event_20231221_cn": "Light-Chasing Sea of Stars", + "event_20231221_cn": "星海逐光", "event_20240229_cn": "雪境迷蹤", "event_20240425_cn": "共鳴的PASSION", "event_20240521_cn": "Light of the Martyrium", From bc3c602eccba627c8fa84914a4e541770f9202dc Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:43:46 +0800 Subject: [PATCH 22/30] Fix: Re-order retire popup handles by display hierarchy --- module/retire/retirement.py | 38 +++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/module/retire/retirement.py b/module/retire/retirement.py index f08ca8295..249932bbf 100644 --- a/module/retire/retirement.py +++ b/module/retire/retirement.py @@ -89,6 +89,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler): executed = False for button in [SHIP_CONFIRM, SHIP_CONFIRM_2, EQUIP_CONFIRM, EQUIP_CONFIRM_2, GET_ITEMS_1, SR_SSR_CONFIRM]: self.interval_clear(button) + self.popup_interval_clear() timeout = Timer(10, count=10).start() while 1: if skip_first_screenshot: @@ -110,34 +111,41 @@ class Retirement(Enhancement, QuickRetireSettingHandler): timeout.reset() # Click - if self.match_template_color(SHIP_CONFIRM, offset=(30, 30), interval=2): - self.device.click(SHIP_CONFIRM) - continue - if self.appear(SHIP_CONFIRM_2, offset=(30, 30), interval=2): + # Ship confirm, order by display hierarchy + if self._unable_to_enhance \ + or self.config.OldRetire_SR \ + or self.config.OldRetire_SSR \ + or self.config.Retirement_RetireMode == 'one_click_retire': + if self.handle_popup_confirm(name='RETIRE_SR_SSR', offset=(20, 50)): + self.interval_reset([SHIP_CONFIRM, SHIP_CONFIRM_2]) + continue + if self.config.SERVER in ['cn', 'jp', 'tw'] and \ + self.appear_then_click(SR_SSR_CONFIRM, offset=(20, 50), interval=2): + self.interval_reset([SHIP_CONFIRM, SHIP_CONFIRM_2]) + continue + if self.match_template_color(SHIP_CONFIRM_2, offset=(30, 30), interval=2): if self.retire_keep_common_cv and not self._have_kept_cv: self.keep_one_common_cv() self.device.click(SHIP_CONFIRM_2) + # GET_ITEMS_1 is going to appear, avoid re-entering ship confirm self.interval_clear(GET_ITEMS_1) + self.interval_reset([SHIP_CONFIRM, SHIP_CONFIRM_2]) continue + if self.match_template_color(SHIP_CONFIRM, offset=(30, 30), interval=2): + self.device.click(SHIP_CONFIRM) + continue + # Equip confirm if self.appear_then_click(EQUIP_CONFIRM, offset=(30, 30), interval=2): executed = True continue if self.appear_then_click(EQUIP_CONFIRM_2, offset=(30, 30), interval=2): self.interval_clear(GET_ITEMS_1) continue + # Get items if self.appear(GET_ITEMS_1, offset=(30, 30), interval=2): self.device.click(GET_ITEMS_1_RETIREMENT_SAVE) self.interval_reset(SHIP_CONFIRM) continue - if self._unable_to_enhance \ - or self.config.OldRetire_SR \ - or self.config.OldRetire_SSR \ - or self.config.Retirement_RetireMode == 'one_click_retire': - if self.handle_popup_confirm(name='RETIRE_SR_SSR', offset=(20, 50)): - continue - if self.config.SERVER in ['cn', 'jp', 'tw'] and \ - self.appear_then_click(SR_SSR_CONFIRM, offset=(20, 50), interval=2): - continue def retirement_appear(self): return self.appear(RETIRE_APPEAR_1, offset=30) \ @@ -452,6 +460,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler): count = 0 RETIRE_COIN.load_color(self.device.image) RETIRE_COIN._match_init = True + self.interval_clear(SHIP_CONFIRM_2) while 1: if skip_first_screenshot: @@ -466,7 +475,7 @@ class Retirement(Enhancement, QuickRetireSettingHandler): logger.warning('_retire_select_one failed after 3 trial') return False - if self.appear(SHIP_CONFIRM_2, offset=(30, 30), interval=3): + if self.appear(SHIP_CONFIRM_2, offset=(30, 30), interval=2): self.device.click(button) count += 1 continue @@ -556,3 +565,4 @@ class Retirement(Enhancement, QuickRetireSettingHandler): if button is not None: self._retire_select_one(button) self._have_kept_cv = True + logger.info('Keep one common CV end') From bc3f54927e1d0adeb2d89be64ad1c1e72185a388 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:55:18 +0800 Subject: [PATCH 23/30] i18n: [EN] Rename "SpecialRadar" to "OpSi Data Logger" --- module/config/i18n/en-US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 27cd5ef02..484571696 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -2305,8 +2305,8 @@ "help": "At the beginning of each month OpSi is reset\nThe following must be satisfied:\n- OpSi story and practice battles must be complete\nEach zone will be visited in a clockwise direction every 27 minutes until world has been been completely opened\nNo need to consume 5000 oil for special radar in OpSi voucher shop" }, "SpecialRadar": { - "name": "SpecialRadar Bought", - "help": "Enable if you have purchased the special radar\nAllows Alas to explore OpSi continously without having to wait 27 minutes between each zone clear\nCannot be selected when not purchased and used manually" + "name": "OpSi Data Logger Bought (5k oil item)", + "help": "Purchase Operation Siren Data Logger is not a must, enable if you have purchased.\nAllows Alas to explore OpSi continously without having to wait 27 minutes between each zone clear\nCannot be selected when not purchased and used manually" }, "ForceRun": { "name": "Force Run", From 63c091ba94a3d0ff52afb918131c098ab650763f Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:29:50 +0800 Subject: [PATCH 24/30] Fix: Double clicking EQUIP_OFF after popup confirm on slow devices --- module/equipment/equipment.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/module/equipment/equipment.py b/module/equipment/equipment.py index 0198a4928..3fd4ee9ac 100644 --- a/module/equipment/equipment.py +++ b/module/equipment/equipment.py @@ -154,6 +154,7 @@ class Equipment(StorageHandler): return False def ship_equipment_take_off(self, skip_first_screenshot=True): + logger.info('Equipment take off') bar_timer = Timer(5) off_timer = Timer(5) confirm_timer = Timer(5) @@ -175,11 +176,14 @@ class Equipment(StorageHandler): if confirm_timer.reached() and self.handle_popup_confirm('EQUIPMENT_TAKE_OFF'): confirm_timer.reset() + off_timer.reset() + bar_timer.reset() continue if off_timer.reached(): if not self.info_bar_count() and self.appear_then_click(EQUIP_OFF, offset=(20, 20)): off_timer.reset() + bar_timer.reset() continue if bar_timer.reached(): @@ -188,6 +192,8 @@ class Equipment(StorageHandler): bar_timer.reset() continue + logger.info('Equipment take off ended') + def fleet_equipment_take_off(self, enter, long_click, out): """ Args: @@ -208,6 +214,7 @@ class Equipment(StorageHandler): self.equipment_has_take_on = False def ship_equipment_take_on_preset(self, index, skip_first_screenshot=True): + logger.info('Equipment take on preset') bar_timer = Timer(5) on_timer = Timer(5) @@ -238,8 +245,11 @@ class Equipment(StorageHandler): self.device.click(EQUIP_3) on_timer.reset() + bar_timer.reset() continue + logger.info('Equipment take on ended') + def fleet_equipment_take_on_preset(self, preset_record, enter, long_click, out): """ Args: From a3bb25a95ddfab1290890e23014199a2b161df70 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:57:05 +0800 Subject: [PATCH 25/30] Fix: [ALAS] Fallback to auto if nemu_ipc and ldopengl are selected on non-corresponding emulators --- module/device/device.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/module/device/device.py b/module/device/device.py index 11a10172a..9e9402d58 100644 --- a/module/device/device.py +++ b/module/device/device.py @@ -137,13 +137,22 @@ class Device(Screenshot, Control, AppControl): # self.config.Emulator_ControlMethod = 'minitouch' # Allow Hermit on VMOS only if self.config.Emulator_ControlMethod == 'Hermit' and not self.is_vmos: - logger.warning('ControlMethod is allowed on VMOS only') + logger.warning('ControlMethod Hermit is allowed on VMOS only') self.config.Emulator_ControlMethod = 'MaaTouch' if self.config.Emulator_ScreenshotMethod == 'ldopengl' \ and self.config.Emulator_ControlMethod == 'minitouch': logger.warning('Use MaaTouch on ldplayer') self.config.Emulator_ControlMethod = 'MaaTouch' - pass + + # Fallback to auto if nemu_ipc and ldopengl are selected on non-corresponding emulators + if self.config.Emulator_ScreenshotMethod == 'nemu_ipc': + if not (self.is_emulator and self.is_mumu_family): + logger.warning('ScreenshotMethod nemu_ipc is available on MuMu Player 12 only, fallback to auto') + self.config.Emulator_ScreenshotMethod = 'auto' + if self.config.Emulator_ScreenshotMethod == 'ldopengl': + if not (self.is_emulator and self.is_ldplayer_bluestacks_family): + logger.warning('ScreenshotMethod ldopengl is available on LD Player only, fallback to auto') + self.config.Emulator_ScreenshotMethod = 'auto' def handle_night_commission(self, daily_trigger='21:00', threshold=30): """ From cc2cf0d0463963172d1e9004a554aa3abaaaaea8 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:07:58 +0800 Subject: [PATCH 26/30] Refactor: Abstract chapter switches from 20240725 to 20241219 --- ...0241024_EX.png => CHAPTER_20241219_EX.png} | Bin ...1024_AB.png => CHAPTER_20241219_PART1.png} | Bin ...1024_CD.png => CHAPTER_20241219_PART2.png} | Bin ...0241024_SP.png => CHAPTER_20241219_SP.png} | Bin ..._COMBAT.png => SWITCH_20241219_COMBAT.png} | Bin ...25_STORY.png => SWITCH_20241219_STORY.png} | Bin assets/en/campaign/SWITCH_20240725_COMBAT.png | Bin 7698 -> 0 bytes assets/en/campaign/SWITCH_20240725_STORY.png | Bin 6779 -> 0 bytes assets/jp/campaign/SWITCH_20240725_COMBAT.png | Bin 7698 -> 0 bytes assets/jp/campaign/SWITCH_20240725_STORY.png | Bin 6779 -> 0 bytes assets/tw/campaign/SWITCH_20240725_COMBAT.png | Bin 7698 -> 0 bytes assets/tw/campaign/SWITCH_20240725_STORY.png | Bin 6779 -> 0 bytes campaign/event_20240725_cn/campaign_base.py | 14 +-- campaign/event_20240829_cn/campaign_base.py | 15 +-- campaign/event_20240912_cn/campaign_base.py | 12 ++- campaign/event_20241024_cn/campaign_base.py | 46 ++------ campaign/event_20241024_cn/sp.py | 1 + campaign/event_20241024_cn/t1.py | 1 + campaign/event_20241121_cn/campaign_base.py | 52 +++------ campaign/event_20241121_cn/sp.py | 1 + campaign/event_20241121_cn/t1.py | 1 + campaign/event_20241121_cn/ttl1.py | 1 + campaign/event_20241219_cn/campaign_base.py | 9 ++ module/campaign/assets.py | 12 +-- module/campaign/campaign_ui.py | 101 +++++++++++++++++- module/config/config_manual.py | 2 + module/map/map_operation.py | 2 +- 27 files changed, 151 insertions(+), 119 deletions(-) rename assets/cn/campaign/{CHAPTER_20241024_EX.png => CHAPTER_20241219_EX.png} (100%) rename assets/cn/campaign/{CHAPTER_20241024_AB.png => CHAPTER_20241219_PART1.png} (100%) rename assets/cn/campaign/{CHAPTER_20241024_CD.png => CHAPTER_20241219_PART2.png} (100%) rename assets/cn/campaign/{CHAPTER_20241024_SP.png => CHAPTER_20241219_SP.png} (100%) rename assets/cn/campaign/{SWITCH_20240725_COMBAT.png => SWITCH_20241219_COMBAT.png} (100%) rename assets/cn/campaign/{SWITCH_20240725_STORY.png => SWITCH_20241219_STORY.png} (100%) delete mode 100644 assets/en/campaign/SWITCH_20240725_COMBAT.png delete mode 100644 assets/en/campaign/SWITCH_20240725_STORY.png delete mode 100644 assets/jp/campaign/SWITCH_20240725_COMBAT.png delete mode 100644 assets/jp/campaign/SWITCH_20240725_STORY.png delete mode 100644 assets/tw/campaign/SWITCH_20240725_COMBAT.png delete mode 100644 assets/tw/campaign/SWITCH_20240725_STORY.png create mode 100644 campaign/event_20241219_cn/campaign_base.py diff --git a/assets/cn/campaign/CHAPTER_20241024_EX.png b/assets/cn/campaign/CHAPTER_20241219_EX.png similarity index 100% rename from assets/cn/campaign/CHAPTER_20241024_EX.png rename to assets/cn/campaign/CHAPTER_20241219_EX.png diff --git a/assets/cn/campaign/CHAPTER_20241024_AB.png b/assets/cn/campaign/CHAPTER_20241219_PART1.png similarity index 100% rename from assets/cn/campaign/CHAPTER_20241024_AB.png rename to assets/cn/campaign/CHAPTER_20241219_PART1.png diff --git a/assets/cn/campaign/CHAPTER_20241024_CD.png b/assets/cn/campaign/CHAPTER_20241219_PART2.png similarity index 100% rename from assets/cn/campaign/CHAPTER_20241024_CD.png rename to assets/cn/campaign/CHAPTER_20241219_PART2.png diff --git a/assets/cn/campaign/CHAPTER_20241024_SP.png b/assets/cn/campaign/CHAPTER_20241219_SP.png similarity index 100% rename from assets/cn/campaign/CHAPTER_20241024_SP.png rename to assets/cn/campaign/CHAPTER_20241219_SP.png diff --git a/assets/cn/campaign/SWITCH_20240725_COMBAT.png b/assets/cn/campaign/SWITCH_20241219_COMBAT.png similarity index 100% rename from assets/cn/campaign/SWITCH_20240725_COMBAT.png rename to assets/cn/campaign/SWITCH_20241219_COMBAT.png diff --git a/assets/cn/campaign/SWITCH_20240725_STORY.png b/assets/cn/campaign/SWITCH_20241219_STORY.png similarity index 100% rename from assets/cn/campaign/SWITCH_20240725_STORY.png rename to assets/cn/campaign/SWITCH_20241219_STORY.png diff --git a/assets/en/campaign/SWITCH_20240725_COMBAT.png b/assets/en/campaign/SWITCH_20240725_COMBAT.png deleted file mode 100644 index dea5a57fc79066cdbad0c123cae626b546352784..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7698 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 A2 > A3 > B1 > B2 > B3', + 'C1 > C2 > C3', + 'D1 > D2 > D3', + ] diff --git a/module/campaign/assets.py b/module/campaign/assets.py index 83f4da2fe..97088d89c 100644 --- a/module/campaign/assets.py +++ b/module/campaign/assets.py @@ -4,10 +4,10 @@ from module.base.template import Template # This file was automatically generated by dev_tools/button_extract.py. # Don't modify it manually. -CHAPTER_20241024_AB = Button(area={'cn': (17, 226, 34, 241), 'en': (17, 226, 34, 241), 'jp': (17, 226, 34, 241), 'tw': (17, 226, 34, 241)}, color={'cn': (162, 169, 196), 'en': (162, 169, 196), 'jp': (162, 169, 196), 'tw': (162, 169, 196)}, button={'cn': (17, 226, 34, 241), 'en': (17, 226, 34, 241), 'jp': (17, 226, 34, 241), 'tw': (17, 226, 34, 241)}, file={'cn': './assets/cn/campaign/CHAPTER_20241024_AB.png', 'en': './assets/cn/campaign/CHAPTER_20241024_AB.png', 'jp': './assets/cn/campaign/CHAPTER_20241024_AB.png', 'tw': './assets/cn/campaign/CHAPTER_20241024_AB.png'}) -CHAPTER_20241024_CD = Button(area={'cn': (17, 299, 34, 314), 'en': (17, 299, 34, 314), 'jp': (17, 299, 34, 314), 'tw': (17, 299, 34, 314)}, color={'cn': (168, 176, 204), 'en': (168, 176, 204), 'jp': (168, 176, 204), 'tw': (168, 176, 204)}, button={'cn': (17, 299, 34, 314), 'en': (17, 299, 34, 314), 'jp': (17, 299, 34, 314), 'tw': (17, 299, 34, 314)}, file={'cn': './assets/cn/campaign/CHAPTER_20241024_CD.png', 'en': './assets/cn/campaign/CHAPTER_20241024_CD.png', 'jp': './assets/cn/campaign/CHAPTER_20241024_CD.png', 'tw': './assets/cn/campaign/CHAPTER_20241024_CD.png'}) -CHAPTER_20241024_EX = Button(area={'cn': (17, 446, 34, 461), 'en': (17, 446, 34, 461), 'jp': (17, 446, 34, 461), 'tw': (17, 446, 34, 461)}, color={'cn': (169, 178, 207), 'en': (169, 178, 207), 'jp': (169, 178, 207), 'tw': (169, 178, 207)}, button={'cn': (17, 446, 34, 461), 'en': (17, 446, 34, 461), 'jp': (17, 446, 34, 461), 'tw': (17, 446, 34, 461)}, file={'cn': './assets/cn/campaign/CHAPTER_20241024_EX.png', 'en': './assets/cn/campaign/CHAPTER_20241024_EX.png', 'jp': './assets/cn/campaign/CHAPTER_20241024_EX.png', 'tw': './assets/cn/campaign/CHAPTER_20241024_EX.png'}) -CHAPTER_20241024_SP = Button(area={'cn': (17, 372, 34, 388), 'en': (17, 372, 34, 388), 'jp': (17, 372, 34, 388), 'tw': (17, 372, 34, 388)}, color={'cn': (163, 172, 201), 'en': (163, 172, 201), 'jp': (163, 172, 201), 'tw': (163, 172, 201)}, button={'cn': (17, 372, 34, 388), 'en': (17, 372, 34, 388), 'jp': (17, 372, 34, 388), 'tw': (17, 372, 34, 388)}, file={'cn': './assets/cn/campaign/CHAPTER_20241024_SP.png', 'en': './assets/cn/campaign/CHAPTER_20241024_SP.png', 'jp': './assets/cn/campaign/CHAPTER_20241024_SP.png', 'tw': './assets/cn/campaign/CHAPTER_20241024_SP.png'}) +CHAPTER_20241219_EX = Button(area={'cn': (17, 446, 34, 461), 'en': (17, 446, 34, 461), 'jp': (17, 446, 34, 461), 'tw': (17, 446, 34, 461)}, color={'cn': (169, 178, 207), 'en': (169, 178, 207), 'jp': (169, 178, 207), 'tw': (169, 178, 207)}, button={'cn': (17, 446, 34, 461), 'en': (17, 446, 34, 461), 'jp': (17, 446, 34, 461), 'tw': (17, 446, 34, 461)}, file={'cn': './assets/cn/campaign/CHAPTER_20241219_EX.png', 'en': './assets/cn/campaign/CHAPTER_20241219_EX.png', 'jp': './assets/cn/campaign/CHAPTER_20241219_EX.png', 'tw': './assets/cn/campaign/CHAPTER_20241219_EX.png'}) +CHAPTER_20241219_PART1 = Button(area={'cn': (17, 226, 34, 241), 'en': (17, 226, 34, 241), 'jp': (17, 226, 34, 241), 'tw': (17, 226, 34, 241)}, color={'cn': (162, 169, 196), 'en': (162, 169, 196), 'jp': (162, 169, 196), 'tw': (162, 169, 196)}, button={'cn': (17, 226, 34, 241), 'en': (17, 226, 34, 241), 'jp': (17, 226, 34, 241), 'tw': (17, 226, 34, 241)}, file={'cn': './assets/cn/campaign/CHAPTER_20241219_PART1.png', 'en': './assets/cn/campaign/CHAPTER_20241219_PART1.png', 'jp': './assets/cn/campaign/CHAPTER_20241219_PART1.png', 'tw': './assets/cn/campaign/CHAPTER_20241219_PART1.png'}) +CHAPTER_20241219_PART2 = Button(area={'cn': (17, 299, 34, 314), 'en': (17, 299, 34, 314), 'jp': (17, 299, 34, 314), 'tw': (17, 299, 34, 314)}, color={'cn': (168, 176, 204), 'en': (168, 176, 204), 'jp': (168, 176, 204), 'tw': (168, 176, 204)}, button={'cn': (17, 299, 34, 314), 'en': (17, 299, 34, 314), 'jp': (17, 299, 34, 314), 'tw': (17, 299, 34, 314)}, file={'cn': './assets/cn/campaign/CHAPTER_20241219_PART2.png', 'en': './assets/cn/campaign/CHAPTER_20241219_PART2.png', 'jp': './assets/cn/campaign/CHAPTER_20241219_PART2.png', 'tw': './assets/cn/campaign/CHAPTER_20241219_PART2.png'}) +CHAPTER_20241219_SP = Button(area={'cn': (17, 372, 34, 388), 'en': (17, 372, 34, 388), 'jp': (17, 372, 34, 388), 'tw': (17, 372, 34, 388)}, color={'cn': (163, 172, 201), 'en': (163, 172, 201), 'jp': (163, 172, 201), 'tw': (163, 172, 201)}, button={'cn': (17, 372, 34, 388), 'en': (17, 372, 34, 388), 'jp': (17, 372, 34, 388), 'tw': (17, 372, 34, 388)}, file={'cn': './assets/cn/campaign/CHAPTER_20241219_SP.png', 'en': './assets/cn/campaign/CHAPTER_20241219_SP.png', 'jp': './assets/cn/campaign/CHAPTER_20241219_SP.png', 'tw': './assets/cn/campaign/CHAPTER_20241219_SP.png'}) CHAPTER_NEXT = Button(area={'cn': (1216, 362, 1232, 388), 'en': (1216, 362, 1232, 388), 'jp': (1216, 362, 1232, 388), 'tw': (1216, 362, 1232, 388)}, color={'cn': (121, 150, 198), 'en': (121, 150, 198), 'jp': (121, 150, 198), 'tw': (121, 150, 198)}, button={'cn': (1216, 362, 1232, 388), 'en': (1216, 362, 1232, 388), 'jp': (1216, 362, 1232, 388), 'tw': (1216, 362, 1232, 388)}, file={'cn': './assets/cn/campaign/CHAPTER_NEXT.png', 'en': './assets/en/campaign/CHAPTER_NEXT.png', 'jp': './assets/jp/campaign/CHAPTER_NEXT.png', 'tw': './assets/tw/campaign/CHAPTER_NEXT.png'}) CHAPTER_PREV = Button(area={'cn': (42, 360, 58, 387), 'en': (42, 360, 58, 387), 'jp': (42, 360, 58, 387), 'tw': (42, 360, 58, 387)}, color={'cn': (105, 133, 169), 'en': (105, 133, 169), 'jp': (105, 133, 169), 'tw': (105, 133, 169)}, button={'cn': (42, 360, 58, 387), 'en': (42, 360, 58, 387), 'jp': (42, 360, 58, 387), 'tw': (42, 360, 58, 387)}, file={'cn': './assets/cn/campaign/CHAPTER_PREV.png', 'en': './assets/en/campaign/CHAPTER_PREV.png', 'jp': './assets/jp/campaign/CHAPTER_PREV.png', 'tw': './assets/tw/campaign/CHAPTER_PREV.png'}) COMMISSION_NOTICE_AT_CAMPAIGN = Button(area={'cn': (1077, 637, 1083, 643), 'en': (1077, 637, 1083, 643), 'jp': (1077, 637, 1083, 643), 'tw': (1077, 637, 1083, 643)}, color={'cn': (172, 72, 49), 'en': (172, 72, 49), 'jp': (172, 72, 49), 'tw': (172, 72, 49)}, button={'cn': (1077, 637, 1083, 643), 'en': (1077, 637, 1083, 643), 'jp': (1077, 637, 1083, 643), 'tw': (1077, 637, 1083, 643)}, file={'cn': './assets/cn/campaign/COMMISSION_NOTICE_AT_CAMPAIGN.png', 'en': './assets/en/campaign/COMMISSION_NOTICE_AT_CAMPAIGN.png', 'jp': './assets/jp/campaign/COMMISSION_NOTICE_AT_CAMPAIGN.png', 'tw': './assets/tw/campaign/COMMISSION_NOTICE_AT_CAMPAIGN.png'}) @@ -18,8 +18,8 @@ OCR_OIL = Button(area={'cn': (614, 23, 714, 51), 'en': (614, 23, 714, 51), 'jp': OCR_OIL_CHECK = Button(area={'cn': (573, 30, 592, 49), 'en': (573, 30, 592, 49), 'jp': (573, 30, 592, 49), 'tw': (573, 30, 592, 49)}, color={'cn': (82, 82, 82), 'en': (82, 82, 82), 'jp': (82, 82, 82), 'tw': (82, 82, 82)}, button={'cn': (573, 30, 592, 49), 'en': (573, 30, 592, 49), 'jp': (573, 30, 592, 49), 'tw': (573, 30, 592, 49)}, file={'cn': './assets/cn/campaign/OCR_OIL_CHECK.png', 'en': './assets/en/campaign/OCR_OIL_CHECK.png', 'jp': './assets/jp/campaign/OCR_OIL_CHECK.png', 'tw': './assets/tw/campaign/OCR_OIL_CHECK.png'}) SWITCH_1_HARD = Button(area={'cn': (82, 641, 148, 675), 'en': (87, 642, 148, 676), 'jp': (24, 645, 150, 697), 'tw': (82, 641, 148, 675)}, color={'cn': (233, 141, 128), 'en': (234, 139, 124), 'jp': (219, 116, 106), 'tw': (236, 159, 148)}, button={'cn': (82, 641, 148, 675), 'en': (87, 642, 148, 676), 'jp': (24, 645, 150, 697), 'tw': (82, 641, 148, 675)}, file={'cn': './assets/cn/campaign/SWITCH_1_HARD.png', 'en': './assets/en/campaign/SWITCH_1_HARD.png', 'jp': './assets/jp/campaign/SWITCH_1_HARD.png', 'tw': './assets/tw/campaign/SWITCH_1_HARD.png'}) SWITCH_1_NORMAL = Button(area={'cn': (80, 641, 148, 675), 'en': (79, 638, 147, 675), 'jp': (24, 644, 150, 697), 'tw': (79, 641, 148, 675)}, color={'cn': (157, 180, 227), 'en': (157, 180, 227), 'jp': (143, 169, 222), 'tw': (156, 179, 227)}, button={'cn': (80, 641, 148, 675), 'en': (79, 638, 147, 675), 'jp': (24, 644, 150, 697), 'tw': (79, 641, 148, 675)}, file={'cn': './assets/cn/campaign/SWITCH_1_NORMAL.png', 'en': './assets/en/campaign/SWITCH_1_NORMAL.png', 'jp': './assets/jp/campaign/SWITCH_1_NORMAL.png', 'tw': './assets/tw/campaign/SWITCH_1_NORMAL.png'}) -SWITCH_20240725_COMBAT = Button(area={'cn': (39, 659, 71, 691), 'en': (39, 659, 71, 691), 'jp': (39, 659, 71, 691), 'tw': (39, 659, 71, 691)}, color={'cn': (133, 96, 49), 'en': (133, 96, 49), 'jp': (133, 96, 49), 'tw': (133, 96, 49)}, button={'cn': (39, 659, 71, 691), 'en': (39, 659, 71, 691), 'jp': (39, 659, 71, 691), 'tw': (39, 659, 71, 691)}, file={'cn': './assets/cn/campaign/SWITCH_20240725_COMBAT.png', 'en': './assets/en/campaign/SWITCH_20240725_COMBAT.png', 'jp': './assets/jp/campaign/SWITCH_20240725_COMBAT.png', 'tw': './assets/tw/campaign/SWITCH_20240725_COMBAT.png'}) -SWITCH_20240725_STORY = Button(area={'cn': (327, 657, 352, 688), 'en': (327, 657, 352, 688), 'jp': (327, 657, 352, 688), 'tw': (327, 657, 352, 688)}, color={'cn': (105, 77, 31), 'en': (105, 77, 31), 'jp': (105, 77, 31), 'tw': (105, 77, 31)}, button={'cn': (327, 657, 352, 688), 'en': (327, 657, 352, 688), 'jp': (327, 657, 352, 688), 'tw': (327, 657, 352, 688)}, file={'cn': './assets/cn/campaign/SWITCH_20240725_STORY.png', 'en': './assets/en/campaign/SWITCH_20240725_STORY.png', 'jp': './assets/jp/campaign/SWITCH_20240725_STORY.png', 'tw': './assets/tw/campaign/SWITCH_20240725_STORY.png'}) +SWITCH_20241219_COMBAT = Button(area={'cn': (39, 659, 71, 691), 'en': (39, 659, 71, 691), 'jp': (39, 659, 71, 691), 'tw': (39, 659, 71, 691)}, color={'cn': (133, 96, 49), 'en': (133, 96, 49), 'jp': (133, 96, 49), 'tw': (133, 96, 49)}, button={'cn': (39, 659, 71, 691), 'en': (39, 659, 71, 691), 'jp': (39, 659, 71, 691), 'tw': (39, 659, 71, 691)}, file={'cn': './assets/cn/campaign/SWITCH_20241219_COMBAT.png', 'en': './assets/cn/campaign/SWITCH_20241219_COMBAT.png', 'jp': './assets/cn/campaign/SWITCH_20241219_COMBAT.png', 'tw': './assets/cn/campaign/SWITCH_20241219_COMBAT.png'}) +SWITCH_20241219_STORY = Button(area={'cn': (327, 657, 352, 688), 'en': (327, 657, 352, 688), 'jp': (327, 657, 352, 688), 'tw': (327, 657, 352, 688)}, color={'cn': (105, 77, 31), 'en': (105, 77, 31), 'jp': (105, 77, 31), 'tw': (105, 77, 31)}, button={'cn': (327, 657, 352, 688), 'en': (327, 657, 352, 688), 'jp': (327, 657, 352, 688), 'tw': (327, 657, 352, 688)}, file={'cn': './assets/cn/campaign/SWITCH_20241219_STORY.png', 'en': './assets/cn/campaign/SWITCH_20241219_STORY.png', 'jp': './assets/cn/campaign/SWITCH_20241219_STORY.png', 'tw': './assets/cn/campaign/SWITCH_20241219_STORY.png'}) SWITCH_2_EX = Button(area={'cn': (272, 658, 310, 676), 'en': (251, 644, 313, 697), 'jp': (186, 638, 314, 692), 'tw': (241, 640, 312, 692)}, color={'cn': (253, 168, 98), 'en': (254, 163, 80), 'jp': (205, 136, 64), 'tw': (254, 161, 72)}, button={'cn': (272, 658, 310, 676), 'en': (251, 644, 313, 697), 'jp': (186, 638, 314, 692), 'tw': (241, 640, 312, 692)}, file={'cn': './assets/cn/campaign/SWITCH_2_EX.png', 'en': './assets/en/campaign/SWITCH_2_EX.png', 'jp': './assets/jp/campaign/SWITCH_2_EX.png', 'tw': './assets/tw/campaign/SWITCH_2_EX.png'}) SWITCH_2_HARD = Button(area={'cn': (246, 641, 311, 675), 'en': (244, 640, 312, 684), 'jp': (233, 655, 310, 681), 'tw': (245, 641, 311, 674)}, color={'cn': (233, 140, 127), 'en': (228, 121, 106), 'jp': (223, 110, 96), 'tw': (237, 161, 150)}, button={'cn': (246, 641, 311, 675), 'en': (244, 640, 312, 684), 'jp': (233, 655, 310, 681), 'tw': (245, 641, 311, 674)}, file={'cn': './assets/cn/campaign/SWITCH_2_HARD.png', 'en': './assets/en/campaign/SWITCH_2_HARD.png', 'jp': './assets/jp/campaign/SWITCH_2_HARD.png', 'tw': './assets/tw/campaign/SWITCH_2_HARD.png'}) TEMPLATE_EVENT_20230817_STORY_E1 = Template(file={'cn': './assets/cn/campaign/TEMPLATE_EVENT_20230817_STORY_E1.png', 'en': './assets/en/campaign/TEMPLATE_EVENT_20230817_STORY_E1.png', 'jp': './assets/jp/campaign/TEMPLATE_EVENT_20230817_STORY_E1.png', 'tw': './assets/tw/campaign/TEMPLATE_EVENT_20230817_STORY_E1.png'}) diff --git a/module/campaign/campaign_ui.py b/module/campaign/campaign_ui.py index e8cd110ad..38582d37d 100644 --- a/module/campaign/campaign_ui.py +++ b/module/campaign/campaign_ui.py @@ -24,6 +24,17 @@ MODE_SWITCH_2 = ModeSwitch('Mode_switch_2', offset=(30, 10)) MODE_SWITCH_2.add_state('hard', SWITCH_2_HARD) MODE_SWITCH_2.add_state('ex', SWITCH_2_EX) +# Event mode switches changing from 20240725 to 20241219 +# I think it stable at 20241219, so give them names with date 20241219 +MODE_SWITCH_20241219 = ModeSwitch('Mode_switch_20241219', is_selector=True, offset=(30, 30)) +MODE_SWITCH_20241219.add_state('combat', SWITCH_20241219_COMBAT) +MODE_SWITCH_20241219.add_state('story', SWITCH_20241219_STORY) +ASIDE_SWITCH_20241219 = ModeSwitch('Aside_switch_20241219', is_selector=True, offset=(30, 30)) +ASIDE_SWITCH_20241219.add_state('part1', CHAPTER_20241219_PART1) +ASIDE_SWITCH_20241219.add_state('part2', CHAPTER_20241219_PART2) +ASIDE_SWITCH_20241219.add_state('sp', CHAPTER_20241219_SP) +ASIDE_SWITCH_20241219.add_state('ex', CHAPTER_20241219_EX) + class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): ENTRANCE = Button(area=(), color=(), button=(), name='default_button') @@ -83,9 +94,6 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): """ Args: mode (str): 'normal', 'hard', 'ex' - - Returns: - bool: If mode changed. """ if mode == 'hard': self.config.override(Campaign_Mode='hard') @@ -113,6 +121,34 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): else: logger.warning(f'Unknown campaign mode: {mode}') + def campaign_ensure_mode_20241219(self, mode='combat'): + """ + Args: + mode (str): 'combat' or 'story' + """ + if mode in ['normal', 'hard', 'ex', 'combat']: + MODE_SWITCH_20241219.set('combat', main=self) + elif mode in ['story']: + MODE_SWITCH_20241219.set('story', main=self) + else: + logger.warning(f'Unknown campaign mode: {mode}') + + def campaign_ensure_aside_20241219(self, chapter): + """ + Args: + chapter: 'part1', 'part2', 'sp', 'ex' + """ + if chapter in ['part1', 'a', 'c', 't']: + MODE_SWITCH_20241219.set('part1', main=self) + elif chapter in ['part2', 'b', 'd']: + MODE_SWITCH_20241219.set('part2', main=self) + elif chapter in ['sp', 'ex_sp']: + MODE_SWITCH_20241219.set('sp', main=self) + elif chapter in ['ex', 'ex_ex']: + MODE_SWITCH_20241219.set('sp', main=self) + else: + logger.warning(f'Unknown campaign aside: {chapter}') + def campaign_get_mode_names(self, name): """ Get stage names in both 'normal' and 'hard' @@ -136,6 +172,22 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): return [f'b{name[1:]}', f'd{name[1:]}'] return [name] + def _campaign_name_is_hard(self, name): + """ + Reuse manual defination in campaign_get_mode_names() + + Args: + name: 'a1', 'ht1', 'sp1' + + Returns: + bool: If stage is hard mode + """ + mode_names = self.campaign_get_mode_names(name) + if len(mode_names) == 2 and mode_names[1] == name: + return True + else: + return False + def campaign_get_entrance(self, name): """ Args: @@ -145,7 +197,7 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): Button: """ entrance_name = name - if self.config.MAP_HAS_MODE_SWITCH: + if self.config.MAP_HAS_MODE_SWITCH or self.config.MAP_CHAPTER_SWITCH_20241219: for mode_name in self.campaign_get_mode_names(name): if mode_name in self.stage_entrance: name = mode_name @@ -195,16 +247,55 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): else: return False + def campaign_set_chapter_20241219(self, chapter, stage, mode='combat'): + if not self.config.MAP_CHAPTER_SWITCH_20241219: + return False + + if self._campaign_name_is_hard(f'{chapter}{stage}'): + self.config.override(Campaign_Mode='hard') + + if mode == 'story': + MODE_SWITCH_20241219.set('story', main=self) + return True + if chapter in ['a', 'c', 't']: + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('part1', main=self) + self.campaign_ensure_chapter(index=chapter) + return True + if chapter in ['b', 'd', 'ttl']: + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('part2', main=self) + self.campaign_ensure_chapter(index=chapter) + return True + if chapter in ['ex_sp']: + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('sp', main=self) + self.campaign_ensure_chapter(index=chapter) + return True + if chapter in ['ex_ex']: + self.ui_goto_event() + MODE_SWITCH_20241219.set('combat', main=self) + ASIDE_SWITCH_20241219.set('ex', main=self) + self.campaign_ensure_chapter(index=chapter) + return True + else: + return False + def campaign_set_chapter(self, name, mode='normal'): """ Args: name (str): Campaign name, such as '7-2', 'd3', 'sp3'. mode (str): 'normal' or 'hard'. """ - chapter, _ = self._campaign_separate_name(name) + chapter, stage = self._campaign_separate_name(name) if self.campaign_set_chapter_main(chapter, mode): pass + elif self.campaign_set_chapter_20241219(chapter, stage, mode): + pass elif self.campaign_set_chapter_event(chapter, mode): pass elif self.campaign_set_chapter_sp(chapter, mode): diff --git a/module/config/config_manual.py b/module/config/config_manual.py index 140f53a57..b1b0ab466 100644 --- a/module/config/config_manual.py +++ b/module/config/config_manual.py @@ -112,6 +112,8 @@ class ManualConfig: module.map.fleet """ MAP_HAS_MODE_SWITCH = False # event_20240725_cn has mode switch in map preparation + # Events from 20240725 to 20241219 introduced new chapter switches + MAP_CHAPTER_SWITCH_20241219 = False MAP_HAS_CLEAR_PERCENTAGE = True MAP_HAS_WALK_SPEEDUP = False MAP_HAS_AMBUSH = True diff --git a/module/map/map_operation.py b/module/map/map_operation.py index 4a9de599a..2fae8cd76 100644 --- a/module/map/map_operation.py +++ b/module/map/map_operation.py @@ -265,7 +265,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand bool: If map mode satisfied Always True if map doesn't have mode switch in map preparation """ - if not self.config.MAP_HAS_MODE_SWITCH: + if not self.config.MAP_HAS_MODE_SWITCH and not self.config.MAP_CHAPTER_SWITCH_20241219: return True if mode == 'normal': From b77f486d8a93b435c5955cc3ba8ac76ae2225600 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:29:42 +0800 Subject: [PATCH 27/30] Add: Chapter ABCD --- campaign/event_20241219_cn/a1.py | 98 +++++++++++++++++++++++++++++++ campaign/event_20241219_cn/a2.py | 79 +++++++++++++++++++++++++ campaign/event_20241219_cn/a3.py | 82 ++++++++++++++++++++++++++ campaign/event_20241219_cn/b1.py | 99 ++++++++++++++++++++++++++++++++ campaign/event_20241219_cn/b2.py | 80 ++++++++++++++++++++++++++ campaign/event_20241219_cn/b3.py | 84 +++++++++++++++++++++++++++ campaign/event_20241219_cn/c1.py | 98 +++++++++++++++++++++++++++++++ campaign/event_20241219_cn/c2.py | 79 +++++++++++++++++++++++++ campaign/event_20241219_cn/c3.py | 83 ++++++++++++++++++++++++++ campaign/event_20241219_cn/d1.py | 99 ++++++++++++++++++++++++++++++++ campaign/event_20241219_cn/d2.py | 89 ++++++++++++++++++++++++++++ campaign/event_20241219_cn/d3.py | 93 ++++++++++++++++++++++++++++++ 12 files changed, 1063 insertions(+) create mode 100644 campaign/event_20241219_cn/a1.py create mode 100644 campaign/event_20241219_cn/a2.py create mode 100644 campaign/event_20241219_cn/a3.py create mode 100644 campaign/event_20241219_cn/b1.py create mode 100644 campaign/event_20241219_cn/b2.py create mode 100644 campaign/event_20241219_cn/b3.py create mode 100644 campaign/event_20241219_cn/c1.py create mode 100644 campaign/event_20241219_cn/c2.py create mode 100644 campaign/event_20241219_cn/c3.py create mode 100644 campaign/event_20241219_cn/d1.py create mode 100644 campaign/event_20241219_cn/d2.py create mode 100644 campaign/event_20241219_cn/d3.py diff --git a/campaign/event_20241219_cn/a1.py b/campaign/event_20241219_cn/a1.py new file mode 100644 index 000000000..78a1b1b22 --- /dev/null +++ b/campaign/event_20241219_cn/a1.py @@ -0,0 +1,98 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + +MAP = CampaignMap('A1') +MAP.shape = 'I8' +MAP.camera_data = ['D2', 'D6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + -- -- ME ++ ++ ++ -- ++ ++ + ++ ME -- MS -- MS -- ++ ++ + -- -- -- -- MB -- -- Me ++ + -- ME -- -- __ -- -- Me -- + ME -- ++ SP -- SP ++ ++ ++ + -- ME ++ -- -- -- -- ME -- + -- -- Me -- Me ++ -- -- ME + ++ -- -- -- -- ++ -- ME -- +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1, 'boss': 1}, + {'battle': 4, 'enemy': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, \ + = MAP.flatten() + + +class Config: + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MOVABLE_NORMAL_ENEMY = True + MAP_SIREN_HAS_BOSS_ICON_SMALL = True + + MOVABLE_NORMAL_ENEMY_TURN = (2,) + MAP_SIREN_MOVE_WAIT = 0.7 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (80, 255 - 33), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 33, 255), + 'prominence': 10, + 'distance': 50, + # 'width': (0, 7), + 'wlen': 1000 + } + MAP_SWIPE_MULTIPLY = (1.127, 1.148) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.090, 1.110) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.058, 1.077) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_3(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/a2.py b/campaign/event_20241219_cn/a2.py new file mode 100644 index 000000000..e410acf79 --- /dev/null +++ b/campaign/event_20241219_cn/a2.py @@ -0,0 +1,79 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .a1 import Config as ConfigBase + +MAP = CampaignMap('A2') +MAP.shape = 'I8' +MAP.camera_data = ['D2', 'D6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + ME -- -- ME -- -- ME -- -- + -- -- ME -- ++ ME -- ME -- + ME -- -- ME -- -- -- ++ ++ + ++ -- Me ++ ++ ++ -- Me ++ + ++ -- -- MS MB MS -- -- -- + -- -- -- -- MS -- -- -- ME + -- ++ Me -- __ -- Me ++ -- + ++ ME -- SP -- SP -- ME ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.108, 1.129) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.072, 1.092) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.041, 1.059) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/a3.py b/campaign/event_20241219_cn/a3.py new file mode 100644 index 000000000..d53d3e43b --- /dev/null +++ b/campaign/event_20241219_cn/a3.py @@ -0,0 +1,82 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .a1 import Config as ConfigBase + +MAP = CampaignMap('A3') +MAP.shape = 'K9' +MAP.camera_data = ['D2', 'D5', 'D7', 'H2', 'H5', 'H7'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + ME -- -- -- ME -- -- -- ME ++ ++ + -- -- ++ -- -- -- ME -- -- ++ -- + ME -- ME ++ ++ ++ ++ ++ -- ME -- + ++ -- -- ++ ++ ++ MB ++ -- -- ME + ++ -- Me ++ ++ ++ -- Me -- -- -- + Me -- -- SP -- SP -- -- -- ME ME + -- -- -- -- __ -- -- Me ++ -- -- + ++ ++ MS -- MS -- MS -- ME ++ ++ + -- ++ -- ME -- ME -- ++ -- ++ ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, K7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, K8, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, J9, K9, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.127, 1.148) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.090, 1.110) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.058, 1.077) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/b1.py b/campaign/event_20241219_cn/b1.py new file mode 100644 index 000000000..1bb2f5647 --- /dev/null +++ b/campaign/event_20241219_cn/b1.py @@ -0,0 +1,99 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + +MAP = CampaignMap('B1') +MAP.shape = 'K8' +MAP.camera_data = ['D2', 'D6', 'H2', 'H6'] +MAP.camera_data_spawn_point = ['D2'] +MAP.map_data = """ + -- -- -- ++ SP -- SP ++ ++ ++ MB + -- ME ++ ++ -- __ -- -- Me -- -- + ME -- Me -- -- MS -- -- -- -- ME + -- -- -- -- MS -- MS Me -- ++ -- + -- ME ++ Me -- -- ++ ++ -- ME -- + -- -- ++ -- Me ++ ++ ++ -- -- -- + ++ -- ME -- -- ME -- ME -- -- ME + -- -- -- -- ++ -- ME -- -- ME ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2, 'boss': 1}, + {'battle': 5, 'enemy': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, K7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, K8, \ + = MAP.flatten() + + +class Config: + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MOVABLE_NORMAL_ENEMY = True + MAP_SIREN_HAS_BOSS_ICON_SMALL = True + + MOVABLE_NORMAL_ENEMY_TURN = (2,) + MAP_SIREN_MOVE_WAIT = 0.7 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (80, 255 - 33), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 33, 255), + 'prominence': 10, + 'distance': 50, + # 'width': (0, 7), + 'wlen': 1000 + } + MAP_SWIPE_MULTIPLY = (1.254, 1.278) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.213, 1.235) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.178, 1.199) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/b2.py b/campaign/event_20241219_cn/b2.py new file mode 100644 index 000000000..fbf267678 --- /dev/null +++ b/campaign/event_20241219_cn/b2.py @@ -0,0 +1,80 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .b1 import Config as ConfigBase + +MAP = CampaignMap('B2') +MAP.shape = 'K8' +MAP.camera_data = ['D2', 'D5', 'H2', 'H5'] +MAP.camera_data_spawn_point = ['D5'] +MAP.map_data = """ + -- -- ME ++ ++ MB ++ ++ -- -- -- + -- ME -- ++ MS -- MS ++ Me ++ ++ + ME -- -- Me -- MS -- Me -- ++ ++ + ++ -- Me -- -- __ -- -- -- ME ME + ++ -- -- -- SP -- SP -- -- -- -- + ME -- ME -- ++ ++ ++ Me -- ++ -- + -- ++ ++ ME ++ -- ++ -- ME -- ME + -- -- ++ -- -- ++ -- -- ++ ++ ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 1}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, K7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, K8, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.168, 1.190) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.130, 1.151) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.097, 1.117) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/event_20241219_cn/b3.py b/campaign/event_20241219_cn/b3.py new file mode 100644 index 000000000..57757b55a --- /dev/null +++ b/campaign/event_20241219_cn/b3.py @@ -0,0 +1,84 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .b1 import Config as ConfigBase + +MAP = CampaignMap('B3') +MAP.shape = 'K9' +MAP.camera_data = ['D2', 'D5', 'D7', 'H2', 'H5', 'H7'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_covered = ['I3', 'I4'] +MAP.map_data = """ + ME -- -- -- ME -- -- -- ME ++ ++ + -- -- ++ -- -- -- ME -- -- ++ -- + ME -- ME ++ ++ ++ ++ ++ -- ME -- + ++ -- -- ++ ++ ++ MB ++ -- -- ME + ++ -- Me ++ ++ ++ -- Me -- -- -- + Me -- -- SP -- SP -- -- -- ME ME + -- -- -- -- __ -- -- Me ++ -- -- + ++ ++ MS -- MS -- MS -- ME ++ ++ + -- ++ -- ME -- ME -- ++ -- ++ ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, K7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, K8, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, J9, K9, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.135, 1.157) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.098, 1.118) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.066, 1.085) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/event_20241219_cn/c1.py b/campaign/event_20241219_cn/c1.py new file mode 100644 index 000000000..c1c9ec977 --- /dev/null +++ b/campaign/event_20241219_cn/c1.py @@ -0,0 +1,98 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + +MAP = CampaignMap('C1') +MAP.shape = 'I8' +MAP.camera_data = ['D2', 'D6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + -- -- ME ++ ++ ++ -- ++ ++ + ++ ME -- MS -- MS -- ++ ++ + -- -- -- -- MB -- -- Me ++ + -- ME -- -- __ -- -- Me -- + ME -- ++ SP -- SP ++ ++ ++ + -- ME ++ -- -- -- -- ME -- + -- -- Me -- Me ++ -- -- ME + ++ -- -- -- -- ++ -- ME -- +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, \ + = MAP.flatten() + + +class Config: + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MOVABLE_NORMAL_ENEMY = True + MAP_SIREN_HAS_BOSS_ICON_SMALL = True + + MOVABLE_NORMAL_ENEMY_TURN = (2,) + MAP_SIREN_MOVE_WAIT = 0.7 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (80, 255 - 33), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 33, 255), + 'prominence': 10, + 'distance': 50, + # 'width': (0, 7), + 'wlen': 1000 + } + MAP_SWIPE_MULTIPLY = (1.127, 1.148) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.090, 1.110) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.058, 1.077) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/c2.py b/campaign/event_20241219_cn/c2.py new file mode 100644 index 000000000..ce20b276d --- /dev/null +++ b/campaign/event_20241219_cn/c2.py @@ -0,0 +1,79 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .c1 import Config as ConfigBase + +MAP = CampaignMap('C2') +MAP.shape = 'I8' +MAP.camera_data = ['D2', 'D6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + ME -- -- ME -- -- ME -- -- + -- -- ME -- ++ ME -- ME -- + ME -- -- ME -- -- -- ++ ++ + ++ -- Me ++ ++ ++ -- Me ++ + ++ -- -- MS MB MS -- -- -- + -- -- -- -- MS -- -- -- ME + -- ++ Me -- __ -- Me ++ -- + ++ ME -- SP -- SP -- ME ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.108, 1.129) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.072, 1.092) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.041, 1.059) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20241219_cn/c3.py b/campaign/event_20241219_cn/c3.py new file mode 100644 index 000000000..25c1cab33 --- /dev/null +++ b/campaign/event_20241219_cn/c3.py @@ -0,0 +1,83 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .c1 import Config as ConfigBase + +MAP = CampaignMap('C3') +MAP.shape = 'K9' +MAP.camera_data = ['D2', 'D5', 'D7', 'H2', 'H5', 'H7'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + ME -- -- -- ME -- -- -- ME ++ ++ + -- -- ++ -- -- -- ME -- -- ++ -- + ME -- ME ++ ++ ++ ++ ++ -- ME -- + ++ -- -- ++ ++ ++ MB ++ -- -- ME + ++ -- Me ++ ++ ++ -- Me -- -- -- + Me -- -- SP -- SP -- -- -- ME ME + -- -- -- -- __ -- -- Me ++ -- -- + ++ ++ MS -- MS -- MS -- ME ++ ++ + -- ++ -- ME -- ME -- ++ -- ++ ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1}, + {'battle': 5, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, K7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, K8, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, J9, K9, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.127, 1.148) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.090, 1.110) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.058, 1.077) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/event_20241219_cn/d1.py b/campaign/event_20241219_cn/d1.py new file mode 100644 index 000000000..6f4e356d7 --- /dev/null +++ b/campaign/event_20241219_cn/d1.py @@ -0,0 +1,99 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + +MAP = CampaignMap('D1') +MAP.shape = 'K8' +MAP.camera_data = ['D2', 'D6', 'H2', 'H6'] +MAP.camera_data_spawn_point = ['D2'] +MAP.map_data = """ + -- -- -- ++ SP -- SP ++ ++ ++ MB + -- ME ++ ++ -- __ -- -- Me -- -- + ME -- Me -- -- MS -- -- -- -- ME + -- -- -- -- MS -- MS Me -- ++ -- + -- ME ++ Me -- -- ++ ++ -- ME -- + -- -- ++ -- Me ++ ++ ++ -- -- -- + ++ -- ME -- -- ME -- ME -- -- ME + -- -- -- -- ++ -- ME -- -- ME ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, K7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, K8, \ + = MAP.flatten() + + +class Config: + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + STAGE_ENTRANCE = ['half', '20240725'] + MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MOVABLE_NORMAL_ENEMY = True + MAP_SIREN_HAS_BOSS_ICON_SMALL = True + + MOVABLE_NORMAL_ENEMY_TURN = (2,) + MAP_SIREN_MOVE_WAIT = 0.7 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (80, 255 - 33), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 33, 255), + 'prominence': 10, + 'distance': 50, + # 'width': (0, 7), + 'wlen': 1000 + } + MAP_SWIPE_MULTIPLY = (1.254, 1.278) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.213, 1.235) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.178, 1.199) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/event_20241219_cn/d2.py b/campaign/event_20241219_cn/d2.py new file mode 100644 index 000000000..88c97a265 --- /dev/null +++ b/campaign/event_20241219_cn/d2.py @@ -0,0 +1,89 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .d1 import Config as ConfigBase + +MAP = CampaignMap('D2') +MAP.shape = 'K8' +MAP.camera_data = ['D2', 'D5', 'H2', 'H5'] +MAP.camera_data_spawn_point = ['D5'] +MAP.map_data = """ + -- -- ME ++ ++ MB ++ ++ -- -- -- + -- ME -- ++ MS -- MS ++ Me ++ ++ + ME -- -- Me -- MS -- Me -- ++ ++ + ++ -- Me -- -- __ -- -- -- ME ME + ++ -- -- -- SP -- SP -- -- -- -- + ME -- ME -- ++ ++ ++ Me -- ++ -- + -- ++ ++ ME ++ -- ++ -- ME -- ME + -- -- ++ -- -- ++ -- -- ++ ++ ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2, 'siren': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1}, + {'battle': 6, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, K7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, K8, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.168, 1.190) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.130, 1.151) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.097, 1.117) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_5(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_6(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/event_20241219_cn/d3.py b/campaign/event_20241219_cn/d3.py new file mode 100644 index 000000000..177658e72 --- /dev/null +++ b/campaign/event_20241219_cn/d3.py @@ -0,0 +1,93 @@ +from .campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger +from .d1 import Config as ConfigBase + +MAP = CampaignMap('D3') +MAP.shape = 'K9' +MAP.camera_data = ['D2', 'D5', 'D7', 'H2', 'H5', 'H7'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_covered = ['I3', 'I4'] +MAP.map_data = """ + ME -- -- -- ME -- -- -- ME ++ ++ + -- -- ++ -- -- -- ME -- -- ++ -- + ME -- ME ++ ++ ++ ++ ++ -- ME -- + ++ -- -- ++ ++ ++ MB ++ -- -- ME + ++ -- Me ++ ++ ++ -- Me -- -- -- + Me -- -- SP -- SP -- -- -- ME ME + -- -- -- -- __ -- -- Me ++ -- -- + ++ ++ MS -- MS -- MS -- ME ++ ++ + -- ++ -- ME -- ME -- ++ -- ++ ++ +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 2}, + {'battle': 1, 'enemy': 1}, + {'battle': 2, 'enemy': 2, 'siren': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'enemy': 1}, + {'battle': 6, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, K6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, K7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, K8, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, J9, K9, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = [] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.135, 1.157) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.098, 1.118) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.066, 1.085) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_5(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_6(self): + return self.fleet_boss.clear_boss() From be48eca020a5c5a83cd4bd069860d25d447b8554 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:47:41 +0800 Subject: [PATCH 28/30] Fix: Avoid clicking EXERCISE_PREPARATION after DORM_FURNITURE_SHOP_FIRST (#4422) --- module/dorm/buy_furniture.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/module/dorm/buy_furniture.py b/module/dorm/buy_furniture.py index 2f9e62579..8bc58a1a2 100644 --- a/module/dorm/buy_furniture.py +++ b/module/dorm/buy_furniture.py @@ -37,11 +37,11 @@ class BuyFurniture(UI): # Enter furniture shop page from page_dorm, only need to enter once if self.appear(DORM_CHECK, offset=(20, 20), interval=3): self.device.click(DORM_FURNITURE_SHOP_ENTER) - self.interval_reset(GET_SHIP) + self.interval_reset([GET_SHIP, EXERCISE_PREPARATION]) continue if self.appear(DORM_FURNITURE_SHOP_FIRST_SELECTED, offset=(20, 20)): - self.interval_reset(EXERCISE_PREPARATION) + self.interval_reset([GET_SHIP, EXERCISE_PREPARATION]) # Enter furniture details page from furniture shop page if self.appear(DORM_FURNITURE_DETAILS_ENTER, offset=(20, 20), interval=3): self.device.click(DORM_FURNITURE_DETAILS_ENTER) @@ -50,6 +50,7 @@ class BuyFurniture(UI): # Re select the first piece of furniture on left side of furniture list below. elif self.appear(DORM_FURNITURE_SHOP_FIRST, offset=(20, 20), interval=3): self.device.click(DORM_FURNITURE_SHOP_FIRST) + self.interval_reset([GET_SHIP, EXERCISE_PREPARATION]) continue if self.appear(DORM_FURNITURE_DETAILS_QUIT, offset=(20, 20)): From c9fff1efa29f9e62cdf44d158f343748b2841a28 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:58:34 +0800 Subject: [PATCH 29/30] Fix: Handle mis-clicked to page_exercise (#4434) --- module/combat/combat.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/module/combat/combat.py b/module/combat/combat.py index e35efac73..c703f5b48 100644 --- a/module/combat/combat.py +++ b/module/combat/combat.py @@ -1,21 +1,21 @@ import numpy as np from module.base.timer import Timer -from module.base.utils import get_color, color_similar +from module.base.utils import color_similar, get_color from module.combat.assets import * -from module.combat_ui.assets import * from module.combat.combat_auto import CombatAuto from module.combat.combat_manual import CombatManual from module.combat.hp_balancer import HPBalancer from module.combat.level import Level from module.combat.submarine import SubmarineCall +from module.combat_ui.assets import * from module.handler.auto_search import AutoSearchHandler from module.logger import logger from module.map.assets import MAP_OFFENSIVE from module.retire.retirement import Retirement from module.statistics.azurstats import DropImage from module.template.assets import TEMPLATE_COMBAT_LOADING -from module.ui.assets import BACK_ARROW, MUNITIONS_CHECK +from module.ui.assets import BACK_ARROW, EXERCISE_CHECK, MUNITIONS_CHECK class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatManual, AutoSearchHandler): @@ -423,7 +423,12 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan Returns: bool: """ - if self.appear(MUNITIONS_CHECK, offset=(20, 20), interval=2): + if self.appear(MUNITIONS_CHECK, offset=(20, 20), interval=5): + logger.info(f'{MUNITIONS_CHECK} -> {BACK_ARROW}') + self.device.click(BACK_ARROW) + return True + if self.appear(EXERCISE_CHECK, offset=(20, 20), interval=5): + logger.info(f'{EXERCISE_CHECK} -> {BACK_ARROW}') self.device.click(BACK_ARROW) return True From 7b4a71627346d11883abcee4b5dec6f239109f76 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 20 Dec 2024 23:31:49 +0800 Subject: [PATCH 30/30] Add: Chapter SP - Spilt map config MAP_CHAPTER_SWITCH_20241219 and MAP_HAS_MODE_SWITCH --- campaign/event_20241219_cn/a1.py | 1 + campaign/event_20241219_cn/b1.py | 1 + campaign/event_20241219_cn/b3.py | 2 +- campaign/event_20241219_cn/c1.py | 1 + campaign/event_20241219_cn/d1.py | 1 + campaign/event_20241219_cn/d3.py | 14 +++++++------- module/campaign/campaign_ui.py | 2 +- module/map/map_operation.py | 2 +- 8 files changed, 14 insertions(+), 10 deletions(-) diff --git a/campaign/event_20241219_cn/a1.py b/campaign/event_20241219_cn/a1.py index 78a1b1b22..9210d6a5a 100644 --- a/campaign/event_20241219_cn/a1.py +++ b/campaign/event_20241219_cn/a1.py @@ -59,6 +59,7 @@ class Config: STAGE_ENTRANCE = ['half', '20240725'] MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MODE_SWITCH = True MAP_HAS_MOVABLE_NORMAL_ENEMY = True MAP_SIREN_HAS_BOSS_ICON_SMALL = True diff --git a/campaign/event_20241219_cn/b1.py b/campaign/event_20241219_cn/b1.py index 1bb2f5647..21ee7f025 100644 --- a/campaign/event_20241219_cn/b1.py +++ b/campaign/event_20241219_cn/b1.py @@ -60,6 +60,7 @@ class Config: STAGE_ENTRANCE = ['half', '20240725'] MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MODE_SWITCH = True MAP_HAS_MOVABLE_NORMAL_ENEMY = True MAP_SIREN_HAS_BOSS_ICON_SMALL = True diff --git a/campaign/event_20241219_cn/b3.py b/campaign/event_20241219_cn/b3.py index 57757b55a..6df3e57c7 100644 --- a/campaign/event_20241219_cn/b3.py +++ b/campaign/event_20241219_cn/b3.py @@ -75,7 +75,7 @@ class Campaign(CampaignBase): def battle_0(self): if self.clear_siren(): return True - if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + if self.clear_enemy(sort=('weight', 'cost_2', 'cost_1')): return True return self.battle_default() diff --git a/campaign/event_20241219_cn/c1.py b/campaign/event_20241219_cn/c1.py index c1c9ec977..e920799f7 100644 --- a/campaign/event_20241219_cn/c1.py +++ b/campaign/event_20241219_cn/c1.py @@ -59,6 +59,7 @@ class Config: STAGE_ENTRANCE = ['half', '20240725'] MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MODE_SWITCH = True MAP_HAS_MOVABLE_NORMAL_ENEMY = True MAP_SIREN_HAS_BOSS_ICON_SMALL = True diff --git a/campaign/event_20241219_cn/d1.py b/campaign/event_20241219_cn/d1.py index 6f4e356d7..f3262f32c 100644 --- a/campaign/event_20241219_cn/d1.py +++ b/campaign/event_20241219_cn/d1.py @@ -60,6 +60,7 @@ class Config: STAGE_ENTRANCE = ['half', '20240725'] MAP_CHAPTER_SWITCH_20241219 = True + MAP_HAS_MODE_SWITCH = True MAP_HAS_MOVABLE_NORMAL_ENEMY = True MAP_SIREN_HAS_BOSS_ICON_SMALL = True diff --git a/campaign/event_20241219_cn/d3.py b/campaign/event_20241219_cn/d3.py index 177658e72..cf5cf8d2e 100644 --- a/campaign/event_20241219_cn/d3.py +++ b/campaign/event_20241219_cn/d3.py @@ -25,11 +25,11 @@ MAP.weight_data = """ 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 - 50 50 50 50 50 50 50 50 50 50 50 - 50 50 50 50 50 50 50 50 50 50 50 - 50 50 50 50 50 50 50 50 50 50 50 - 50 50 50 50 50 50 50 50 50 50 50 - 50 50 50 50 50 50 50 50 50 50 50 + 50 50 10 50 50 50 50 50 50 50 50 + 50 50 10 10 10 10 10 50 50 50 50 + 50 50 10 10 10 10 10 50 50 50 50 + 50 50 10 10 10 10 10 50 50 50 50 + 50 50 10 10 10 10 10 50 50 50 50 """ MAP.spawn_data = [ {'battle': 0, 'enemy': 2, 'siren': 2}, @@ -76,7 +76,7 @@ class Campaign(CampaignBase): def battle_0(self): if self.clear_siren(): return True - if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + if self.clear_enemy(sort=('weight', 'cost_2', 'cost_1')): return True return self.battle_default() @@ -84,7 +84,7 @@ class Campaign(CampaignBase): def battle_5(self): if self.clear_siren(): return True - if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + if self.clear_enemy(sort=('weight', 'cost_2', 'cost_1')): return True return self.battle_default() diff --git a/module/campaign/campaign_ui.py b/module/campaign/campaign_ui.py index 38582d37d..32e424b16 100644 --- a/module/campaign/campaign_ui.py +++ b/module/campaign/campaign_ui.py @@ -197,7 +197,7 @@ class CampaignUI(MapOperation, CampaignEvent, CampaignOcr): Button: """ entrance_name = name - if self.config.MAP_HAS_MODE_SWITCH or self.config.MAP_CHAPTER_SWITCH_20241219: + if self.config.MAP_HAS_MODE_SWITCH: for mode_name in self.campaign_get_mode_names(name): if mode_name in self.stage_entrance: name = mode_name diff --git a/module/map/map_operation.py b/module/map/map_operation.py index 2fae8cd76..4a9de599a 100644 --- a/module/map/map_operation.py +++ b/module/map/map_operation.py @@ -265,7 +265,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand bool: If map mode satisfied Always True if map doesn't have mode switch in map preparation """ - if not self.config.MAP_HAS_MODE_SWITCH and not self.config.MAP_CHAPTER_SWITCH_20241219: + if not self.config.MAP_HAS_MODE_SWITCH: return True if mode == 'normal':