From 418f7a43103d1051056dbda4644f629d57f7e27a Mon Sep 17 00:00:00 2001 From: sui-feng-cb <2518179942@qq.com> Date: Sat, 10 Jan 2026 17:12:22 +0800 Subject: [PATCH] Add: preparation for chapter 16 --- assets/cn/handler/AIR_ATTACK_CONFIRM.png | Bin 0 -> 5823 bytes assets/cn/handler/AIR_ATTACK_ENTER.png | Bin 0 -> 9085 bytes assets/cn/map/FLEET_SUPPORT_EMPTY.png | Bin 0 -> 7307 bytes campaign/campaign_main/campaign_16_1.py | 86 +++++++++ campaign/campaign_main/campaign_16_2.py | 82 ++++++++ campaign/campaign_main/campaign_16_3.py | 99 ++++++++++ campaign/campaign_main/campaign_16_4.py | 107 +++++++++++ campaign/campaign_main/campaign_16_base.py | 211 +++++++++++++++++++++ module/config/config_manual.py | 1 + module/handler/assets.py | 2 + module/handler/fast_forward.py | 6 + module/handler/strategy.py | 56 ++++++ module/map/assets.py | 1 + module/map/camera.py | 3 +- module/map/map_operation.py | 3 + 15 files changed, 656 insertions(+), 1 deletion(-) create mode 100644 assets/cn/handler/AIR_ATTACK_CONFIRM.png create mode 100644 assets/cn/handler/AIR_ATTACK_ENTER.png create mode 100644 assets/cn/map/FLEET_SUPPORT_EMPTY.png create mode 100644 campaign/campaign_main/campaign_16_1.py create mode 100644 campaign/campaign_main/campaign_16_2.py create mode 100644 campaign/campaign_main/campaign_16_3.py create mode 100644 campaign/campaign_main/campaign_16_4.py create mode 100644 campaign/campaign_main/campaign_16_base.py diff --git a/assets/cn/handler/AIR_ATTACK_CONFIRM.png b/assets/cn/handler/AIR_ATTACK_CONFIRM.png new file mode 100644 index 0000000000000000000000000000000000000000..0f5af1f13fc5a6a37e9b46c50d97f7106f1e123a GIT binary patch literal 5823 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 KthnL*^Zx=f#>B4x literal 0 HcmV?d00001 diff --git a/assets/cn/handler/AIR_ATTACK_ENTER.png b/assets/cn/handler/AIR_ATTACK_ENTER.png new file mode 100644 index 0000000000000000000000000000000000000000..ef8b8b97578f0f6ac74df76607f8c86d0d189599 GIT binary patch literal 9085 zcmeHN`#aNp`1f?69!cktQ^gZcL^(6;Q4UG*$b+17N`yI%ZEW>8g;*(shA7G$Vi?9& z5h28!wwdKPr#a7@wtYWzegB8=_qv|XFWa^4`n=!o*L~lw*XzFbiT~T&MCA9gzY7Qm zh?w3mydxm6A9%ax$ALY-%U{wre-jWmCtzx5@J~3EMMu2y@QjpO)M561D9X;BpUxOj z`Shr<n8PBsQ1RH(9<20@z)lw9*oa54LRy2 zd&Dxzu>RIF0kbCAa|wG7HC|Z_vFHkM>hxp;`@kREmi?D3OW{Sx7g=C*(u?Ldm|NS! z9FE3WU_1X^{~_=n0{`WgKQ|$*7p}eZ)LpRVsdg&l9C&6~ zpPO?fBAc*K4;v|EtfGORJ<3teO$#<%2YXz#77!E=xNDfEB&oOfi5o{V)de4$v^WOG zDQI7l@pqYhT!8#($pJe$%^>_u!vN1)KRb zqHq`Nvv4wctcLEdJOue`{}};+dq+)E!rk017%(g$I#GE`Xm(=|Id$0Nc zy<%ZFOi@z$_#?oI40@)>9<1fi7(5!9+d1Kye@Ltb-pNdXWSqA6E@XZ-%GHep^-46x z9{n16TkEh`O{w6*lh5p@KQD1(h4ov9e!fp88f_dW6ckGF6Ty7)jFjaifxW;^6MG=4 z-o1OX+AopUR(y|WT-&3B{Y$)J@ox>?nwb!Fypw0&eJSgPifhjlN8$1zGD>+q=R#HH z(yZl1Yl^MM-ZGQQ52yRjIo|mbo=1H>kOf1n`&;aU_1?gl{`=TWzx-P%TH}n0v)hiH z?-)M1At&P%JX0NnqDFd$e5i}LE?=n^qKA(}lAk>uD|#(~r(vK3^|}zX+gWC5Ph(}U z4S^>v1d_m0oUI0D6cmaeX&%^&GSG%R33c8-hEYCUZGqcGe6S-Gx#D9SkgdmunMlH# zo(CR-4Am?ZC^PCj+Y8{mA!ny=O6vu~B)<3*|d2efoDF!`Pa6d8KYUD{hfC zT{+ALE5Yevr0SJtB@TEb6U5la2X78ZhHbOl(co;^4mCftqMc5;ukP)vK)s*Z9@LRy z#*vtOAK%sF;qH$6H@;X;`BmAobEk}P8BKw5e(#PSZ@-AYrALHS49xa*ylaPr-un$m z%tuboVArQEzj$l^6z`lB=05{@^1&O{8$7@puqFBMmdw(XxT!81sp<9Oe{dKme&_&q zgQ?HopiumWrr32jOLtk1Nn{zrcjEk#f5wom2GKWyV)#!|y7^3M+VV4`v?B@0CTkfrRL@184{H#>be#5UOtg$5#yt-S$=hm1)K+QTHT#pY;p7}ESannAH?jJaM zQ28b~=fPK9-)RYGsS{!CU*S&Dn()BuCF+1;&Y4-xeY`m&gQ7zhwW}dCz-Kzwup30W zqIh)6y&s~}nq}2+S5Wkbfu3YI5aTkJ+XshJR)~N*#A%O z<;=zvr!czu5YKK6IA@|#-4r>8x0>OV-R)m!J%gbSi)2)T9N73GazNio5kt6DbDaJx zYNe>|^#Hw05VXdrQzcuWb+>=ct@)i7iaS&9_H|xHCVyo>`qXZ*v19e#=e=KQl=gp> zosC?M%IcUj5RwexZ7`i8*{O1v)h}8~cnG7vWKTdU0(&fg?Q$|{cHdB0;wSEi1J~7T z3aR|MA{1_Rqf;(Be7kA@Ia!cZx;0~4#$OatKDN7r`n9&CSV=7M4qXIW&&)>jmk?OY z`d-&K$+dWki0w87FuQ&%+5ZH0ek}v3tXsWCaKL@O6`sxNh?ReR+Ah4b2O=5Sh(>D8 zhHZITN`-$yN^k#+A+V|)Qe+iuOC!d_88x{Kr{jt{B$@tLoL$VoAg=Mg3q{E%Y;k!l zVBXSc;^KaHcQaJB**kCeTzLy`M4I;*_%J456pdWX&nO|YpdgQFts5rSOXs0@aNSjO ze$CJu`HBle^6i_XkhyM*Ia6$F^wKAQ)pegolla^PKDmLv`m)of%HHdBU~}kzQ`kt% zX(bn;ZZvJga8$ReC?;(=g83X|yWO!;WZA!0RCn3&&Sde2p#726;=E7>OP8wmZSSkJDQDJ|G!36iE8U<_sei+LcM$ouGwnmvrRZ z8jY02gynz7Lomg*wwJDNIuRo_M&hKARPQpa#ZP-h_1FLUm3tMzsrm1Z_H~8oX_%80 zA37pfr_s90wGaD?{qeN?{CszJ_wyIPec~ZH7$%s%G2n#gkOObDGWc8uYPH*hQzVH1 zfR;vW3%jg;`NJ%2J1Zji*WnXKYZQy9zIGZHGQ82tzqaN5YiscY%U)lN_6mvn-LB^({n1Yq#iU)j7sBv< zG!0c;C=^N)^Qz}{@#b)HIT1lx6W&7#$z$qOD7pVl5)ZPw17sarHoR`@LSSYu^u;jV zI-k)>eleMls$gPF^;T#atD*R|1mff?@C__K&7puszBI$>mu7A(ZHh>5vDzDrN#JiQ z^_fBvUoA5LHt&9ChBNSl!;1#8M9_KiI+#vd;&Lk49OU-~Jm`+rgHEK{{=Snyb7$?+ z{3t4VLrRH~BISAV&gDTyxQu84iI&!;rnYCu!a_MTn3oQOt)dTWtGrrRx6~|-;)J0% z^Mr-nvc3CPpoFrMj{Rl3-UqU#L%s#)<>X9N_l?C6*^wN27{xQ)DY!%3mC)yN@YjNk zNZX0O^NYjOo70m-8HW&U#+GSz`A5G5EG0pPHfL-}n~RO27AfOX`Do-8jX^Y{YKhKv z(yINoqAVh3X1capn@uz{$WRG6Z|vn=HT`>3OG;Y2q-XKU30v?=-nUs+^L0_Z6Dc$C z01;5V3t3-`p3al_ycNdQa9Jelp^RBGm1U00DDYO$72e&HN}~tCDn*tBRp?q#Y;UV5 z>WpO_dDPf0e<_<&&%}fc!&)T69B;fQ%?m=b8g=!mgY-VI(FsHs%gNu81qwj;A;w#{$x)KYSQj3ve2}@3hA}Q&`+` z|Ggcq$V4U935O8qK8Bvz6Acs!g+)WqDH(naoI&?K6a9##gL3r`&d(2dS5a9|yR;GQ zE}Q{L8rp1%lRv#CF2|b!Vww0k8&R^+D&TlH-5~ST8R5qvKW6n-f1u^$G{Xq=y@&6v zOk+!R*Iwn%jxLs_7fhdzlUPnIi}3XEDUxqD(EnCZJTQA$kbC(B`d6gt1RO2)mL3bR zEFSVRN@?iF9B&S_zYmm4#zjbJc({$WImopTcFWwHjSAbndZ0w6ntne{lQwN9_lXJRXuTSEJLwZb=r#XnI#c!K?t^hj{&NwtC zKRzs-lC zaQ@8asL%I&d}aVTyaU1RYHWuo`^j zv$eK3UvLBtkk=AozLq(e#h>h^x@SqZR}@>?1cw2lvJ=J_+I2g65jXNAu`vfjb`RLsW7pT_jM zS|36BgJSAK&eBr%BH*Az1i6asc^3{V0}KMR6pq9oqsRcMAc$&d&~mjO*^}}xtogdY z;W%qs1^D?*^INwZL;I}bB%y<3UNoQ=EmvD4Z~>&>PVQ94S9wE<>#=z#qCGuMjdp`v zDW9m7EJW8$esLeCu2S)qJRKe2@Uo+_PwBc^=^n_?gAgjF^9t7{|9xTwW5Fe;^$!qv zVX>`|*8?WyC+qkakSc!l3ojchAXNtzlRcjaMitNSUmK2KZ0uuevJ|~NDY>dDDmcH3 zO+SlNL)2YrzLSf)pAfie^;rcalE2j`=GR~fa5SOT#ZzJQ&;c*-7Uf86m)6)*D1rM@ zrFPe{Dp?I6XB8cp9Y-Erf9a=@1?usYY0Fnha)4RNc2v@lc|GDL9Un2(JppbhYGYb+ z?QzU;%ay#9F76mO0!ku8ayQwks;XQhie49b+9{;l8NpJYd&`D&s@&KqcQb88erIci zqQ_78_h9O+Ow)#Qch0I^Z%V>q3TPZP zptm;Ea8!M{;x7M3Y%$|uZ|aCYo`B>=fpGqmZ(qpwev}Csdi7mhh+IIFtv4`e^`7wX z2#quUq${))e``b;IcsDs9W{|aPQ8iiRNkY!>gcj7zd46L_3aym|M{~;15DiVS0Jl7 zmAe@|_kv|}5Dm`|au?H12DicI7gu}72d7ffNptVcbCZ*jxQo24(yc`0fp!_c+hiMs z+p`B7`>dRAD|MpAG?DI<3QxY2@vqWPbaL7^e5}G>)imw0xcI8j`Pxp6EC;;5q@-j? zp0D1zw!#;5afTXWJyx|7#stMOW6x~UNn4~W4GT7$US96Wva#>{v%(k;0`ZOM{$XRy zNq}wH(A>z3C#eB#joU}2GhY#^Z~3z1{x(#HrFOe9QMY~eO=r~f^zsRL4_xUt&7wML6@OC3OI=sGEh1UT=7Sd*`9WXYT%~i6dZF$cNN=Up}l9VJXx_uZ* z^OIixB&5`^+5;stKv`5&V9eV@W#ZkPKJn-2iWcXt-@8~g!4rmv5YxNxj2NqnJxc7YT@wuRI$6(osks^@vo>u97cI?bkR z*o!^W*X1G5zaN;l-#e;(kIw~%w0<2~$|Spp9hHHRofE#Dga=@eBEpbBytw( z%q|-7w9uJ;JC?ll4oGT1X+~#AHxIl%e?Bhw`Zeyr|Qz=?C}y+B6v88*-q0Y;3IX zbx_;>(~gT)OybY|%35oK?%>roqIy7`pLPx(`AtY7U>L@1V3#Z?CaR#9YtC9Z875tF z5S=CT^`+duqYQp6mvkhW#xhTl71!{s8BI_qC2r0DqV~>2I8Y zwUM(3;_%ffOV)QngFnVfJPN6KsanJW);QZ0&U$D8UjG@zkHH#s7u?P&CGj>taz{i_ z>lN)vAm9t<)jSvCBvl-HAwVK7SA70>>EME>RAh)1x5EDBpW6=%JvF9ICq-0is?tOd zXB`TvQJY2JhD}w}uCrQ9ED{2xHy*@WrkaG!iA>||@p$=3$qZ@u)SCq)2Vt%?620yf zl3Yaa2!+&6q?Mt#vy9Dt{Fm$_aK>tZW+Z}6H@&0&DFGY!!!*qkIP2XiAL|}p8&-8f z5h(=qBZp4pE_Gx~Gan&t9TP@`5~g$aK@-<6~r*B7sBo(dH_3K7T10%HFIi{ieMUwGVxb|-&CM;u*Vk+FcFO&4gVwgi@;?11f4)^R zV0xYyPROqja|?Vue_%YxSnTrLHKQG>|Kw&V`bg^Hu&a7Cfc%qe*REjn-L2GWO)(Dbo8co-A9Gz8I-ho=>(ai3TVEy? zjhx4U@k#Ayf<^4AxO1sc@INXWW-N_4baQNuZiag@+p@p|bDybiglrXC4-e zGLeG)v%`D$4E}NYkTA-EAlivrrPSJ$Vq9n40&z=!C8O&*SY==D!_wNxIb0WUx*#j< zvO~W=I(nZ(rF}_d-(hWy3@o?5sKXUBwQ*W(emcZBEjg)d7;<~?z_jKZqaL>00!DtM zu`W-gtMA+4v#W_~L+*Gh9@&h!K!uRFNbUfNTZAozPbj$5hD`SWE`P_;$w+T}^TX4t zgC^RbgiC|Qrm4sm9$K;5Vu#}n+ttxnok^*sr}TY#>2HMWz#Z64h|}+-7x#z;L$aBG zJIK<|s}7yL)u8p_;#~k)#YbbB0-%M&Aq*=)qc44bc?4+NzK62O$4ED8+Uw6~P1=K0Q|ipDQXUW>*1a8JI|hjx+T+6wrK)yOOMC+)5~R>_z2_+ez!t zpLO_PYl`Hd<@!UOe79H84At9{>LSRrdBTqtq0)%c&yijU;wWwJ~&TjC9CnZX>G>2tP z2pgX&V)O-oF0}_S?LQ|qTS+h+HAc1Us(kRkOlLkfl!HP2y6J4*Jsj-IK{*Ndl&eMb zaQ%}PI&2%i((D6`mbkM$x_avDZZkd;n5(xuFn2e3h<)I^8mW`=R#T&zK^SH;L;-WD zs8GrUqZD`d_%OOY)6=eyY}WwK!GPLfm=BXeROHb(e5NUm?%ySXD5ZiH>O`$%O)G%R zZ{Q4rx>jP6xV*I;evnxo({}L@5ZJ4=^DKZ(ehyAIE0d~os9Z*dg<`)|xp`fybCU}% zUcSOH_N;1nc-HP@(%QENYU&MsKcrtK1wBRqt%-o*9utX3H_QWO3~gD?S=ZF(wZk{f z%!j=Cb9NPqW4r&3Aou>7qh-L0Ao=s#5`X6JJc#h$^&bNNj}cI;;R_*NA54`$$nD=b OhN+RcVd=Gd|Nak~aL$eZ literal 0 HcmV?d00001 diff --git a/assets/cn/map/FLEET_SUPPORT_EMPTY.png b/assets/cn/map/FLEET_SUPPORT_EMPTY.png new file mode 100644 index 0000000000000000000000000000000000000000..cdb45ca5cb477ddf41b8b82707489d62c0e4e7a6 GIT binary patch literal 7307 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 WIx*n7`>)+onqR*5H`c`c-~Rz6*5U*J literal 0 HcmV?d00001 diff --git a/campaign/campaign_main/campaign_16_1.py b/campaign/campaign_main/campaign_16_1.py new file mode 100644 index 000000000..96092589b --- /dev/null +++ b/campaign/campaign_main/campaign_16_1.py @@ -0,0 +1,86 @@ +from module.logger import logger +from module.campaign.campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids + +from .campaign_16_base import CampaignBase +from .campaign_16_base import Config as ConfigBase + +MAP = CampaignMap('16-1') +MAP.shape = 'I9' +MAP.camera_data = ['C2', 'C6', 'F2', 'F6'] +MAP.camera_data_spawn_point = ['F6'] +MAP.camera_sight = (-2, -1, 3, 2) +MAP.map_data = """ + ++ ME -- -- ME -- -- ME -- + ME -- -- ME ++ ME -- -- ME + -- -- ME -- ME -- -- ME ++ + -- ME ++ ME -- ME -- ME Me + -- -- ME -- Me ++ __ -- -- + -- ME -- __ ME -- -- -- MB + -- ++ Me -- -- -- Me ++ ++ + -- ME -- -- -- -- -- ++ ++ + -- -- -- -- SP SP ++ ++ -- +""" +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 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2}, + {'battle': 1, 'enemy': 2}, + {'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, \ +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, \ +A9, B9, C9, D9, E9, F9, G9, H9, I9, \ + = MAP.flatten() + + +class Config: + # ===== Start of generated config ===== + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = False + MAP_HAS_AMBUSH = True + + # ===== End of generated config ===== + MAP_HAS_SUBMARINE_SUPPORT = True + + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_5(self): + boss = self.map.select(is_boss=True) + if boss: + return self.fleet_boss.clear_boss() + + 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/campaign_main/campaign_16_2.py b/campaign/campaign_main/campaign_16_2.py new file mode 100644 index 000000000..6138b10d4 --- /dev/null +++ b/campaign/campaign_main/campaign_16_2.py @@ -0,0 +1,82 @@ +from module.logger import logger +from module.campaign.campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids + +from .campaign_16_base import CampaignBase +from .campaign_16_base import Config as ConfigBase + +MAP = CampaignMap('16-2') +MAP.shape = 'J8' +MAP.camera_data = ['C2', 'C6', 'G2', 'G6'] +MAP.camera_data_spawn_point = ['C6'] +MAP.camera_sight = (-2, -1, 3, 2) +MAP.map_data = """ + MB ++ -- ME -- -- -- -- -- -- + -- Me ME -- ME ++ ME -- -- -- + -- __ -- -- -- -- -- ME ++ ++ + ME -- ME ME ME -- ME -- ++ -- + Me -- -- Me ++ -- Me -- -- -- + ++ ME -- ME ME -- ME ++ ++ -- + -- -- -- -- -- Me -- -- ++ -- + -- SP SP -- ++ ++ ++ -- -- -- +""" +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 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 3}, + {'battle': 1, 'enemy': 2}, + {'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, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, J6, \ +A7, B7, C7, D7, E7, F7, G7, H7, I7, J7, \ +A8, B8, C8, D8, E8, F8, G8, H8, I8, J8, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = False + MAP_HAS_AMBUSH = True + # ===== End of generated config ===== + MAP_HAS_SUBMARINE_SUPPORT = True + + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_5(self): + boss = self.map.select(is_boss=True) + if boss: + return self.fleet_boss.clear_boss() + + 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/campaign_main/campaign_16_3.py b/campaign/campaign_main/campaign_16_3.py new file mode 100644 index 000000000..244b7e55d --- /dev/null +++ b/campaign/campaign_main/campaign_16_3.py @@ -0,0 +1,99 @@ +from module.logger import logger +from module.campaign.campaign_base import CampaignBase +from module.map.map_grids import SelectedGrids, RoadGrids + +from .campaign_16_base import CampaignBase, CampaignMap +from .campaign_16_base import Config as ConfigBase + +MAP = CampaignMap('16-3') +MAP.shape = 'K6' +MAP.camera_data = ['C2', 'C5', 'F2', 'F5', 'H2', 'H5'] +MAP.camera_data_spawn_point = ['C5'] +MAP.camera_sight = (-2, -1, 3, 2) +MAP.map_data = """ + -- -- ++ ++ ++ -- -- ME ++ -- MB + -- ME -- ++ -- ME -- -- ++ -- -- + -- -- ME ME -- ME ++ ME ++ -- -- + -- -- -- ++ ++ __ ME ME -- -- -- + SP -- -- ++ -- ME ++ -- -- -- -- + SP -- -- 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 40 40 40 40 50 50 50 50 50 + 50 50 50 50 50 40 40 40 50 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': 3}, + {'battle': 1, 'enemy': 6}, + {'battle': 2, 'enemy': 3}, + {'battle': 3, '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, \ + = MAP.flatten() + +roads = [RoadGrids([C3, D3, F3, G4, H4])] + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = False + MAP_HAS_AMBUSH = True + # ===== End of generated config ===== + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (120, 255 - 17), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 50, 255), + 'prominence': 10, + 'distance': 50, + 'wlen': 1000 + } + INTERNAL_LINES_HOUGHLINES_THRESHOLD = 25 + EDGE_LINES_HOUGHLINES_THRESHOLD = 25 + + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if not self.map_is_clear_mode: + self.destroy_land_base(D2, C3, D3) + + if self.clear_roadblocks(roads): + return True + if self.clear_potential_roadblocks(roads): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_3(self): + if not self.map_is_clear_mode: + self.destroy_land_base(K6, J5, J6) + + boss = self.map.select(is_boss=True) + if boss: + return self.fleet_boss.brute_clear_boss() + + if self.clear_roadblocks(roads): + return True + if self.clear_potential_roadblocks(roads): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() diff --git a/campaign/campaign_main/campaign_16_4.py b/campaign/campaign_main/campaign_16_4.py new file mode 100644 index 000000000..5205ee989 --- /dev/null +++ b/campaign/campaign_main/campaign_16_4.py @@ -0,0 +1,107 @@ +from module.logger import logger +from module.campaign.campaign_base import CampaignBase +from module.map.map_grids import SelectedGrids, RoadGrids + +from .campaign_16_base import CampaignBase, CampaignMap +from .campaign_16_base import Config as ConfigBase + +MAP = CampaignMap('16-4') +MAP.shape = 'K8' +MAP.camera_data = ['C2', 'C6', 'F2', 'F6', 'H2', 'H6'] +MAP.camera_data_spawn_point = ['C6'] +MAP.camera_sight = (-2, -1, 3, 2) +MAP.map_data = """ + -- -- ++ -- -- -- ++ ME -- -- MB + ME ++ ++ ++ -- -- ME ++ -- -- -- + -- -- ME -- -- ++ ++ ME -- -- -- + -- -- -- ME ++ -- ME -- ++ ++ -- + -- -- ME -- -- ME ++ -- ME ++ -- + -- __ -- ++ ++ -- ++ ME ME -- -- + SP -- -- ME -- -- ME ++ -- ++ ++ + SP -- -- -- ++ -- ++ ++ -- -- ++ +""" +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 40 50 50 50 + 50 50 50 40 50 40 40 40 50 50 50 + 50 50 50 40 40 40 50 50 50 50 50 + 50 50 50 50 50 50 50 50 50 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': 5}, + {'battle': 1, 'enemy': 4}, + {'battle': 2, 'enemy': 5}, + {'battle': 3}, + {'battle': 4, '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() + +roads = [RoadGrids([D4, F5, G4, H3])] + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = False + MAP_HAS_AMBUSH = True + # ===== End of generated config ===== + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (120, 255 - 17), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 50, 255), + 'prominence': 10, + 'distance': 50, + 'wlen': 1000 + } + INTERNAL_LINES_HOUGHLINES_THRESHOLD = 25 + EDGE_LINES_HOUGHLINES_THRESHOLD = 25 + + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if not self.map_is_clear_mode: + self.destroy_land_base(C1, D1, D1) + + if self.clear_roadblocks(roads): + return True + if self.clear_potential_roadblocks(roads): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=1): + return True + + return self.battle_default() + + def battle_4(self): + if not self.map_is_clear_mode: + # destroy K8 and G8 land base in one air attack + self.destroy_land_base(K8, J6, I8) + + boss = self.map.select(is_boss=True) + if boss: + return self.fleet_boss.brute_clear_boss() + + if self.clear_roadblocks(roads): + return True + if self.clear_potential_roadblocks(roads): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() diff --git a/campaign/campaign_main/campaign_16_base.py b/campaign/campaign_main/campaign_16_base.py new file mode 100644 index 000000000..e58285218 --- /dev/null +++ b/campaign/campaign_main/campaign_16_base.py @@ -0,0 +1,211 @@ +import numpy as np + +from campaign.campaign_main.campaign_15_base import MASK_MAP_UI_W15 +from module.base.timer import Timer +from module.campaign.campaign_base import CampaignBase as CampaignBase_ +from module.logger import logger +from module.map.assets import FLEET_SUPPORT_EMPTY +from module.map.map_base import CampaignMap as CampaignMap_ +from module.map.map_grids import SelectedGrids +from module.map.utils import location_ensure +from module.map_detection.grid import GridInfo +from module.map_detection.utils_assets import ASSETS + + +class CampaignMap(CampaignMap_): + def update(self, grids, camera, mode='normal'): + """ + Args: + grids: + camera (tuple): + mode (str): Scan mode, such as 'init', 'normal', 'carrier', 'movable' + """ + offset = np.array(camera) - np.array(grids.center_loca) + + for grid in grids.grids.values(): + loca = tuple(offset + grid.location) + if loca in self.grids: + if self.ignore_prediction_match(globe=loca, local=grid): + continue + self.grids[loca].merge(grid, mode=mode) + if mode == 'init': + self.fixup_submarine_fleet() + return True + + +class Config: + # Ambushes can be avoid by having more DDs. + MAP_WALK_TURNING_OPTIMIZE = False + MAP_HAS_MYSTERY = False + + # HOMO_CANNY_THRESHOLD = (50, 100) + # MAP_SWIPE_MULTIPLY = (0.993, 1.011) + # MAP_SWIPE_MULTIPLY_MINITOUCH = (0.960, 0.978) + # MAP_SWIPE_MULTIPLY_MAATOUCH = (0.932, 0.949) + MAP_SWIPE_MULTIPLY = (1.391, 1.417) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.345, 1.370) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.306, 1.329) + + +class CampaignBase(CampaignBase_): + ENEMY_FILTER = '1L > 1M > 1E > 2L > 3L > 2M > 2E > 1C > 2C > 3M > 3E > 3C' + has_support_fleet = True + destroyed_land_base = [] + + def map_init(self, map_): + if self.config.MAP_HAS_SUBMARINE_SUPPORT and self.has_support_fleet: + logger.hr(f'{self.FUNCTION_NAME_BASE}SUBMARINE', level=2) + self.combat(balance_hp=False, emotion_reduce=False, save_get_items=False) + super().map_init(map_) + + def map_data_init(self, map_): + super().map_data_init(map_) + self.destroyed_land_base = [] + # Patch ui_mask, get rid of supporting fleet + _ = ASSETS.ui_mask + ASSETS.ui_mask = MASK_MAP_UI_W15.image + + def can_use_auto_search_continue(self): + return False + + def fleet_preparation(self, skip_first_screenshot=True): + if self.appear(FLEET_SUPPORT_EMPTY, offset=(5, 5)): + self.has_support_fleet = False + logger.attr('Has support fleet', self.has_support_fleet) + return super().fleet_preparation(skip_first_screenshot=skip_first_screenshot) + + def strategy_set_execute(self, formation=None, sub_view=None, sub_hunt=None): + super().strategy_set_execute( + formation=formation, + sub_view=sub_view, + sub_hunt=sub_hunt, + ) + logger.attr("Map has air attack", self.strategy_has_air_attack()) + + def _map_swipe(self, vector, box=(239, 159, 1175, 628)): + # Left border to 239, avoid swiping on support fleet + return super()._map_swipe(vector, box=box) + + def air_attackable(self, location): + """ + Check if air attack can be used at location. + This requires that: + 1. location grid is in the map (not exceeding the boundaries) + 2. location is not a land grid + + Args: + location (tuple): Location of air attack. + + Returns: + bool: if attackable. + """ + location = location_ensure(location) + attackable = True + + try: + logger.info(f'location: {self.map[location]}') + except KeyError as e: + logger.exception(f'Given coordinates are outside the map.') + raise e + + if self.map[location].is_land: + logger.error(f'{self.map[location]} is a land grid.') + attackable = False + + if not attackable: + logger.error(f'Cannot air attack at {self.map[location]}.') + + return attackable + + def _air_attack(self, location): + """ + Select the location for air attack. + + Args: + location (tuple, str, GridInfo): Location of air attack. + + Returns: + bool: If selected. + + Pages: + in: AIR_ATTACK_CONFIRM + out: AIR_ATTACK_CONFIRM + """ + location = location_ensure(location) + + self.in_sight(location) + grid = self.convert_global_to_local(location) + grid.__str__ = location + + logger.info('Select mob to move') + skip_first_screenshot = True + interval = Timer(2, count=4) + clicked_count = 0 + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + # End + if self.is_in_strategy_mob_move(): + self.view.update(image=self.device.image) + # temporary method to end + if clicked_count >= 1: + break + # Click + if interval.reached() and self.is_in_strategy_air_attack(): + self.device.click(grid) + clicked_count += 1 + interval.reset() + continue + + def air_attack(self, location): + """ + Open strategy, use air attack at location, close strategy. + + Args: + location (tuple, str, GridInfo): Location of air attack. + + Returns: + bool: If attacked + + Pages: + in: IN_MAP + out: IN_MAP + """ + if not self.air_attackable(location): + return False + + self.strategy_open() + if not self.strategy_has_air_attack(): + logger.warning(f'No remain air attack trials, will abandon attacking') + self.strategy_close() + return False + self.strategy_air_attack_enter() + self._air_attack(location) + self.strategy_air_attack_confirm() + self.strategy_close(skip_first_screenshot=False) + return True + + def destroy_land_base(self, land_base_grid, goto_grid, attack_grid): + """ + Args: + land_base_grid (GridInfo): location of land base + goto_grid (GridInfo): location for current fleet to go to + attack_grid (GridInfo): location to use air attack + + Returns: + bool: False + """ + if land_base_grid in self.destroyed_land_base: + logger.info(f'Land base {land_base_grid} already destroyed') + elif goto_grid.is_accessible: + logger.info(f'Destroy land base on {land_base_grid}') + self.goto(goto_grid, turning_optimize=self.config.MAP_WALK_TURNING_OPTIMIZE) + if self.air_attack(attack_grid): + self.destroyed_land_base.append(land_base_grid) + else: + logger.info(f'Land base {land_base_grid} not accessible, will check in next battle') + + return False diff --git a/module/config/config_manual.py b/module/config/config_manual.py index 764b75006..5b08399df 100644 --- a/module/config/config_manual.py +++ b/module/config/config_manual.py @@ -146,6 +146,7 @@ class ManualConfig: MAP_HAS_MISSILE_ATTACK = False # event_202111229_cn, missile attack covers the feature area of sirens. MAP_HAS_BOUNCING_ENEMY = False # event_20220224_cn, enemy is bouncing in a fixed route. MAP_HAS_DECOY_ENEMY = False # event_20220428, decoy enemy on map, disappear when fleet reach there. + MAP_HAS_SUBMARINE_SUPPORT = False # campaign 16-1 and 16-2 has submarine support fleet. MAP_FOCUS_ENEMY_AFTER_BATTLE = False # Operation siren MAP_ENEMY_TEMPLATE = ['Light', 'Main', 'Carrier', 'Treasure'] MAP_SIREN_TEMPLATE = ['DD', 'CL', 'CA', 'BB', 'CV'] diff --git a/module/handler/assets.py b/module/handler/assets.py index 63b8de7d1..764fdbb1c 100644 --- a/module/handler/assets.py +++ b/module/handler/assets.py @@ -4,6 +4,8 @@ from module.base.template import Template # This file was automatically generated by dev_tools/button_extract.py. # Don't modify it manually. +AIR_ATTACK_CONFIRM = Button(area={'cn': (1161, 645, 1222, 675), 'en': (1161, 645, 1222, 675), 'jp': (1161, 645, 1222, 675), 'tw': (1161, 645, 1222, 675)}, color={'cn': (130, 166, 212), 'en': (130, 166, 212), 'jp': (130, 166, 212), 'tw': (130, 166, 212)}, button={'cn': (1161, 645, 1222, 675), 'en': (1161, 645, 1222, 675), 'jp': (1161, 645, 1222, 675), 'tw': (1161, 645, 1222, 675)}, file={'cn': './assets/cn/handler/AIR_ATTACK_CONFIRM.png', 'en': './assets/cn/handler/AIR_ATTACK_CONFIRM.png', 'jp': './assets/cn/handler/AIR_ATTACK_CONFIRM.png', 'tw': './assets/cn/handler/AIR_ATTACK_CONFIRM.png'}) +AIR_ATTACK_ENTER = Button(area={'cn': (1194, 456, 1249, 530), 'en': (1194, 456, 1249, 530), 'jp': (1194, 456, 1249, 530), 'tw': (1194, 456, 1249, 530)}, color={'cn': (123, 124, 131), 'en': (123, 124, 131), 'jp': (123, 124, 131), 'tw': (123, 124, 131)}, button={'cn': (1194, 456, 1249, 530), 'en': (1194, 456, 1249, 530), 'jp': (1194, 456, 1249, 530), 'tw': (1194, 456, 1249, 530)}, file={'cn': './assets/cn/handler/AIR_ATTACK_ENTER.png', 'en': './assets/cn/handler/AIR_ATTACK_ENTER.png', 'jp': './assets/cn/handler/AIR_ATTACK_ENTER.png', 'tw': './assets/cn/handler/AIR_ATTACK_ENTER.png'}) ANDROID_NO_RESPOND = Button(area={'cn': (341, 433, 391, 472), 'en': (341, 433, 391, 472), 'jp': (341, 433, 391, 472), 'tw': (341, 433, 391, 472)}, color={'cn': (217, 237, 235), 'en': (217, 237, 235), 'jp': (217, 237, 235), 'tw': (217, 237, 235)}, button={'cn': (341, 433, 391, 472), 'en': (341, 433, 391, 472), 'jp': (341, 433, 391, 472), 'tw': (341, 433, 391, 472)}, file={'cn': './assets/cn/handler/ANDROID_NO_RESPOND.png', 'en': './assets/en/handler/ANDROID_NO_RESPOND.png', 'jp': './assets/jp/handler/ANDROID_NO_RESPOND.png', 'tw': './assets/tw/handler/ANDROID_NO_RESPOND.png'}) AUTO_SEARCH_MAP_OPTION_OFF = Button(area={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1204, 547, 1276, 568), 'tw': (1205, 546, 1275, 567)}, color={'cn': (196, 169, 169), 'en': (151, 132, 138), 'jp': (179, 153, 156), 'tw': (153, 132, 137)}, button={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1204, 547, 1276, 568), 'tw': (1205, 546, 1275, 567)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MAP_OPTION_OFF.png', 'en': './assets/en/handler/AUTO_SEARCH_MAP_OPTION_OFF.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MAP_OPTION_OFF.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MAP_OPTION_OFF.png'}) AUTO_SEARCH_MAP_OPTION_ON = Button(area={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1203, 547, 1276, 568), 'tw': (1204, 546, 1276, 567)}, color={'cn': (149, 176, 193), 'en': (113, 135, 157), 'jp': (132, 158, 177), 'tw': (110, 133, 156)}, button={'cn': (1205, 549, 1275, 566), 'en': (1203, 552, 1277, 564), 'jp': (1203, 547, 1276, 568), 'tw': (1204, 546, 1276, 567)}, file={'cn': './assets/cn/handler/AUTO_SEARCH_MAP_OPTION_ON.png', 'en': './assets/en/handler/AUTO_SEARCH_MAP_OPTION_ON.png', 'jp': './assets/jp/handler/AUTO_SEARCH_MAP_OPTION_ON.png', 'tw': './assets/tw/handler/AUTO_SEARCH_MAP_OPTION_ON.png'}) diff --git a/module/handler/fast_forward.py b/module/handler/fast_forward.py index 77723c217..0d055562d 100644 --- a/module/handler/fast_forward.py +++ b/module/handler/fast_forward.py @@ -572,3 +572,9 @@ class FastForwardHandler(AutoSearchHandler): self.device.click(MAP_WALK_SPEEDUP) interval.reset() continue + + def handle_submarine_cost_popup(self): + if self.config.MAP_HAS_SUBMARINE_SUPPORT and self.handle_popup_confirm('SUBMARINE_COST'): + return True + + return False diff --git a/module/handler/strategy.py b/module/handler/strategy.py index 4cf9a4956..4e1649757 100644 --- a/module/handler/strategy.py +++ b/module/handler/strategy.py @@ -259,3 +259,59 @@ class StrategyHandler(InfoHandler): if self.appear_then_click(MOB_MOVE_CANCEL, offset=(20, 20), interval=5): continue + + def is_in_strategy_air_attack(self): + """ + Returns: + bool: + """ + return self.appear(AIR_ATTACK_CONFIRM, offset=(20, 20)) + + def strategy_has_air_attack(self): + """ + Pages: + in: STRATEGY_OPENED + out: STRATEGY_OPENED + """ + if self.match_template_color(AIR_ATTACK_ENTER, offset=MOB_MOVE_OFFSET): + return True + else: + return False + + def strategy_air_attack_enter(self, skip_first_screenshot=True): + """ + Pages: + in: STRATEGY_OPENED, AIR_ATTACK_ENTER + out: AIR_ATTACK_CONFIRM + """ + logger.info('Air attack enter') + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.appear(AIR_ATTACK_CONFIRM, offset=(20, 20)): + break + + if self.appear_then_click(AIR_ATTACK_ENTER, offset=MOB_MOVE_OFFSET, interval=5): + continue + + def strategy_air_attack_confirm(self, skip_first_screenshot=True): + """ + Pages: + in: AIR_ATTACK_CONFIRM + out: STRATEGY_OPENED, AIR_ATTACK_ENTER + """ + logger.info('Air attack confirm') + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + if self.appear(AIR_ATTACK_ENTER, offset=MOB_MOVE_OFFSET): + break + + if self.appear_then_click(AIR_ATTACK_CONFIRM, offset=(20, 20), interval=5): + continue diff --git a/module/map/assets.py b/module/map/assets.py index 2b2713fdf..5263daa87 100644 --- a/module/map/assets.py +++ b/module/map/assets.py @@ -21,6 +21,7 @@ FLEET_NUM_1 = Button(area={'cn': (213, 76, 224, 101), 'en': (213, 76, 224, 101), FLEET_NUM_2 = Button(area={'cn': (212, 75, 226, 101), 'en': (212, 75, 226, 101), 'jp': (212, 75, 226, 101), 'tw': (212, 75, 226, 101)}, color={'cn': (52, 150, 194), 'en': (52, 150, 194), 'jp': (52, 150, 194), 'tw': (52, 150, 194)}, button={'cn': (212, 75, 226, 101), 'en': (212, 75, 226, 101), 'jp': (212, 75, 226, 101), 'tw': (212, 75, 226, 101)}, file={'cn': './assets/cn/map/FLEET_NUM_2.png', 'en': './assets/en/map/FLEET_NUM_2.png', 'jp': './assets/jp/map/FLEET_NUM_2.png', 'tw': './assets/tw/map/FLEET_NUM_2.png'}) FLEET_PREPARATION = Button(area={'cn': (1013, 558, 1141, 588), 'en': (1048, 569, 1086, 595), 'jp': (1046, 558, 1107, 587), 'tw': (1014, 557, 1142, 588)}, color={'cn': (242, 211, 160), 'en': (241, 201, 148), 'jp': (241, 205, 151), 'tw': (242, 208, 157)}, button={'cn': (980, 549, 1181, 612), 'en': (988, 556, 1145, 606), 'jp': (983, 549, 1185, 612), 'tw': (980, 548, 1180, 612)}, file={'cn': './assets/cn/map/FLEET_PREPARATION.png', 'en': './assets/en/map/FLEET_PREPARATION.png', 'jp': './assets/jp/map/FLEET_PREPARATION.png', 'tw': './assets/tw/map/FLEET_PREPARATION.png'}) FLEET_PREPARATION_CHECK = Button(area={'cn': (1146, 107, 1174, 136), 'en': (1129, 111, 1158, 140), 'jp': (1146, 107, 1174, 136), 'tw': (1145, 106, 1175, 136)}, color={'cn': (180, 98, 111), 'en': (189, 105, 109), 'jp': (180, 98, 111), 'tw': (180, 90, 92)}, button={'cn': (1146, 107, 1174, 136), 'en': (1129, 111, 1158, 140), 'jp': (1146, 107, 1174, 136), 'tw': (1145, 106, 1175, 136)}, file={'cn': './assets/cn/map/FLEET_PREPARATION_CHECK.png', 'en': './assets/en/map/FLEET_PREPARATION_CHECK.png', 'jp': './assets/jp/map/FLEET_PREPARATION_CHECK.png', 'tw': './assets/tw/map/FLEET_PREPARATION_CHECK.png'}) +FLEET_SUPPORT_EMPTY = Button(area={'cn': (454, 470, 538, 540), 'en': (454, 470, 538, 540), 'jp': (454, 470, 538, 540), 'tw': (454, 470, 538, 540)}, color={'cn': (47, 54, 77), 'en': (47, 54, 77), 'jp': (47, 54, 77), 'tw': (47, 54, 77)}, button={'cn': (454, 470, 538, 540), 'en': (454, 470, 538, 540), 'jp': (454, 470, 538, 540), 'tw': (454, 470, 538, 540)}, file={'cn': './assets/cn/map/FLEET_SUPPORT_EMPTY.png', 'en': './assets/cn/map/FLEET_SUPPORT_EMPTY.png', 'jp': './assets/cn/map/FLEET_SUPPORT_EMPTY.png', 'tw': './assets/cn/map/FLEET_SUPPORT_EMPTY.png'}) MAP_CAT_ATTACK = Button(area={'cn': (1237, 103, 1252, 153), 'en': (1237, 103, 1252, 153), 'jp': (1237, 103, 1252, 153), 'tw': (1237, 103, 1252, 153)}, color={'cn': (43, 45, 52), 'en': (43, 45, 52), 'jp': (43, 45, 52), 'tw': (43, 45, 52)}, button={'cn': (1148, 653, 1262, 705), 'en': (1147, 651, 1263, 701), 'jp': (1149, 653, 1261, 704), 'tw': (1148, 653, 1262, 705)}, file={'cn': './assets/cn/map/MAP_CAT_ATTACK.png', 'en': './assets/en/map/MAP_CAT_ATTACK.png', 'jp': './assets/jp/map/MAP_CAT_ATTACK.png', 'tw': './assets/tw/map/MAP_CAT_ATTACK.png'}) MAP_CAT_ATTACK_MIRROR = Button(area={'cn': (147, 145, 187, 157), 'en': (147, 145, 187, 157), 'jp': (147, 145, 187, 157), 'tw': (147, 145, 187, 157)}, color={'cn': (214, 191, 99), 'en': (214, 191, 99), 'jp': (214, 191, 99), 'tw': (214, 191, 99)}, button={'cn': (147, 145, 187, 157), 'en': (147, 145, 187, 157), 'jp': (147, 145, 187, 157), 'tw': (147, 145, 187, 157)}, file={'cn': './assets/cn/map/MAP_CAT_ATTACK_MIRROR.png', 'en': './assets/en/map/MAP_CAT_ATTACK_MIRROR.png', 'jp': './assets/jp/map/MAP_CAT_ATTACK_MIRROR.png', 'tw': './assets/tw/map/MAP_CAT_ATTACK_MIRROR.png'}) MAP_MODE_SWITCH_HARD = Button(area={'cn': (341, 580, 374, 617), 'en': (341, 580, 374, 617), 'jp': (341, 580, 374, 617), 'tw': (341, 580, 374, 617)}, color={'cn': (234, 179, 179), 'en': (234, 179, 179), 'jp': (234, 179, 179), 'tw': (234, 179, 179)}, button={'cn': (341, 580, 374, 617), 'en': (341, 580, 374, 617), 'jp': (341, 580, 374, 617), 'tw': (341, 580, 374, 617)}, file={'cn': './assets/cn/map/MAP_MODE_SWITCH_HARD.png', 'en': './assets/cn/map/MAP_MODE_SWITCH_HARD.png', 'jp': './assets/cn/map/MAP_MODE_SWITCH_HARD.png', 'tw': './assets/cn/map/MAP_MODE_SWITCH_HARD.png'}) diff --git a/module/map/camera.py b/module/map/camera.py index 280a71712..b5a8c7c52 100644 --- a/module/map/camera.py +++ b/module/map/camera.py @@ -114,7 +114,8 @@ class Camera(MapOperation): try: if not self.is_in_map() \ and not self.is_in_strategy_submarine_move() \ - and not self.is_in_strategy_mob_move(): + and not self.is_in_strategy_mob_move() \ + and not self.is_in_strategy_air_attack(): logger.warning('Image to detect is not in_map') raise MapDetectionError('Image to detect is not in_map') self.view.load(self.device.image) diff --git a/module/map/map_operation.py b/module/map/map_operation.py index cffde2948..c06e8e22f 100644 --- a/module/map/map_operation.py +++ b/module/map/map_operation.py @@ -211,6 +211,9 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand if self.handle_2x_book_popup(): continue + if self.handle_submarine_cost_popup(): + continue + # Story skip if self.handle_story_skip(): campaign_timer.reset()