From 52363f2e9ed614124764dbc380e93448e4c3a79f Mon Sep 17 00:00:00 2001 From: uye <99072975+ABA2396@users.noreply.github.com> Date: Fri, 2 Jan 2026 00:15:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=9A=90=E7=A7=98=E6=88=98=E7=BA=BF?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=B3=BB=E5=88=97=E4=BB=BB=E5=8A=A1=20(#1524?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 隐秘战线支持系列事件 * fix: 漏了个事件 * fix: 避免修改任务导致后续无法进入系列任务 * fix: 怎么小游戏没禁用运行时修改 * rft: 提取通用逻辑 * i18n: KR * i18n: EN * i18n: KR fix typo * i18n: JP * i18n: txwy * perf: 优化解析逻辑 * fix: 隐秘战线结局与事件无法保存 * chore: text → ocrReplace * chore: 再改一點 gui 上的東西 --------- Co-authored-by: HX3N Co-authored-by: Constrat <56174894+Constrat@users.noreply.github.com> Co-authored-by: Manicsteiner Co-authored-by: momomochi987 --- .../resource/tasks/MiniGame/SecretFront.json | 7 + .../resource/tasks/MiniGame/SecretFront.json | 9 +- .../resource/tasks/MiniGame/SecretFront.json | 7 + .../MiniGame@SecretFront@ClickAnywhere.png | Bin 3930 -> 2595 bytes .../resource/tasks/MiniGame/SecretFront.json | 7 + resource/tasks/MiniGame/SecretFront.json | 29 +-- src/MaaCore/Task/Interface/CustomTask.cpp | 92 ++++++++- src/MaaCore/Task/Interface/CustomTask.h | 2 + .../Task/MiniGame/SecretFrontTaskPlugin.cpp | 174 ++++++++++++------ .../Task/MiniGame/SecretFrontTaskPlugin.h | 19 +- src/MaaWpfGui/Constants/ConfigurationKeys.cs | 1 + src/MaaWpfGui/Res/Localizations/en-us.xaml | 6 +- src/MaaWpfGui/Res/Localizations/ja-jp.xaml | 4 + src/MaaWpfGui/Res/Localizations/ko-kr.xaml | 8 +- src/MaaWpfGui/Res/Localizations/zh-cn.xaml | 4 + src/MaaWpfGui/Res/Localizations/zh-tw.xaml | 12 +- .../ViewModels/UI/ToolboxViewModel.cs | 31 +++- src/MaaWpfGui/Views/UI/ToolboxView.xaml | 31 +++- 18 files changed, 324 insertions(+), 119 deletions(-) diff --git a/resource/global/YoStarEN/resource/tasks/MiniGame/SecretFront.json b/resource/global/YoStarEN/resource/tasks/MiniGame/SecretFront.json index 3287c52a41..019b827d37 100644 --- a/resource/global/YoStarEN/resource/tasks/MiniGame/SecretFront.json +++ b/resource/global/YoStarEN/resource/tasks/MiniGame/SecretFront.json @@ -2,6 +2,13 @@ "MiniGame@SecretFront@End": { "roi": [576, 540, 435, 135] }, + "MiniGame@SecretFront@PreClickDetect": { + "ocrReplace": [ + ["Support Platform", "支援作战平台"], + ["Knight-errant", "游侠"], + ["Sly Shadows", "鬼影迷踪"] + ] + }, "MiniGame@SecretFront@EventReplace": { "ocrReplace": [ [" ", ""], diff --git a/resource/global/YoStarJP/resource/tasks/MiniGame/SecretFront.json b/resource/global/YoStarJP/resource/tasks/MiniGame/SecretFront.json index ac20c0cab1..b0349ed676 100644 --- a/resource/global/YoStarJP/resource/tasks/MiniGame/SecretFront.json +++ b/resource/global/YoStarJP/resource/tasks/MiniGame/SecretFront.json @@ -4,6 +4,13 @@ [" ", ""], ["エンディング", "4"] ], - "roi": [7, 253, 160, 35] + "roi": [7, 253, 162, 35] + }, + "MiniGame@SecretFront@PreClickDetect": { + "ocrReplace": [ + ["作戦プラットフォーム支援任務", "支援作战平台"], + ["遊侠", "游侠"], + ["揺蕩う影", "鬼影迷踪"] + ] } } diff --git a/resource/global/YoStarKR/resource/tasks/MiniGame/SecretFront.json b/resource/global/YoStarKR/resource/tasks/MiniGame/SecretFront.json index 8cb05e8a36..09a86f5f89 100644 --- a/resource/global/YoStarKR/resource/tasks/MiniGame/SecretFront.json +++ b/resource/global/YoStarKR/resource/tasks/MiniGame/SecretFront.json @@ -4,5 +4,12 @@ [" ", ""], ["엔딩", "4"] ] + }, + "MiniGame@SecretFront@PreClickDetect": { + "ocrReplace": [ + ["작전플랫폼지원", "支援作战平台"], + ["레인저", "游侠"], + ["수상한그림자", "鬼影迷踪"] + ] } } diff --git a/resource/global/YoStarKR/resource/template/MiniGame/SecretFront/MiniGame@SecretFront@ClickAnywhere.png b/resource/global/YoStarKR/resource/template/MiniGame/SecretFront/MiniGame@SecretFront@ClickAnywhere.png index 59d7a285cfb17713c209428bfe4ea9a6d09be419..4a0e052baa57e416fdcfafe837c3ce336a0d7597 100644 GIT binary patch delta 2587 zcmV+$3gq?L9-|Z?iBL{Q4GJ0x0000DNk~Le0000+0000X2m$~A0PDR$J$`P zU>hvUAPIy3-S

>h9^DqbN3&mS*(k^_%YZdsjb^zyIxLlMTg`!w^4WQZYC~QRs~g zHbo^YKEKH|70RH}{DUEWv_tzf^rS))_2>{#L<`cnhH=77H zj)-Ft57b&db^XcK;=R>SBrKwBGMPkxyngCv-4K~YxyaEDzeF+i!X$5yM=@>XTUJzJU1IP;debY&}foF(7c2qpWf*0@yh9P9(2k*R)uT`ps1u3(*#3wLV5l89k zbl<%A6%1!VcAt@m9C>8PG~i+*@v+2o8Z(W0?eP6BOCDsw?7~7~Iuq1&d}S4Joa_cG z8z>K;_^P0pf8pV}dbLJ(WN2a99`_Pu=5slm)#=$;(3ER{Zy3FAU$R`YSPV5S9M)0F zJMC7pUiXjK=o_SfBA=La!gm{vWD`Y z$kb#se^JcIWMcE_v-spBCTf+NcKI?g5{r(;ao5B8i+}zCd)a%w*@eZaWD*bAPoIt| zyEq%!B+Ot%=8)0uv>87b2oCp?R>7AzAz6W^$Xrrj-Ia}pz(X;qKH`JS>yVB~QN>Zw z_>R1Q^4-@qx8f5MAaf_5kB<&f{q=C@!K1CIe^d&E3!!rG?swY5FeeSYgfAHAA@Eke z-(&7Zt0T0;k4k*Fv!w8LlM1J$m*@w}b^EoOiU`C0U^x1{9=`2%8#I^#kwI+zD%a0c zHEm{o4i84ZxAXGVb-85aF!^)puV~3K3k&;i-`Jb-d@g%^ITX7n=J8^^R%ag6^$63F zeIN8c(%q_04~NrR7|~2aTXPpuqb5ZnxWRw!R*J z4(VYC10-ObX0xDM&6Zdq91N*6xjWtN)#Zig$#A?d?II(iHp>pL{I?wI0Fkbks@+^O z{$wf*838_#PBVTqHu~_%GldRX{g{v!e-gdgt#eVU| zZ+vnRH8Y?43ML|XW_Dhb$Dkf!SkY&iPA`cfE`yR$Xp7b#Kf(j>9_;LhD5+E{f8wYK zhV*1+8g!%9U~e;cZiZ_jCSVU+IlQLe};(Fbv-#fJ1<@<;D?(cp=dg>~6Yj9T^P?QhT59&b(0%%UnVf1&cl`Qh#k z{tXC8M({_u2=x-xO3u@AF8j#BDYXi}q*y@Lah@=FyOx7q|Kr}?&GmIM4I4)Tp%C(? zUaP{orSo%`dAYAj zT_AwrWpuhYuN3yaDFa5*Hi()PV>0R-el$@`Q;KauY#NKl?T_|Wl3_y~!fdSOeZ7(h%deI^oh-S5|vNYKuMjXe}q(A(2PNdkhDh( zzJlk)Ehk*{$!%kcqZ#=1Kr#mXMzzZLV%kNcKd4uGAkb~fTyp9SYud57X_FDdK^eCX zhl+$z!c9)Qz)K!Tv=eXs_OE|S_nF?+<)B>|K0Hq(JYxGL9G&35fBSnl907fvFS^&i zW5~SD9iQM&5Xj3?e@W;`7ADdT<14N>bh^khyojMY5wt6j)TiK|(U|gj?U+obt!gNtSP~?Y%j6x0gI;fY(v{ x5|Bm{cBwl8?+!3rY01Aci~d(&a@$)o>c3ceanov#q1pfd002ovPDHLkV1m`T1SJ3f literal 3930 zcmV-g52f&lP)cBf00001b5ch_0Itp) z=>Px#32;bRa{vGizW@LZzX3P}QzQTY4*y9+K~#9!Y*GiR1wjnb-HZL+`~9!-G9NM# zL7od!j430_zxT(rfn}681yMr)y=?4IE5y&@nrNcc^8wym$t;ZJus}LOEhGqvi8^Yl zaWXu!2S|DsxN^95WX@W9kRi#%@0a^)E{Dali1X~WSexBevRbcrahmA@hV#r5CTK?@ zbk#?FNSWF6Sw*!8)~Cyy(fYjCkH_0^c~D6}=4)t>Gkt9_!sQ`*iMK5BBwJQh5~R(R z!?s#N9_P_SfvYYPRjI`CU?Gcc=XvfBDUD)J6sFP$q+osuKwG{bM;7HC=sbt+>*8RD&&Yq>5n*1)BQn@V3 zDw7pKRxk$fOxYUx`S}5Z@#HU>r<7yko(|6jv$Ph4%jW~uf)K8TZR#9;Ph&V2cH7;~ zN)*>3Yjrvpo?oxq_j~<*5zFx#vP#CY8CNx~s>8`7u|}>$zy9TkA`@3CRe5)ice>AB zSN4WSuRnq&LDsZ`0rL4g94!)+B!H#FVU4*D-`N1CnHqN0LkyA{s= zC5_r>w#KvhWIk{Ax_;m}P`GO_9zz2dj7NdZh*C-dJ&~aSdO;VArZfF~0=NgWf+UHG z!W0GJOGH^ihdqSR&a}XWOJQhaol>-IO#3tCa)l}Xj97(RBTv$XWFixH$KBz#{(a~E z*PcIi?sqr@hb4rNjfc1=n|ql$irzeHLw2^ir`q1GdaJ7E2aKK+s?}^}Qb~i!WHOua z0c=6Dqf8J}nau|^D1y;qL8aI2LV`X%$*^0bRefl}TCH|+Zo%#M%X%3jwOg%1K6iO? zQm<7>iX04geA=HtDa@_VIQFf)1bS+%X+KBfhzQj#vu{LjT|d=WCsXU^92J2 zlM!W_K->pO(p!QG69h!L8jbD&^=`LZEQyY}-D*RU6ppwQq(~JuLrIcm7MFMvvcZ5x z+UNYrI=46L^_!~;0-5PjbV*>_#r2Je={dd8z>Ex&G#HIL|NLjW)nvGs`bk$Y6|*bv z7o)8_e=$JK{9Iq0mkI^;>C7WsWkqA0d8OksQ~Jj}8cn9)*hDOH{pfW>Q9@Z6jln{- zUT++3Z$gBbv05y#>CiF4@z4sWBcU_5yuxeYV*tz`4PNDaHXFG(qrrJ6v>+1q`9OAy zTAdC?kWReo^?F`^P@~n2PE6G*m3$`4%Qsmp>~sOMqC3L_!Gmh3@Ai2=8aLv(yNR$J z@M3r=#HP@ar_RHq#lykT(WUhblmUwh%_`N(vuB-lD;d8dSRjc4x+;u8#;*KB5fzky zp~dc?cAB$zUtoqpef9ZEU`O!=-Z(0y&}%hYA}KT)4JwLHr`@KJ=?kMwsAEgB%c~ll z7PX7;VJs4P-|wY-!HrjMRFZmXZowZK<)Oe}G&<2B>$iv~*a{XvE%Y7E%I0#h)f){a zc=9JBrET)sxT3HIUUsJw#O)s)wc2cuh@4+YS~Ymt=5(#TcmbP>U0)Xq_ZSPE1YPE6 z_z`ifMhn~L^Z{+@WTH|k^ZcQSNqpe7pdbEgwc5M?{s#}u(epq$0h4FG6DgHs%r?6x z;K!F*wQ_zGCL#uXjf{_b0wJ9&<0le=po0&LAwtaI;dwYLM9Q%A8SXzaF==(S_Rkc!ttftj$)T@=d+i0uN5CYl{0OGT-+U$~~L31{dsMjilM!VgL-`=_dL5Ss2smSFS z9pBjWtTz}WGZsVh0c!3lT0re^9u}*W+1c)L;wPj;A20?*Ae0&4?5F4FK*S&i%vURw zBbS$ncnrvNhK5cK_e+I)Xf(_#Ff!H;_ONMa2?zw^NWhMc4iAS4^?L1KbCVE29cR_; zDoa&l(SCYx@Ze!XAC)=7l12-*0?=-M@a@TQHk}mx5b!ZuZC+mh zE!A43S}KET2>}>gU$BQ#Ax|^Z8?}p*6Kc0QoL)fBbV}n2DD%T8L(wFShH7B>yPF#n zVCML7P{D^qRmG*kn%^z0uh*(o=xHvU&ZaX6x<)4_=U0{?2_-n*-%lqJG!)`)S(YJy z?u_zNZb!DF8df{q9+es-sgg931me+1|NO)6Awes+&*vq0Kjsv4d`vE3O)K9#2l3%U zrBrO@b4IhpWVPWf8{~ig=?^$U@m>Srw%hAPC3ll zVUC<%*|0gCD8?dh5ho!8lnwfY)eX1Lhfo1FetNK{B*mo454wyW8nUS*dUd-Uw38nf zfm08w@Fq4Bbes|4G8I3LveWGjj*g*%L>ASn^D{UbkT9c&2>PA2&&hsRx87ms?F{|^*)y&U8cW&x81E2OUxgT#?$R|@M|`kD5H@< zem^Y6m;_m0Ex`$lqVm4mIo#SVLLZ=@HAqeGJr3GOcSBw>htGNF*L-ZH+UmDoLgBY8ew3? zA$Zu0*RNpTz<1!WIP4^e?D5&_|Kn&?l>EbQfAN4a^M0jV-u>4(csj+JKJe1<%J&7J*i)rIqK;Lt|qQYt(9oJ3DBE zUL%x(6jDu5ml<&``vW1BB#HPkoE%f)#}o2bx+#1_S^3v&G-Ht{#znX~IXg#lAPnN{ z^dfvbq*g%~Jb+ZYS#RP4l5gTklwo=>Mius?RwG{rnbLGf+bBdwn8E2Q#}AEqofnqR zWvk^fNZjFaAxcTaZqWz|3XY6%K%$fN1R?HH`XAF}p+H&;%37OQSWu}YEDvrfn@ZzT zXnX=b2m692A4vANN`-^%Z7|r!u0{tD9}psnqe$+F;HZQ=dvR?Q6&%6o;USwhf6>R4$H&O0apOg45~NazIB#G;4e5dW+w;@47q37* zc-tIK-~zazDG*EE#jelKSbS=J0iUS+QIv^`7uMEB#wWoQ80utyANL{9EqsbzUJ=!f zPtQ!u&WOZ3pv#D<6hky47Wh2MY(kxfz^aY{0%VE81JRX`mCHsG&GY=t|Fi2B@8b<- zqd}1YuV4_qPCeS${{J8S7+ad2U$oflpc;4v+^eAzE@rMyPuZujb>k;4_xkKCG&TXQ zba{QamIESK3?%BcT6kv*aXy2HTT`G+s0`vu%C3>tUcN#`gA0LPr+XF-AMYQ4`e-}3 zYz99RY!0|7p!~@PZV7MX&`A8a@#9zf)HaORE(#i;eMxX*BE)Q!T%B84B5((=M)*PE z16MGyXw<^T3#RSS~i zKrA|DW_~}u|9?29WyWDcBq|>=X3_*S>McSo*{?i_ec%N{QATkprXGwLCu;6u~Xd}(2RWofZ4yntvh1z%}96cOgRu>O!orUTx!ix+ky!Lo9G z^FF0MPJ}qNk=u8o6ItjO70T2XM~(M`W$_dQ%S~_)23a^DqoVpG!_i~(TkpK1mzn(x zA}WXYs|b>cOQqzH3X69k{GzMJ%n~xK&3`6vRB}>@fG*G>P}y0(Ean+m>=k5t1(DRa*(|jH&2gG#tAnjb2HN{9fRc)E@W0fCR!Fvuu@z$mSaQ|`}eos zC7*Q72u+5`ZVKHU!W$$st9#n+;KKo$AK*6%emM@){4)*afqCR=K&;pq40NMdiQ zbHMUa>Tj_6|BpZL+;eA2I_dk_0X9T80vI0|+1{!ltt~IJB{O{U{-<3r$wjZc%+d~e zgoV}ckwySC!B^cCt{Fp+=g&X)e4^td_&K4*+yu=@MQQL0#*wm_#hM1Fqo@JoeW8I= zk%6hP->pQ>}b)p4G)i2B}%?d7sKHoJAbpnOtI6=KFVi(9T)5~9PF2^@WneH z*^S+#%OTzZc5$w*l0aYa7>_jNdNSsd7qS5c^fJ$~_4&76e%;^IJ!15OhYD)%8yeqGynhq07*qoM6N<$f&tHYuK)l5 diff --git a/resource/global/txwy/resource/tasks/MiniGame/SecretFront.json b/resource/global/txwy/resource/tasks/MiniGame/SecretFront.json index cf47a7c6f8..7137aac251 100644 --- a/resource/global/txwy/resource/tasks/MiniGame/SecretFront.json +++ b/resource/global/txwy/resource/tasks/MiniGame/SecretFront.json @@ -4,5 +4,12 @@ [" ", ""], ["結局", "4"] ] + }, + "MiniGame@SecretFront@PreClickDetect": { + "ocrReplace": [ + ["支援作戰平台", "支援作战平台"], + ["遊俠", "游侠"], + ["詭影迷蹤", "鬼影迷踪"] + ] } } diff --git a/resource/tasks/MiniGame/SecretFront.json b/resource/tasks/MiniGame/SecretFront.json index a64627cfcd..f15a9c649c 100644 --- a/resource/tasks/MiniGame/SecretFront.json +++ b/resource/tasks/MiniGame/SecretFront.json @@ -4,26 +4,6 @@ "algorithm": "JustReturn", "next": ["MiniGame@SecretFront@Run"] }, - "MiniGame@SecretFront@EndingA@Begin": { - "doc": "隐秘战线:结局A入口(插件根据入口名确定结局与路线)", - "baseTask": "MiniGame@SecretFront@Begin" - }, - "MiniGame@SecretFront@EndingB@Begin": { - "doc": "隐秘战线:结局B入口(插件根据入口名确定结局与路线)", - "baseTask": "MiniGame@SecretFront@Begin" - }, - "MiniGame@SecretFront@EndingC@Begin": { - "doc": "隐秘战线:结局C入口(插件根据入口名确定结局与路线)", - "baseTask": "MiniGame@SecretFront@Begin" - }, - "MiniGame@SecretFront@EndingD@Begin": { - "doc": "隐秘战线:结局D入口(插件根据入口名确定结局与路线)", - "baseTask": "MiniGame@SecretFront@Begin" - }, - "MiniGame@SecretFront@EndingE@Begin": { - "doc": "隐秘战线:结局E入口(插件根据入口名确定结局与路线)", - "baseTask": "MiniGame@SecretFront@Begin" - }, "MiniGame@SecretFront@Run": { "doc": "从隐秘战线主界面开始,后续交由插件控制路线与结局", "algorithm": "JustReturn", @@ -78,7 +58,14 @@ "MiniGame@SecretFront@RouteSelect": { "roi": [0, 280, 150, 230], "preDelay": 1500, - "next": ["MiniGame@SecretFront@EventDetected", "MiniGame@SecretFront@ClickThenActionsDetected"] + "next": ["MiniGame@SecretFront@EventDetected", "MiniGame@SecretFront@PreClickDetect", "MiniGame@SecretFront@ClickThenActionsDetected"] + }, + "MiniGame@SecretFront@PreClickDetect": { + "doc": "隐秘战线:在默认点击前做一次事件识别(供插件拦截并在当前页选卡)", + "algorithm": "OcrDetect", + "text": ["支援作战平台", "游侠", "鬼影迷踪"], + "roi": [200, 320, 1020, 200], + "next": ["MiniGame@SecretFront@ActionsDetected", "MiniGame@SecretFront@RouteSelect"] }, "MiniGame@SecretFront@ClickThenActionsDetected": { "template": "MiniGame@SecretFront@RouteSelect.png", diff --git a/src/MaaCore/Task/Interface/CustomTask.cpp b/src/MaaCore/Task/Interface/CustomTask.cpp index 04165b3ab8..f85169f09f 100644 --- a/src/MaaCore/Task/Interface/CustomTask.cpp +++ b/src/MaaCore/Task/Interface/CustomTask.cpp @@ -30,18 +30,96 @@ bool asst::CustomTask::set_params(const json::value& params) Log.error("set_params failed, task is not string"); return false; } - if (Task.get(t.as_string()) == nullptr) { - Log.error("set_params failed, task not found: ", t.as_string()); + + std::string task_name = t.as_string(); + std::string resolved_task = task_name; + + if (parse_and_register_secretfront(task_name, resolved_task)) { + Log.info("Parsed and registered SecretFront task: ", task_name, " -> ", resolved_task); + } + /* else if (parse_and_register_xxx) */ + else { + resolved_task = task_name; + } + + if (Task.get(resolved_task) == nullptr) { + Log.error("set_params failed, task not found: ", resolved_task); return false; } - if (t.as_string().starts_with("MiniGame@SecretFront@")) { - m_custom_task_ptr->register_plugin(); - } - - tasks.emplace_back(t.as_string()); + tasks.emplace_back(std::move(resolved_task)); } m_custom_task_ptr->set_tasks(std::move(tasks)); m_subtasks.emplace_back(m_custom_task_ptr); return true; } + +bool asst::CustomTask::parse_and_register_secretfront(const std::string& task_name, std::string& resolved_task) +{ + if (!task_name.starts_with("MiniGame@SecretFront@Begin")) { + return false; + } + + std::optional event_name; + std::optional ending_token; + + // 允许识别形式:MiniGame@SecretFront@Begin@Ending[ABCDE]@[事件名] + size_t start = strlen("MiniGame@SecretFront@Begin"); + if (start < task_name.size() && task_name[start] == '@') { + ++start; // skip the separator '@' + } + + // 提取 Ending token(直到下一个 '@' 或末尾) + size_t next_at = task_name.find('@', start); + std::string ending; + if (start < task_name.size()) { + if (next_at == std::string::npos) { + ending = task_name.substr(start); + } + else { + ending = task_name.substr(start, next_at - start); + } + } + + if (!ending.empty()) { + ending_token = ending; + } + + // 如果存在第二个 '@' 分隔,后续内容视为事件名(直到末尾) + if (next_at != std::string::npos && next_at + 1 < task_name.size()) { + event_name = task_name.substr(next_at + 1); + } + + auto plugin_ptr = m_custom_task_ptr->register_plugin(); + if (!plugin_ptr) { + Log.error("Failed to register SecretFrontTaskPlugin"); + return false; + } + + if (event_name && !event_name->empty()) { + plugin_ptr->set_event_name(*event_name); + Log.info("Set SecretFront event name:", *event_name); + } + if (ending_token && !ending_token->empty()) { + if (*ending_token == "EndingA") { + plugin_ptr->set_ending(SecretFrontTaskPlugin::Ending::A); + } + else if (*ending_token == "EndingB") { + plugin_ptr->set_ending(SecretFrontTaskPlugin::Ending::B); + } + else if (*ending_token == "EndingC") { + plugin_ptr->set_ending(SecretFrontTaskPlugin::Ending::C); + } + else if (*ending_token == "EndingD") { + plugin_ptr->set_ending(SecretFrontTaskPlugin::Ending::D); + } + else if (*ending_token == "EndingE") { + plugin_ptr->set_ending(SecretFrontTaskPlugin::Ending::E); + } + Log.info("Set SecretFront ending:", *ending_token); + } + + resolved_task = "MiniGame@SecretFront@Begin"; + + return true; +} diff --git a/src/MaaCore/Task/Interface/CustomTask.h b/src/MaaCore/Task/Interface/CustomTask.h index 7689b13e48..1ecffb311d 100644 --- a/src/MaaCore/Task/Interface/CustomTask.h +++ b/src/MaaCore/Task/Interface/CustomTask.h @@ -14,6 +14,8 @@ public: virtual ~CustomTask() override = default; virtual bool set_params(const json::value& params) override; + bool parse_and_register_secretfront(const std::string& task_name, std::string& resolved_task); + private: std::shared_ptr m_custom_task_ptr = nullptr; }; diff --git a/src/MaaCore/Task/MiniGame/SecretFrontTaskPlugin.cpp b/src/MaaCore/Task/MiniGame/SecretFrontTaskPlugin.cpp index 8b2bdd7e9b..fafda59ca3 100644 --- a/src/MaaCore/Task/MiniGame/SecretFrontTaskPlugin.cpp +++ b/src/MaaCore/Task/MiniGame/SecretFrontTaskPlugin.cpp @@ -28,32 +28,21 @@ constexpr std::array Target_EndC { 350, 550, 350 }; constexpr std::array Target_EndD { 365, 475, 495 }; constexpr std::array Target_EndE { 370, 385, 620 }; -constexpr std::string_view HookTaskLegacy = "MiniGame@SecretFront@Begin"; -constexpr std::string_view HookTaskA = "MiniGame@SecretFront@EndingA@Begin"; -constexpr std::string_view HookTaskB = "MiniGame@SecretFront@EndingB@Begin"; -constexpr std::string_view HookTaskC = "MiniGame@SecretFront@EndingC@Begin"; -constexpr std::string_view HookTaskD = "MiniGame@SecretFront@EndingD@Begin"; -constexpr std::string_view HookTaskE = "MiniGame@SecretFront@EndingE@Begin"; - constexpr std::string_view RouteA[] { "1A", "2A", "3A", "4A" }; constexpr std::string_view RouteB[] { "1A", "2A", "3A", "4B" }; constexpr std::string_view RouteC[] { "1A", "2A", "3B", "4C" }; constexpr std::string_view RouteD[] { "1A", "2B", "3C", "4D" }; constexpr std::string_view RouteE[] { "1A", "2B", "3C", "4E" }; -constexpr SecretFrontTaskPlugin::EndingInfo EndingTable[] { - { HookTaskA, Target_EndA, SecretFrontTaskPlugin::Ending::A }, - { HookTaskB, Target_EndB, SecretFrontTaskPlugin::Ending::B }, - { HookTaskC, Target_EndC, SecretFrontTaskPlugin::Ending::C }, - { HookTaskD, Target_EndD, SecretFrontTaskPlugin::Ending::D }, - { HookTaskE, Target_EndE, SecretFrontTaskPlugin::Ending::E }, - { HookTaskLegacy, Target_EndE, SecretFrontTaskPlugin::Ending::E }, -}; - constexpr std::pair> StageTargets[] { { "1A", Target_1A }, { "2A", Target_2A }, { "2B", Target_2B }, { "3A", Target_3A }, { "3B", Target_3B }, { "3C", Target_3C }, }; +constexpr std::pair> EndingTargets[] { + { SecretFrontTaskPlugin::Ending::A, Target_EndA }, { SecretFrontTaskPlugin::Ending::B, Target_EndB }, + { SecretFrontTaskPlugin::Ending::C, Target_EndC }, { SecretFrontTaskPlugin::Ending::D, Target_EndD }, + { SecretFrontTaskPlugin::Ending::E, Target_EndE }, +}; bool SecretFrontTaskPlugin::verify(AsstMsg msg, const json::value& details) const { @@ -64,19 +53,7 @@ bool SecretFrontTaskPlugin::verify(AsstMsg msg, const json::value& details) cons const std::string& task = details.get("details", "task", ""); - // 1) 若命中结局入口(Begin 节点),仅用于确定 m_ending / m_target,不触发插件运行 - for (const auto& e : EndingTable) { - if (!task.ends_with(e.hook)) { - continue; - } - - m_target = e.target; - m_ending = e.ending; - - return false; - } - - // 2) 卡片判断:区分有数值的 Actions 与基于文本的 Event + // 卡片判断:区分有数值的 Actions 与基于文本的 Event if (task.ends_with("MiniGame@SecretFront@ActionsDetected")) { m_mode = Mode::Actions; Log.info(__FUNCTION__, "| detect Actions"); @@ -87,8 +64,13 @@ bool SecretFrontTaskPlugin::verify(AsstMsg msg, const json::value& details) cons Log.info(__FUNCTION__, "| detect Event"); return true; } + if (task.ends_with("MiniGame@SecretFront@PreClickDetect")) { + m_mode = Mode::DetectBeforeClick; + Log.info(__FUNCTION__, "| detect PreClickDetect (pre-click detect)"); + return true; + } - // 3) SelectTeam 进入插件,由插件根据结局选择对应分队 + // SelectTeam 进入插件,由插件根据结局选择对应分队 if (task.ends_with("MiniGame@SecretFront@SelectTeam")) { m_mode = Mode::SelectTeam; return true; @@ -97,6 +79,22 @@ bool SecretFrontTaskPlugin::verify(AsstMsg msg, const json::value& details) cons return false; } +void SecretFrontTaskPlugin::set_event_name(std::string name) +{ + m_event_name = std::move(name); +} + +void SecretFrontTaskPlugin::set_ending(Ending e) +{ + m_ending = e; + for (const auto& [name, target] : EndingTargets) { + if (e == name) { + m_target = target; + break; + } + } +} + Point SecretFrontTaskPlugin::card_pos_base(int total, int idx) { if (total == 3) { @@ -260,6 +258,7 @@ SecretFrontTaskPlugin::BestChoice SecretFrontTaskPlugin::choose_best_card( int last_page = 0; // 当前页评分 + double percentage = 0; for (int idx = 0; idx < current_total; ++idx) { double sc = calc_score(cards[idx]); if (sc > best.score) { @@ -267,44 +266,59 @@ SecretFrontTaskPlugin::BestChoice SecretFrontTaskPlugin::choose_best_card( best.page = 0; best.total = current_total; best.idx = idx; + percentage = cards[idx].percentage; } } - // 尝试遍历后续两页进行全局比较(翻页操作) - for (int page = 1; page < 3; ++page) { - ProcessTask(*this, { "MiniGame@SecretFront@ClickNextPage" }).run(); - sleep(1000); + // 前期遇到事件很有可能无法完成,成功率太低就先发育 + if (m_only_current_page && percentage < 0.8) { + Log.info(__FUNCTION__, "| only_current_page set and max_percentage < 0.8: skip scanning subsequent pages"); + m_only_current_page = false; + } - auto image = ctrler()->get_image(); - if (image.empty()) { - break; - } + // 如果只需在当前页内做选择(由 pre-click 识别触发),跳过后续页遍历 + if (m_only_current_page) { + Log.info(__FUNCTION__, "| only_current_page set: skip scanning subsequent pages"); + // 重置标志以便下次操作不受影响 + m_only_current_page = false; + } + else { + // 尝试遍历后续两页进行全局比较(翻页操作) + for (int page = 1; page < 3; ++page) { + ProcessTask(*this, { "MiniGame@SecretFront@ClickNextPage" }).run(); + sleep(1000); - const int t = estimate_total_cards(image); - if (t <= 0) { - continue; - } - - last_page = page; - - std::vector page_cards; - page_cards.reserve(t); - for (int i = 0; i < t; ++i) { - if (auto card = read_card(image, t, i)) { - page_cards.emplace_back(*card); + auto image = ctrler()->get_image(); + if (image.empty()) { + break; } - else { - page_cards.emplace_back(CardData {}); - } - } - for (int idx = 0; idx < t; ++idx) { - double sc = calc_score(page_cards[idx]); - if (sc > best.score) { - best.score = sc; - best.page = page; - best.total = t; - best.idx = idx; + const int t = estimate_total_cards(image); + if (t <= 0) { + continue; + } + + last_page = page; + + std::vector page_cards; + page_cards.reserve(t); + for (int i = 0; i < t; ++i) { + if (auto card = read_card(image, t, i)) { + page_cards.emplace_back(*card); + } + else { + page_cards.emplace_back(CardData {}); + } + } + + for (int idx = 0; idx < t; ++idx) { + double sc = calc_score(page_cards[idx]); + if (sc > best.score) { + best.score = sc; + best.page = page; + best.total = t; + best.idx = idx; + } } } } @@ -473,6 +487,44 @@ bool SecretFrontTaskPlugin::_run() return false; } + // 所有事件都会优先进这个,但如果设了目标事件就只在当前页检测 + if (m_mode == Mode::DetectBeforeClick) { + auto try_ocr_and_click = [&](const std::optional& target_event = std::nullopt) -> bool { + OCRer ocr(image); + ocr.set_task_info(Task.get("MiniGame@SecretFront@PreClickDetect")); + ocr.set_image(image); + + if (target_event.has_value()) { + ocr.set_required({ target_event.value() }); + } + + if (ocr.analyze()) { + ctrler()->click(ocr.get_result().front().rect); + sleep(1000); + m_only_current_page = true; + return true; + } + return false; + }; + + // 第一次尝试:优先识别指定事件(如果有) + if (m_event_name.has_value()) { + if (try_ocr_and_click(m_event_name)) { + return true; + } + } + + // 第二次尝试:优先系列任务 + if (try_ocr_and_click()) { + return true; + } + + // 一般来说不会走到这里,没识别到就点默认位置 + ctrler()->click(Task.get("MiniGame@SecretFront@ClickThenActionsDetected")->specific_rect); + sleep(1000); + return true; + } + // 如果是 Event 模式,直接按事件页文本处理 if (m_mode == Mode::Event) { Log.info(__FUNCTION__, "| event mode: handle by text OCR"); diff --git a/src/MaaCore/Task/MiniGame/SecretFrontTaskPlugin.h b/src/MaaCore/Task/MiniGame/SecretFrontTaskPlugin.h index 51b83c3bc2..d2cdc25e0d 100644 --- a/src/MaaCore/Task/MiniGame/SecretFrontTaskPlugin.h +++ b/src/MaaCore/Task/MiniGame/SecretFrontTaskPlugin.h @@ -35,12 +35,8 @@ public: E }; - struct EndingInfo - { - std::string_view hook; - std::array target; - Ending ending; - }; + void set_event_name(std::string name); + void set_ending(Ending e); protected: virtual bool _run() override; @@ -64,11 +60,15 @@ private: enum class Mode { - Actions, // 行动列表选卡(有数值) - Event, // 事件卡(无数值,仅基于文本选择) - SelectTeam, // 选择分队 + Actions, // 行动列表选卡(有数值) + Event, // 事件卡(无数值,仅基于文本选择) + SelectTeam, // 选择分队 + DetectBeforeClick, // RouteSelect 点击前的事件识别(ClickThenActionsDetected 前触发) }; + // 当在 ClickThenActionsDetected 前识别到指定事件时,插件只在当前页做最优选择 + mutable bool m_only_current_page { false }; + static Point card_pos_base(int total, int idx); std::optional ocr_int(const cv::Mat& image, const Rect& roi) const; @@ -107,6 +107,7 @@ private: mutable std::array m_target { 370, 385, 620 }; mutable Ending m_ending { Ending::E }; + mutable std::optional m_event_name; mutable Mode m_mode { Mode::Actions }; }; } diff --git a/src/MaaWpfGui/Constants/ConfigurationKeys.cs b/src/MaaWpfGui/Constants/ConfigurationKeys.cs index be1cf42b92..c210255425 100644 --- a/src/MaaWpfGui/Constants/ConfigurationKeys.cs +++ b/src/MaaWpfGui/Constants/ConfigurationKeys.cs @@ -334,6 +334,7 @@ public static class ConfigurationKeys public const string MiniGameTaskName = "MiniGame.TaskName"; public const string MiniGameSecretFrontEnding = "MiniGame.SecretFrontEnding"; + public const string MiniGameSecretFrontEvent = "MiniGame.SecretFrontEvent"; public const string AchievementPopupDisabled = "Achievement.PopupDisabled"; public const string AchievementPopupAutoClose = "Achievement.PopupAutoClose"; diff --git a/src/MaaWpfGui/Res/Localizations/en-us.xaml b/src/MaaWpfGui/Res/Localizations/en-us.xaml index c9c869020c..a5d3b36e9e 100644 --- a/src/MaaWpfGui/Res/Localizations/en-us.xaml +++ b/src/MaaWpfGui/Res/Localizations/en-us.xaml @@ -833,8 +833,12 @@ Only level rewards can be farmed; to get the Engraved Medal, all 「Key Objectiv Contest Channel: Honeydew {key=MiniGame@ALL@GreenGrassTip} Ending + Priority for Series of Events + Support Platform + Knight-errant + Sly Shadows Hidden Front - Start from the squad selection screen; if there is a saved game, please delete it manually. Close the tutorial after playing it for the first time. Recommended to enable in-game 「Inherit previous squad's data」. + Start from the squad selection screen; If there is a saved game, please delete it manually. Close the tutorial after playing it for the first time. Recommended to enable in-game 「Inherit previous squad's data」. Green Ticket Store Buy everything on the 1st floor. Buy Headhunting Permits and Recruitment Permits on the 2nd floor. Yellow Ticket Store diff --git a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml index 07d603e790..98404f92c2 100644 --- a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml +++ b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml @@ -834,6 +834,10 @@ C:\\leidian\\LDPlayer9 デュエルチャンネル:ハニーフルーツシティ(非公式) {key=MiniGame@ALL@GreenGrassTip} エンディング + シリーズ事件の優先順位 + 作戦プラットフォーム支援任務 + 遊侠 + 揺蕩う影 隠密戦線 小隊選択画面で開始します。セーブデータがある場合は手動で削除する必要があります。ゲーム内の「前の小隊からのデータを引き継ぐ」にチェックすることを推奨します。 緑チケット商店 diff --git a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml index bcb436bbc0..924b549bfb 100644 --- a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml +++ b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml @@ -834,9 +834,13 @@ C:\\leidian\\LDPlayer9 튜토리얼 대화를 수동으로 건너뛴 후, 바로 종료할 수 있어요 이벤트 메인 화면에서 임무를 시작하세요 (오른쪽 아래 「경기 진행」) 덕로드만 믿고 따라가면 손해는 없을거에요 듀얼 채널: 허니 듀 (비공식) {key=MiniGame@ALL@GreenGrassTip} - 결말 + 엔딩 + 우선 시리즈 사건 + 작전 플랫폼 지원 + 레인저 + 수상한 그림자 비밀 전선 - 소대 선택 화면에서 시작합니다. 기존 세이브 데이터가 있으면 수동으로 삭제해야 합니다. 게임 내 「이전 소대의 데이터 전승받기」 옵션을 체크하는 것을 권장합니다. + 소대 선택 화면에서 시작합니다. 기존 세이브 데이터가 있으면 수동으로 삭제해야 합니다.\n게임 내 「이전 소대의 데이터 전승받기」 옵션을 체크하는 것을 권장합니다. 자격증명서 상점 1단계는 전부 구매하고 2단계는 헤드헌팅 허가증과 모집 허가증을 구매해요 고급증명서 상점 diff --git a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml index 9d82077a02..5dbdf19623 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml @@ -834,6 +834,10 @@ C:\\leidian\\LDPlayer9。\n 争锋频道:蜜果城 手动跳过教程对话,然后可以直接退出。 在活动主界面(右下角有 「加入赛事」 处)开始任务。 跟着鸭总喝口汤。 结局 + 优先系列事件 + 支援作战平台 + 游侠 + 鬼影迷踪 隐秘战线 在选小队界面开始,如有存档须手动删除。 第一次打自己看完把教程关了。 推荐勾选游戏内 「继承上一支队伍发回的数据」 绿票商店 diff --git a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml index e3b1c9ed04..41f053e897 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml @@ -737,9 +737,9 @@ C:\\leidian\\LDPlayer9。\n 重新整理 確定 取消 - 左鍵:開/關日誌懸浮窗 -右鍵:選擇目標進程 - 開/關日誌懸浮窗 + 左鍵:開啟/關閉日誌浮動視窗 +右鍵:選擇目標程式 + 開啟/關閉日誌浮動視窗 小工具 @@ -834,8 +834,12 @@ C:\\leidian\\LDPlayer9。\n 爭鋒頻道:蜜果城 {key=MiniGame@ALL@GreenGrassTip} 結局 + 優先系列事件 + 支援作戰平台 + 遊俠 + 詭影迷蹤 隱秘戰線 - 在選擇小隊介面開始,如有存檔須手動刪除,推薦勾選遊戲內「繼承上一支隊伍發回的數據」 + 在選擇小隊介面開始,如有存檔須手動刪除 第一次打請先看完教學後關閉。 推薦勾選遊戲內「繼承上一支隊伍發回的數據」 綠票商店 1 層全買。 2 層買尋訪憑證和招聘許可。 黃票商店 diff --git a/src/MaaWpfGui/ViewModels/UI/ToolboxViewModel.cs b/src/MaaWpfGui/ViewModels/UI/ToolboxViewModel.cs index 37d2067497..7ba32f5bae 100644 --- a/src/MaaWpfGui/ViewModels/UI/ToolboxViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/ToolboxViewModel.cs @@ -32,6 +32,7 @@ using MaaWpfGui.Main; using MaaWpfGui.Models; using MaaWpfGui.Models.AsstTasks; using MaaWpfGui.States; +using MaaWpfGui.Utilities.ValueType; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Serilog; @@ -1234,9 +1235,8 @@ public class ToolboxViewModel : Screen public string GetMiniGameTask() { - return MiniGameTaskName switch - { - "MiniGame@SecretFront" => $"{MiniGameTaskName}@Ending{SecretFrontEnding}@Begin", + return MiniGameTaskName switch { + "MiniGame@SecretFront" => $"{MiniGameTaskName}@Begin@Ending{SecretFrontEnding}{(string.IsNullOrEmpty(SecretFrontEvent) ? string.Empty : $"@{SecretFrontEvent}")}", _ => MiniGameTaskName, }; } @@ -1303,12 +1303,33 @@ public class ToolboxViewModel : Screen public List SecretFrontEndingList { get; set; } = ["A", "B", "C", "D", "E"]; - private string _secretFrontEnding = ConfigurationHelper.GetGlobalValue(ConfigurationKeys.MiniGameSecretFrontEnding, "A"); + private string _secretFrontEnding = ConfigurationHelper.GetValue(ConfigurationKeys.MiniGameSecretFrontEnding, "A"); public string SecretFrontEnding { get => _secretFrontEnding; - set => SetAndNotify(ref _secretFrontEnding, value); + set { + SetAndNotify(ref _secretFrontEnding, value); + ConfigurationHelper.SetValue(ConfigurationKeys.MiniGameSecretFrontEnding, value); + } + } + + public List> SecretFrontEventList { get; set; } = [ + new GenericCombinedData { Display = LocalizationHelper.GetString("NotSelected"), Value = string.Empty }, + new GenericCombinedData { Display = LocalizationHelper.GetString("MiniGame@SecretFront@Event1"), Value = "支援作战平台" }, + new GenericCombinedData { Display = LocalizationHelper.GetString("MiniGame@SecretFront@Event2"), Value = "游侠" }, + new GenericCombinedData { Display = LocalizationHelper.GetString("MiniGame@SecretFront@Event3"), Value = "鬼影迷踪" }, + ]; + + private string _secretFrontEvent = ConfigurationHelper.GetValue(ConfigurationKeys.MiniGameSecretFrontEvent, string.Empty); + + public string SecretFrontEvent + { + get => _secretFrontEvent; + set { + SetAndNotify(ref _secretFrontEvent, value); + ConfigurationHelper.SetValue(ConfigurationKeys.MiniGameSecretFrontEvent, value); + } } public void StartMiniGame() diff --git a/src/MaaWpfGui/Views/UI/ToolboxView.xaml b/src/MaaWpfGui/Views/UI/ToolboxView.xaml index d91b8766cb..901eae78a8 100644 --- a/src/MaaWpfGui/Views/UI/ToolboxView.xaml +++ b/src/MaaWpfGui/Views/UI/ToolboxView.xaml @@ -639,17 +639,32 @@ Margin="10,0" hc:TitleElement.Title="{DynamicResource MiniGameName}" DisplayMemberPath="Display" + IsEnabled="{Binding Idle}" ItemsSource="{Binding MiniGameTaskList}" SelectedValue="{Binding MiniGameTaskName}" SelectedValuePath="Value" /> - - - + + + + + + + + + + +