From 6fa1ea2882f941464918665abbd12709281d1aa3 Mon Sep 17 00:00:00 2001 From: MistEO Date: Mon, 17 Jan 2022 01:11:31 +0800 Subject: [PATCH] =?UTF-8?q?feat.=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=B9=B2=E5=91=98=E8=81=8C=E4=B8=9A=E5=8F=8A=E8=B4=B9=E7=94=A8?= =?UTF-8?q?=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/tasks.json | 150 +++++++++++++++ resource/template/BattleOperRoleCaster.png | Bin 0 -> 15869 bytes resource/template/BattleOperRoleMedic.png | Bin 0 -> 16009 bytes resource/template/BattleOperRolePioneer.png | Bin 0 -> 16004 bytes resource/template/BattleOperRoleSniper.png | Bin 0 -> 1403 bytes resource/template/BattleOperRoleSpecial.png | Bin 0 -> 16127 bytes resource/template/BattleOperRoleSupport.png | Bin 0 -> 15726 bytes resource/template/BattleOperRoleTank.png | Bin 0 -> 15945 bytes resource/template/BattleOperRoleWarrior.png | Bin 0 -> 15630 bytes resource/template/BattleOpersFlag.png | Bin 0 -> 14766 bytes src/MeoAssistant/AbstractImageAnalyzer.cpp | 7 +- src/MeoAssistant/AbstractImageAnalyzer.h | 4 +- src/MeoAssistant/Assistant.cpp | 15 +- src/MeoAssistant/AsstDef.h | 18 +- src/MeoAssistant/BattleImageAnalyzer.cpp | 173 ++++++++++++++++++ src/MeoAssistant/BattleImageAnalyzer.h | 57 ++++++ src/MeoAssistant/BattleTask.cpp | 11 ++ src/MeoAssistant/BattleTask.h | 16 ++ src/MeoAssistant/HashImageAnalyzer.cpp | 161 ++++++++++++++++ src/MeoAssistant/HashImageAnalyzer.h | 39 ++++ src/MeoAssistant/MatchImageAnalyzer.cpp | 2 +- src/MeoAssistant/MeoAssistant.vcxproj | 6 + src/MeoAssistant/MeoAssistant.vcxproj.filters | 18 ++ src/MeoAssistant/MultiMatchImageAnalyzer.cpp | 2 +- src/MeoAssistant/ProcessTaskImageAnalyzer.h | 2 +- src/MeoAssistant/TaskData.cpp | 21 ++- 26 files changed, 681 insertions(+), 21 deletions(-) create mode 100644 resource/template/BattleOperRoleCaster.png create mode 100644 resource/template/BattleOperRoleMedic.png create mode 100644 resource/template/BattleOperRolePioneer.png create mode 100644 resource/template/BattleOperRoleSniper.png create mode 100644 resource/template/BattleOperRoleSpecial.png create mode 100644 resource/template/BattleOperRoleSupport.png create mode 100644 resource/template/BattleOperRoleTank.png create mode 100644 resource/template/BattleOperRoleWarrior.png create mode 100644 resource/template/BattleOpersFlag.png create mode 100644 src/MeoAssistant/BattleImageAnalyzer.cpp create mode 100644 src/MeoAssistant/BattleImageAnalyzer.h create mode 100644 src/MeoAssistant/BattleTask.cpp create mode 100644 src/MeoAssistant/BattleTask.h create mode 100644 src/MeoAssistant/HashImageAnalyzer.cpp create mode 100644 src/MeoAssistant/HashImageAnalyzer.h diff --git a/resource/tasks.json b/resource/tasks.json index 38edf3af43..d0a197a8c0 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -3007,5 +3007,155 @@ "ClueGiveToNextPage", "ClueGiveTo1stConfirm" ] + }, + "BattleOpersFlag": { + "templThreshold": 0.85, + "roi": [ + 0, + 588, + 1280, + 18 + ], + "maskRange": [ + 1, + 255 + ] + }, + "BattleOperClickRange": { + "algorithm": "JustReturn", + "rectMove": [ + -59, + 6, + 98, + 120 + ] + }, + "BattleOperAvailable": { + "template": "empty.png", + "specialThreshold": 100, + "specialThreshold_Doc": "用于做V通道平均值阈值" + }, + "BattleOperRoleRange": { + "algorithm": "JustReturn", + "rectMove": [ + -41, + 6, + 31, + 25 + ] + }, + "BattleOperRoleCaster": { + }, + "BattleOperRoleMedic": { + }, + "BattleOperRolePioneer": { + }, + "BattleOperRoleSniper": { + }, + "BattleOperRoleSpecial": { + }, + "BattleOperRoleSupport": { + }, + "BattleOperRoleTank": { + }, + "BattleOperRoleWarrior": { + }, + "BattleOperCostRange": { + "algorithm": "hash", + "hash": [ + ], + "rectMove": [ + -10, + 12, + 30, + 17 + ] + }, + "BattleOperCostChannelH": { + "algorithm": "hash", + "hash": [ + ], + "maskRange": [ + 0, + 255 + ] + }, + "BattleOperCostChannelS": { + "algorithm": "hash", + "hash": [ + ], + "maskRange": [ + 0, + 50 + ] + }, + "BattleOperCostChannelV": { + "algorithm": "hash", + "hash": [ + ], + "maskRange": [ + 80, + 255 + ] + }, + "BattleOperCost0": { + "algorithm": "hash", + "hash": [ + "1ff83ffc3c3e780e700ee007e007e007e007e007e007f00f780e7c1e3ffc1ff8" + ] + }, + "BattleOperCost1": { + "algorithm": "hash", + "hash": [ + "00ff03ff1fff7fffffffff3f003f003f003f003f003f003f003f003f003f003f" + ] + }, + "BattleOperCost2": { + "algorithm": "hash", + "hash": [ + "3ffe7ffefc3ff81fe01f001f001f003e007e01fc07f81fe03fc07e00ffffffff" + ] + }, + "BattleOperCost3": { + "algorithm": "hash", + "hash": [ + "3ffe7ffffc3ff81fe01f001f03fe07fc07fe003f001fe01fe01ff83fffff7ffe" + ] + }, + "BattleOperCost4": { + "algorithm": "hash", + "hash": [ + "007c00fc01fc03fc03fc0fbc1f3c3f3c7e3cfc3cffffffffffff003c003c003c" + ] + }, + "BattleOperCost5": { + "algorithm": "hash", + "hash": [ + "3ffe7c007c0078007ff87ffe7ffe7c3f781f0007e007f81ffc1fffff7ffe3ffc" + ] + }, + "BattleOperCost6": { + "algorithm": "hash", + "hash": [ + "03c003800f001f003e003ff87ffc7c3ef00ee00fe007e00ff00e7c3e3ffc1ff8" + ] + }, + "BattleOperCost7": { + "algorithm": "hash", + "hash": [ + "ffffffff003e007e007c00f800f801f001f003c003c00f801f001f003e003e00" + ] + }, + "BattleOperCost8": { + "algorithm": "hash", + "hash": [ + "3ffc7ffe7c3e700e700e7c7e7ffc3ffc7ffefefef00fe00ff00ff03ffffe7ffe" + ] + }, + "BattleOperCost9": { + "algorithm": "hash", + "hash": [ + "3ffc7ffe7c3ff00ff007f007f00ffc0f7fff3ffe1ffe00fc01f803f00ff00fc0" + ] } } diff --git a/resource/template/BattleOperRoleCaster.png b/resource/template/BattleOperRoleCaster.png new file mode 100644 index 0000000000000000000000000000000000000000..46156e08f8202e1fbdd424c3d9d32ab0f5ace490 GIT binary patch literal 15869 zcmeI3YgiM<8pqdatwoebTA?TyLv3j#JRi8?urcNgyE{E?T5u z)hdEFsESG{&jG5Xs3@Rl(Y7L5Jz(*I7NzMqsHj0vDbNid42W)fo_^dsPqHsF|NlF) zzjjEUrI|`5^+|Lw zUSuE3fTVC?5Q(qSz$B)dCq+zmXOZTx-RMja z%_I>Idem_mdEe9|Vk#_$6|hpLMSb*9eQKFXr_#z)FBo#L`-KCeYa$d5jy>vp6^c=Z z*6MuYQ3L}>M|En$64Wp?1lFqJG-B8{9=#Rk(e7S{NQV|@SSCx)3+E0SkQdl2J^i-6 zQs`eHE_$yVz6KWQRGKiAO3v%AvY=-ONqoMgs^*Yf79&cDO0Rv7OC4z$TJNE3ut21P zc?=p_LNq#s?iog-ahMDa!*e!`#i7xFB>l<}IfN<+B25@A2b<&00&@V$kvY))BN6FD ze<=&71@cJ59H~m95b1b`LKF>C)yiltb*K?g4ptS1uac`Y=(J#-JC{0IGvMm0G>*Sg zs}m{3u)lzZHn<^(gySim!&>3t!KBccOff~wL?brK(}O~nvLqgC7K^@uDei+P6oU5W zQVUe#ILmGsNGvD370yob!b|H&H)?p z|1MyEe!%|XG4wK6`LYewpQ=yw;v`y?RHqkdV4rBTCjQD^@6S5iHZnbzYB~Mo$nb^e zZymlq&nou$_2gO(H4a(}=)6RIYe45xpKBW$xnCb{SyOpjpMDO5hJL7Y%kO{{>>g>2 zc)1nq9%&tNE>a>o9%Ep%5zTd5Aijv&MU%fkf-x_`Bn zs${SSJR>_sHAVnN56}p73>f>D3ix;4x03y>=)(f~oI)LZP#M0+gB#%=4uINRn210DjtiuL8RNJx5rG067f1s$#&KaH0tGlOkOpRquDae*{2V;mPIB2a+i0%>5zI4(>?pa91O(!h*yT$qSJ0gel#ff?ht zFcE!Z5y~SMzj->v)ZEpad;gaM7q=tRZ`-?G z3XTm}x=66i*Wc&6v}C`MBk!%O-Lrpg>i1uL?Z0DR!Q{D$O_wLxX4qto^R4;N?vw9= z8-#0SMzI2dNZ(%Y$SiJrRJlF#w9~Qe37yNPy1#YtTdVpcqp_2+v!^qqzCF(@i51ms z4OsA>mCWMuRCU{y-dm;Sm0NDUstFd@Tkqc=_Kt#ipuYQ@=|S&LNyeKshdV*={j&(ZQ!d-bxA%F1F$oLuFZ|?uKuC zGLp5Xc9wj{g_X?AJ=WxD`WVKhnz8NGolfiD_K7)r$;psfGk4XDo*P|}c}E3NDr!_k zZ}LvV1OF4tZ&oa7W@ncb7Z<1M$IUaJU#zEho93|fJ-a*_8#5NnYGxwJY!VDIQyyKVP^n`bFL{_{I>ZIT^rWWkjWAH_a+!ac_`d+?VY z(zIXG->+&X1#ai#f|`Oy74 zg*#iC6Ystf{4c{phhu*{RE4|d98Nkj)hVHa9)IVu?{e$t#m^~~0% z`EOTinw(eLj+v6&)pZh`@dw$nTPGJ)hvwd?YP2$W9T^*%A9SOY|C#-ht1bPwwBx`3fvE=grldh;NGIy8h_ZfxklwYH=TWp(L$VB+C=`NMVa~J20_}&g1Qqe zwm$Y*XQgksPMs2*$4Xku|EBVkrX)RNwM%}`)f4$eTPw`<=R)1K`e&ultbddyLbCED z;ifUi+}j#tBU{fO998hqgO;uTQ^=f4F5(F)AKU;_O;{)C>wwn`ezG~Da z!8ws{5XOxuN-n;u4OpZsI@q1Lw?99nKyX!kYlDgSEOH`)ic{Nr!`;*)Ha zR%*wI{0Ql?lAuOD0sg(MExO3~<@ZLNeSY@tu%pgrCUuk(1T%lKU%k+#TpDok1)6;V9k1*kV-@Pb@&B|>6IMZ#5rfGClwm~0kU2}w+X1jT|-EVig< z)fTKGTI!2JD+&k*FQQVcRxD~kpf6Bw5rlfdiXvh+fG{F<+xPV2=A2|7X8!+YW`Fa{ zuwQnopRb3p(G(*Hf{eX9`2pB{LLfA!N+>@r5-xk3d&V<+mqqoDM!Eg;Bg z78)p23k5zLSRP3cN#tULqKQ;sY6#+fs8NXE2t-X1BVnk_nf%~%Ihlk?oXJ5f0ZpKA zMZ!_f4N7F`2H!w5N+$J5)Dg~P z7u|p)p}>#iDpw*T7G=IYOlLZg9N82)i|xd4up=>O^!Zd8n@V@Er_nhyHit$h4SdKv zBP`-7C83-EzWcy%*k8`%aJ5>&p;Duxqbbo$id-2+rL);=Dvd#9Fzhjfy(&hg7HRBd zDy#k^uk-K`6|6)RYE&*G>GF!i@+h@4nXDUV@H22+0>R+eBhEK6a>Suk zYPaI#q!&3WOSfsN_*f7;#&V-HO#ncdtf6hZg6JOuC+T&K=R9@3855`fYus z(7!@l>|QypN<^fVD+A?nDX+iE{9Yp@xw`7A%8_IpgvuoHXw^I}b+~0{y@#?Pe32UA zF=$u`(dhQ{`GGVV#{sKHM_ZZ`heiXE^eczu5XdEHXv_#XSZofH2Ic^i!*gK!M)x_V~>-|~Zv<*+srRq+9Df;F@ z^tZmbKCdhG?e*m94mA!|3)s9weQQAHQr~JD8o6ID)UByBs!#98pkW^>UH3a+1-pk^ zSNydV>>h3%axRmhY93=?v|-JFOLy}29}7#dhXjN-pT%ObxYXgz!ySI829XN+D7Jr9 zx~gQb2Ry?&Ml`Mfj1Hg?=om2eFBS0byl*A@Td{`)>^X%x_@MIUA`fnae>oVsqklOu zn2s0(q=6d~xNs4H0s3#5S?6S!~@fdT>- zNCP(}aN!~X1q3dT25wB?!bJoM2wWfy+?c?HiwG1DxIh}XF@Xyg5hx&Vfi!Sq0v9eK zP(a`UY2d~LE?h*QfWQUPz>Nu9xQIXjfeWO88xy#25rF~%7f1s)CUD^*0tEyvkOpo{ z;KD@&3J6>v4cz!IaTyK0`iID{_x+-=7yEWES6sth3M9dv0RjlxFdKs66CvpNE9}|| zK~XdaYFh(AoUb6rT)r!$(j9^fn!WfgftrT=y|3iU?o6)=x$>o|{7KW(LbZN)&>2#c z%+vjZB+37a z3A#8Dbr~ZNMRvU0a6o&~vbtkWLR7glC`%3HGyrg35|va6SQ zZw}!0-hgwOkN;Ml&>)$T#accpIO6(6aIFFUAiLEi!AJy=clW4_zQ{ZuR+Pj)R z3hIh_>cgD0d9~tPx1a~!*$XJJPvn6DGrym%T!uBUVeDtO*JA2|s)vg0&!;*TYkt$~iq)=8 z-?z`-==|8im6R`hkNl|ZTv?>qSySVmUu$P?zw^=^i>Xts_I2E;_)eD2G|tH?i2d&w zjJ=hWm5+}P)KaEx&}MYXexLMmN$(^?uhdpt))iGc%|4($xBT z+n)B;BdKevPSxGJXMOI7ufMg~m}4`o$Su>(&0X^Ff781jy!#U&d$y|*VK7_^Zmq{&pek0Zqgcbbab@G-LBHNIq?ZAm=^3Gl5w*>EF<&q;Y2Hs z?FzK*s!jTy0K*nHa>MogXw?1K_`7$bo;-cJtK;&7&#Njv^%VYKljSvMq8*hwan}83 zP1`rdJgRJdSo=}jnrDIAD=RDiGd04>PrKHIvP^czjAmM~afye==p|3Ou3f#FU~G5O zz~3l@!rxmHY|30zEIgDqGqE;3gxjeqIbHIBg~giCP&3##J8;6${9nW-$+IVTMoI72 z=C(JU+A+H1^aCe!cl^QQ0S2t)*1NrPKC0vA2^Y<~QLGwGEqn4&(RnVHz6cInp! zJf4xuy1A(%@Px~m%^sGs_g%jF)sL~;&RPW;_#bInz-V=No|GDpol>wjD{FT3f$6UE z1krWxwb#G2Vx*08u_LKx-aL$KE6}gKy6^I?qsQRT&@ElQ8FzbMELhRpliN5xA=&tT zX^Z}@>{$ZY&SODg#p^pb)N<~bJ+?vXD7Wv`oQnCqIOGI+%{{+X2tT?wk@IQSjHxHT zxqodMPo3PFy?Et|n`3#JSDR+EZAz{#+b?0HrB~@!*i34QIs5aY2T5+L<6iFgn$7%7 zH9mK;H~Q@n>Z6{{@|$~tR=b(jFO1k`=7PFq1&HoFTi4sIEhja>DI%F{yJNE^F)k(U zr;5bU^+oHfT7BNj<4?0C-|jT{xa-ACrPYz**ylES=Wo$V%~Q7aXv;iib*ElfRMI9$ zuDVs!*1HydxU7YiqgW?S2q}N4*Vx$D8uxfiL!s2&HtT}9(QWr?#mnw#-X2%K4?KH; z+%$6`BaMH)4jPpo5T12-sb>VATV0%9lAD*TF*7%RraiCkcvmitGTA)m3$Jd=2j?sj zYmU~YjXiha9$DJ-tGk_9R9pm_yR}N3Usy0<$6Q7AFM51*TkRH_T6+40Y`(mHQKnzA zp52fz9K7>H*YW4 f!e>;(VQ-eN&+J)gkTy#9vbdL$NuZ@Fcx?&LHljJ-g3m|2*^g%*-|S{kyK~KHuxQ z?>YaR-EOX5%+k=-fFNj=GneCzeJ{q|TIy4==qU&g+sOSl_k5Hw?+>OBcc&(MM( zD+7_Ix7?eznkhiTP5DAJ5HVGTOE5JAv22wRz91BllLC=ok=TaZbE%k25(#a{>lr*M zPvVG#h`3v%h{qOJPr;T@fwhoq%hq5inOJ~uM9wEE!^6ZfrqYHy6qkwFs$rN+8j{FE zZOHbjgd}gC8_5xsA|!^X1w}wJvm#kqo6;E8R&;X{5}iu3fT`9n&74A|F{#!}DvdPq zBC|CxizO8XG2J;UN77-xY{(&Uxr7PB3WdT{VP=X-gJGJrwKYto!*n_YQ&414VmV() z5zF+4gS?BwL1Y4{NFo=ZVv;H@KM;+O+mOksM5C{f>k5~QCKAg=*kKf5C0_#5OsVih zPC~(`k0e4GHncRM07k-)a6~MZVScm;{Ujl%9F>KjA4xen{n5l&O?bS~+!L-hJbc2` zGPzSEhF}Eggq||bC44Py^?ncF*rBu7V8S6Pw_*NenLR3S5B!Q)-V>OB?>;qc`M zn@+`Ah)Sc-EIg@HrnwcNH~uR2u>jvf%ed$Cfqf zIpQYze+xJqA8`M84n+te{-X^w9BN4Q@gOoZNUq>Z5&K}QC;rS{ACCII+xYM-Sate` ziQX^7@aX&XdDpQY){~_=)RG7@`{@4|q9`8ElxlSySv*{z*#tj2rs*`v4Sn$AZ5)ifpgJH&C!Q+R=d$@^|NSHTA zgzaCMsw?T%BR=CNCUo2YIGTfwz{H5-aH)X5^Px%(k773q*nJ8-dZY4wkw-Ve-)xLs z(cc^x3`ZmZY2exfE?h*QfWQUPz_kfnxQIXjfeWO8YZJI|5rF~%7f1uwCUD^*0tEyv zkOrv4P2YRg^LIj5V$}ZxHf?c7ZE5RaDg;%Z2}iAB2Yl!0%_pd1TI`e zpn$*y(!jL|T)2op0f7sofol`Ea1nt50vAXF*Cue`A_4^jE|3PUP2j>s1PTaTAProb zz=ewl6cD&T8n`xr3l|Y6AaH>+aBTt?E+SAs-~wsj+5|3KM4*7c1=7H^30$~{KmmaZ zq=9P_xNs4H0s1hau=Q^nicaN(h?rvopuuQ~7h+!4~w>`o(3o{dbNYVdRzQFdAlC zc+;=Xw*RNY)QWCL4Grm%uq>CiaXK1rpLy;#);+~tTlW%vaO|ADbn(gk+MhHwyiP3L z{^)7q%#t%MOSty@-Lodw=hmlO3GKOcW$WQzN$7EkQn|}YOtomX{Owat(mKXuO zNYm%+fy({s%L9@g=A=#HJWtoDKj;GI{517k&%@Bgh z|Ii&j+v)A&@>A{I|87fAGdlfr-kGI)AAM~i;-A8Cngv7()zg5w^sN5v}>E4&jJ^h-E)3zW7JN~uXz>RbOPXveDi$tW zsF%Wh5T)y4WFcxVTBW>?D2U>ocuZ@<`q6W5*^nMmof|mG5e?{jEC3m1brZe^0y0-TPo! zbkE|iv$J;;HlHfbH_6D#iutBGbJ+!H8@)d7Oa0Vma~#~fy}4}#;pYkqJ=U$8pI_yC zF~fCpu=GGt5&PglccGUTRsCkw^3Ki|u`gdY^t^dvbfw+H63keMs^tQVvJ=?wVtGVR~TOCg?=&wv& zW{-OAIqh3m)7*TgafPDDpt$GN>ZbGub>9~M=47IsjE%cGmcoe~GR&;+?da?8uWfA9 zIlS3s9>c<-rm5*xwZ@FzhPEt*mHCa%&h^TT->W&OcXxMpTFDbLi;EU!FS5hRQHZXQ zbk#WNmbJd_+PCER`AkipwQKD|LzhQCuRC^rHdLyvjTI;6Kb8JRjzq2gJk?&kZ$DK$X*%}r9`uB}k(ZYj)+%e)mY|y3>{hK>6=IRZdBm`u?;z=I=M}vO zjIG$WWLK<{lT%+`pPR4mDuYi-wBqlj4-6Q*dGltqt7~4me1C0!%OC^o$z2)Lq6dXv zE%-g=$7=S9>dsEXqUUFF0~}!(?zoYnUsqQbkW?9ITv=E5>9RHdn#r5u=C>&Oc&lYy zeQ`Xpf#YYX{9JAF4I$mzr7eDMQW7cQYQ8}VH=(`#^X9Gnhr$}ZpHyvjKiGnlSQdD{ zL5+nqMM&rhj0o5~BqvB#MgP1EzwC`?G>8ac!8@h87rs>u8D(o>dZW5NC6ZMM@i zb%nLx@N#oQOsX6bf4uPBnw+qfqN90vx&`6i⋙)L@{N`TzSEgX(@CQlh=u92L@+B YJt_G@rN`b_>>mMgc5>z9IRtF~A2J)D$^ZZW literal 0 HcmV?d00001 diff --git a/resource/template/BattleOperRoleSniper.png b/resource/template/BattleOperRoleSniper.png new file mode 100644 index 0000000000000000000000000000000000000000..2903cd93e744b496d01924f6dfc9352b6c8a183e GIT binary patch literal 1403 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 literal 0 HcmV?d00001 diff --git a/resource/template/BattleOperRoleTank.png b/resource/template/BattleOperRoleTank.png new file mode 100644 index 0000000000000000000000000000000000000000..ee37e76a2a256e79198a0272666492623ea97185 GIT binary patch literal 15945 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 MbMkULY9EpI2LRk`ivR!s literal 0 HcmV?d00001 diff --git a/resource/template/BattleOperRoleWarrior.png b/resource/template/BattleOperRoleWarrior.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb666a225a1b7788d4aa7535187873200f738c5 GIT binary patch literal 15630 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 Cw;GlJ literal 0 HcmV?d00001 diff --git a/src/MeoAssistant/AbstractImageAnalyzer.cpp b/src/MeoAssistant/AbstractImageAnalyzer.cpp index 4d03377695..432dbcd7d1 100644 --- a/src/MeoAssistant/AbstractImageAnalyzer.cpp +++ b/src/MeoAssistant/AbstractImageAnalyzer.cpp @@ -9,8 +9,7 @@ asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat image) , m_image_draw(image.clone()) #endif -{ -} +{} asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat image, const Rect& roi) : m_image(image), @@ -23,7 +22,7 @@ asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat image, const Re ; } -void asst::AbstractImageAnalyzer::set_image(const cv::Mat image) +void asst::AbstractImageAnalyzer::_set_image(const cv::Mat image) { m_image = image; #ifdef ASST_DEBUG @@ -33,7 +32,7 @@ void asst::AbstractImageAnalyzer::set_image(const cv::Mat image) void asst::AbstractImageAnalyzer::set_image(const cv::Mat image, const Rect& roi) { - set_image(image); + _set_image(image); m_roi = empty_rect_to_full(roi, image); } diff --git a/src/MeoAssistant/AbstractImageAnalyzer.h b/src/MeoAssistant/AbstractImageAnalyzer.h index 65f3cb5295..e8ca93b713 100644 --- a/src/MeoAssistant/AbstractImageAnalyzer.h +++ b/src/MeoAssistant/AbstractImageAnalyzer.h @@ -16,8 +16,7 @@ namespace asst AbstractImageAnalyzer(AbstractImageAnalyzer&&) = delete; virtual ~AbstractImageAnalyzer() = default; - virtual void set_image(const cv::Mat image); - virtual void set_image(const cv::Mat image, const Rect& roi); + virtual void set_image(const cv::Mat image, const Rect& roi = Rect()); virtual void set_roi(const Rect& roi) noexcept; virtual bool analyze() = 0; void correct_roi() noexcept; @@ -29,6 +28,7 @@ namespace asst AbstractImageAnalyzer& operator=(AbstractImageAnalyzer&&) = delete; protected: + virtual void _set_image(const cv::Mat image); static Rect empty_rect_to_full(const Rect& rect, const cv::Mat image) noexcept; cv::Mat m_image; diff --git a/src/MeoAssistant/Assistant.cpp b/src/MeoAssistant/Assistant.cpp index 944a2861c3..650792f41d 100644 --- a/src/MeoAssistant/Assistant.cpp +++ b/src/MeoAssistant/Assistant.cpp @@ -12,6 +12,7 @@ #include "Resource.h" #include "CreditShoppingTask.h" +#include "BattleTask.h" #include "InfrastDormTask.h" #include "InfrastInfoTask.h" #include "InfrastMfgTask.h" @@ -335,15 +336,13 @@ bool Assistant::append_debug() return false; } - //std::unique_lock lock(m_mutex); + std::unique_lock lock(m_mutex); - //{ - // constexpr static const char* DebugTaskChain = "Debug"; - // auto shift_task_ptr = std::make_shared(task_callback, (void*)this, DebugTaskChain); - // shift_task_ptr->set_work_mode(infrast::WorkMode::Aggressive); - // shift_task_ptr->set_product("MoodAddition"); - // m_tasks_queue.emplace(shift_task_ptr); - //} + { + constexpr static const char* DebugTaskChain = "Debug"; + auto debug_task_ptr = std::make_shared(task_callback, (void*)this, DebugTaskChain); + m_tasks_queue.emplace(debug_task_ptr); + } return true; } diff --git a/src/MeoAssistant/AsstDef.h b/src/MeoAssistant/AsstDef.h index 602ff7ee13..374dae9653 100644 --- a/src/MeoAssistant/AsstDef.h +++ b/src/MeoAssistant/AsstDef.h @@ -85,6 +85,10 @@ namespace asst { return "[ " + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(width) + ", " + std::to_string(height) + " ]"; } + Rect move(Rect move) const + { + return Rect(x + move.x, y + move.y, move.width, move.height); + } int x = 0; int y = 0; @@ -122,8 +126,8 @@ namespace asst Invaild = -1, JustReturn, MatchTemplate, - CompareHist, - OcrDetect + OcrDetect, + Hash }; struct MatchRect @@ -222,6 +226,16 @@ namespace asst std::pair mask_range; // 掩码的二值化范围 }; + // hash 计算任务的信息 + struct HashTaskInfo : public TaskInfo + { + virtual ~HashTaskInfo() = default; + std::vector hashs; // 多个哈希值 + int hamming_threshold = 0; // 汉明距离阈值 + std::pair mask_range; // 掩码的二值化范围 + bool bound = false; // 是否裁剪周围黑边 + }; + struct HandleInfo { std::string class_name; diff --git a/src/MeoAssistant/BattleImageAnalyzer.cpp b/src/MeoAssistant/BattleImageAnalyzer.cpp new file mode 100644 index 0000000000..a89568e282 --- /dev/null +++ b/src/MeoAssistant/BattleImageAnalyzer.cpp @@ -0,0 +1,173 @@ +#include "BattleImageAnalyzer.h" + +#include "MultiMatchImageAnalyzer.h" +#include "MatchImageAnalyzer.h" +#include "HashImageAnalyzer.h" +#include "Logger.hpp" +#include "TaskData.h" + +bool asst::BattleImageAnalyzer::analyze() +{ + return opers_analyze(); +} + +const std::vector& asst::BattleImageAnalyzer::get_opers() const noexcept +{ + return m_opers; +} + +bool asst::BattleImageAnalyzer::opers_analyze() +{ + MultiMatchImageAnalyzer flags_analyzer(m_image); + flags_analyzer.set_task_info("BattleOpersFlag"); + if (!flags_analyzer.analyze()) { + return false; + } + flags_analyzer.sort_result(); + + const auto click_move = Task.get("BattleOperClickRange")->rect_move; + const auto role_move = Task.get("BattleOperRoleRange")->rect_move; + const auto cost_move = Task.get("BattleOperCostRange")->rect_move; + + for (const MatchRect& flag_mrect : flags_analyzer.get_result()) { + Oper oper; + oper.rect = flag_mrect.rect.move(click_move); + oper.available = oper_available_analyze(oper.rect); + +#ifdef ASST_DEBUG + if (oper.available) { + cv::rectangle(m_image_draw, utils::make_rect(oper.rect), cv::Scalar(0, 255, 0), 2); + } + else { + cv::rectangle(m_image_draw, utils::make_rect(oper.rect), cv::Scalar(0, 0, 255), 2); + } +#endif + + Rect role_rect = flag_mrect.rect.move(role_move); + oper.role = oper_role_analyze(role_rect); + Rect cost_rect = flag_mrect.rect.move(cost_move); + + oper.cost = oper_cost_analyze(cost_rect); + + m_opers.emplace_back(std::move(oper)); + } + + return true; +} + +asst::BattleImageAnalyzer::Role asst::BattleImageAnalyzer::oper_role_analyze(const Rect& roi) +{ + static const std::unordered_map RolesName = { + { Role::Caster, "Caster" }, + { Role::Medic, "Medic" }, + { Role::Pioneer, "Pioneer" }, + { Role::Sniper, "Sniper" }, + { Role::Special, "Special" }, + { Role::Support, "Support" }, + { Role::Tank, "Tank" }, + { Role::Warrior, "Warrior" } + }; + + MatchImageAnalyzer role_analyzer(m_image); + + Role result = Role::Invaild; + double max_score = 0; + for (auto&& [role, role_name] : RolesName) { + role_analyzer.set_task_info("BattleOperRole" + role_name); + role_analyzer.set_roi(roi); + if (!role_analyzer.analyze()) { + continue; + } + if (double cur_socre = role_analyzer.get_result().score; + max_score < cur_socre) { + result = role; + max_score = cur_socre; + } + } + +#ifdef ASST_DEBUG + cv::putText(m_image_draw, RolesName.at(result), cv::Point(roi.x, roi.y - 5), 1, 1, cv::Scalar(0, 255, 255)); +#endif + + return result; +} + +int asst::BattleImageAnalyzer::oper_cost_analyze(const Rect& roi) +{ + static const std::array NumName = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" + }; + static bool inited = false; + static cv::Scalar range_lower, range_upper; + + static HashImageAnalyzer hash_analyzer; + if (!inited) { + auto [h_l, h_u] = std::dynamic_pointer_cast( + Task.get("BattleOperCostChannelH"))->mask_range; + auto [s_l, s_u] = std::dynamic_pointer_cast( + Task.get("BattleOperCostChannelS"))->mask_range; + auto [v_l, v_u] = std::dynamic_pointer_cast( + Task.get("BattleOperCostChannelV"))->mask_range; + range_lower = cv::Scalar(h_l, s_l, v_l); + range_upper = cv::Scalar(h_u, s_u, v_u); + std::unordered_map num_hashs; + for (auto&& num : NumName) { + auto hashs_vec = std::dynamic_pointer_cast( + Task.get("BattleOperCost" + num))->hashs; + for (size_t i = 0; i != hashs_vec.size(); ++i) { + num_hashs.emplace(num + "_" + std::to_string(i), hashs_vec.at(i)); + } + } + hash_analyzer.set_hash_templates(std::move(num_hashs)); + hash_analyzer.set_need_bound(true); + hash_analyzer.set_need_split(true); + inited = true; + } + + cv::Mat hsv; + cv::cvtColor(m_image(utils::make_rect(roi)), hsv, cv::COLOR_BGR2HSV); + cv::Mat bin; + cv::inRange(hsv, range_lower, range_upper, bin); + hash_analyzer.set_image(bin); + hash_analyzer.analyze(); + + int cost = 0; + for (const std::string& num_name : hash_analyzer.get_result()) { + if (num_name.empty()) { + Log.error("hash result is empty"); + return 0; + } + cost *= 10; + cost += num_name.at(0) - '0'; + } + +#ifdef ASST_DEBUG + cv::putText(m_image_draw, std::to_string(cost), cv::Point(roi.x, roi.y - 20), 1, 1, cv::Scalar(0, 0, 255)); +#endif + + return cost; +} + +bool asst::BattleImageAnalyzer::oper_available_analyze(const Rect& roi) +{ + cv::Mat hsv; + cv::cvtColor(m_image(utils::make_rect(roi)), hsv, cv::COLOR_BGR2HSV); + cv::Scalar avg = cv::mean(hsv); + Log.trace("oper available, mean", avg[2]); + + static int thres = std::dynamic_pointer_cast(Task.get("BattleOperAvailable"))->special_threshold; + if (avg[2] < thres) { + return false; + } + return true; +} + +bool asst::BattleImageAnalyzer::home_analyze() +{ + return false; +} + +bool asst::BattleImageAnalyzer::placed_analyze() +{ + return false; +} diff --git a/src/MeoAssistant/BattleImageAnalyzer.h b/src/MeoAssistant/BattleImageAnalyzer.h new file mode 100644 index 0000000000..922627c316 --- /dev/null +++ b/src/MeoAssistant/BattleImageAnalyzer.h @@ -0,0 +1,57 @@ +#pragma once +#include "AbstractImageAnalyzer.h" + +namespace asst +{ + class BattleImageAnalyzer : public AbstractImageAnalyzer + { + public: + enum class Role + { + Invaild, + Caster, + Medic, + Pioneer, + Sniper, + Special, + Support, + Tank, + Warrior + }; + struct Oper + { + int cost = 0; + Role role = Role::Invaild; + bool available = false; + Rect rect; + }; + public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + virtual ~BattleImageAnalyzer() = default; + + virtual bool analyze() override; + + const std::vector& get_opers() const noexcept; + protected: + + bool opers_analyze(); // 识别干员 + Role oper_role_analyze(const Rect& roi); + int oper_cost_analyze(const Rect& roi); + bool oper_available_analyze(const Rect& roi); + bool home_analyze(); // 识别蓝色的家门 + bool placed_analyze(); // 识别可放置干员的位置 + + std::vector m_opers; + + private: + // 该分析器不支持外部设置ROI + virtual void set_roi(const Rect& roi) noexcept override + { + AbstractImageAnalyzer::set_roi(roi); + } + virtual void set_image(const cv::Mat image, const Rect& roi) + { + AbstractImageAnalyzer::set_image(image, roi); + } + }; +} diff --git a/src/MeoAssistant/BattleTask.cpp b/src/MeoAssistant/BattleTask.cpp new file mode 100644 index 0000000000..6cf61bfa92 --- /dev/null +++ b/src/MeoAssistant/BattleTask.cpp @@ -0,0 +1,11 @@ +#include "BattleTask.h" + +#include "BattleImageAnalyzer.h" +#include "Controller.h" + +bool asst::BattleTask::_run() +{ + BattleImageAnalyzer oper_analyzer(Ctrler.get_image()); + oper_analyzer.analyze(); + return false; +} diff --git a/src/MeoAssistant/BattleTask.h b/src/MeoAssistant/BattleTask.h new file mode 100644 index 0000000000..1e7f6c4869 --- /dev/null +++ b/src/MeoAssistant/BattleTask.h @@ -0,0 +1,16 @@ +#pragma once + +#include "AbstractTask.h" + +namespace asst +{ + class BattleTask : public AbstractTask + { + public: + using AbstractTask::AbstractTask; + virtual ~BattleTask() = default; + + protected: + virtual bool _run() override; + }; +} diff --git a/src/MeoAssistant/HashImageAnalyzer.cpp b/src/MeoAssistant/HashImageAnalyzer.cpp new file mode 100644 index 0000000000..5344cf3166 --- /dev/null +++ b/src/MeoAssistant/HashImageAnalyzer.cpp @@ -0,0 +1,161 @@ +#include "HashImageAnalyzer.h" + +#include "AsstUtils.hpp" + +bool asst::HashImageAnalyzer::analyze() +{ + m_hash_result.clear(); + m_hamming_result.clear(); + m_result.clear(); + + cv::Mat roi = m_image(utils::make_rect(m_roi)); + + if (m_mask_range.first != 0 || m_mask_range.second != 0) { + cv::Mat bin; + cv::inRange(roi, m_mask_range.first, m_mask_range.second, bin); + roi = bin; + } + + std::vector to_hash_vector; + if (m_need_split) { + to_hash_vector = split_bin(roi); + } + else { + to_hash_vector.emplace_back(roi); + } + + for (auto&& to_hash : to_hash_vector) { + if (m_need_bound) { + to_hash = bound_bin(to_hash); + } + std::string hash_result = shash(to_hash); + + decltype(m_hamming_result)::value_type cur_hamming; + std::string min_hm_name; + + int min_hamming = INT_MAX; + for (auto&& [name, templ] : m_hash_templates) { + int hm = hamming(hash_result, templ); + cur_hamming.emplace(name, hm); + if (hm < min_hamming) { + min_hm_name = name; + min_hamming = hm; + } + } + m_result.emplace_back(min_hm_name); + m_hamming_result.emplace_back(std::move(cur_hamming)); + + m_hash_result.emplace_back(std::move(hash_result)); + } + + return true; +} + +void asst::HashImageAnalyzer::set_mask_range(int lower, int upper) noexcept +{ + m_mask_range = std::make_pair(lower, upper); +} + +void asst::HashImageAnalyzer::set_mask_range(std::pair mask_range) noexcept +{ + m_mask_range = std::move(mask_range); +} + +void asst::HashImageAnalyzer::set_hash_templates(std::unordered_map hash_templates) noexcept +{ + m_hash_templates = std::move(hash_templates); +} + +void asst::HashImageAnalyzer::set_need_split(bool need_split) noexcept +{ + m_need_split = need_split; +} + +void asst::HashImageAnalyzer::set_need_bound(bool need_bound) noexcept +{ + m_need_bound = need_bound; +} + +const std::vector& asst::HashImageAnalyzer::get_result() const noexcept +{ + return m_result; +} + +std::string asst::HashImageAnalyzer::shash(const cv::Mat& gray) +{ + constexpr static int HashKernelSize = 16; + cv::Mat resized; + cv::resize(gray, resized, cv::Size(HashKernelSize, HashKernelSize)); + std::stringstream hash_value; + uchar* pix = resized.data; + int tmp_dec = 0; + for (int ro = 0; ro < 256; ro++) { + tmp_dec = tmp_dec << 1; + if (*pix > 127) + tmp_dec++; + if (ro % 4 == 3) { + hash_value << std::hex << tmp_dec; + tmp_dec = 0; + } + pix++; + } + return hash_value.str(); +} + +std::vector asst::HashImageAnalyzer::split_bin(const cv::Mat& bin) +{ + std::vector result; + + int range_start = 0; + bool started = false; + for (int i = 0; i != bin.cols; ++i) { + bool line_without_true = true; + for (int j = 0; j != bin.rows; ++j) { + cv::uint8_t value = bin.at(j, i); + + if (value) { + line_without_true = false; + if (!started) { + started = true; + range_start = i; + } + break; + } + } + if (started && line_without_true) { + started = false; + cv::Mat range_img = bin(cv::Range::all(), cv::Range(range_start, i)); + result.emplace_back(range_img); + } + } + if (started) { + started = false; + cv::Mat range_img = bin(cv::Range::all(), cv::Range(range_start, bin.cols)); + result.emplace_back(range_img); + } + + return result; +} + +cv::Mat asst::HashImageAnalyzer::bound_bin(const cv::Mat& bin) +{ + return bin(cv::boundingRect(bin)); +} + +int asst::HashImageAnalyzer::hamming(std::string hash1, std::string hash2) +{ + constexpr static int HammingFlags = 64; + + hash1.insert(hash1.begin(), HammingFlags - hash1.size(), '0'); + hash2.insert(hash2.begin(), HammingFlags - hash2.size(), '0'); + int dist = 0; + for (int i = 0; i < HammingFlags; i = i + 16) { + unsigned long long x = strtoull(hash1.substr(i, 16).c_str(), nullptr, 16) + ^ strtoull(hash2.substr(i, 16).c_str(), nullptr, 16); + while (x) { + dist++; + x = x & (x - 1); + } + } + return dist; +} diff --git a/src/MeoAssistant/HashImageAnalyzer.h b/src/MeoAssistant/HashImageAnalyzer.h new file mode 100644 index 0000000000..30b17fc7b8 --- /dev/null +++ b/src/MeoAssistant/HashImageAnalyzer.h @@ -0,0 +1,39 @@ +#pragma once +#include "AbstractImageAnalyzer.h" + +#include + +namespace asst +{ + class HashImageAnalyzer : public AbstractImageAnalyzer + { + public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + virtual ~HashImageAnalyzer() = default; + + virtual bool analyze() override; + + void set_mask_range(int lower, int upper) noexcept; + void set_mask_range(std::pair mask_range) noexcept; + void set_hash_templates(std::unordered_map hash_templates) noexcept; + void set_need_split(bool need_split) noexcept; + void set_need_bound(bool need_bound) noexcept; + + const std::vector& get_result() const noexcept; + + protected: + static std::string shash(const cv::Mat& gray); + static int hamming(std::string hash1, std::string hash2); + static std::vector split_bin(const cv::Mat& bin); + static cv::Mat bound_bin(const cv::Mat& bin); + + std::pair m_mask_range; + std::unordered_map m_hash_templates; + bool m_need_split = false; + bool m_need_bound = false; + + std::vector m_hash_result; + std::vector> m_hamming_result; + std::vector m_result; + }; +} diff --git a/src/MeoAssistant/MatchImageAnalyzer.cpp b/src/MeoAssistant/MatchImageAnalyzer.cpp index 34c88152df..8217a5004a 100644 --- a/src/MeoAssistant/MatchImageAnalyzer.cpp +++ b/src/MeoAssistant/MatchImageAnalyzer.cpp @@ -87,7 +87,7 @@ bool asst::MatchImageAnalyzer::match_templ(const cv::Mat templ) "templ size:", templ.cols, templ.rows); return false; } - if (m_mask_range.first == m_mask_range.second) { + if (m_mask_range.first == 0 && m_mask_range.second == 0) { cv::matchTemplate(image_roi, templ, matched, cv::TM_CCOEFF_NORMED); } else { diff --git a/src/MeoAssistant/MeoAssistant.vcxproj b/src/MeoAssistant/MeoAssistant.vcxproj index fc407b934b..462c30c982 100644 --- a/src/MeoAssistant/MeoAssistant.vcxproj +++ b/src/MeoAssistant/MeoAssistant.vcxproj @@ -24,10 +24,13 @@ + + + @@ -73,9 +76,12 @@ + + + diff --git a/src/MeoAssistant/MeoAssistant.vcxproj.filters b/src/MeoAssistant/MeoAssistant.vcxproj.filters index c1a5ac8653..df94deef5f 100644 --- a/src/MeoAssistant/MeoAssistant.vcxproj.filters +++ b/src/MeoAssistant/MeoAssistant.vcxproj.filters @@ -234,6 +234,15 @@ 头文件\TaskPlugin + + 头文件\Task + + + 头文件\ImageAnalyzer + + + 头文件\ImageAnalyzer\General + @@ -377,6 +386,15 @@ 源文件\TaskPlugin + + 源文件\Task + + + 源文件\ImageAnalyzer + + + 源文件\ImageAnalyzer\General + diff --git a/src/MeoAssistant/MultiMatchImageAnalyzer.cpp b/src/MeoAssistant/MultiMatchImageAnalyzer.cpp index d0f504c4fc..18dd2e9161 100644 --- a/src/MeoAssistant/MultiMatchImageAnalyzer.cpp +++ b/src/MeoAssistant/MultiMatchImageAnalyzer.cpp @@ -95,7 +95,7 @@ bool asst::MultiMatchImageAnalyzer::multi_match_templ(const cv::Mat templ) return false; } - if (m_mask_range.first == m_mask_range.second) { + if (m_mask_range.first == 0 && m_mask_range.second == 0) { cv::matchTemplate(image_roi, templ, matched, cv::TM_CCOEFF_NORMED); } else { diff --git a/src/MeoAssistant/ProcessTaskImageAnalyzer.h b/src/MeoAssistant/ProcessTaskImageAnalyzer.h index 8e8ff7826f..48a244cb35 100644 --- a/src/MeoAssistant/ProcessTaskImageAnalyzer.h +++ b/src/MeoAssistant/ProcessTaskImageAnalyzer.h @@ -21,7 +21,7 @@ namespace asst virtual ~ProcessTaskImageAnalyzer(); virtual bool analyze() override; - virtual void set_image(const cv::Mat image) override; + void set_image(const cv::Mat image); void set_tasks(std::vector tasks_name) { diff --git a/src/MeoAssistant/TaskData.cpp b/src/MeoAssistant/TaskData.cpp index 88f9e855b9..9522803201 100644 --- a/src/MeoAssistant/TaskData.cpp +++ b/src/MeoAssistant/TaskData.cpp @@ -54,8 +54,9 @@ bool asst::TaskData::parse(const json::value& json) else if (algorithm_str == "ocrdetect") { algorithm = AlgorithmType::OcrDetect; } - // CompareHist是MatchTemplate的衍生算法,不应作为单独的配置参数出现 - // else if (algorithm_str == "comparehist") {} + else if (algorithm_str == "hash") { + algorithm = AlgorithmType::Hash; + } else { m_last_error = "algorithm error " + algorithm_str; return false; @@ -96,6 +97,22 @@ bool asst::TaskData::parse(const json::value& json) } task_info_ptr = ocr_task_info_ptr; } break; + case AlgorithmType::Hash: + { + auto hash_task_info_ptr = std::make_shared(); + for (const json::value& hash : task_json.at("hash").as_array()) { + hash_task_info_ptr->hashs.emplace_back(hash.as_string()); + } + hash_task_info_ptr->hamming_threshold = task_json.get("threshold", 0.0); + if (task_json.exist("maskRange")) { + hash_task_info_ptr->mask_range = std::make_pair( + task_json.at("maskRange")[0].as_integer(), + task_json.at("maskRange")[1].as_integer()); + } + hash_task_info_ptr->bound = task_json.get("bound", true); + + task_info_ptr = hash_task_info_ptr; + } break; } task_info_ptr->cache = task_json.get("cache", true); task_info_ptr->algorithm = algorithm;