From 659a6a892ef6eb1961eb43b4d7b4b1bf99e2ae4d Mon Sep 17 00:00:00 2001 From: wangli <342970955@qq.com> Date: Mon, 19 Dec 2022 20:45:59 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20MaaCore=E6=B7=BB=E5=8A=A0=E8=82=89?= =?UTF-8?q?=E9=B8=BD=E5=8A=A9=E6=88=98=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/3.1-集成文档.md | 4 +- resource/tasks.json | 173 +++++++++++++++- .../template/Roguelike@RefreshSupportBtn.png | Bin 0 -> 7777 bytes .../RoguelikeRecruitSupportElite0.png | Bin 0 -> 1499 bytes .../RoguelikeRecruitSupportElite1.png | Bin 0 -> 1552 bytes .../RoguelikeRecruitSupportElite2.png | Bin 0 -> 1647 bytes src/MaaCore/Common/AsstBattleDef.h | 22 ++ .../Config/Miscellaneous/BattleDataConfig.cpp | 3 + src/MaaCore/MaaCore.vcxproj | 2 + src/MaaCore/MaaCore.vcxproj.filters | 6 + src/MaaCore/Status.h | 2 + src/MaaCore/Task/Interface/RoguelikeTask.cpp | 6 + .../RoguelikeCustomStartTaskPlugin.cpp | 2 + .../RoguelikeCustomStartTaskPlugin.h | 2 + .../Roguelike/RoguelikeRecruitTaskPlugin.cpp | 113 +++++++++- .../Roguelike/RoguelikeRecruitTaskPlugin.h | 5 + .../RoguelikeRecruitSupportAnalyzer.cpp | 193 ++++++++++++++++++ .../RoguelikeRecruitSupportAnalyzer.h | 34 +++ 18 files changed, 560 insertions(+), 7 deletions(-) create mode 100644 resource/template/Roguelike@RefreshSupportBtn.png create mode 100644 resource/template/RoguelikeRecruitSupportElite0.png create mode 100644 resource/template/RoguelikeRecruitSupportElite1.png create mode 100644 resource/template/RoguelikeRecruitSupportElite2.png create mode 100644 src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp create mode 100644 src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h diff --git a/docs/3.1-集成文档.md b/docs/3.1-集成文档.md index 0947e0e106..f23145dcc6 100644 --- a/docs/3.1-集成文档.md +++ b/docs/3.1-集成文档.md @@ -200,7 +200,9 @@ AsstTaskId ASSTAPI AsstAppendTask(AsstHandle handle, const char* type, const cha // 投资满了自动停止任务,可选,默认 false "squad": string, // 开局分队,可选,例如 "突击战术分队" 等,默认 "指挥分队" "roles": string, // 开局职业组,可选,例如 "先手必胜" 等,默认 "取长补短" - "core_char": string // 开局干员名,可选,仅支持单个干员中!文!名!。默认识别练度自动选择 + "core_char": string, // 开局干员名,可选,仅支持单个干员中!文!名!。默认识别练度自动选择 + "use_support": bool, // 开局干员是否为助战干员,可选,默认 false + "use_nonfriend_support": bool // 是否可以是非好友助战干员,可选,默认 false,use_support为true时有效, } ``` diff --git a/resource/tasks.json b/resource/tasks.json index 16e4499af5..1dbafad9c2 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -8071,7 +8071,8 @@ "Phantom@Roguelike@ChooseDifficulty": {}, "Phantom@Roguelike@ChooseOper": { "next": [ - "Phantom@Roguelike@ChooseOperConfirm" + "Phantom@Roguelike@ChooseOperConfirm", + "Phantom@Roguelike@RecruitSkip" ] }, "Phantom@Roguelike@CloseCollection": { @@ -8257,7 +8258,8 @@ "next": [ "Mizuki@Roguelike@CloseCollectionContinue", "Mizuki@Roguelike@CloseCollectionClose", - "Mizuki@Roguelike@ChooseOperConfirm" + "Mizuki@Roguelike@ChooseOperConfirm", + "Mizuki@Roguelike@RecruitSkip" ] }, "Mizuki@Roguelike@CloseCollection": { @@ -9135,5 +9137,172 @@ "next": [ "RoguelikeWaitBattleStart" ] + }, + "RoguelikeRecruitSupportOcr": { + "algorithm": "OcrDetect", + "doc": "肉鸽招募助战时干员姓名位置,仅使用roi域", + "text": [], + "roi": [ + 120, + 565, + 1160, + 35 + ] + }, + "RoguelikeRecruitSupportOff1": { + "template": "empty.png", + "rectMove_Doc": "肉鸽招募助战时,干员名下方颜色区域相对名字中心的偏移", + "rectMove": [ + -64, + 54, + 45, + 33 + ], + "specificRect_Doc":"元素1为判定是否为好友的r通道阈值", + "specificRect": [ + 70, + 0, + 0, + 0 + ] + }, + "RoguelikeRecruitSupportEliteOff": { + "template": "empty.png", + "rectMove_Doc": "肉鸽招募助战时,精英化状态相对名字中心的偏移", + "rectMove": [ + -26, + -133, + 50, + 41 + ], + "specificRect_Doc":"元素1为二值化的阈值", + "specificRect": [ + 250, + 0, + 0, + 0 + ] + }, + "RoguelikeRecruitSupportLevelOff": { + "template": "empty.png", + "rectMove_Doc": "肉鸽招募助战时,等级相对名字中心的偏移", + "rectMove": [ + 18, + -81, + 33, + 21 + ], + "specificRect_Doc":"元素1为二值化的阈值", + "specificRect": [ + 250, + 0, + 0, + 0 + ] + }, + "RoguelikeRefreshSupportBtnOcr":{ + "algorithm": "OcrDetect", + "text":["更新助战列表"], + "roi":[ + 1097, + 13, + 164, + 38 + ], + "postDelay": 3000, + "postDelay_Doc": "点击刷新后的等待时间" + }, + "RoguelikeChooseSupportBtnOcr":{ + "algorithm": "OcrDetect", + "text":["选择助战"], + "roi":[ + 1011, + 9, + 177, + 47 + ] + }, + "RoguelikeSupportSwipeRight":{ + "algorithm": "JustReturn", + "action": "Swipe", + "postDelay": 1000, + "specificRect": [ + 1000, + 395, + 20, + 20 + ], + "rectMove": [ + 262, + 395, + 20, + 20 + ], + "specialParams": [ + 200, + 1, + 2, + 0 + ] + }, + "RoguelikeSupportSwipeLeft":{ + "algorithm": "JustReturn", + "action": "Swipe", + "postDelay": 1000, + "specificRect": [ + 262, + 395, + 20, + 20 + ], + "rectMove": [ + 1000, + 395, + 20, + 20 + ], + "specialParams": [ + 200, + 1, + 2, + 0 + ] + }, + "Roguelike@RecruitSupportConfirm": { + "algorithm": "OcrDetect", + "action": "ClickSelf", + "text": [ + "招募助战" + ], + "roi": [ + 770, + 500, + 160, + 40 + ], + "preDelay": 200 + }, + "RoguelikeRecruitSupportElite": { + "Doc": "base_task", + "template": "empty.png" + }, + "RoguelikeRecruitSupportElite0": { + "algorithm": "MatchTemplate" + }, + "RoguelikeRecruitSupportElite1": { + "algorithm": "MatchTemplate" + }, + "RoguelikeRecruitSupportElite2": { + "algorithm": "MatchTemplate" + }, + "RoguelikeRecruitSupportEnterFlag":{ + "template": "Roguelike@RefreshSupportBtn.png", + "action": "DoNothing", + "roi": [ + 1094, + 10, + 172, + 47 + ] } } diff --git a/resource/template/Roguelike@RefreshSupportBtn.png b/resource/template/Roguelike@RefreshSupportBtn.png new file mode 100644 index 0000000000000000000000000000000000000000..27a8d9d928e6e87045e174aa23d231bbc7c3bebe GIT binary patch literal 7777 zcmbVR2|UyP|6h(AQ9?zI)NeXR8w8ctM zoJe#u0HCZ&A)zr2SXZDe7KbMwK})r7K|s765@af`2hk&GVDI9!@4H})?&}+4?mJ-M zb|6(1pfUwP7vO|-MFS~Ljs!A-f&~337eR0Tx-AU?{t0n)K!VhMDFoiuy8+Z7x?q8D zDTpKn0)YS(;ZjgVC`Lxn9t@O$$jC@TVA4=A~V*Zx*U zze0lUy1J4O($XFt9#S5%QbZS=G!zbpOG9L&Wn?7j2uZRh!4*xBB#=e^k)VksV_fhg zS3Hpb{3Q`>OLTKZg6O9H<%1LHw^#!C?>NyTCQU(;q@hxfUq1Z_w8Q+yk=$Gy|5R>= zk;Xb=ov;K~G93&3jV0YBx)RBEiT{o0-`)Q~0X?>QdcSr2OIw_reybq6UUjFt@pnM} zC7NvPNy17SV#!1|7Yz2QJKao?U*3=q8ZKD0E78T6NOb&%sT=>843v?Rf&ed>;t6&{ z5Ax-IHvp@NcEut=zgUxm(!VfcCNt=*b`lx&~%6KPG}rfnnb{XfWO0t&>%VzUFeGG*2(^9g`OTlhd_2k z6EIjEO(cl!vlJe0hmeKA>|tmuOcE;##YoD_%E?N?p|%i77)%BWgWB24Vq_Kn8Lvsi zxcy@GpYe9|@rtro8F@Jcj3infq9|!AE2ltjp%o;tc2KmfJp^hC$NnB~;DV=5EwtnR zTh6Zu_bVk3+ITXZEYH7Zgb~*H@0KGT_-7U%(3oG@jRawS(TcSL{e2t%FCy@N^WZPB z9(S>Hq5lKjf5FH^dsh#%3swzBkJkU1PSXFK@np37|7`mIjl6$p`hQKo|BvZ^hZS=d zO~7I4wNe`N%NglkMddG2r2jKtfA;;m0`w1@Ue$gzf7es`#qWxYCD28?(CaNmf7%HE z;M3L7R5PZeESP&z;yxxGrmjhP^wjfkYpb)OF8Bch^)&Ox4NYH|NQ8fJ7MLy#*AGtY z4hv6hyEDY^P*~_7QHVL!n_rdT4=xk&0@|HFW=3#{sU6*Qnod=%N3li%5U5?{Rj*Zp z?z&O<(&oX)TK$seD4AdQKIimu7vPY5jCB0a7pAqL$U5DAaCA)Y^C&PyWLJP`drtK_ z6Qo2<#NXx)1Pt*bYYTF-F!BkwN0YaFn+HXcL!kOx&oSP|T$CmqsPQ9mo*$wPK0VV4 zs735l%5VBXAq=?bONZqXbJ1OVQ7~s*j5sQWT~_L)IFk+g^&e9si_4)yXAeutugZeC z_D5GGS%D>Eg-`s-t*u$P^l@35d2B4cjl#{BC#=~tFI%P(@Q474#+dK7Rs+i*?d*|L zGB}l7L-QodwC{-t=?BaC!&HL$_&fHCtm@u5-_LX2H&M$Mhl>j9&pyYLodhr((JWyC zA8CDl{hRXZOi_~Vw+{l&$Gd@=)V48`xD8TR^9_aTOh?Vesm~{_=`uBBXosE8OV0Za zV_^)j@g@H-MYrKaY*VUFnaMuF_dq@vC0gYgja&9!Fp~>pK9Mzfc;s_#fz3&`;MX=y zkVnh;W#Z1=X7xRuhXu6DA~Mpx&OS|YEs-@mlkV+V<#l0K_^ycrH1MO6Zef7pOpOQq z>!aQ{g5J%k6luFh&h$s#3>7i9R}9_OxsPbwry614FZv4>uL9meU1h(+}nW zA23Xu%39g^s}s*s6$@c=UrZH*XV`Kb&oDj+<($?$+-$cGxi^IxN;VamZ(o5Ck}PWO zTJ*lqJpp)J?uqr3mP~VR6Bq)qaqh+LJ3U_0viDgwC0pjpQM`CArO5FIn8s~C*SgTX z;r2-C!2>4`hzh7bX4>s&3Ae!;`u7us)G z!rC-qbs^-5chPK97{{~KR>|^P=N9XITWWMtG9{v8 z!*%kmdz)$?T{Fyhz-^Hjc;|YPgNR7bgaMt7V_w3gYg}t zM8yyd@sQY^#L(-zWfK8R@DHDfVWrFt#qb3E{3G9zO@m{(P+K|ngNBY!%CM?er=={7 zhYHYEEX*xRiI$TznTjYHQAG@S^>jWF(%MxNoY{W=r3T?kF~DgLa%u}wBjFOu@bqC< ziU#Tmt8?3{vZRw{931KmWg-fljg8-%0r;0ztXIY@#yLvB8RnpD{*+RiCW^CpzkzhI z+2EvgY3;{XcYkISk@~)c?b~#J3fCT2Ocr21iHU@(e&u<;23M_HYucPeoZFf>?PcFF z5b#_*eQs=fLitJTDmxQ{az5C3V^EgHBGFgaE=?ppAX@K|F3y&V?K&EsP|5`YVn1|M zT$!AC8qB}E{AdCI!_LrFt{R-m7FvAD>YS-KQGnz-Y^l-6`CM;Nd@ts@CR4mzK^0+f zmbKND>^E;?SiBVOcZ7qu=+v`WV;^siEYHhOEqZ2ue=+Ii3c<21KOlAU<1N6};>+Z- z_eGkxfd0)#_nt^L*0LZ}KY23wi=DC`2x~Wl8Cm9cCu&3kvf;5WjN(Ma$imn80-QeF z+N*5kU6x2RkIJlh?L8gU+HGT@h-I@1-s@})O3ZBp&OI2D$H?qUJ@?eS7+2xcr!)o-%7|WzTdt*Hs3DBfUyd8jMknV=kSzgjAT#L+O<_RBSU)TEDUA)L_0+KzII5R z^<6So%GB8p%U%N{}op4I;+=uv;8cx?BB<;Cu_gjW7? z)?yzI`>&8CUoYI(5&+EZV5h6xBHwO_wY6HQpC@E)-`R=ax%fysxG}@DYWI82HAHNPC(&R-q~ywUgkNN}#UGXC~yHox#Q zs`|}|^pt6wzUk{d(>z3t+s?Z46=F3s!Y~tCrF4OhF*o2~`Ivd~;X?F1z_!YC>KWs` zHr#HovIslF6(qDyDc`v@ClhdEQKecBR;xik<@l5=1|Ix%^ zULjx10_yC*m)g^gjx!(ijy2TOO35%Mswen=eUfc%dr&;M&Wr)rbqkx-EtLlmLYEs- zXw_Ix;v(K_JOHWv<7b3)JiK=KbC0I=e6^C)n>BwK6l=*u8{9mwgnBWcG+U!vm%KmM z@^MXK)wLhgP|{3PP}GzwufJH6BMC@+S8_UB`d}k)T>j~Bt&)1mdGu|RSX>xwwV9Lr zNrd&hVt>Doj@2gZno=Y?C|%?J!On~5OuMgn@PvEeyYr>1;1j$$H!pDU53ofH4N!g(6-D*=rw+$G9zvU6;ZZoAUQy|y zHIJj|)j1n!U!b8@7NY}am~o6Z7pRwWFl+sR&pol#HKOyqovsl*mn$EQH7|smFUG(@CSNBlqx~r3kF1?;*Ie-s4iqVz`4qF! zH9KL>7L!G=F1fDiwGJ1wfp^jb+j`r#iLc0za;J>hb9gq!R9C!Xh$L|A0ms9evD zpE!JuE5PU``xDY+?!C|EW~k<^mgHjrpp3t7+jX1!J0~#sna9baK0OW+U;CWpB2Ip@ zyFVsbc;s<8$1|By;Jb-Pp(oj9hSnA=kyDYx1Y?V|iMq9}!uQ$dU-r%SR`+IaTUkyI zp{C!M53ZDNZd%Du0S(jv02t~Th6OBt%4N}2gXmuB8|@NO6l)tgsSoz?nM>{OIAd^j zyOSCu=F58esU(d;IjAyy>$t$)5-johxPgM2{eV-<6Z?qnr}u&eGXOvkDzbTbpC4;} zhtd2C!^Lx%`hn=^=I!#qv>yI7nB7s5ja`t#_ET8~=z>?V6a4%w=YcbWJL6woj;5lg zb7ZEig6D5dDiRV!OJfBpwbxtSlQCim>UQ#05g@;=dpx2PI1@ZaXJ1C{JRU` z@w+1<=-Ui|a=EK*$++-N5)W&N;>NXI?j3C0Bh0F}DsiJ2zZ~#Rt{~WS;Q<jxzGS5zEL zyX|!&iejx^*=^jZ04{x9xbzfRSg9u=@Z$TC&aCS%XBh;Ny?~quURvL3Y8SH=F38;T zo{x`#uDQ3>dyO-<>RuP`KeHBer+syI2rErXxTg6HOf^%rtXrx-_EHYzO=`al5j%WT zs28JUSAh8AHQstCYczi`bgo7;$T-I-#|xohJ>b#A6`ZeS1uY^u67D!$a)01_%UXSC zh(EAdjj9zTA9_?gHB60F27K)F!}gL3p!hFI93l9pb-j2|Gw0#G?djmiKgMn}m|ZGz za`ZkUEq^NG>yvGT?@`P1kr^^qNeB78OcWw8i^+X4gqD z<-RGF*sB%l+8hWUOR+SL8a)|qy-RB?EIgv!XX-J*!O*wSyF*v{n@vlZofqz-ntd?@y%>OlngHcexuT(m&Rfzr-#Ejt3l+s7C9)2bS;aAq|Mi-ang97B+72 zqz^WNvHs;7Nf(m60ZG=;a4uK@7^M$TSfbH zHPe^a1A)VmyrFYJMn?n?V&6ER$xx6#a5(#lZqF(`LZbY_2@KqoJzsEl&&nL_PWF1& zq4G(o7~GRZ1MKj$ZjOFE9maq0RpGo3CV}2#nK!p^R%s{Qm zs~BH(z6$V9dYEu9(UGqT|N65%A9>>f*Lk$=r4FC<=*Ixz-N`H9e2p;xNN4TwYnI2X z0ksw>l}9rb!K0HA7u@A{mTD;sepP5S#j^6cH5yriZNG7|e(#-!eYZr%es2qp@SWEZ zC$C?L3!0NkoB7eg2kYl>z@V1s%aw_axIS4rMb1SpYgDimc^t}!_Anpyuo_-plUU)3|Hc|cD;rcDVU)q8iUD{L-L-x$xkNMq{wF#FY-(3FP7C;sn+pI->S8+ z_cvXcQ8LKGH>9S8ewu>d%==BmxdhAYt0)@_!k1jm2etCkr1sII+k3U5XY6V|3|Wt^ z_6^v#x7|Ju^1tAWiWig#_F=~Yrq3-ASpdNn@e4;tYmvV6*Vc2% z4}QLOh3B?@VJ0?BiXTpv4)rSh~( zG_Mvd(YKobDIxPuJRdjQAC16oR8kvH=u0A_=x;utaE-x<1*yV14 z+wCD7YY|>wij<1&Ty6$|S6X7OEzWT4yE}-GHr(XaTcZ!R#)N~xKigg#b>MXRlT>6- zvW4lzs$04HLiF|6-k5Ru$GxR?j?L`MPAGh&3`~63lQ`{mI=AEC#lXNhMqE_j@grT_ zmQj;sCpPA1=7T$HMc(5Sgb3`-2!D5_MCS23O%O@$4fh$27NuR=%TUKh3}b;S8}%FJ zhgG72I(9fpM_Jw4wdoeuwY;A{-;R7>HDl0dDj864_5FOx8?Awyzf?r?=n;-ta&~l@ z^QtVG_-HA)a(6Y+Em)lFgUXGsFR7d950YxPP%O-ANwgMZ`?|RiUgz{MHdp8dgNFKG zX#cRUkrGw0sP4^cd`nuzM6hAdL3U}aNL78_rUA8)u!xER> za`UtIP%a~IQfweVAXBKRdv;nizI_=XYzA97_#C&%TXPuN&1@*WasL`E!tF=U zkK~g_E?p<%<5|TxQL+)i@}MJVbR8 z1U{`z?^wXCwCYrCIZc_%0tX9eDa;GCCv0Y$_h%Pp3rLh9HqyUIj-B|RoRs%)n3BW$~c^HaX@&l*s{>SU9QDX zN?(bdT2X1>m#MnPb3#AxwyizmS*v{*(+Ff1Iy7ZvMGV(nN}+^0eM=kE0i}MK6>@C8 zSfI@{UbV)j+e$hMY^Z24inHkm$cUBQQVL*(F>~8ns?K+u@Ph)z5*|Rf%nXe(wFRGB z6l3l=-?~}TQSu^BgcVdGYbmJGmFr%He4b>aDzr5bP!FFxqkk5za)?h-~mkKE{;!smwpOYSsw8)Bx%Riht0<>NMdx3sR2%IH>@%0a)sYobWrU(!*e)j z8q_m3H~uoRix2RQcW7u=Mdj;);bf*{ Lea#|u8~=X;2Zc1{ literal 0 HcmV?d00001 diff --git a/resource/template/RoguelikeRecruitSupportElite0.png b/resource/template/RoguelikeRecruitSupportElite0.png new file mode 100644 index 0000000000000000000000000000000000000000..355106a199f0b6266ed69406a1cad656e412b2e3 GIT binary patch literal 1499 zcmbVMO=uid93M;2Hl`kIkt&G8KLw3ixs)gp=>qS$qF)e%o#m1>&A&2#p4`5Tzt3)q=J;nvSUMUD!o$0g=KCM(X$f@5H5^nf_hgq*-942ezO3u4$br8Y0*>ttJ%#5RO2m{K88D9+kbVv$Bf zG!!5^001#(C|XW)^jvknr~_SBL0;8{Wi1bZ4l_ey_ma4}2v^~>b*QV1|Cv&qu@I_i zqtQ?rLkf**s%9953UpQ1WsZ>J<$$549K?e?28+Z_*CjpB&R!gyAoiZBN>Az5?$9E~{gezf@X%H4(@{Tn!$*n zdxEh8t2FWvci8n&ji_Nzlf*QPut+@`amC!ap|%ya4a-5yP~ecVWlG#<#dR@6I?fO4 z7|Zh{o0oIhOhz_9p70k!c>@oFtd{M`Thv)hGTW2K|5N_?$mOkty!+-!8ZIdbSaM^Y ztmSS;%#fG6E6)|%RREEbRJSQPNm>b(y4&u3N1#L7s1r{5AL;JEVp?Sl6p=!WN9&&D zq;licqz^lOsQ+}aeRfCB_TYRFB+GOJ@t<^vk$_LGh>xg;HeR~L$J>drRj8C!ziSm9 zIVKMD-TLd3YXxWEF>m6FwO4lGrA*%oKRzv8@PF%n@zKrV`jv6_t<8<~_rLk!{I}a% zxf%EH+Bxs1@I>^Q6&`)Fb?oHr2L~@5eBH~cyU$*%YB#S=TwYyov~I8M`}~h9H$HrO z^Uxb(J3fADb~Qfr=jFL~z8v`I*S!kwVL*JfeUEsE`8>DWEXkms%94sAYRz z-+cf3pB*22xTkw(H^VSJnY5mzqd>p=wr{2XSMbhrbm(%^2YiOPeMj_dVcwj-hher| z#kt8~(%1`3VhhkBQ%DGHm!cVF_ny#&W(fuC6e?gx<9=Q|!?D=Xxcx(hWVoZKh|_fs zP1MJ7X1!#p7Pseqc6SIUfsF#lhIZNUL8x&JUO?y3w#cy!NKn$akw_prX^gX@#6zqq zNW3XY5}Qy3IU$=$BEO4OBt;RWq$m&bauP@ih{xF0!%=abl?Pe?RXgOq4w4bZFNX#OiR6G-1J_(YIt-A;MN#8r`X+OV43r@-lo*s~_@pM(AQsNoHiO28LF{@c%^3>@ z@P=T^1bO1wkQ$6_SU{rd6galyMleds#G`_#cCm&Qh5<5;A3(=M8C~P3%>u?2P+)vG zDXVc_jU{AWQsOZ_shA1gQjlap3#nE-*}kt6vl2zNec$?@_aF5zEiG8SX`ZOyqLct> z?9*V?S`{&Y4z^}x%r>$BKr_m2jWeULB8zKnv`HVi2OV55j z_i67@X?se{Z)|+@6P!K3Ex22L442=Vo7nck*@eCP_EkjY@GXaD=09FcGBazBd~jLr zT02<(W6)V)e(}EOIrjaf$CjSHIJWZsl$*pI*sa!rAW9 zQ@t~n?*dbg&n=LbrdQwk?z2yR&t2e8>PH{=((7AXdi8k!`r5h5j_xP$`6uzN`Q;aD z$E4-b^wm>W1`dVUBLk_`A5M8kUgL#Bw=I41`n&qYLZRaPd1d|iUpIcc_3NJJ?z|(m R{#VqqGpRBC^n=el`!@}H1NQ&` literal 0 HcmV?d00001 diff --git a/resource/template/RoguelikeRecruitSupportElite2.png b/resource/template/RoguelikeRecruitSupportElite2.png new file mode 100644 index 0000000000000000000000000000000000000000..44873180b4b7be9f1561f2216bf7952684b319c2 GIT binary patch literal 1647 zcmbVNYitx%6rSRPwq$wu2u3j(rh!s)p55+t#}3VQx3Hz%?%Hl>8*I|snY+7VcOIRY zcDD@?gS4qeW11Gzh?0PSk%k&p1dLHZqEQmk9~x7O)Wn3O)PVW}5(%;1*;l|+iA-j8 z?#z7mob#RUoU^Zno3}YipDe{N%+V0=x1c+Xt_{V@&~N8l`Mc;=q6a!m3|sN2eHCGE zzw-o!J$PM?v|H`LCSKGO3P@Tvq*98G&=^)zo6>>U11-E8#$;6>e*5$b0+%I$XsZe` zLEQ)Aa$wMat%J=Gaj-}9NJQ-xye7pX0}8YNo>CI3$)^M&W0yy3`!!AA8Hm**5MJ9M z-X09&KFxr*hhj*PVHn))p;$L7a_;D}ILB}t%~aE@i)5>LhU1-;ck;pigL>eZb0#(aIShj%G+J zaqVAB7e3$R0Od9qEcjS57DXwzFs=H2L?f4wCDCSNK!%rcve&if1q zEX{~$S|YDhI3F_3IVlF;)F!KvmNYBw*Z}>&f&yWyMzZLvj&LlGgkzo0Fi1Wo3k_U1NG?}DP5k$?s636?}Ysu>DEgk=T9Ag!x00xzVI_h|{uK#oy#m6<4lLB2sX zEuf09!7mVqGbPIsUk#z?=At6W0FfaXw-b`x5(`LI6;sK%84rlUEDN;XuZexOvibee z|J)xkWYk(9ao=|AhO6=~UVnkYdjYI6X1@ecS2Z2gYwc|Ne?&o4<7hlb=lexas)S*{iA1qwAWLANG%h4^KMIl)rNR zuxI7oZQm^jotI)?mPLjqsn$zecy99AM>pmYqr|#nf+Ot+ojo*istgG?dXng3<_W9ypXX0;h9UY@PKD+p*WA_Ve)s?Q% zuU}hz@PqYdA4u1DAL|Xh=*3QjehEMKIMsMz*`n|E*g`t3)E7N{VBR%-<@T2?~HrLt&3~^ShM-{J>Rf94+fO$KaDSx^`~9$P0uRMDOYi6f_I~>>p`pInKULSY=WlXvF1i2! literal 0 HcmV?d00001 diff --git a/src/MaaCore/Common/AsstBattleDef.h b/src/MaaCore/Common/AsstBattleDef.h index 613a5f3877..0c0462dc49 100644 --- a/src/MaaCore/Common/AsstBattleDef.h +++ b/src/MaaCore/Common/AsstBattleDef.h @@ -221,6 +221,28 @@ namespace asst int level = 0; }; + enum class RoguelikeSupportAnalyzeMode + { + ChooseSupportBtn, + AnalyzeChars, + RefreshSupportBtn + }; + + struct RoguelikeRecruitSupportCharInfo + { + BattleRecruitOperInfo oper_info; + bool is_friend; // 是否为好友助战 + int max_elite; // 两次招募后的实际精英化与等级 + int max_level; + }; + + struct RoguelikeRefreshSupportInfo + { + Rect rect; + bool in_cooldown; + int remain_secs; // 刷新冷却时间 + }; + using BattleAttackRange = std::vector; static constexpr std::string_view RoguelikePhantomThemeName = "Phantom"; diff --git a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp index b5c8d29ad4..c85754c96f 100644 --- a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp +++ b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp @@ -43,6 +43,9 @@ bool asst::BattleDataConfig::parse(const json::value& json) data.location_type = iter->second; } + const auto& rarity = char_data_json.at("rarity").as_integer(); + data.rarity = rarity; + m_chars.emplace(std::move(name), std::move(data)); } for (const auto& [id, points_json] : json.at("ranges").as_object()) { diff --git a/src/MaaCore/MaaCore.vcxproj b/src/MaaCore/MaaCore.vcxproj index b73052e7f1..528535353c 100644 --- a/src/MaaCore/MaaCore.vcxproj +++ b/src/MaaCore/MaaCore.vcxproj @@ -47,6 +47,7 @@ + @@ -162,6 +163,7 @@ + diff --git a/src/MaaCore/MaaCore.vcxproj.filters b/src/MaaCore/MaaCore.vcxproj.filters index 40f5d8e07a..fa782e535e 100644 --- a/src/MaaCore/MaaCore.vcxproj.filters +++ b/src/MaaCore/MaaCore.vcxproj.filters @@ -153,6 +153,9 @@ 源文件\Vision\Roguelike + + 源文件\Vision\Roguelike + 源文件\Config @@ -491,6 +494,9 @@ 源文件\Vision\Roguelike + + 源文件\Vision\Roguelike + 源文件\Config diff --git a/src/MaaCore/Status.h b/src/MaaCore/Status.h index 4fcca06849..e26ccfb171 100644 --- a/src/MaaCore/Status.h +++ b/src/MaaCore/Status.h @@ -42,6 +42,8 @@ namespace asst static inline const std::string RoguelikeCharRarityPrefix = "RoguelikeRarity-"; static inline const std::string RoguelikeCharOverview = "RoguelikeOverview"; static inline const std::string RoguelikeCoreChar = "RoguelikeCoreChar"; + static inline const std::string RoguelikeUseSupport = "RoguelikeUseSupport"; + static inline const std::string RoguelikeUseNonfriendSupport = "RoguelikeUseNonfriendSupport"; static inline const std::string RoguelikeTraderNoLongerBuy = "RoguelikeNoLongerBuy"; static inline const std::string RoguelikeSkillUsagePrefix = "RoguelikeSkillUsage-"; static inline const std::string RoguelikeTeamFullWithoutRookie = "RoguelikeTeamFullWithoutRookie"; diff --git a/src/MaaCore/Task/Interface/RoguelikeTask.cpp b/src/MaaCore/Task/Interface/RoguelikeTask.cpp index b876b359fb..8ac63665f5 100644 --- a/src/MaaCore/Task/Interface/RoguelikeTask.cpp +++ b/src/MaaCore/Task/Interface/RoguelikeTask.cpp @@ -104,6 +104,12 @@ bool asst::RoguelikeTask::set_params(const json::value& params) if (auto core_char_opt = params.find("core_char"); core_char_opt && !core_char_opt->empty()) { m_custom_start_task_ptr->set_custom(RoguelikeCustomType::CoreChar, *core_char_opt); } + if (auto use_support_opt = params.find("use_support"); use_support_opt) { + m_custom_start_task_ptr->set_custom(RoguelikeCustomType::UseSupport, *use_support_opt ? "1" : "0"); + } + if (auto use_nonfriend_opt = params.find("use_nonfriend_support"); use_nonfriend_opt) { + m_custom_start_task_ptr->set_custom(RoguelikeCustomType::UseNonfriendSupport, *use_nonfriend_opt ? "1" : "0"); + } return true; } diff --git a/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.cpp index 4d0d1c5d59..1c6b5f1048 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.cpp @@ -141,6 +141,8 @@ bool asst::RoguelikeCustomStartTaskPlugin::hijack_core_char() sleep(Task.get("RoguelikeCustom-HijackCoChar")->pre_delay); + status()->set_str(Status::RoguelikeUseSupport, m_customs[RoguelikeCustomType::UseSupport]); + status()->set_str(Status::RoguelikeUseNonfriendSupport, m_customs[RoguelikeCustomType::UseNonfriendSupport]); status()->set_str(Status::RoguelikeCoreChar, char_name); return true; } diff --git a/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.h b/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.h index 1d79a11e15..069dc59247 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.h +++ b/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.h @@ -9,6 +9,8 @@ namespace asst Squad, // 分队类型, like 指挥分队, 矛头分队, etc Roles, // 职业类型, like 先手必胜, 稳扎稳打, etc CoreChar, // 首选干员, 干员名 + UseSupport, // 使用助战 + UseNonfriendSupport // 可以使用非好友助战 // CoCoreChar, // 次选干员, 干员名 }; diff --git a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp index 48a8b85c32..a5f94b75d5 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp @@ -1,13 +1,16 @@ #include "RoguelikeRecruitTaskPlugin.h" -#include "Controller.h" -#include "Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h" #include "Config/Miscellaneous/BattleDataConfig.h" #include "Config/Roguelike/RoguelikeRecruitConfig.h" #include "Config/TaskData.h" +#include "Controller.h" #include "Status.h" #include "Task/ProcessTask.h" #include "Utils/Logger.hpp" +#include "Utils/NoWarningCV.h" +#include "Vision/OcrImageAnalyzer.h" +#include "Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h" +#include "Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h" bool asst::RoguelikeRecruitTaskPlugin::verify(AsstMsg msg, const json::value& details) const { @@ -51,8 +54,17 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() { LogTraceFunction; - if (check_core_char()) { - return true; + // 开局干员 + bool use_support = get_status_bool(Status::RoguelikeUseSupport); + if (use_support) { + if (check_support_char()) { + return true; + } + } + else { + if (check_core_char()) { + return true; + } } bool recruited = false; @@ -370,6 +382,93 @@ bool asst::RoguelikeRecruitTaskPlugin::check_char(const std::string& char_name, return false; } +bool asst::RoguelikeRecruitTaskPlugin::check_support_char() +{ + LogTraceFunction; + const int MaxRefreshTimes = 3; + + auto core_opt = status()->get_str(Status::RoguelikeCoreChar); + status()->set_str(Status::RoguelikeCoreChar, ""); + if (core_opt && !core_opt->empty()) { + if (check_support_char(core_opt.value(), MaxRefreshTimes)) return true; + } + return false; +} + +bool asst::RoguelikeRecruitTaskPlugin::check_support_char(const std::string& name, const int max_refresh) +{ + LogTraceFunction; + + // 判断是否存在“选择助战”按钮,存在则点击 + auto screen_choose = ctrler()->get_image(); + auto analyzer_choose = RoguelikeRecruitSupportAnalyzer(screen_choose); + analyzer_choose.set_mode(RoguelikeSupportAnalyzeMode::ChooseSupportBtn); + if (!analyzer_choose.analyze()) { // 非开局招募,无助战按钮 + return false; + } + const auto& choose_btn_rect = analyzer_choose.get_result_choose_support(); + Log.info(__FUNCTION__, "| check choose support btn ", choose_btn_rect); + ctrler()->click(choose_btn_rect); + ProcessTask(*this, { "RoguelikeRecruitSupportEnterFlag" }).run(); // 等待页面加载 + + + // 识别所有干员,应该最多两页 + const int SwipeTimes = 1; + + std::vector satisfied_chars; + for (int retry = 0; retry <= max_refresh; ++retry) { + for (int swipe = 0; swipe < SwipeTimes; ++swipe) { + auto screen_char = ctrler()->get_image(); + auto analyzer_char = RoguelikeRecruitSupportAnalyzer(screen_char); + analyzer_char.set_mode(RoguelikeSupportAnalyzeMode::AnalyzeChars); + analyzer_char.set_required({ name }); + if (analyzer_char.analyze()) { + auto& chars_page = analyzer_char.get_result_char(); + + bool use_nonfriend_support = get_status_bool(Status::RoguelikeUseNonfriendSupport); + auto check_satisfiy = [&use_nonfriend_support](const RoguelikeRecruitSupportCharInfo& chara) { + return chara.is_friend || use_nonfriend_support; + }; + std::copy_if(chars_page.begin(), chars_page.end(), + std::inserter(satisfied_chars, std::begin(satisfied_chars)), check_satisfiy); + + if (satisfied_chars.size()) break; + } + ProcessTask(*this, { "RoguelikeSupportSwipeRight" }).run(); + } + if (satisfied_chars.size()) break; + + // 刷新助战 + if (retry >= max_refresh) break; + auto screen_refresh = ctrler()->get_image(); + auto analyzer_refresh = RoguelikeRecruitSupportAnalyzer(screen_refresh); + analyzer_refresh.set_mode(RoguelikeSupportAnalyzeMode::RefreshSupportBtn); + if (!analyzer_refresh.analyze()) { + ProcessTask(*this, { "Return" }).run(); + return false; + } + auto& refresh_info = analyzer_refresh.get_result_refresh(); + if (refresh_info.in_cooldown) sleep(refresh_info.remain_secs * 1000); + ctrler()->click(refresh_info.rect); + sleep(Task.get("RoguelikeRefreshSupportBtnOcr")->post_delay); + ProcessTask(*this, { "RoguelikeSupportSwipeLeft" }).run(); + } + if (satisfied_chars.empty()) { + // 找不到需要的助战干员,返回正常招募逻辑 + Log.info(__FUNCTION__, "| can't find support char `", name, "`"); + ProcessTask(*this, { "Return" }).run(); + return false; + } + + // 点击干员并记录信息 + auto& satisfied_char = satisfied_chars.front(); + select_oper(satisfied_char.oper_info); + + // 确认选择 + ProcessTask(*this, { "Roguelike@RecruitSupportConfirm" }).run(); + return true; +} + bool asst::RoguelikeRecruitTaskPlugin::check_core_char() { LogTraceFunction; @@ -400,6 +499,12 @@ void asst::RoguelikeRecruitTaskPlugin::select_oper(const BattleRecruitOperInfo& status()->set_str(Status::RoguelikeCharOverview, overview.to_string()); } +inline bool asst::RoguelikeRecruitTaskPlugin::get_status_bool(const std::string& key) +{ + auto value_opt = status()->get_str(key); + return value_opt && value_opt->size() == 1 && value_opt.value()[0] == '1'; +} + void asst::RoguelikeRecruitTaskPlugin::swipe_to_the_left_of_operlist(int loop_times) { for (int i = 0; i != loop_times; ++i) { diff --git a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h index 3466badb98..d366343b3b 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h +++ b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h @@ -29,6 +29,9 @@ namespace asst void slowly_swipe(bool to_left); private: + bool check_support_char(); + bool check_support_char(const std::string& name, const int max_refresh); + bool check_core_char(); BattleRole get_oper_role(const std::string& name); bool is_oper_melee(const std::string& name); @@ -40,5 +43,7 @@ namespace asst // 返回值: 招募结果 (true: 成功; false: 失败) bool check_char(const std::string& char_name, bool is_rtl = false); void select_oper(const BattleRecruitOperInfo& oper); + + inline bool get_status_bool(const std::string& key); }; } diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp new file mode 100644 index 0000000000..1470f542ea --- /dev/null +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp @@ -0,0 +1,193 @@ +#include "RoguelikeRecruitSupportAnalyzer.h" + +#include +#include + +#include "Config/Miscellaneous/BattleDataConfig.h" +#include "Config/TaskData.h" +#include "Controller.h" +#include "Utils/Logger.hpp" +#include "Utils/NoWarningCV.h" +#include "Vision/MatchImageAnalyzer.h" +#include "Vision/OcrImageAnalyzer.h" +#include "Vision/OcrWithFlagTemplImageAnalyzer.h" + +bool asst::RoguelikeRecruitSupportAnalyzer::analyze() +{ + LogTraceFunction; + + if (m_mode == RoguelikeSupportAnalyzeMode::ChooseSupportBtn) { + // 识别“选择助战” + OcrImageAnalyzer analyzer(m_image); + const auto& task = Task.get("RoguelikeChooseSupportBtnOcr"); + analyzer.set_roi(task->roi); + analyzer.set_required(task->text); + if (!analyzer.analyze()) return false; + m_choose_support_result = analyzer.get_result().front().rect; + Log.info(__FUNCTION__, "| ChooseSupportBtn"); + return true; + } + else if (m_mode == RoguelikeSupportAnalyzeMode::AnalyzeChars) { + // 识别干员 + OcrImageAnalyzer analyzer(m_image); + analyzer.set_roi(Task.get("RoguelikeRecruitSupportOcr")->roi); + analyzer.set_required(m_required); + if (!analyzer.analyze()) return false; + + const auto& char_name_rects = analyzer.get_result(); + const auto& task_off1 = Task.get("RoguelikeRecruitSupportOff1"); + const auto& task_off_elite = Task.get("RoguelikeRecruitSupportEliteOff"); + const auto& task_off_level = Task.get("RoguelikeRecruitSupportLevelOff"); + m_char_result.clear(); + for (const auto& char_rect : char_name_rects) { + Rect name_rect = char_rect.rect; + name_rect.x = name_rect.x + name_rect.width / 2; + + // 识别等级区域 + Rect level_roi = name_rect.move(task_off_level->rect_move); + int char_level = match_level(level_roi); + if (char_level <= 0) { + // 等级识别失败,可能希望不足,舍弃结果 + Log.info(__FUNCTION__, "| match_level failed ", char_rect.text, char_level); + continue; + } + + // 判断是否为好友助战 + Rect color_roi = name_rect.move(task_off1->rect_move); + bool is_friend = judge_is_friend(color_roi, task_off1->specific_rect.x); + + // 匹配精英化状态 + Rect elite_roi = name_rect.move(task_off_elite->rect_move); + int char_elite = match_elite(elite_roi, task_off_elite->specific_rect.x); + + RoguelikeRecruitSupportCharInfo char_info { + { char_rect.text, char_rect.rect, char_elite, char_level }, is_friend, char_elite, char_level + }; + + // 助战招募最多精一 + int rarity = BattleData.get_rarity(char_rect.text); + if (rarity == 0) { + // 非干员名,可能是屏幕边角,舍弃结果 + Log.info(__FUNCTION__, "| can't get rarity of `", char_rect.text, "`"); + continue; + } + else if (rarity <= 3) { + // 无需处理 + } + else { + if (char_elite == 2) { + char_info.oper_info.elite = 1; + char_info.oper_info.level = 60 + 10 * (rarity - 4); + } + } + + Log.info(__FUNCTION__, "| AnalyzeChars append ", char_info.oper_info.name, char_info.oper_info.rect, + char_elite, char_level, is_friend, char_info.max_elite, char_info.max_level); + m_char_result.push_back(char_info); + } + return !m_char_result.empty(); + } + else if (m_mode == RoguelikeSupportAnalyzeMode::RefreshSupportBtn) { + // 识别“更新助战列表” + OcrImageAnalyzer analyzer(m_image); + const auto& task = Task.get("RoguelikeRefreshSupportBtnOcr"); + analyzer.set_roi(task->roi); + if (!analyzer.analyze()) return false; + const auto& results = analyzer.get_result(); + for (const auto& result : results) { + Log.info(__FUNCTION__, "| RefreshSupportBtn parse `", result.text, "`", result.score); + if (std::count(task->text.begin(), task->text.end(), result.text)) { + m_refresh_result = { result.rect, false, 0 }; + return true; + } + else if (std::regex_match(result.text, std::regex("[0-9]{2}:[0-9]{2}:[0-9]{2}"))) { + const auto& hour = std::atoi(result.text.substr(2).c_str()); + const auto& min = std::atoi(result.text.substr(3, 2).c_str()); + const auto& sec = std::atoi(result.text.substr(7, 2).c_str()); + m_refresh_result = { result.rect, true, 3600 * hour + 60 * min + sec }; + return true; + } + } + return false; + } + + return false; +} + +int asst::RoguelikeRecruitSupportAnalyzer::match_elite(const Rect& roi, const int threshold) +{ + LogTraceFunction; + + static const std::unordered_map EliteTaskName = { + { "RoguelikeRecruitSupportElite0", 0 }, + { "RoguelikeRecruitSupportElite1", 1 }, + { "RoguelikeRecruitSupportElite2", 2 }, + }; + + int elite_result = 0; + double max_score = 0; + + // 二值化,避免干员立绘影响识别,精英化标识主体颜色是纯白色 + cv::Mat bin_img; + cv::threshold(m_image, bin_img, threshold, 255, cv::THRESH_BINARY); + + for (const auto& [task_name, elite] : EliteTaskName) { + MatchImageAnalyzer analyzer(bin_img); + auto task_ptr = Task.get(task_name); + analyzer.set_task_info(task_ptr); + analyzer.set_roi(roi); + analyzer.set_threshold(0.3); + + if (!analyzer.analyze()) { + continue; + } + + double score = analyzer.get_result().score; + if (score > max_score) { + max_score = score; + elite_result = elite; + } + } + + Log.info(__FUNCTION__, "| ", roi, elite_result, max_score); + return elite_result; +} + +int asst::RoguelikeRecruitSupportAnalyzer::judge_is_friend(const Rect& roi, const double threshold) +{ + LogTraceFunction; + + const auto& color_img = m_image(make_rect(roi)); // 截取颜色部分 + + // 用red通道判断是橙色还是蓝色,橙色为好友助战 + std::vector channels; + split(color_img, channels); + const auto& red_channel = channels.at(2); + auto r_mean = cv::mean(red_channel)[0]; + bool is_friend = (r_mean > threshold); + + Log.info(__FUNCTION__, "| ", roi, r_mean, threshold); + + return is_friend; +} + +int asst::RoguelikeRecruitSupportAnalyzer::match_level(const Rect& roi) +{ + LogTraceFunction; + + OcrWithPreprocessImageAnalyzer analyzer(m_image); + analyzer.set_task_info("NumberOcrReplace"); + analyzer.set_roi(roi); + analyzer.set_expansion(1); + + if (!analyzer.analyze()) { + return -1; + } + + Log.info(__FUNCTION__, "| ", roi, "`", analyzer.get_result().front().text, "`"); + const std::string& level = analyzer.get_result().front().text; + if (level.empty() || !ranges::all_of(level, [](char c) -> bool { return std::isdigit(c); })) { + return 0; + } + return std::stoi(level); +} diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h new file mode 100644 index 0000000000..bb2e319716 --- /dev/null +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h @@ -0,0 +1,34 @@ +#pragma once +#include "Vision/AbstractImageAnalyzer.h" + +#include "Common/AsstBattleDef.h" + +namespace asst +{ + class RoguelikeRecruitSupportAnalyzer final : public AbstractImageAnalyzer + { + public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + virtual ~RoguelikeRecruitSupportAnalyzer() noexcept override = default; + + bool analyze() override; + + void set_mode(RoguelikeSupportAnalyzeMode mode) noexcept { m_mode = mode; } + void set_required(std::vector required) noexcept { m_required = required; } + + const auto& get_result_choose_support() { return m_choose_support_result; } + const auto& get_result_char() { return m_char_result; } + const auto& get_result_refresh() { return m_refresh_result; } + + private: + RoguelikeSupportAnalyzeMode m_mode; + Rect m_choose_support_result; + RoguelikeRefreshSupportInfo m_refresh_result; + std::vector m_char_result; + std::vector m_required; + + int judge_is_friend(const Rect& roi, const double r_threshold); + int match_elite(const Rect& roi, const int threshold); + int match_level(const Rect& roi); + }; +} From 34b08a661faf8b9d43c5455521e98f9372a87faa Mon Sep 17 00:00:00 2001 From: wangli <342970955@qq.com> Date: Tue, 20 Dec 2022 10:21:48 +0800 Subject: [PATCH 2/2] change specificRect to specialParmas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完善外语继承文档的肉鸽助战参数 task.json 助战swipe继承base_task 助战相关结构体添加初始值 修改变量定义语法风格 修改Return task为封装函数 更改精英化图标识别阈值,合并识别task模板 --- docs/en-us/3.1-INTEGRATION.md | 4 +- docs/ja-jp/3.1-統合ドキュメント.md | 4 +- docs/zh-tw/3.1-集成文件.md | 4 +- resource/tasks.json | 80 ++++--------------- src/MaaCore/Common/AsstBattleDef.h | 10 +-- .../Roguelike/RoguelikeRecruitTaskPlugin.cpp | 10 +-- .../RoguelikeRecruitSupportAnalyzer.cpp | 6 +- 7 files changed, 37 insertions(+), 81 deletions(-) diff --git a/docs/en-us/3.1-INTEGRATION.md b/docs/en-us/3.1-INTEGRATION.md index 087bf813c1..0340e875d7 100644 --- a/docs/en-us/3.1-INTEGRATION.md +++ b/docs/en-us/3.1-INTEGRATION.md @@ -188,7 +188,9 @@ Appends a task. // Stop the task when investment is full, optional, by default false "squad": string, // Squad name like "assault squad", optional, by default "Default Squad" "roles": string, // Roles, optional - "core_char": string // Operator name, optional. Will recognize auto-selection of levels. + "core_char": string, // Operator name, optional. Will recognize auto-selection of levels. + "use_support": bool, // Whether "core_char" is a support unit, optional, by default false + "use_nonfriend_support": bool // Whether support unit can be non-friend support unit, optional, by default false, valid when use_support=true } ``` diff --git a/docs/ja-jp/3.1-統合ドキュメント.md b/docs/ja-jp/3.1-統合ドキュメント.md index 646a27d27f..b898f9ae33 100644 --- a/docs/ja-jp/3.1-統合ドキュメント.md +++ b/docs/ja-jp/3.1-統合ドキュメント.md @@ -187,7 +187,9 @@ TaskId ASSTAPI AsstAppendTask(AsstHandle handle, const char* type, const char* p // 投資額が一杯になったら動作停止, オプション, デフォルトは false "squad": string, // "突撃戦術分隊" などの分隊指定, オプション, デフォルトは "デフォルト" "roles": string, // 召集組合, オプション - "core_char": string // オペレーター名, オプション. レベルに応じて自動で選択します. + "core_char": string, // オペレーター名, オプション. レベルに応じて自動で選択します. + "use_support": bool, // 開始役員("core_char")がアシストであるかどうかは、オプション、デフォルトは false + "use_nonfriend_support": bool // 非フレンドのアシスタントであるかどうかを、オプション、デフォルトの false、use_supportが true の場合に有効です。 } ``` diff --git a/docs/zh-tw/3.1-集成文件.md b/docs/zh-tw/3.1-集成文件.md index b70c11f54e..945e3775ae 100644 --- a/docs/zh-tw/3.1-集成文件.md +++ b/docs/zh-tw/3.1-集成文件.md @@ -187,7 +187,9 @@ TaskId ASSTAPI AsstAppendTask(AsstHandle handle, const char* type, const char* p // 投資滿了自動停止任務,可選,預設 false "squad": string, // 開局分隊,可選,例如 "突擊戰術分隊" 等,預設 "指揮分隊" "roles": string, // 開局職業組,可選,例如 "先手必勝" 等,預設 "取長補短" - "core_char": string // 開局幹員名,可選,僅支援單個幹員中!文!名! 。預設辨識練度自動選擇 + "core_char": string, // 開局幹員名,可選,僅支援單個幹員中!文!名! 。預設辨識練度自動選擇 + "use_support": bool, // 開局幹員是否為助戰幹員,可選,預設 false + "use_nonfriend_support": bool // 是否可以是非好友助戰幹員,可選,預設 false,use_support為true時有效 } ``` diff --git a/resource/tasks.json b/resource/tasks.json index 1dbafad9c2..c730086fd2 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -9158,12 +9158,9 @@ 45, 33 ], - "specificRect_Doc":"元素1为判定是否为好友的r通道阈值", - "specificRect": [ - 70, - 0, - 0, - 0 + "specialParams_Doc":"判定是否为好友的r通道阈值", + "specialParams": [ + 70 ] }, "RoguelikeRecruitSupportEliteOff": { @@ -9175,12 +9172,9 @@ 50, 41 ], - "specificRect_Doc":"元素1为二值化的阈值", - "specificRect": [ - 250, - 0, - 0, - 0 + "specialParams_Doc":"二值化的阈值", + "specialParams": [ + 250 ] }, "RoguelikeRecruitSupportLevelOff": { @@ -9191,13 +9185,6 @@ -81, 33, 21 - ], - "specificRect_Doc":"元素1为二值化的阈值", - "specificRect": [ - 250, - 0, - 0, - 0 ] }, "RoguelikeRefreshSupportBtnOcr":{ @@ -9223,50 +9210,12 @@ ] }, "RoguelikeSupportSwipeRight":{ - "algorithm": "JustReturn", - "action": "Swipe", - "postDelay": 1000, - "specificRect": [ - 1000, - 395, - 20, - 20 - ], - "rectMove": [ - 262, - 395, - 20, - 20 - ], - "specialParams": [ - 200, - 1, - 2, - 0 - ] + "baseTask": "SwipeToTheRight", + "postDelay": 1000 }, "RoguelikeSupportSwipeLeft":{ - "algorithm": "JustReturn", - "action": "Swipe", - "postDelay": 1000, - "specificRect": [ - 262, - 395, - 20, - 20 - ], - "rectMove": [ - 1000, - 395, - 20, - 20 - ], - "specialParams": [ - 200, - 1, - 2, - 0 - ] + "baseTask": "SwipeToTheLeft", + "postDelay": 1000 }, "Roguelike@RecruitSupportConfirm": { "algorithm": "OcrDetect", @@ -9284,16 +9233,17 @@ }, "RoguelikeRecruitSupportElite": { "Doc": "base_task", - "template": "empty.png" + "template": "empty.png", + "templThreshold": 0.6 }, "RoguelikeRecruitSupportElite0": { - "algorithm": "MatchTemplate" + "baseTask": "RoguelikeRecruitSupportElite" }, "RoguelikeRecruitSupportElite1": { - "algorithm": "MatchTemplate" + "baseTask": "RoguelikeRecruitSupportElite" }, "RoguelikeRecruitSupportElite2": { - "algorithm": "MatchTemplate" + "baseTask": "RoguelikeRecruitSupportElite" }, "RoguelikeRecruitSupportEnterFlag":{ "template": "Roguelike@RefreshSupportBtn.png", diff --git a/src/MaaCore/Common/AsstBattleDef.h b/src/MaaCore/Common/AsstBattleDef.h index 0c0462dc49..6236777431 100644 --- a/src/MaaCore/Common/AsstBattleDef.h +++ b/src/MaaCore/Common/AsstBattleDef.h @@ -231,16 +231,16 @@ namespace asst struct RoguelikeRecruitSupportCharInfo { BattleRecruitOperInfo oper_info; - bool is_friend; // 是否为好友助战 - int max_elite; // 两次招募后的实际精英化与等级 - int max_level; + bool is_friend = false; // 是否为好友助战 + int max_elite = 0; // 两次招募后的实际精英化与等级 + int max_level = 0; }; struct RoguelikeRefreshSupportInfo { Rect rect; - bool in_cooldown; - int remain_secs; // 刷新冷却时间 + bool in_cooldown = false; + int remain_secs = 0; // 刷新冷却时间 }; using BattleAttackRange = std::vector; diff --git a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp index a5f94b75d5..81428dc988 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp @@ -401,7 +401,7 @@ bool asst::RoguelikeRecruitTaskPlugin::check_support_char(const std::string& nam // 判断是否存在“选择助战”按钮,存在则点击 auto screen_choose = ctrler()->get_image(); - auto analyzer_choose = RoguelikeRecruitSupportAnalyzer(screen_choose); + RoguelikeRecruitSupportAnalyzer analyzer_choose(screen_choose); analyzer_choose.set_mode(RoguelikeSupportAnalyzeMode::ChooseSupportBtn); if (!analyzer_choose.analyze()) { // 非开局招募,无助战按钮 return false; @@ -419,7 +419,7 @@ bool asst::RoguelikeRecruitTaskPlugin::check_support_char(const std::string& nam for (int retry = 0; retry <= max_refresh; ++retry) { for (int swipe = 0; swipe < SwipeTimes; ++swipe) { auto screen_char = ctrler()->get_image(); - auto analyzer_char = RoguelikeRecruitSupportAnalyzer(screen_char); + RoguelikeRecruitSupportAnalyzer analyzer_char(screen_char); analyzer_char.set_mode(RoguelikeSupportAnalyzeMode::AnalyzeChars); analyzer_char.set_required({ name }); if (analyzer_char.analyze()) { @@ -441,10 +441,10 @@ bool asst::RoguelikeRecruitTaskPlugin::check_support_char(const std::string& nam // 刷新助战 if (retry >= max_refresh) break; auto screen_refresh = ctrler()->get_image(); - auto analyzer_refresh = RoguelikeRecruitSupportAnalyzer(screen_refresh); + RoguelikeRecruitSupportAnalyzer analyzer_refresh(screen_refresh); analyzer_refresh.set_mode(RoguelikeSupportAnalyzeMode::RefreshSupportBtn); if (!analyzer_refresh.analyze()) { - ProcessTask(*this, { "Return" }).run(); + click_return_button(); return false; } auto& refresh_info = analyzer_refresh.get_result_refresh(); @@ -456,7 +456,7 @@ bool asst::RoguelikeRecruitTaskPlugin::check_support_char(const std::string& nam if (satisfied_chars.empty()) { // 找不到需要的助战干员,返回正常招募逻辑 Log.info(__FUNCTION__, "| can't find support char `", name, "`"); - ProcessTask(*this, { "Return" }).run(); + click_return_button(); return false; } diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp index 1470f542ea..a6ec0e5370 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp @@ -54,11 +54,11 @@ bool asst::RoguelikeRecruitSupportAnalyzer::analyze() // 判断是否为好友助战 Rect color_roi = name_rect.move(task_off1->rect_move); - bool is_friend = judge_is_friend(color_roi, task_off1->specific_rect.x); + bool is_friend = judge_is_friend(color_roi, task_off1->special_params.front()); // 匹配精英化状态 Rect elite_roi = name_rect.move(task_off_elite->rect_move); - int char_elite = match_elite(elite_roi, task_off_elite->specific_rect.x); + int char_elite = match_elite(elite_roi, task_off_elite->special_params.front()); RoguelikeRecruitSupportCharInfo char_info { { char_rect.text, char_rect.rect, char_elite, char_level }, is_friend, char_elite, char_level @@ -136,7 +136,7 @@ int asst::RoguelikeRecruitSupportAnalyzer::match_elite(const Rect& roi, const in auto task_ptr = Task.get(task_name); analyzer.set_task_info(task_ptr); analyzer.set_roi(roi); - analyzer.set_threshold(0.3); + analyzer.set_threshold(Task.get(task_name)->templ_threshold); if (!analyzer.analyze()) { continue;