From 028fb2280a088b931f6a31e04694af85005c66ba Mon Sep 17 00:00:00 2001 From: mno <718135749@qq.com> Date: Tue, 2 Sep 2025 10:14:16 +0800 Subject: [PATCH] =?UTF-8?q?js=EF=BC=9AAAA=E7=8B=97=E7=B2=AE=E6=89=B9?= =?UTF-8?q?=E5=8F=911.2.4=20(#1794)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * js:AAA狗粮批发1.2.4 * js:狗粮联机团购1.1.0 --- repo/js/AAA-Artifacts-Bulk-Supply/README.md | 2 + repo/js/AAA-Artifacts-Bulk-Supply/main.js | 26 +- .../AAA-Artifacts-Bulk-Supply/manifest.json | 2 +- .../assets/RecognitionObject/allowEnter.png | Bin 0 -> 5373 bytes .../assets/RecognitionObject/enterUID.png | Bin 0 -> 9359 bytes .../assets/RecognitionObject/requestEnter.png | Bin 0 -> 6910 bytes .../assets/RecognitionObject/search.png | Bin 0 -> 6145 bytes .../assets/RecognitionObject/yUI.png | Bin 0 -> 12648 bytes repo/js/ArtifactsGroupPurchasing/main.js | 2111 +++++++---------- .../js/ArtifactsGroupPurchasing/manifest.json | 2 +- .../js/ArtifactsGroupPurchasing/settings.json | 131 +- .../targets/火山老师.png | Bin 0 -> 7694 bytes 12 files changed, 1026 insertions(+), 1248 deletions(-) create mode 100644 repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/allowEnter.png create mode 100644 repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/enterUID.png create mode 100644 repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/requestEnter.png create mode 100644 repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/search.png create mode 100644 repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/yUI.png create mode 100644 repo/js/ArtifactsGroupPurchasing/targets/火山老师.png diff --git a/repo/js/AAA-Artifacts-Bulk-Supply/README.md b/repo/js/AAA-Artifacts-Bulk-Supply/README.md index 1bb58407b..6f5465503 100644 --- a/repo/js/AAA-Artifacts-Bulk-Supply/README.md +++ b/repo/js/AAA-Artifacts-Bulk-Supply/README.md @@ -82,6 +82,8 @@ https://www.kdocs.cn/wo/sl/v13uXscL ## 更新日志 +### 1.2.3(2025.09.01) +1.修复了日期和时间计算的一个bug,该bug曾导致每个月1号时断点续跑不生效 ### 1.2.3(2025.08.31) 1.修几条路线640,642,激活路线度假村/智障厅 ### 1.2.2(2025.08.30) diff --git a/repo/js/AAA-Artifacts-Bulk-Supply/main.js b/repo/js/AAA-Artifacts-Bulk-Supply/main.js index 82c29120d..511771781 100644 --- a/repo/js/AAA-Artifacts-Bulk-Supply/main.js +++ b/repo/js/AAA-Artifacts-Bulk-Supply/main.js @@ -245,12 +245,30 @@ async function readRecord(accountName) { /* ---------- 判断今日是否运行(北京时间 04:00 分界,手动拼接 UTC 20 点) ---------- */ if (record.lastRunDate) { - const [y, m, d] = record.lastRunDate.split("/").map(Number); - // 东八区 04:00 对应 UTC 20:00 - const lastRun4AM = new Date(`${y}-${String(m).padStart(2, '0')}-${String(d - 1).padStart(2, '0')}T20:00:00.000Z`).getTime(); + const [y, m, d] = record.lastRunDate.split('/').map(Number); + + // 1. 用 UTC 构造记录日期 00:00:00 + const recordUtc = Date.UTC(y, m - 1, d); // 毫秒 + + // 2. 减 24 小时得到“前一天” + const prevUtc = recordUtc - 24 * 60 * 60 * 1000; + + // 3. 从毫秒时间戳里取出 UTC 年月日 + const prev = new Date(prevUtc); + const yy = prev.getUTCFullYear(); + const mm = prev.getUTCMonth() + 1; // 1-based + const dd = prev.getUTCDate(); + + // 4. 严格按模板字符串拼成合法日期 + const lastRun4AM = new Date( + `${yy}-${String(mm).padStart(2, '0')}-${String(dd).padStart(2, '0')}T20:00:00.000Z` + ).getTime(); + //log.info(`lastRun4AM = ${new Date(lastRun4AM).toISOString()}`); - const now = Date.now(); + const now = Date.now(); // 当前毫秒时间戳 + //log.info(`时间差为 ${now - lastRun4AM} ms`); + if (now - lastRun4AM < 24 * 60 * 60 * 1000) { log.info("今日已经运行过狗粮"); state.runnedToday = true; diff --git a/repo/js/AAA-Artifacts-Bulk-Supply/manifest.json b/repo/js/AAA-Artifacts-Bulk-Supply/manifest.json index 0fa07c2fd..0d2cd69c7 100644 --- a/repo/js/AAA-Artifacts-Bulk-Supply/manifest.json +++ b/repo/js/AAA-Artifacts-Bulk-Supply/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "AAA狗粮批发", - "version": "1.2.3", + "version": "1.2.4", "tags": [ "狗粮" ], diff --git a/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/allowEnter.png b/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/allowEnter.png new file mode 100644 index 0000000000000000000000000000000000000000..8c6cd7632d348cbe16cb51a55c3d0c58ed2431f7 GIT binary patch literal 5373 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 bTZ8@&>JyzXkidUA00000NkvXXu0mjf#2#vG literal 0 HcmV?d00001 diff --git a/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/enterUID.png b/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/enterUID.png new file mode 100644 index 0000000000000000000000000000000000000000..d1041e54817a80f0cf761600b80299f1d05a6b15 GIT binary patch literal 9359 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
^P$%1N8QHlIuz#4V&$qHCERbcro>i&C&*kj~?Q~ z4?m!{x8O7N$UuSKpZyOA;kz8x0qp;=_L2vEZv3HT(+RGj zcJt>yeV^T(I=iI?d(|!4(`_gP8(SqxJ7qK}QJ(UVK{s(7g+QR`8oHrV+pm#sOL6+- zC5{|9%+bR~SX*0RacPN(iRVmAJjZIb_^ZGCOGZb>*mo?yB&%G6kSs2*QQF-l5{;1S z%y8!PF&x|C#fuqi#~~64gK}{_hwA=5mhEAhL0q?vsY5Q)&idLq$^%zue)1+fQi&Md zg)X|fyI5K(QLQ!*LiZW;fOmcJK49sC82?hdFRQ07C$uO0Dx?q+DK#|B^)I9po!NE@ zg&fIb6e%H~2UuTTW2;!gvK>4WnxXrjp&KCltp)&^u2I>o^7P4bjvX7~=+P0vkpNqz zHKt}>P~56AI#ytCv=h?|q6wXE??2|&&4*O>YM@Jhw)-z)6O;3#(`hQzDwS%LbUMxP@l%{QaSB5Z`uHJ) zz;#`ulxUiU>$d9>H~8zP|YlH}8JSZng2M z4+qYOP%f+Mn}kDQ5~&bl!=t1VNs8Osly=GpX@F)BFf<1H3!FM}l#?e8(^u#@NTXbW z0UfJpGx21EP%oNpGBGvF^XF3_G!73B(wR=72O~WG{sk*5E0~7P$mlS=eLZX! zizv^dyj$kRjhp=X*T3SsM-OQ>8=O0LhM)cHU(=RKQZ8@v_{AhR7O7~MNUV*gFJ}4v z*;6zr866pJ6K(zP`n?7t_=m7HHx? z)QaBzZhG=L;-LVGtLrSTZ(y}NDhC^^1_uj_9v-5tJ&s|9=*)F9e)0&Tqy6a8WN~4g zn>QY?xUlK(?Z7#(3ni6Wlj(&O9zA}_R&kqLE=NyyH%qJAJo^S>1i{f^bXU`^i@nVY2t)egLnS~Yp+dqEH{MFVx6Nu8a_K2M)d^VO|y zxc}f0rScAkj*KukSYUZ|jqkpDLb0@qZF?LZ9U`CWV6R$ZVrqi@{Thj6lJgfX(B9s` z?CdPt#UeX9B{nuT=pX3i%-K`C_uku_K0A)>xV)H{;Wr#e#j_XF zOiWBLH#d*ebUHe+Bob|O=5ho>K^7KgX*6pXrbeULpi-$|1~f96HWG;>fq+SSCdJVs zqZ~Rk!01qc3ujMo{@iJzkqC>6E8PF~2@?~uG+GTbU8C8kqwA7Ghx!>9>_Rs+lyKRv zH(6d@VY{@A<2Xo7(c9NUt}{c>)M@TFsP68f3758Hgfpj(a_!BF96vfrcUO*3ILyw@ z9^X88%eRNHuzQ0Sgx=Sh%BNh$O zo=S6QWR!fa1Iw{__~<#m`Q4YStQT=TiNZzU+kK`XX*8<{VRP}~Sw8sSJx-k*r#;<4 zb-ziaS|!__q`#0S90{@CXizRU=q?O0G|)?-r<0zpG`-zv3`4Q9vPiSp@I}2-t}r*h zL?jfTf1sam(B%7P6Fi$qyZ*(ErM(URv8iDaFV@fe+t*i?N|TDwWEseb@hA z_=3M@j-~@EOEWl*Lo^!V=<%cU6uQ~n-J@1*(5Tl)#1llKQKqJ6`10#p{N|JEJbE_8 z+{z}7a(VClw>f?01hs0Jhu=Nn$$cK;a^FiPU^s zthl{RrCKAIjC1zvS+bdSOkJWWMJAQx)c6rDUO2^Ie~z|Pz-M=+K<#NHDjs#O&NWwQ7|}ID{q@sZ@-f?i_82I3d#{AO)6f zGdsV)v#B{|mo{*f#8se_1IooPC4rzue}A5zeDD^pzjmH8iOia8W z6%TW0bO<95pk>=Uc<_|Tsd+rNL0ck7Z%;eL`Pp{uV*L8CFPO{l7`Ai!C|M;sfc?>ExJf9e6AI~Ex$+vvj~yc2o<-9GOwY{o{{Y%AfUi5IFFgPN002ov JPDHLkV1h30R8;@~ literal 0 HcmV?d00001 diff --git a/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/requestEnter.png b/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/requestEnter.png new file mode 100644 index 0000000000000000000000000000000000000000..0b2e42bf49e6ac60eab5def693a68045953fa00a GIT binary patch literal 6910 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 Ef+i#`vH$=8 literal 0 HcmV?d00001 diff --git a/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/search.png b/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/search.png new file mode 100644 index 0000000000000000000000000000000000000000..d530600125752379357f141f7a5ab40d674b20c2 GIT binary patch literal 6145 zcmV+c82;ypP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D7nVsxK~#8N?VWpc z9mjp&KQsHd4*&!R5Fq$~_z>R`Op22D7DZ9i%a1yK#I<9$b=+8X8e1nl?P=Vc=A1UC zIcZN@pSp1qyK!vSs$<8hrJlx-99yz=WLc3!ij?>!K!Pt3A_#yW-nhFvGyP+B@7;TG z0fI^R68k;;VRz?sXMVr${$_S&cPP%BJYtlJFiN4d0;2&9!cc+$6CZClB=t0@^Xf3^ z76?(+n(~|i%;nl@^V@v!-?$J_{4+g-DCK!~VhSasgM~mKtuhdVktPTgH8pjFk!Emk z3~{{URg>|ugn=RqA`m84vOO9o8LXP&1d~jB?U5$x)G#qi1t3x+H7SBwBr&P>Ch>88 ztJF9u&YT<*l-&jusCh7GRJcjVG<=Jm|W9DeU@LPqcRGu zHD1QU_dS&I5S>R}Gmhh;BaKozWrUcl2x~wCNyub%O%--6arxgjvATp1NMVo0n3STD z6eKB28H?o$OK@9|C8jYLOp2tHMk)8&B^{R=s2Gr>78LKE%eQVO(3JsK1?8=+I2Rx= zMIDrMZ`-P4aU2KfI0#%MN)QDRT5Cwr(EgB287qCBmQ)3Dkz8lbqW^O7*OiNo7^awSXv9vv@8_ zZBJ|v*SAnA=8_VR_8Yxe0JxzQu<*htr#0>|HiHT`>B@<5? zx8K+syr={bD>+S6;=GoeDnS*vmbR*_ zO{x7Qep#Itk?K|KjS0dL$jW-#VT-Ubl~UqpD~Z)A!*4(#r6D8Gwx=#5V!1lf#7Kx6 z#}qTBh&IWqrY@5xO3{g`Cr28s*+a$b1)+8(; zX1S?S5HUH6Wu$+n)R7j`%tR&yTrH_UF*tR@C6`b>44R}kkm+p2;-}Kv3`bQrs-(SK zk(?&EiUW#B&eP2~ns)Y27E=d(~dCV-;jS3ZMgwgoIAT<~zaD>439UPf5qmURaP|9F% z5RQkDpkzp7LP8b9-H<>g4`5}#RH80`F&b?&T4_`m5fvg#pb(+K(FRuqEr-JFP&C8;M2jxm@Bq>yBMiQ^3(OBLg-O9s|6`DfMZwZOyb}s`znJap5r(OApt|voK{8)aQt*XXL<{q>l>w@ zATlxON{%#ThY}S9NqmXT)rndy>_)_7y;VYV9C*Ey>N4%RO!SQpCyR+fbQJQ0^SSv>jOr+M)gdk{_yTQ;xa6CZyBUr2nJrh3XFmxWca zLh->bNnIt)l%dxLo|R^k7FlJ($hD5qnFKG#jD9JSxob)j$0f2RctOGT6_*{OvEYVbfgx_&-0x z{-ehkE+~B0$9FS~7b31!$<;RT z+U^5<w37Z3!;FK1$5+Ll!ugdB@4h{_=g{TKO643 zlZ+eTyHSdxu{F{JEpoKFrK*QsUzEs~)NV2zoiF~g&;HxWz^QN}Hu6ePTi3wAP(NNy zbKvk%4j(;9YfB4jR_dF)gAe0nTspf>ka1zf?Ms-yU;$0d zO`JM;4k@$bUBi1H+y+J=q#c85t?)g`g4QM$Eof%Z!e$n?&1KQTW>zd;N)!eR4h-9H|jPLox!b=245NJ_}GK z=(R11q0|&ctvjQh8&{2SnLRPL^PS*E0cA|@d()|9Ho?z*` zSv>#pUjFodJwwiQc_|B`6c2briQGA!>Tr zx}vMEV(}_LHwA7jk_MXZJoz*S_Mc#QBEZS!7#_MvQ)3<5wrrvA!UYa=oM0r3*tm8T z8B?I8t(9kge2|O`dH8{?jD;g~UwE6{dpfA8T|iT|z#sj`-$kK_Q2gYDmwD}tLl^_v z7^E-=X)sz~lptT%z=gg`ywh_5KbOaI0_M!A!4p|LkwMA`DHWlDP(giNmUZo`*t~H) z`Aj_NWe3I8*B)+il2#*Q^(m-#DU31Pf8Pe4_`*M?{mvCc%Fx(2n@1km#kQ?mz!*ZM z7z-kHzjc^5kDesoki&Hv5F(2K4jkCe;Ufpg*W}_CO68@Jrejt94Ktm7<&f+XuiJi2o$ z27_=UZyh{N*O^{)RAAA<7T*89oov~(mOJifWA&OAMlTK0*FC_BC9Qnmku7Z9bPwy= z+gZN6l{?qY=l-qhS-EsE{TBxr7#hcO469cx<&IU$aD%@DInuXYO*e?S1e#|lgruTZ$n`m6Ga$c$ zHP7bk+h>r{1%!QB9Dyqxj1feULWdJ9SulsK8`@d6xP^i9!~Eo#H#y%mz}&i7ESuNF z0~=PcZT%WnEpDT`tDnF6-*4cXS~jg;Lv7AS0$D%9thzcou0>#sw&m;O1?DMVQF+(k zmKu{9rO4%LDFh*Z^3A9Do4qIK?>)o9g^g_Ac0XO6U38s&2krQ@%xai92nyM4R`R{pZg?6Xbg&bOycdY(YpJ1A(8wS_5j?c(A-#3e>1E+O13(#_%M3{q#&Y77&NA)UyIblJIc7h5;4W8wVS z2(6J?qmy+k0YVCtQkJAbBFf&pxI6_fpW^zX8X0dXlJ*S_@bzyzPsiz#2;wL2(BapA z{UJ84Uq{9|6_@RyH(0=0J3Q3_bMlS0SVTg=D zXo=8*o{MAr;OW02m-or#ed=mv5k;Ckdk)dpKZ1&vQS*6+EnDyAA8fsgSKsR5d(S@4 z7yrozx$8Z5AWcyGM8!@?X>|MrfDolg|C!>-QePibBjYVa(*BVl{>_)3ByxIDCW|&X zGX5BkKmIY+wXbEc{~|BG^fCw+BV7>0 z?NYQO(T+q#pp?Op9**NPJTb!lqlb87-wF1-d4jBC$U2(4+E=r_eHBYrtVU*X96f%L zul?D#@bU14&%+OHSM-aOW~FxsdhA4gVus4zyD)?@#-<88Rj#e{s{N1YiHzAAFsT=k4(Ov?h6BS_gv)m z+m_%&(9~Mb@b~}+4t8?jUj_+r5|W3m5s+uf3NoTNcwYcP?XNlBb{gKh$LM8AKwlxd7KP1FsITZ^P7gCi8K2-$a_lM`q9 z@EyaZ4eM!{Q%_ya-OO`C4>*O(B-~A@8tYywD zht2C(^79w=@U3q=i%|s@F37X;_BK{8Yh&k*t$41)5lrKUrunirg(-q>c-&GXjg*ex zX!m$!_W{nn(~m1c?p?c@h4WfKht$?J&^m7+Dw2#02lSpl&*8&|=s0 z4R`VR$3Kl@LIhy6K{^hu>ms}ym&QXfHBJ2Gb1(8=|JxrkHad<`5mG>1eItXzqjVfQ zO(8He%*xQdb|ruI?LX(@@E9jfcCv5(n-oI9ZA(^>tEorpxSL1Wv1q*9WZ(9Wt&%p* z6o#52_=d+Vwc-@%0?L3Q%lJfO4X+|hSipB2RHzvp9zlf#7PQWzFgn5PhDIhLMF`Al zn#01yZEU%31D7uK<2j!F+{6f^l(?S9&{%D$Rw914QFKHQ5&GW*Kg~t)AvN4XjwUn6+zHap~fDzWkNv z=<2?}#CRXAbL-gkztl*Lr^GM3Ky_nP3A*N( zG1;J4<6DZPp(t?VXb*q#wI5K>ih@$yxpD~~c;o@T^7U^s6d3CAg1Vfbebq|ZSFd8_ zij`zD8G=HAj$_9-bgYv@9i5yzKZKWUglLo{ZO#1d@B9|wcpp#v^Z(A;ZEf7Ork(X` z+Q~YCtRpx#FwS?MevTuDI~X4;kjvJP&DQYw$A6WT%bO8OA(TQJ7iBz#h9}s6;3%CP z#|aCH&p!Tf+{}di_RrX^UJ6;L_vt}`;3i^;rSWdOopBd0~7+oK>vAkRA9qB?_qT0BK3{S zxj5cqr<}Aw=m4P#)Hb!!H#|Z2xpx>J87H63lh5Q?*w#oC4cVO{v>i8x%1}3}0cBu# za1=-QwB6Ri#f#^PpGTMW^h|rwR9mYsvP#f3$BfCABorVz4(?8U9P{pm>p4V$CJGF` zFYrBy3MNp&1h`pnGB}=#AgT@%7ba14^b6s55iN9S6iBv?SlqG-wBYm{= z@Ew6Kp)fH_Ffoji%_1{dGLDDuc!U!H;dlYz7=)_<7lTV7m|!9(kgLg(%V!BD3}Hdz z`wki3A&L}A>56ws>a8Z%9jw9{oqbgLzPf<#CN*OH8+nWig zCfIefq=EKKbROItcNSF~>dn~TX&^KhVafz#+5VmaV|N5K1|bY!zavUiqlGO)V~^6< zt#+h=(u;GYeK;Zh%p)$q{*);m?H(lMoAo62FaXBZC8u`9m_E$&D+4Q7z!s-u!( zP=3Ru;}bndtH3G*wejV0lf&{baW^q7s%%OJrENqk*W&AV!(Cy@j~1m(c1)2pDWz9o zh|L>E6f>49yI3FWyP~$-io&XteN&;R{IoukV1c|M_EnPhYRZ+MGVfH9LK)f0=~6_p zftk&kPGNHt{hvwiJW?v9a=%S`la!a1Y&lw1$@sKbQp$)3F{#{l8;UlGbs$qcNfoPB z$~DAHesU&h(ufV3re#_gX`7b_nhq`HkwtHs6mIiWYRqo$7>~0Of+jD}8(WL&NT!rx zBt1ZpsHF`ZuS5km6e=n%i2pBwREQKYNr7^OOo>S*xJpv-lhrUOyt0gHQl4BS8-y0= zm+Y2VNzf8ei+4E!Y0_iK5dpqhH zVkVWSY+KxZV>DVRz*rtiyL+xGlGJVtXCp~p0q2CHLaAM&Zo^!`E_x} z@2cmOM5>fjd_}oe3Q=*MzMlNMm0}5=)c?B54C-65bkYM6@8PQLZZDXapjNa2DIGz) z1RVcwc(wqD$7(Z>%8LA*q#EM5GA~mNW#N;|ZR=YRGl>vos8+eMLHy(-WirLh5{H-T zZ;G2@5~fK|8FeEH6t$k%K?-3-#ybiU8%hf58fM2N6#_*16|l-;rbpWFe;)#q867Le{-Qo7~Q-Q7}4NlTYB(%s#-BHi`Ib2iQ# zIKw|P_y6t}m+()@a@d#@m`F%S*b4H}P~iIvcoSow0iQmsbveM-3umaD6jJ3F)gf?z zYAFekL_(^Gef?nk5;%VKMPAz(2??j?-}}XYLx~v@68EEmw4}O+!EBpmoc??={Fd6G z@>eF#VcdJJ|fq?PjWeY*kkz92);n+O7>X%OVYmQeqtlHNQc z#3sl%%zXC+_XQO!BcFj{{&|$)Rk0%#`Y=xEEHag8;GI-CQrvH*47f2FT8J?o^KZ`t zP`52?W7>atMd<&DT(nHL(2m3FVJ`Vl0Etk)LgW}~<%(XuIgCY2Cic|wu zyfTX6h$WDMR7Ss0Odwv|_8&$YshppZ@r(zPRafiJGS>95b(rxD@?Oq0{7Lp z%oF5DK7P&}g*ARIua^A=BMw>-xGeUTR8)CdDF3Bz2UUL;;7<2fc|gwWTzd z8<8-~5sLYB%d@?`4Q^cc@#6NHlVpt|4ab+kZ>T86Rzkt*(Gt3-M5va}p0r7A_lrOk z3)OK8VH7$0{&1=fDdq<)$~Fl4AUq5@Qi1QESWXtlzh+eEHbN`LpyjiJyz&XC!xwl` z-%eF~?r-p5*&jl2YTS;T<|_4jF3b6TM}};7bH_sy;6p|(`W2EXMK>L>mF3aM6cWh= z1)C=>J$hONh7u<&m1%xQJDji9(5L9#5?7wRyOG`^%s8Ghw zS(2AdeRF-pLyzxozUG+Jgb}qky@9I5&{vphlcZEkE-wBG!A4lYV0i|q>TJX!tva56 zY6i3#?NK_8`NYK57I5pi@@tNti)!p1t=;~vC}=w1nwpwgWh}YVp+X>#hvD^m{kiYj zA3kZK2^|mebACpvWHoL0^*0uJ|+w<;FrOshpd`ZW<>FcA2aS4?b z-h8zk}W~jWU?vr8UMqXf&QXj(YRv7VxM_gnD@*n4jM`EnwX5>0In#klD)4Zg;LiG#XLr z`caKvV!56)3+=xQY^!T(PbencMEI##td6H5XBJw{evS2|S4L*0C*sH*U+SA6z5jm6 zV7-$J?r;ick242W&o{mFow*8Ky$)WbiqAb~B1rOuF~|4P_2f>?R5@zJ<>isY<@X1N zhh5|KXM33rXI+cz(yBp&1T8HsH(ST=0^u+!zk9IdSXS3WYl%ABK}v*&s=#sL*K_VE zSjSm%m64WtEys4%(^l&5*LEcTf!PaS6dAka(Tzz1TOb8<>}GdLqw=tQI-FvK@5))O(y zHsy0Iv9|GdwRPqCOFXrx8(uI|Rr?jiac)OsB|=N~HGpbb)7XJYy^cyLcVr>{N77^= zU25o!{~k^E$VS!k?8% zA_p3x={Vuafm=G_UjK!htvHY4&+K~uyTl5XLKGpBz|rTZ{-bIhk=BC#K81J!9PPX> zOHM}iCXwy^&e=)&xKBt&QF*ibG0ph9Ycb<@y#nP(w##h+a^ZXrsgSU_68EVhc57TFBY$+Y3jR zAUux0u-(JrT#JFL(IiDiMTm@!j?Td!Ys;Px?3=IQ;o-0Hr5~R`gkV9z^WN>;jgI@j z+fRY5TJ2u^)cQwSDKgDaOBa`d%gf=n-Tm=Z!2|E@F-0KG&%n|(%7fFj;M zR%$sGSAS-)#uUAh`B!a}y9!Pz7x*RXJ3~*mRdsZsM7$8XL6M`QqdzOn@qho?pLLAq z%S5v9?`pMq@YvanE;Nj8<!RP|s;}756+z6- z(j;Ozo?~t;KtIgeV6TaP-XC3#g@faCc%~5&8k$ZEFVjZX|V9wpx4V9Q;+La_emC(i*KI5)moI@m$O*rZI*z0 z)a79x{J4fwx#X;vir0KoS(K_dBOlSPsy(Nfes2<`*iF`Xyj*UuGadhnF648=jgCWQ zwA$jD)%qnFK-@F26fVQD6i$Rzt=X&W_G}LQmZbXf)T!xdl%G30AO&Q0&B{WDL#Mm@ zd+n9FsAP7{EmCJQ?S5Nf5JZh?m_Bv*ba7lv-ob&(X1>yRvDVz>`l{A?mM!J2eu&-j zN{bWa`1wMcCw~!~psr2?fPokKMIkfk&DM3Q#LnHr5fjzdp9S zJ$tKIM38fAoWV3Y`mY!wf(iOe@CRV|MMc5Z8oa!`QcTb(u=%e9<-eWJU$?YQ=5!r+ z-n|PwS}0U_IezYOK^XdL*J-98?r6EO&JH1fLnZXH<1rLi+-t&VjlI1+mU(OIbxKol z`pghpk7})+2HUyHL*whes~24L7(En~61_h>QB?Q_w?z8jnELwq4_DK=Cyx}4)@NPm z(^+DC5wHxg%LM{2FRvy)wV2&e%A2v+^@7h8bgSYPd)7jg{d%4OdV28YqTp|H{q7x? zoc8N#ak7NdViyz43$@1l`{H79lVuD+MzG>vdF?6yHXscB60PmXfo?cSEd69CUYB4} zGx%M7(5SNPjRr1Vj$^98IMdXJjx+D!8oSKe?|Cqalo|Cg23^m?qSPiq&G&p*N>moi zLv454ga8^bs(0AJt{0k}nW^<%BPxe$2-UE#vE$8$-e9dVV|`GXX2c>Kn#D{p-#mQL zzS{m>`?uPU@&j!thhi1%#LZ1(G3Ne>vJvHVlbbQ@e0fRx^f{oTn9Dp96#j^#2SKc@ ztir%uiu8(o^yt@>y?mAvbx~cfNii(s7CQYTA~8t@MN$$^96Bz9zEQoWVhuAGivae` zY-Z2Hft-ZN`JFw`yzreY266pvKA@T=l?Tel%P}RAK`rO6MyXa>aWK*-X31OmC6n|R zQiqL2HUHISh_Nw$(V#W8inY)oxSjLy&+-vn5h#J0Ex7w7>x8tFgH8NyHs*_2YksVr zpM;oYV_Gx=G1$)TWnrZV)Jr^aYrUH<6#wNHL4&o*R>W$+7M8;xuWYU9n$=gvl z`K*4KV19tZLP&s~5iS&4shy-~N7f>#n&jA{fT<(z7dotmrA;&;n{##n9)I4Uy7ZzR zu!AzQs^V)y`@s-6JH_RDBVku6x&4hN#<(@J9s9wX1L6j|Ba<&*;uL*jjwt6R7Jny5 zPb;mp4u5KoIbOQBx-z*H;l*arWRi?L6+;*AeqEYo%Wh3WcX3vQV8qbamh|K{F<(4^r?I{Exm1UsosWrIu2Ue zJE-1$vmoR@m}pLJxh^r*7fE_(#d~;g@McUbhFUb_O(m(9-34xkh9pjW9}?DAv?Hg}J`JZyk{>skYCvrX@H%yt|pPoGpghZ?$O+DibZ z2l@9MsZ!fg3qG+JWyp->N{s6nmF-d&nJkT_7d9au7_44%p9Y4hwrr;+$f8c zvrXd zHo1~l2Hr>fwso1Iv|rOqoaKpR$Uxbxu|QedFq!Vgc*oPB&5p8fIM z{iRDx?G$6R;);rB#$n=HS7)oN*Q8v$JiJ})-q%gu?qRL&$4=Y**l6hJ&ED5ePJyG?%< z>q2AtKE4>t#B6^!Np>&!T&|=vf`|HIH~l}S<`Dp`4i917T8;`KzFH@LC_^7W-|${E{bzb*-_GlToX zti_tj3H3>%R7gE{cTIgxB!h=jnVMWqnc5#k@vczoF8@1z++iA6Zyzf4XQMSpD=J|9 z_K9UfNO;W)!6K>CTWia~Oc#SNgQ3a4H)_%I8wd~8&91)6Ov9PyYR+nn_>qcuma^>bP?B9CG zfi`YHz&` z4wxASObQ8jv&`w~>BgfG77-AcT8@Ix<&Jk3LV%8PQHaqf|7?8lg5Z}Zb7dl&b#_5^ z-c`>g27uzSB1Vqk{G8q9FPq4cTWTysH}i&G8Q7Ep)rB9Di#ZyDIdvPVT^5=82L`lT z+5*SMl-Zp50m$jE)JMcaoe*=JZ}nSxYt26b64+@`m7@${yeuH+<^oU%_@3j%fhtq6 znuVn$PXb%3+i}06WmF@HYFIy|rG+%Fw6<@xdcXd2M6U|KjC%Tt1t?cdLrJne!s{of;FLWxZ9onVh+bS*2 z>2@o>$x-nf+t})h6gwXw1UTd+B2~!ig2ns#uf~p=%$Kd+R|#72?vf-WkFTYI@Csqk z0NgHVe6+H*?w;Y#$jB%`fAvp;?T=;e2jYrvEG54l86BN$EezZpPObC3|Ln6aXNDse zOS5ZPl`B0BW|Qi1q#7{`0q*A9_d_X+t|QU9k+Cr-{vI;rcZ3L(ZO|q|puw)rxJFV% z1veQBkP1B;af(GwIdbmqg3=l~y1J9W>Jw}ACt|UCG8I}WefP)N8%K@n7SB2LsFFW7 z>gk>~X`lS3i&aYayEXw_&T7pcEGh^51NXmFHYxQR_rewyLq6x3>pN%ll5?CeeEEA{`Zwxp?;&PkNO6h{F~&>$8Vl>!;B*HQjH-`;lMCsKltiA%mR6=h*dKz;`YFMqjI zXHvqRRD8^wfJK>z7u@l93>0%@1TGK8%}=VS;W5!Jr1^Y^&<))O3i*njLc9h3E$FQ# z?^lN?XWFP*%W)dFBm2FTTSC;DSUd`O>S^vt>}uOUERAu_dU@MM*%b1Tu{k^?)1?3{ z4*Fr#23UOmD^)Q$qPe*IbdG4xgUv_V)fQsug0ZsFGgR?BvRTn6%k0$cpSBTCxi1BQSd3Fhy}Y=CycBAc7-{aPDx4Wbd?r2 zAIARcwG2+kA<^iOfTS;G4|g1YCX2EHM=D6+WLvpE6#Tbc;^&rrt9%v(+}_wGdvF1S z!C<>8V;KZ$k$Yz#YbnQUMg#m!+UFbLN`v-@{R(Lee>+5wadJQh#_++KURS ze3J_4iqG5L>yP-7adE~b&7S>C7V$ciwq|W4*hJgJ_7;J}(lCt0zA8-pjD!VFlKajck%TPz+)st!BmReZ1@8O=v*)T;L zlSQ*kv0VB2n5DCMj+nYYrtzXhs%@)tB7_oV0!6+>Y=16jQ!V}MakY4S(thk0>(#Ej zRI=J}vlS_J|ECTxrh$DYiFfjp&S#aOOcV@KP@?LSR_z6N+aSQsilBsuxe65Zy!Ov( z0sny^l$RD7wE9c)p~hrQmBY2>!w1L5hBsr5dwp!ygR<`y&&BlSHH3>Q=mYI^7aEFi z)QBXv7hUuLbK>k)ELiHr___F5Jk%ruPH&Wex~{n!#4#-NWdkWYJG?9=WC-*wM@>aVxMhGp&F=ZV>P zAQ64Y=(ua+q#Y+OX}k{TNH}$R>k;!FfW8xQ-oxs=)a~q}eLkg~o}BcDl}!=8=J#NH zQl$#I(@LqY?$6oZ6pG+?*H7Ksv*_m5yIB^BDOG0!%&81`RBS9C505{bwHt~diHG{{ zP*n6|ec26lyXtmsuH4dum^^EEei+Anas{(OtDLQn zziW5Df%WId_49v=xV>uZ9KA%$^I#P?LM~UPr>7r|)}J`nAOE_OUS0m4E!S)D2?r~b zel9oDmTlJCTV8+q=hUUafA#jFj*H)Obc#6!RMTv=ybZ|%qjkbc^1qlC!XomD%&=9% zU>Q~!1iT}ZS8b|0&emO!^7k1aj8Z2B$ZxHX=;MR9kU>yyBR18?4;JK=iKd4MUv+dy zSTU>>8;(}GmzlXHJDtd#ccZ3@C3e>2lL7FjQk#6x)+%nS3rxYu74&-|xu-qTu6JW7 zeybc;7~oSAW=uBIt>D5g#SN)ZH(B4aKU)H+^5-v(o-Q(9os{v6dhgp4&eG>sznZrC zKp`SL-&>h!CFQhrqo7PBp`c~geJ+Nj#yTocHR^mYK{t{GDH7|^J;vQ`dCRP#uNE_* z&H=Z@P`18|`ev~~$@4Dz==#!MMgbV_;T1rujz|_ z#vFDu3Mh!capF_oV#6q(D5Sw*Syo?!45S4wo0c^mEU%&}?#tfkLZ>ymw_vQkJ&mm5 z74kq~UWYX6$%5E~%v1x6(|T)=VDvH(L`E3_9IsFQ;6WneWaW>y417dCz|A8dZ$o4M z>@Q`*^ycXaF}r30#o&w56r}P5X&D{ih4MrLX(=yD{`+rE*1WQRXcmoKGpR{XZ+kSi z6PE~I%IKhy{_QAzNDebTP54RJl!&oOVxd6DtaN@RS;I$ykEkMB1w1#VosXKbWMx#= z;@vaW{P`?U`hXDTTw1RWo2YTayYdNUU5Vr&UH#FNFSsFza5&S|(eeNauGo7`chX{& z+`Y%^b-;e*Q0pq;7(O^SFanyg*yW5Gm558&yn%N#si6%)OoAYUKzxv+i1TVY4||D+~~j@L7&gEb#uDC(&p*B zDGoSh)8KzH6IiQ37_i@B%uRld;@kJA#nTu)nE0AV}muDkDug~I@6PQY$S8sJWs zElYv7-d|6;(Dhne!a0T)I($V#acM1%JKDeNHsVZv^0iy64Zc6_%;iv%!W(cTxmvIN z{*n@i9SU?R2v8L<2xai=b)ub`m5QL+)io>;BztIzDdYn@QW$r4>WoGJCuL>C756BN zx3vCIU%%-|lVY9-;+c&I*l4IAf}Ts38)&xpbNSs4`CiZvgl5Ckk#HoVBX+Nu=X=dbafUu-LS+OWsc&s3J9qfXknoA`A3(Ad+K0O#;#3aR%o)$-!5GGU;W~5pno%18>Co2(z_#+Dd4a(wHt|r zwZecQhANXk!<=eUWRQAeAAF=s%D~?>GXk1+PI%+=r9LX_mGEj{%TMVoZKiwoirtAP zh|+KBw!0_luV3MV>%M+j&hx{CS0*%`Ui=^DSq9WAK1(iXS>Q}t$rfPLlAF~L&VU=Z1b(1nl_<=u2z#u)Gt@;gPYJl;Hb zS;oi1d$dX0@x<5hbg6mMFKDnU@$h)Dx zOcv{FlFmHh^ZiC-Sd?D;_U3a8l|djLK@Ycrnxwd4VMTp*yZ6=UNl&cf#!ou8KTF*W z)R_zI4k$8Jh!x1&c*ujSOhrs-*x(o9uyH#S5T z8hLjo(6SjaIcOHFs=YmX^BbrrT)`iUNnV=iAX!M;DGwB;Gk(PMFp&TBs>x@QHx&2J z=Af^)aJ3~*qz-cT66hmo<5rqL1B{O!CuJ>sUm>W(gp#S=>E(F+D{JD6GtWJEaX1^k z0w-M0VgJ#&E{QO3VK5#}mt%SQ3w{1%k-72wz?53-v=ku9hB2DoRij;8T2b$W0#tqc zGF1G$xK{Pq|YhDGP>Le9=z@ z1pDcXno7JjpOT77VoEu-e`r^>sJq8*vZhwOH9zJ26?-rgB7?V#b9j7gc|K>)#Ka`z zd&ar@MnYh(U<}B40M0ppHG5}eA^SE}_dR>e_P0X{H!wWkGXDbkJbsvG#&`a^b^)F? z=D#$^#3Z7!@-5Z8#nDoI+xZnEz(W7xz`}*4Wlmzx59dHP62CtYX=&A9 z1VWa=(j@;|AAfEvAis#*VmAxe_e3=&hRiE$1pz*!sV)iJcteO>wQ#2>#k_RsuHI&3 zEJefBN;?OnP)+fmo?_1CKq68Eeboadr={H=-BxVLf?<;TW_9uUxu!srrr#&xrzc61 z2bK0taMX^_zaorire|PC6mrV})hQIostjN<$=PVNC{$3Xj>vr1nGLld$H1;!&oFyC z!5uB)`{;XdRbfT-{lE76t2{a3kYm}0)%E8%nMe|2tG6wA4A7#5W`59ZH)+Rz(|`Dc zg?}Lij=4X>(A20|sJWFgS)#dZQARnceauBIxzvTfJ_t=u^FS+++aq?hRx*{yA8)<& zTI-V-?+8dt3&CWYdyf3~s@kJad1DM8eX(>u4K2ckHHJ(f9Z73%UJ9NvT?u zGN1rm29?066te^{;$_+J`x7HH<$V*R@n&DdGbqAq{)90t=QZ~gq^VKMNE6-&=dyef zwsTh@g)k6UP)NFk`lkYI+ zohFU6phjat&Te{U_BL1NDJCof5B3r{kB2w?Y%9{?WFe-ALuV$)rh+N@e5F-)pD@<; zLzwDZ)$zWlM0D`d&YetN*@Mnn;jqDCja?N`pGYp54R#H+7m9x;)D8 zNZvhp19rW&R0d`Yb!W+&s%Cj%r)ByWQzrN@5FsTNDVk7?z}uesu`@6Q)3ee*^6Jgw zU`Y&01xnbQQc9Z7Bc%lWcXyKvCe@x(`3iVes=0rC5SZ{_smikVw>;Owu-GDZ?C8F8 z{B7K02NF&8*mBs##RWGQ+$(zAR`+Gx z6Z&Bq42Q!fz_T<~+9Z#Uj}K$V4>$m>;3BViY-8K6ceMW$7?<1eOsNI{kdLqDd|YCj zy{fJI=H{mR@&{U-@bPSDNXX5eV5dS6H1D%o85abo0H~tvFw;kk<)sA1j~+L9p_AZf z>-r%TzdJR+n+LQJSVJEu0tl_K(SCRrkP2@J8H6o;E=Yf4&Ar~}u)bIId{=dQF7fRA zwCC^?QfE011U1S9k<2lsZ}pmf;(k3rc{<~IPH^~op4s+zwG3Egwz@KE6h487GUfE! zc5X`I5Kkf?a7ifw#hy>H#+Zs3)?6S7+OKSocHX??dfZ_IfahD+-%Pkdy-oqN^dvF& z0hN{U=qM6Utp7>TOUdMvbd41&NG*8u-+KNA#HpmBL5EK|sey;TrlD^#;6x0(&fU-P zjyd*y_V#(cU*F$NHMB4?`VX))-aRu2|MsED8Q%v0!qUPb1AyXM=Y8b_*>c;rsiuJD zzPP-^M}47LWx$B_!B#gr;GYye(*vQE^tIu6qE=d{dD4PTW?p3{@V0@#21ml{5=`ols;R*|7*1W?3wVy41x^P7aMz&yX4>)@eHiHk>B0o9KeM}FPrtc3`krr( z4fmg)h8u6)pP#g!8lMrOQ7OYSg`jWAgtRz(?>0@yKk|Ta)3wzRd}MC2jQq_W&Ouf0 z+(Qu88Cvx%k=d{ARZ+%53k|qlA)qeab7?v=diqhRfHE11{c6rje8+(BkZ}KS zNxIZxR>L>B=M(%2Wgks=#d`Xu?R-|5EQLHE!d@wt31k#U?eD~HgH@c?x9=BRPmqS^ ztITcl71^k8s22lwEk@7+rfCgcIOB{6yQncIlV5^BVF+pMWR~)+CS(Y1y!xwc)XNNC$d5G5M zcKTub`6?3NW1(8LF8JDRq-1^0Lt!pKP%;g3M85_Pdk<3g!Me199DVv@DGK_&SgwbV(&e0Cv`;&9d6~AY|(){tV zhug}=feE4(GSCnBIkbu95x2w5zgz4JQiR6I#_C)gNw&SkKi++I+8M<6YEj4jJ9aeu zk8>sUWZiW_&GU2ugGYI11vx3XIPs8ehx4y$d{RT^v&W;ASV^pt&%@|2kd^9Q^>Clq zBsxEBraU*ct`7EyDch(44s_rVj@-cVRMvauo4N!+SK)wh=O6oc&dWb7FN*12%u zDoIV(sV+pIQdD%EEu^&!Ogo{F+kR7^80_IS{f;IzLZQ?Shbi(~EKA2rN#Tm$yn$t~ z4L;lWuC`SEAW}#;Sv#DzU962?ZFM(YEPGuB9Va6r+gNoU2Zm3EI;i!nqEkkI;Syjz zO4`|(uXeJEHV>~mrTv<{)%Td-&|yQI1gCZl5BVS?_}F{ov+aX$;Pn$32dHenA1syu$z(Qn?b*y@ zU<7=}e42J2(jpg|E&q+h884m*bhDV9kUVUW7L&IKM&MC&W61MU7ygzD=5-`674^(l zCnzj?=V0O%^oC!uG2ZM=a^8n9B8uS@uC3DnTIO6eeM18g$u~wKanczo37EjlO(<@u zK%2GB?!hv%@YR)mnTEf8F_ zHGw%1t#rA|Hi02wR30+5rv0tXl9;4SymLa+28;@_@8Ky%OSk8vlwKjD`M{@9Dm%|9 z-t;B13z1qfYdvH05@6{tju*IGmUYJSG#1tZ4(*pDv^lvOznF8)ZqKsHjujK76+;|S zoB7olai)NHbSu9&APl9wb5jf7`4pP64FJ))^JXWq&@maxiF-#1{m@J=viDtSm zWZzRAp;%yjh^9(qx_pErucolWmN9fGEm9_?TV~xLhjUeHF-jo|*Zrq`Ja$fx06=iw z{q4O%-qN$dcxe$PU1CM}I$7}bOu4l*{0w{2W3sFBJ~h;lDm0g-bFk=Rs(jE?A^hR@ z8x$?&XyC9GwhaYFL#$k_)!8+avzmY@fYdHMXue{h$L^yuk{8f9_^nQ+Q z&B#JZUvR&C@MKq_i54<{bJeKA!^ii5ppWhOD)Zp)-@UVX;~>0)N?>T%=k^>oetaL8 zm&$cIoMN;NuX6guA!lR5{=A)fwYl%}_3CaJ3k!=owM?LUUBM8nX>V z%K-6wyymnR&w2OoP@n4zpsUj@BL>|8Ld|zF<&Z~Qd@T8#0afz7nfJf6#4?8=G z0Z{r~nZ>eAj@xJ&!lOWLd~mK#O6-QNoq}H&5t}A4==j}02pDVx_Tu|_b=UXPlvP%| z#G2D_e@zJh@4hEu*A8$9GB7c*QT?GiMN<|4yhhG5H3R1(8_FqsC_#Cem3(pliz^pA zKT|*F5_3KWpRV6OJ~waYiatNjZRfUreQuX{ez@^_!4dXxPf_99AK `${i}号`).join(',') + '的世界'; + log.info(msg); + const yourIndex = Number(settings.yourIndex); + if (!yourIndex || yourIndex < 1 || yourIndex > 4) { + throw new Error('yourIndex 必须是 1-4 之间的数字。'); + } + const pos = enteringIndex.indexOf(yourIndex) + 1; // 第几个执行 + log.info(`你的序号是${yourIndex}号,将在第${pos}个执行`); + + // 按 runningOrder 依次进入世界并执行联机收尾 + for (const idx of enteringIndex) { + if (settings.usingCharacter) keyPress(`${settings.usingCharacter}`); + //构造加入idx号世界的autoEnter的settings + let autoEnterSettings; + if (idx === yourIndex) { + // 构造房主配置 + autoEnterSettings = { + enterMode: "等待他人进入", + permissionMode: "白名单", + timeout: 5, + maxEnterCount: enteringIndex.length - 1 + }; + const permits = {}; + let permitIndex = 1; + for (const otherIdx of enteringIndex) { + if (otherIdx !== yourIndex) { + const pName = settings[`p${otherIdx}Name`]; + if (pName) { + permits[`nameToPermit${permitIndex}`] = pName; + permitIndex++; + } + } + } + // 把构造的permits合并到autoEnterSettings中 + Object.assign(autoEnterSettings, permits); + log.info(`等待他人进入自己世界`); + } else { + // 构造队员配置 + autoEnterSettings = { + enterMode: "进入他人世界", + enteringUID: settings[`p${idx}UID`], + timeout: 5 + }; + log.info(`将要进入序号${idx},uid为${settings[`p${idx}UID`]}的世界`); + } + await autoEnter(autoEnterSettings); + //执行对应的联机狗粮 + await runGroupPurchasing(); + } + //如果勾选了额外,在结束后再执行一次额外路线 + if (settings.runExtra) await runGroupPurchasing(); + } +} +)(); + +/** + * 群收尾 / 额外路线统一入口 + * + */ +async function runGroupPurchasing() { + // ===== 1. 读取配置 ===== + const p1EndingRoute = settings.p1EndingRoute || "枫丹高塔"; + const p2EndingRoute = settings.p2EndingRoute || "度假村"; + const p3EndingRoute = settings.p3EndingRoute || "智障厅"; + const p4EndingRoute = settings.p4EndingRoute || "踏鞴砂"; + const runExtra = settings.runExtra || false; + const forceGroupNumber = settings.forceGroupNumber || 0; + + // ===== 2. 图标模板 ===== + const p2InBigMapRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/2pInBigMap.png")); + const p3InBigMapRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/3pInBigMap.png")); + const p4InBigMapRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/4pInBigMap.png")); + const kickAllRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickAll.png")); + const confirmKickRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/confirmKick.png")); + const leaveTeamRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/leaveTeam.png")); + + // ===== 3. 初始化变量 ===== + let _infoPoints = null; + let running = true; + + // ===== 4. 主流程 ===== setGameMetrics(1920, 1080, 1); let groupNumBer = await getPlayerSign(); - if (groupNumBer != 0) { - log.info(`在队伍中编号为${groupNumBer}`); - } else { - log.info(`不处于联机模式或识别异常`); - } + if (groupNumBer !== 0) log.info(`在队伍中编号为${groupNumBer}`); + else log.info(`不处于联机模式或识别异常`); if (forceGroupNumber != 0) { groupNumBer = forceGroupNumber; @@ -58,61 +103,22 @@ let _infoPoints = null; // 缓存 assets/info.json 解析后的数组 } if (groupNumBer === 1) { - // 启用自动拾取的实时任务 dispatcher.addTimer(new RealtimeTimer("AutoPick")); - //自己是房主,检测总人数 log.info("是1p,检测当前总人数"); const totalNumber = await findTotalNumber(); - - //预处理 - /*await readRecord(accountName);//读取记录文件 - - //运行前按自定义配置清理狗粮 - if (settings.decomposeMode === "分解(经验瓶)") { - await processArtifacts(21); - } else { - artifactExperienceDiff -= await processArtifacts(21); - } - - moraDiff -= await mora();*/ - - //循环检测,直到其他人所有人到位 await waitForReady(totalNumber); + for (let i = 1; i <= totalNumber; i++) await runEndingPath(i); - //根据人数决定执行路线 - for (let i = 1; i <= totalNumber; i++) { - //执行第i条收尾路线 - await runEndingPath(i); - } - - //运行结束,解散队伍? await genshin.returnMainUi(); await keyPress("F2"); await sleep(2000); await findAndClick(kickAllRo); await sleep(500); await findAndClick(confirmKickRo); - await waitForMainUI(true);//等待直到回到主界面 + await waitForMainUI(true); await genshin.returnMainUi(); - - //运行后按自定义配置清理狗粮 - /*artifactExperienceDiff += await processArtifacts(21); - moraDiff += await mora(); - log.info(`狗粮路线获取摩拉: ${moraDiff}`); - log.info(`狗粮路线获取狗粮经验: ${artifactExperienceDiff}`); - //修改records - for (let i = record.records.length - 1; i > 0; i--) { - record.records[i] = record.records[i - 1]; - } - record.records[0] = `日期:${record.lastRunDate},狗粮经验${artifactExperienceDiff},摩拉${moraDiff}`; - if (settings.notify) { - notification.Send(`日期:${record.lastRunDate},狗粮经验${artifactExperienceDiff},摩拉${moraDiff}`); - } - await writeRecord(accountName);//修改记录文件*/ } else if (groupNumBer > 1) { - //自己是队员,前往对应的占位点 await goToTarget(groupNumBer); - //等待到房主解散队伍并返回主界面? if (await waitForMainUI(false, 2 * 60 * 60 * 1000)) { await waitForMainUI(true); await genshin.returnMainUi(); @@ -121,963 +127,498 @@ let _infoPoints = null; // 缓存 assets/info.json 解析后的数组 } } else if (runExtra) { log.info("请确保联机收尾已结束,将开始运行额外路线"); - // 启用自动拾取的实时任务 dispatcher.addTimer(new RealtimeTimer("AutoPick")); await runExtraPath(); } - /* - for (i = 0; i < 3; i++) { - //确保回到单机模式 - const finalPlayerSign = await getPlayerSign(); - if (finalPlayerSign != 0) { - await genshin.returnMainUi(); - await keyPress("F2"); - await sleep(2000); - if (finalPlayerSign === 1) { - await findAndClick(kickAllRo); - await sleep(500); - await findAndClick(confirmKickRo); - await waitForMainUI(true);//等待直到回到主界面 - await genshin.returnMainUi(); - } else { - await findAndClick(leaveTeamRo); - await sleep(500); - await waitForMainUI(true);//等待直到回到主界面 - await genshin.returnMainUi(); - } - } else { - log.info("已成功回到单人模式"); - break; - } - } - */ -} -)(); + running = false; + + //等待主界面状态 + async function waitForMainUI(requirement, timeOut = 60 * 1000) { + log.info(`等待至多${timeOut}毫秒`) + const startTime = Date.now(); + while (Date.now() - startTime < timeOut) { + const mainUIState = await isMainUI(); + if (mainUIState === requirement) return true; + + const elapsed = Date.now() - startTime; + const min = Math.floor(elapsed / 60000); + const sec = Math.floor((elapsed % 60000) / 1000); + const ms = elapsed % 1000; + log.info(`已等待 ${min}分 ${sec}秒 ${ms}毫秒`); -//等待主界面状态 -async function waitForMainUI(requirement, timeOut = 60 * 1000) { - log.info(`等待至多${timeOut}毫秒`) - const startTime = Date.now(); - while (Date.now() - startTime < timeOut) { - const mainUIState = await isMainUI(); - if (mainUIState === requirement) return true; - - const elapsed = Date.now() - startTime; - const min = Math.floor(elapsed / 60000); - const sec = Math.floor((elapsed % 60000) / 1000); - const ms = elapsed % 1000; - log.info(`已等待 ${min}分 ${sec}秒 ${ms}毫秒`); - - await sleep(1000); - } - log.error("超时仍未到达指定状态"); - return false; -} - - -//检查是否在主界面 -async function isMainUI() { - // 修改后的图像路径 - const imagePath = "assets/RecognitionObject/MainUI.png"; - // 修改后的识别区域(左上角区域) - const xMin = 0; - const yMin = 0; - const width = 150; // 识别区域宽度 - const height = 150; // 识别区域高度 - let template = file.ReadImageMatSync(imagePath); - let recognitionObject = RecognitionObject.TemplateMatch(template, xMin, yMin, width, height); - - // 尝试次数设置为 5 次 - const maxAttempts = 5; - - let attempts = 0; - while (attempts < maxAttempts) { - try { - - let gameRegion = captureGameRegion(); - let result = gameRegion.find(recognitionObject); - gameRegion.dispose(); - if (result.isExist()) { - //log.info("处于主界面"); - return true; // 如果找到图标,返回 true - } - } catch (error) { - log.error(`识别图像时发生异常: ${error.message}`); - - return false; // 发生异常时返回 false - } - attempts++; // 增加尝试次数 - await sleep(50); // 每次检测间隔 50 毫秒 - } - return false; // 如果尝试次数达到上限或取消,返回 false -} - - -//获取联机世界的当前玩家标识 -async function getPlayerSign() { - const picDic = { - "1P": "assets/RecognitionObject/1P.png", - "2P": "assets/RecognitionObject/2P.png", - "3P": "assets/RecognitionObject/3P.png", - "4P": "assets/RecognitionObject/4P.png" - } - await genshin.returnMainUi(); - await sleep(500); - const p1Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["1P"]), 344, 22, 45, 45); - const p2Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["2P"]), 344, 22, 45, 45); - const p3Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["3P"]), 344, 22, 45, 45); - const p4Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["4P"]), 344, 22, 45, 45); - moveMouseTo(1555, 860); // 移走鼠标,防止干扰识别 - const gameRegion = captureGameRegion(); - // 当前页面模板匹配 - let p1 = gameRegion.Find(p1Ro); - let p2 = gameRegion.Find(p2Ro); - let p3 = gameRegion.Find(p3Ro); - let p4 = gameRegion.Find(p4Ro); - gameRegion.dispose(); - if (p1.isExist()) return 1; - if (p2.isExist()) return 2; - if (p3.isExist()) return 3; - if (p4.isExist()) return 4; - return 0; -} - -async function findTotalNumber() { - await genshin.returnMainUi(); - await keyPress("F2"); - await sleep(2000); - - // 定义模板 - const kick2pRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"), 1520, 277, 230, 120); - const kick3pRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"), 1520, 400, 230, 120); - const kick4pRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"), 1520, 527, 230, 120); - - moveMouseTo(1555, 860); // 防止鼠标干扰 - const gameRegion = captureGameRegion(); - await sleep(200); - - let count = 1; // 先算上自己 - - // 依次匹配 2P - if (gameRegion.Find(kick2pRo).isExist()) { - log.info("发现 2P"); - count++; - } - - // 依次匹配 3P - if (gameRegion.Find(kick3pRo).isExist()) { - log.info("发现 3P"); - count++; - } - - // 依次匹配 4P - if (gameRegion.Find(kick4pRo).isExist()) { - log.info("发现 4P"); - count++; - } - - gameRegion.dispose(); - - log.info(`当前联机世界玩家总数(含自己):${count}`); - return count; -} - -/** - * 等待所有队友(2P/3P/4P)就位 - * @param {number} totalNumber 联机总人数(包含自己) - * @param {number} timeOut 最长等待毫秒 - */ -async function waitForReady(totalNumber, timeOut = 300000) { - await genshin.tpToStatueOfTheSeven(); - - // 实际需要检测的队友编号:2 ~ totalNumber - const needCheck = totalNumber - 1; // 队友人数 - const readyFlags = new Array(needCheck).fill(false); // 下标 0 代表 2P,1 代表 3P … - - const startTime = Date.now(); - while (Date.now() - startTime < timeOut) { - - let allReady = true; - await genshin.returnMainUi(); - await keyPress("M"); // 打开多人地图/界面 - await sleep(2000); // 给 UI 一点加载时间 - - for (let i = 0; i < needCheck; i++) { - // 已就绪的队友跳过 - if (readyFlags[i]) continue; - - const playerIndex = i + 2; // 2P/3P/4P - const ready = await checkReady(playerIndex); - - if (ready) { - log.info(`玩家 ${playerIndex}P 已就绪`); - readyFlags[i] = true; - } else { - allReady = false; // 还有没就绪的 - } - } - - if (allReady) { - log.info("所有队友已就绪"); - return true; - } - - // 每轮检测后稍等,防止刷屏 - await sleep(500); - } - - log.warn("等待队友就绪超时"); - return false; -} - -async function checkReady(i) { - /* 1. 先把地图移到目标点位(point 来自 info.json) */ - const point = await getPointByPlayer(i); - if (!point) return false; - await genshin.moveMapTo(Math.round(point.x), Math.round(point.y)); - - /* 2. 取图标屏幕坐标 */ - const pos = await getPlayerIconPos(i); - if (!pos || !pos.found) return false; - - /* 3. 屏幕坐标 → 地图坐标(图标)*/ - const mapZoomLevel = 2.0; - await genshin.setBigMapZoomLevel(mapZoomLevel); - const mapScaleFactor = 2.361; - - const center = genshin.getPositionFromBigMap(); // 仅用于坐标系转换 - const iconScreenX = pos.x; - const iconScreenY = pos.y; - - const iconMapX = (960 - iconScreenX) * mapZoomLevel / mapScaleFactor + center.x; - const iconMapY = (540 - iconScreenY) * mapZoomLevel / mapScaleFactor + center.y; - - /* 4. 计算“图标地图坐标”与“目标点位”的距离 */ - const dx = iconMapX - point.x; - const dy = iconMapY - point.y; - const dist = Math.sqrt(dx * dx + dy * dy); - - /* 5. 打印两种坐标及距离 */ - log.info(`玩家 ${i}P`); - log.info(`├─ 屏幕坐标: (${iconScreenX}, ${iconScreenY})`); - log.info(`├─ 图标地图坐标: (${iconMapX.toFixed(2)}, ${iconMapY.toFixed(2)})`); - log.info(`├─ 目标点位坐标: (${point.x}, ${point.y})`); - log.info(`└─ 图标与目标点位距离: ${dist.toFixed(2)} m`); - - return dist <= 20; // 20 m 阈值,可按需调整 -} - - -/** - * 根据玩家编号返回该路线在 assets/info.json 中记录的点位坐标 - * @param {number} playerIndex 1 | 2 | 3 | 4 - * @returns {{x:number,y:number}|null} - */ -async function getPointByPlayer(playerIndex) { - // 1. 只读一次:第一次调用时加载并缓存 - if (_infoPoints === null) { - try { - const jsonStr = file.ReadTextSync('assets/info.json'); - _infoPoints = JSON.parse(jsonStr); - if (!Array.isArray(_infoPoints)) { - log.error('assets/info.json 不是数组格式'); - _infoPoints = []; // 防止后续再读 - return null; - } - } catch (err) { - log.error(`读取或解析 assets/info.json 失败: ${err.message}`); - _infoPoints = []; - return null; - } - } - - // 2. 外部已准备好的路线名称映射 - const routeMap = { - 1: p1EndingRoute, - 2: p2EndingRoute, - 3: p3EndingRoute, - 4: p4EndingRoute - }; - const routeName = routeMap[playerIndex]; - if (!routeName) { - log.error(`无效玩家编号: ${playerIndex}`); - return null; - } - - // 3. 遍历缓存数组 - for (let i = 0; i < _infoPoints.length; i++) { - const p = _infoPoints[i]; - if (p && p.name === routeName) { - return { x: p.position.x, y: p.position.y }; - } - } - - log.warn(`在 info.json 中找不到 name 为 "${routeName}" 的点`); - return null; -} - -/** - * 根据玩家编号获取 2/3/4P 图标在屏幕上的坐标 - * @param {number} playerIndex 2 | 3 | 4 - * @param {number} timeout 最长查找毫秒,默认 2000 - * @returns {Promise<{x:number,y:number,width:number,height:number,found:boolean}|null>} - * found=true 时坐标有效;未找到/取消/异常返回 null - */ -async function getPlayerIconPos(playerIndex, timeout = 2000) { - // 把路径封装在函数内部 - const map = { - 2: "assets/RecognitionObject/2pInBigMap.png", - 3: "assets/RecognitionObject/3pInBigMap.png", - 4: "assets/RecognitionObject/4pInBigMap.png" - }; - const tplPath = map[playerIndex]; - if (!tplPath) { - log.error(`无效玩家编号: ${playerIndex}`); - return null; - } - - const template = file.ReadImageMatSync(tplPath); - const recognitionObj = RecognitionObject.TemplateMatch(template, 0, 0, 1920, 1080); // 全屏查找,可自行改区域 - - const start = Date.now(); - while (Date.now() - start < timeout) { - let gameRegion = null; - try { - gameRegion = captureGameRegion(); - const res = gameRegion.find(recognitionObj); - if (res.isExist()) { - log.info(`${playerIndex}P,在屏幕上的坐标为(${res.x + 10},${res.y + 10})`);//图标大小为20*20 - return { - x: res.x + 10, - y: res.y + 10, - found: true - }; - } - } catch (e) { - log.error(`模板匹配异常: ${e.message}`); - return null; - } finally { - if (gameRegion) gameRegion.dispose(); - } - await sleep(100); - } - return null; -} - -/** - * 根据玩家编号执行执行路线的全部 JSON 文件 - * @param {number} i 1 | 2 | 3 | 4 - */ -async function runEndingPath(i) { - const routeMap = { - 1: p1EndingRoute, - 2: p2EndingRoute, - 3: p3EndingRoute, - 4: p4EndingRoute - }; - - const folderName = routeMap[i]; - if (!folderName) { - log.error(`无效玩家编号: ${i}`); - return; - } - - const folderPath = `assets/ArtifactsPath/${folderName}/执行`; - const files = await readFolder(folderPath, true); - - if (files.length === 0) { - log.warn(`文件夹 ${folderPath} 下未找到任何 JSON 路线文件`); - return; - } - - for (const { fullPath } of files) { - log.info(`开始执行路线: ${fullPath}`); - await pathingScript.runFile(fullPath); - } - - log.info(`${folderName} 的全部路线已跑完`); -} - -/** - * 执行额外路线 - */ -async function runExtraPath() { - - const folderPath = `assets/ArtifactsPath/额外/执行`; - const files = await readFolder(folderPath, true); - - if (files.length === 0) { - log.warn(`文件夹 ${folderPath} 下未找到任何 JSON 路线文件`); - return; - } - - for (const { fullPath } of files) { - log.info(`开始执行路线: ${fullPath}`); - await pathingScript.runFile(fullPath); - } - - log.info(`额外 的全部路线已跑完`); -} - -/** - * 根据玩家编号执行占位路线的全部 JSON 文件 - * @param {number} i 1 | 2 | 3 | 4 - */ -async function goToTarget(i) { - const routeMap = { - 1: p1EndingRoute, - 2: p2EndingRoute, - 3: p3EndingRoute, - 4: p4EndingRoute - }; - - const folderName = routeMap[i]; - if (!folderName) { - log.error(`无效玩家编号: ${i}`); - return; - } - - const folderPath = `assets/ArtifactsPath/${folderName}/占位`; - const files = await readFolder(folderPath, true); - - if (files.length === 0) { - log.warn(`文件夹 ${folderPath} 下未找到任何 JSON 路线文件`); - return; - } - - for (const { fullPath } of files) { - log.info(`开始执行路线: ${fullPath}`); - await pathingScript.runFile(fullPath); - } - - log.info(`${folderName} 的全部路线已跑完`); -} - -async function readRecord(accountName) { - /* ---------- 文件名合法性校验 ---------- */ - const illegalCharacters = /[\\/:*?"<>|]/; - const reservedNames = [ - "CON", "PRN", "AUX", "NUL", - "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", - "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" - ]; - - let finalAccountName = accountName; - - if (accountName === "" || - accountName.startsWith(" ") || - accountName.endsWith(" ") || - illegalCharacters.test(accountName) || - reservedNames.includes(accountName.toUpperCase()) || - accountName.length > 255 - ) { - log.error(`账户名 "${accountName}" 不合法,将使用默认值`); - finalAccountName = "默认账户"; - await sleep(5000); - } else { - log.info(`账户名 "${accountName}" 合法`); - } - - /* ---------- 读取记录文件 ---------- */ - const recordFolderPath = "records/"; - const recordFilePath = `records/${finalAccountName}.txt`; - - const filesInSubFolder = file.ReadPathSync(recordFolderPath); - let fileExists = false; - for (const filePath of filesInSubFolder) { - if (filePath === `records\\${accountName}.txt`) { - fileExists = true; - break; - } - } - - - - /* ---------- 初始化记录对象 ---------- */ - record = { - records: new Array(14).fill(""), - version: "" - }; - - let recordIndex = 0; - - if (fileExists) { - log.info(`记录文件 ${recordFilePath} 存在`); - } else { - log.warn(`无记录文件,将使用默认数据`); - return; - } - - const content = await file.readText(recordFilePath); - const lines = content.split("\n"); - - - - /* ---------- 逐行解析 ---------- */ - for (const rawLine of lines) { - const line = rawLine.trim(); - if (!line) continue; - - if (line.startsWith("日期") && recordIndex < record.records.length) { - record.records[recordIndex++] = line; - } - } - - log.info(`上次运行日期: ${record.lastRunDate}`); - log.info(`上次激活路线开始时间: ${record.lastActivateTime.toLocaleString()}`); - - /* ---------- 读取 manifest 版本 ---------- */ - try { - const manifest = JSON.parse(await file.readText("manifest.json")); - record.version = manifest.version; - log.info(`当前版本为${record.version}`); - } catch (err) { - log.error("读取或解析 manifest.json 失败:", err); - } - -} - -async function writeRecord(accountName) { - if (state.cancel) return; - const recordFilePath = `records/${accountName}.txt`; - - const lines = [ - ...record.records.filter(Boolean) - ]; - - const content = lines.join('\n'); - - try { - await file.writeText(recordFilePath, content, false); - log.info(`记录已写入 ${recordFilePath}`); - } catch (e) { - log.error(`写入 ${recordFilePath} 失败:`, e); - } -} - -async function findAndClick(target, maxAttempts = 20) { - for (let attempts = 0; attempts < maxAttempts; attempts++) { - const gameRegion = captureGameRegion(); - try { - const result = gameRegion.find(target); - if (result.isExist) { - result.click(); - return true; // 成功立刻返回 - } - log.warn(`识别失败,第 ${attempts + 1} 次重试`); - } catch (err) { - } finally { - gameRegion.dispose(); - } - if (attempts < maxAttempts - 1) { // 最后一次不再 sleep - await sleep(250); - } - } - log.error("已达到重试次数上限,仍未找到目标"); - return false; -} - -async function processArtifacts(times = 1) { - await genshin.returnMainUi(); - await sleep(500); - let result = 0; - try { - if (settings.decomposeMode === "销毁(摩拉)") { - result = await destroyArtifacts(times); - } else { - result = await decomposeArtifacts(); - } - } catch (error) { - log.error(`处理狗粮分解时发生异常: ${error.message}`); - } - await genshin.returnMainUi(); - return result; - - async function decomposeArtifacts() { - keyPress("B"); - await sleep(1000); - await click(670, 45); - await sleep(500); - if (!await findAndClick(decomposeRo)) { - await genshin.returnMainUi(); - return 0; - } - await sleep(1000); - - //识别已储存经验(1570-880-1650-930) - let regionToCheck1 = { x: 1570, y: 880, width: 80, height: 50 }; - let initialNum = await recognizeTextInRegion(regionToCheck1); - let initialValue = 0; - - if (initialNum && !isNaN(parseInt(initialNum, 10))) { - initialValue = parseInt(initialNum, 10); - log.info(`已储存经验识别成功: ${initialValue}`); - } else { - log.warn(`在指定区域未识别到有效数字: ${initialValue}`); - } - let regionToCheck3 = { x: 100, y: 885, width: 170, height: 50 }; - let decomposedNum = 0; - let firstNumber = 0; - let firstNumber2 = 0; - - if (settings.keep4Star) { - if (!await findAndClick(quickChooseRo)) { - await genshin.returnMainUi(); - return 0; - } - moveMouseTo(960, 540); - await sleep(1000); - - // 点击“确认选择”按钮 - if (!await findAndClick(confirmRo)) { - await genshin.returnMainUi(); - return 0; - } - await sleep(1000); - - decomposedNum = await recognizeTextInRegion(regionToCheck3); - - // 使用正则表达式提取第一个数字 - const match = decomposedNum.match(/已选(\d+)/); - - // 检查是否匹配成功 - if (match) { - // 将匹配到的第一个数字转换为数字类型并存储在变量中 - firstNumber = Number(match[1]); - log.info(`1-4星总数量: ${firstNumber}`); - } else { - log.info("识别失败"); - } - keyPress("VK_ESCAPE"); - - await sleep(500); - if (!await findAndClick(decomposeRo)) { - await genshin.returnMainUi(); - return 0; - } - await sleep(500); - } - if (!await findAndClick(quickChooseRo)) { - await genshin.returnMainUi(); - return 0; - } - moveMouseTo(960, 540); - await sleep(1000); - - if (settings.keep4Star) { - await click(370, 370);//取消选择四星 await sleep(1000); } - // 点击“确认选择”按钮 - if (!await findAndClick(confirmRo)) { - await genshin.returnMainUi(); - return 0; - } - await sleep(1500); - - let decomposedNum2 = await recognizeTextInRegion(regionToCheck3); - - // 使用正则表达式提取第一个数字 - const match2 = decomposedNum2.match(/已选(\d+)/); - - // 检查是否匹配成功 - if (match2) { - // 将匹配到的第一个数字转换为数字类型并存储在变量中 - firstNumber2 = Number(match2[1]); - log.info(`分解总数是: ${firstNumber2}`); - } else { - log.info("识别失败"); - } - //识别当前总经验 - if (settings.notify) { - notification.Send(`当前经验如图`); - } - let regionToCheck2 = { x: 1470, y: 880, width: 205, height: 70 }; - let newNum = await recognizeTextInRegion(regionToCheck2); - let newValue = 0; - - if (newNum && !isNaN(parseInt(newNum, 10))) { - newValue = parseInt(newNum, 10); - log.info(`当前总经验识别成功: ${newValue}`); - } else { - log.warn(`在指定区域未识别到有效数字: ${newValue}`); - } - - if (settings.decomposeMode === "分解(经验瓶)") { - log.info(`用户选择了分解,执行分解`); - // 根据用户配置,分解狗粮 - await sleep(1000); - // 点击分解按钮 - if (!await findAndClick(doDecomposeRo)) { - await genshin.returnMainUi(); - return 0; - } - await sleep(500); - - // 4. "进行分解"按钮// 点击进行分解按钮 - if (!await findAndClick(doDecompose2Ro)) { - await genshin.returnMainUi(); - return 0; - } - await sleep(1000); - - // 5. 关闭确认界面 - await click(1340, 755); - await sleep(1000); - } - else { - log.info(`用户未选择分解,不执行分解`); - } - - // 7. 计算分解获得经验=总经验-上次剩余 - const resinExperience = Math.max(newValue - initialValue, 0); - log.info(`分解可获得经验: ${resinExperience}`); - let fourStarNum = firstNumber - firstNumber2; - if (settings.keep4Star) { - log.info(`保留的四星数量: ${fourStarNum}`); - } - let resultExperience = resinExperience; - if (resultExperience === 0) { - resultExperience = initialValue; - } - const result = resultExperience; - await genshin.returnMainUi(); - return result; + log.error("超时仍未到达指定状态"); + return false; } + //检查是否在主界面 + async function isMainUI() { + // 修改后的图像路径 + const imagePath = "assets/RecognitionObject/MainUI.png"; + // 修改后的识别区域(左上角区域) + const xMin = 0; + const yMin = 0; + const width = 150; // 识别区域宽度 + const height = 150; // 识别区域高度 + let template = file.ReadImageMatSync(imagePath); + let recognitionObject = RecognitionObject.TemplateMatch(template, xMin, yMin, width, height); - async function destroyArtifacts(times = 1) { - await genshin.returnMainUi(); - await sleep(250); - keyPress("B"); - await sleep(500); - await findAndClick(ArtifactsButtonRo, 5) - try { - for (let i = 0; i < times; i++) { - // 点击摧毁 - if (!await findAndClick(DeleteButtonRo)) { - await genshin.returnMainUi(); - return; - } - await sleep(600); - // 点击自动添加 - if (!await findAndClick(AutoAddButtonRo)) { - await genshin.returnMainUi(); - return; - } - await sleep(900); - click(150, 150); - await sleep(300); - click(150, 220); - await sleep(300); - click(150, 300); - if (!settings.keep4Star) { - await sleep(300); - click(150, 370); - } - // 点击快捷放入 - if (!await findAndClick(ConfirmButtonRo)) { - await genshin.returnMainUi(); - return; - } - await sleep(600); - // 点击摧毁 - if (!await findAndClick(DestoryButtonRo)) { - await genshin.returnMainUi(); - return; - } - await sleep(600); - // 弹出页面点击摧毁 - if (!await findAndClick(MidDestoryButtonRo)) { - await genshin.returnMainUi(); - return; - } - await sleep(600); - click(960, 1000);// 点击空白处 - await sleep(1000); - } - } catch (ex) { - log.info("背包里的圣遗物已摧毁完毕,提前结束") - } finally { - await genshin.returnMainUi(); - } + // 尝试次数设置为 5 次 + const maxAttempts = 5; - } -} - -async function mora() { - // 定义一个函数用于识别图像 - async function recognizeImage(recognitionObject, timeout = 5000) { - let startTime = Date.now(); - while (Date.now() - startTime < timeout) { + let attempts = 0; + while (attempts < maxAttempts) { try { - // 尝试识别图像 - const gameRegion = captureGameRegion(); - let imageResult = gameRegion.find(recognitionObject); - gameRegion.dispose; - if (imageResult) { - // log.info(`成功识别图像,坐标: x=${imageResult.x}, y=${imageResult.y}`); - // log.info(`图像尺寸: width=${imageResult.width}, height=${imageResult.height}`); - return { success: true, x: imageResult.x, y: imageResult.y }; + + let gameRegion = captureGameRegion(); + let result = gameRegion.find(recognitionObject); + gameRegion.dispose(); + if (result.isExist()) { + //log.info("处于主界面"); + return true; // 如果找到图标,返回 true } } catch (error) { log.error(`识别图像时发生异常: ${error.message}`); + + return false; // 发生异常时返回 false } - await sleep(500); // 短暂延迟,避免过快循环 + attempts++; // 增加尝试次数 + await sleep(50); // 每次检测间隔 50 毫秒 } - log.warn(`经过多次尝试,仍然无法识别图像`); - return { success: false }; + return false; // 如果尝试次数达到上限或取消,返回 false } - let result = 0; - let tryTimes = 0; - while (result === 0 && tryTimes < 3) { + + + //获取联机世界的当前玩家标识 + async function getPlayerSign() { + const picDic = { + "1P": "assets/RecognitionObject/1P.png", + "2P": "assets/RecognitionObject/2P.png", + "3P": "assets/RecognitionObject/3P.png", + "4P": "assets/RecognitionObject/4P.png" + } await genshin.returnMainUi(); - await sleep(100); - log.info("开始尝试识别摩拉"); - // 按下 C 键 - keyPress("C"); - await sleep(1500); - let recognized = false; - // 识别“角色菜单”图标或“天赋”文字 - let startTime = Date.now(); - while (Date.now() - startTime < 5000) { - // 尝试识别“角色菜单”图标 - let characterMenuResult = await recognizeImage(CharacterMenuRo, 5000); - if (characterMenuResult.success) { - await click(177, 433); - await sleep(500); - recognized = true; - break; - } - - // 尝试识别“天赋”文字 - let targetText = "天赋"; - let ocrRegion = { x: 133, y: 395, width: 115, height: 70 }; // 设置对应的识别区域 - let talentResult = await recognizeTextAndClick(targetText, ocrRegion); - if (talentResult.success) { - log.info(`点击天赋文字,坐标: x=${talentResult.x}, y=${talentResult.y}`); - recognized = true; - break; - } - - await sleep(1000); // 短暂延迟,避免过快循环 - } - - let recognizedText = ""; - - // 如果识别到了“角色菜单”或“天赋”,则识别“摩拉数值” - if (recognized) { - if (settings.notify) { - notification.Send(`当前摩拉如图`); - } - let ocrRegionMora = { x: 1620, y: 25, width: 152, height: 46 }; // 设置对应的识别区域 - recognizedText = await recognizeTextInRegion(ocrRegionMora); - if (recognizedText) { - log.info(`成功识别到摩拉数值: ${recognizedText}`); - result = recognizedText; - } else { - log.warn("未能识别到摩拉数值。"); - } - } else { - log.warn("未能识别到角色菜单或天赋"); - } await sleep(500); - tryTimes++; + const p1Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["1P"]), 344, 22, 45, 45); + const p2Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["2P"]), 344, 22, 45, 45); + const p3Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["3P"]), 344, 22, 45, 45); + const p4Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["4P"]), 344, 22, 45, 45); + moveMouseTo(1555, 860); // 移走鼠标,防止干扰识别 + const gameRegion = captureGameRegion(); + // 当前页面模板匹配 + let p1 = gameRegion.Find(p1Ro); + let p2 = gameRegion.Find(p2Ro); + let p3 = gameRegion.Find(p3Ro); + let p4 = gameRegion.Find(p4Ro); + gameRegion.dispose(); + if (p1.isExist()) return 1; + if (p2.isExist()) return 2; + if (p3.isExist()) return 3; + if (p4.isExist()) return 4; + return 0; + } + + async function findTotalNumber() { await genshin.returnMainUi(); - } - return Number(result); -} + await keyPress("F2"); + await sleep(2000); -// 定义一个独立的函数用于在指定区域进行 OCR 识别并输出识别内容 -async function recognizeTextInRegion(ocrRegion, timeout = 5000) { - let startTime = Date.now(); - let retryCount = 0; // 重试计数 - while (Date.now() - startTime < timeout) { - try { - // 在指定区域进行 OCR 识别 - const gameRegion = captureGameRegion(); - let ocrResult = gameRegion.find(RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height)); - gameRegion.dispose(); - if (ocrResult) { - let correctedText = ocrResult.text; - return correctedText; // 返回识别到的内容 - } else { - log.warn(`OCR 识别区域未找到内容`); - return null; // 如果 OCR 未识别到内容,返回 null - } - } catch (error) { - retryCount++; // 增加重试计数 - log.warn(`OCR 识别失败,正在进行第 ${retryCount} 次重试...`); + // 定义模板 + const kick2pRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"), 1520, 277, 230, 120); + const kick3pRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"), 1520, 400, 230, 120); + const kick4pRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"), 1520, 527, 230, 120); + + moveMouseTo(1555, 860); // 防止鼠标干扰 + const gameRegion = captureGameRegion(); + await sleep(200); + + let count = 1; // 先算上自己 + + // 依次匹配 2P + if (gameRegion.Find(kick2pRo).isExist()) { + log.info("发现 2P"); + count++; } - await sleep(500); // 短暂延迟,避免过快循环 - } - log.warn(`经过多次尝试,仍然无法在指定区域识别到文字`); - return null; // 如果未识别到文字,返回 null -} -// 定义一个函数用于识别文字并点击 -async function recognizeTextAndClick(targetText, ocrRegion, timeout = 3000) { - let startTime = Date.now(); - let retryCount = 0; // 重试计数 - while (Date.now() - startTime < timeout) { - try { - // 尝试 OCR 识别 - const gameRegion = captureGameRegion(); - let resList = gameRegion.findMulti(RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height)); // 指定识别区域 - gameRegion.dispose(); - // 遍历识别结果,检查是否找到目标文本 - for (let res of resList) { - let correctedText = res.text; - if (correctedText.includes(targetText)) { - // 如果找到目标文本,计算并点击文字的中心坐标 - let centerX = Math.round(res.x + res.width / 2); - let centerY = Math.round(res.y + res.height / 2); - await click(centerX, centerY); - await sleep(500); // 确保点击后有足够的时间等待 - return { success: true, x: centerX, y: centerY }; + // 依次匹配 3P + if (gameRegion.Find(kick3pRo).isExist()) { + log.info("发现 3P"); + count++; + } + + // 依次匹配 4P + if (gameRegion.Find(kick4pRo).isExist()) { + log.info("发现 4P"); + count++; + } + + gameRegion.dispose(); + + log.info(`当前联机世界玩家总数(含自己):${count}`); + return count; + } + + /** + * 等待所有队友(2P/3P/4P)就位 + * @param {number} totalNumber 联机总人数(包含自己) + * @param {number} timeOut 最长等待毫秒 + */ + async function waitForReady(totalNumber, timeOut = 300000) { + await genshin.tpToStatueOfTheSeven(); + + // 实际需要检测的队友编号:2 ~ totalNumber + const needCheck = totalNumber - 1; // 队友人数 + const readyFlags = new Array(needCheck).fill(false); // 下标 0 代表 2P,1 代表 3P … + + const startTime = Date.now(); + while (Date.now() - startTime < timeOut) { + + let allReady = true; + await genshin.returnMainUi(); + await keyPress("M"); // 打开多人地图/界面 + await sleep(2000); // 给 UI 一点加载时间 + + for (let i = 0; i < needCheck; i++) { + // 已就绪的队友跳过 + if (readyFlags[i]) continue; + + const playerIndex = i + 2; // 2P/3P/4P + const ready = await checkReady(playerIndex); + + if (ready) { + log.info(`玩家 ${playerIndex}P 已就绪`); + readyFlags[i] = true; + } else { + allReady = false; // 还有没就绪的 } } - } catch (error) { - retryCount++; // 增加重试计数 - log.warn(`页面标志识别失败,正在进行第 ${retryCount} 次重试...`); + + if (allReady) { + log.info("所有队友已就绪"); + if (settings.runDebug) await sleep(10000); + return true; + } + + // 每轮检测后稍等,防止刷屏 + await sleep(500); } - await sleep(1000); // 短暂延迟,避免过快循环 + + log.warn("等待队友就绪超时"); + return false; } - log.warn(`经过多次尝试,仍然无法识别文字: ${targetText},尝试点击默认中心位置`); - let centerX = Math.round(ocrRegion.x + ocrRegion.width / 2); - let centerY = Math.round(ocrRegion.y + ocrRegion.height / 2); - await click(centerX, centerY); - await sleep(1000); - return { success: false }; -} -// 定义 readFolder 函数 -async function readFolder(folderPath, onlyJson) { - // 新增一个堆栈,初始时包含 folderPath - const folderStack = [folderPath]; + async function checkReady(i) { + /* 1. 先把地图移到目标点位(point 来自 info.json) */ + const point = await getPointByPlayer(i); + if (!point) return false; + // 把路径封装在函数内部 + const map = { + 2: "assets/RecognitionObject/2pInBigMap.png", + 3: "assets/RecognitionObject/3pInBigMap.png", + 4: "assets/RecognitionObject/4pInBigMap.png" + }; + const tplPath = map[i]; + if (!tplPath) { + log.error(`无效玩家编号: ${i}`); + return null; + } - // 新增一个数组,用于存储文件信息对象 - const files = []; + const template = file.ReadImageMatSync(tplPath); + const recognitionObj = RecognitionObject.TemplateMatch(template, 0, 0, 1920, 1080); // 全屏查找,可自行改区域 + if (await findAndClick(recognitionObj, 5)) await sleep(1000); - // 当堆栈不为空时,继续处理 - while (folderStack.length > 0) { - // 从堆栈中弹出一个路径 - const currentPath = folderStack.pop(); + await genshin.moveMapTo(Math.round(point.x), Math.round(point.y)); - // 读取当前路径下的所有文件和子文件夹路径 - const filesInSubFolder = file.ReadPathSync(currentPath); + /* 2. 取图标屏幕坐标 */ + const pos = await getPlayerIconPos(i); + if (!pos || !pos.found) return false; - // 临时数组,用于存储子文件夹路径 - const subFolders = []; - for (const filePath of filesInSubFolder) { - if (file.IsFolder(filePath)) { - // 如果是文件夹,先存储到临时数组中 - subFolders.push(filePath); - } else { - // 如果是文件,根据 onlyJson 判断是否存储 - if (onlyJson) { - if (filePath.endsWith(".json")) { + /* 3. 屏幕坐标 → 地图坐标(图标)*/ + const mapZoomLevel = 2.0; + await genshin.setBigMapZoomLevel(mapZoomLevel); + const mapScaleFactor = 2.361; + + const center = genshin.getPositionFromBigMap(); // 仅用于坐标系转换 + const iconScreenX = pos.x; + const iconScreenY = pos.y; + + const iconMapX = (960 - iconScreenX) * mapZoomLevel / mapScaleFactor + center.x; + const iconMapY = (540 - iconScreenY) * mapZoomLevel / mapScaleFactor + center.y; + + /* 4. 计算“图标地图坐标”与“目标点位”的距离 */ + const dx = iconMapX - point.x; + const dy = iconMapY - point.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + /* 5. 打印两种坐标及距离 */ + log.info(`玩家 ${i}P`); + log.info(`├─ 屏幕坐标: (${iconScreenX}, ${iconScreenY})`); + log.info(`├─ 图标地图坐标: (${iconMapX.toFixed(2)}, ${iconMapY.toFixed(2)})`); + log.info(`├─ 目标点位坐标: (${point.x}, ${point.y})`); + log.info(`└─ 图标与目标点位距离: ${dist.toFixed(2)} m`); + + return dist <= 20; // 20 m 阈值,可按需调整 + } + + + /** + * 根据玩家编号返回该路线在 assets/info.json 中记录的点位坐标 + * @param {number} playerIndex 1 | 2 | 3 | 4 + * @returns {{x:number,y:number}|null} + */ + async function getPointByPlayer(playerIndex) { + // 1. 只读一次:第一次调用时加载并缓存 + if (_infoPoints === null) { + try { + const jsonStr = file.ReadTextSync('assets/info.json'); + _infoPoints = JSON.parse(jsonStr); + if (!Array.isArray(_infoPoints)) { + log.error('assets/info.json 不是数组格式'); + _infoPoints = []; // 防止后续再读 + return null; + } + } catch (err) { + log.error(`读取或解析 assets/info.json 失败: ${err.message}`); + _infoPoints = []; + return null; + } + } + + // 2. 外部已准备好的路线名称映射 + const routeMap = { + 1: p1EndingRoute, + 2: p2EndingRoute, + 3: p3EndingRoute, + 4: p4EndingRoute + }; + const routeName = routeMap[playerIndex]; + if (!routeName) { + log.error(`无效玩家编号: ${playerIndex}`); + return null; + } + + // 3. 遍历缓存数组 + for (let i = 0; i < _infoPoints.length; i++) { + const p = _infoPoints[i]; + if (p && p.name === routeName) { + return { x: p.position.x, y: p.position.y }; + } + } + + log.warn(`在 info.json 中找不到 name 为 "${routeName}" 的点`); + return null; + } + + /** + * 根据玩家编号获取 2/3/4P 图标在屏幕上的坐标 + * @param {number} playerIndex 2 | 3 | 4 + * @param {number} timeout 最长查找毫秒,默认 2000 + * @returns {Promise<{x:number,y:number,width:number,height:number,found:boolean}|null>} + * found=true 时坐标有效;未找到/取消/异常返回 null + */ + async function getPlayerIconPos(playerIndex, timeout = 2000) { + // 把路径封装在函数内部 + const map = { + 2: "assets/RecognitionObject/2pInBigMap.png", + 3: "assets/RecognitionObject/3pInBigMap.png", + 4: "assets/RecognitionObject/4pInBigMap.png" + }; + const tplPath = map[playerIndex]; + if (!tplPath) { + log.error(`无效玩家编号: ${playerIndex}`); + return null; + } + + const template = file.ReadImageMatSync(tplPath); + const recognitionObj = RecognitionObject.TemplateMatch(template, 0, 0, 1920, 1080); // 全屏查找,可自行改区域 + + const start = Date.now(); + while (Date.now() - start < timeout) { + let gameRegion = null; + try { + gameRegion = captureGameRegion(); + const res = gameRegion.find(recognitionObj); + if (res.isExist()) { + log.info(`${playerIndex}P,在屏幕上的坐标为(${res.x + 10},${res.y + 10})`);//图标大小为20*20 + return { + x: res.x + 10, + y: res.y + 10, + found: true + }; + } + } catch (e) { + log.error(`模板匹配异常: ${e.message}`); + return null; + } finally { + if (gameRegion) gameRegion.dispose(); + } + await sleep(100); + } + return null; + } + + /** + * 根据玩家编号执行执行路线的全部 JSON 文件 + * @param {number} i 1 | 2 | 3 | 4 + */ + async function runEndingPath(i) { + const routeMap = { + 1: p1EndingRoute, + 2: p2EndingRoute, + 3: p3EndingRoute, + 4: p4EndingRoute + }; + + const folderName = routeMap[i]; + if (!folderName) { + log.error(`无效玩家编号: ${i}`); + return; + } + + const folderPath = `assets/ArtifactsPath/${folderName}/执行`; + const files = await readFolder(folderPath, true); + + if (files.length === 0) { + log.warn(`文件夹 ${folderPath} 下未找到任何 JSON 路线文件`); + return; + } + if (!settings.runDebug) { + for (const { fullPath } of files) { + log.info(`开始执行路线: ${fullPath}`); + await fakeLog(`${fullPath}`, false, true, 0); + await pathingScript.runFile(fullPath); + await fakeLog(`${fullPath}`, false, false, 0); + } + + log.info(`${folderName} 的全部路线已完成`); + } else { + log.info("当前为调试模式,跳过执行路线"); + } + } + + /** + * 执行额外路线 + */ + async function runExtraPath() { + + const folderPath = `assets/ArtifactsPath/额外/执行`; + const files = await readFolder(folderPath, true); + + if (files.length === 0) { + log.warn(`文件夹 ${folderPath} 下未找到任何 JSON 路线文件`); + return; + } + + if (!settings.runDebug) { + for (const { fullPath } of files) { + log.info(`开始执行路线: ${fullPath}`); + await fakeLog(`${fullPath}`, false, true, 0); + await pathingScript.runFile(fullPath); + await fakeLog(`${fullPath}`, false, false, 0); + } + + log.info(`额外 的全部路线已完成`); + } else { + log.info("当前为调试模式,跳过执行路线"); + } + } + + /** + * 根据玩家编号执行占位路线的全部 JSON 文件 + * @param {number} i 1 | 2 | 3 | 4 + */ + async function goToTarget(i) { + const routeMap = { + 1: p1EndingRoute, + 2: p2EndingRoute, + 3: p3EndingRoute, + 4: p4EndingRoute + }; + + const folderName = routeMap[i]; + if (!folderName) { + log.error(`无效玩家编号: ${i}`); + return; + } + + const folderPath = `assets/ArtifactsPath/${folderName}/占位`; + const files = await readFolder(folderPath, true); + + if (files.length === 0) { + log.warn(`文件夹 ${folderPath} 下未找到任何 JSON 路线文件`); + return; + } + + for (const { fullPath } of files) { + log.info(`开始执行路线: ${fullPath}`); + await fakeLog(`${fullPath}`, false, true, 0); + await pathingScript.runFile(fullPath); + await fakeLog(`${fullPath}`, false, false, 0); + } + + log.info(`${folderName} 的全部路线已完成`); + } + + async function findAndClick(target, maxAttempts = 20) { + for (let attempts = 0; attempts < maxAttempts; attempts++) { + const gameRegion = captureGameRegion(); + try { + const result = gameRegion.find(target); + if (result.isExist()) { + result.click(); + return true; // 成功立刻返回 + } + log.warn(`识别失败,第 ${attempts + 1} 次重试`); + } catch (err) { + } finally { + gameRegion.dispose(); + } + if (attempts < maxAttempts - 1) { // 最后一次不再 sleep + await sleep(250); + } + } + //log.error("已达到重试次数上限,仍未找到目标"); + return false; + } + + // 定义 readFolder 函数 + async function readFolder(folderPath, onlyJson) { + // 新增一个堆栈,初始时包含 folderPath + const folderStack = [folderPath]; + + // 新增一个数组,用于存储文件信息对象 + const files = []; + + // 当堆栈不为空时,继续处理 + while (folderStack.length > 0) { + // 从堆栈中弹出一个路径 + const currentPath = folderStack.pop(); + + // 读取当前路径下的所有文件和子文件夹路径 + const filesInSubFolder = file.ReadPathSync(currentPath); + + // 临时数组,用于存储子文件夹路径 + const subFolders = []; + for (const filePath of filesInSubFolder) { + if (file.IsFolder(filePath)) { + // 如果是文件夹,先存储到临时数组中 + subFolders.push(filePath); + } else { + // 如果是文件,根据 onlyJson 判断是否存储 + if (onlyJson) { + if (filePath.endsWith(".json")) { + const fileName = filePath.split('\\').pop(); // 提取文件名 + const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组 + files.push({ + fullPath: filePath, + fileName: fileName, + folderPathArray: folderPathArray + }); + //log.info(`找到 JSON 文件:${filePath}`); + } + } else { const fileName = filePath.split('\\').pop(); // 提取文件名 const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组 files.push({ @@ -1085,211 +626,379 @@ async function readFolder(folderPath, onlyJson) { fileName: fileName, folderPathArray: folderPathArray }); - //log.info(`找到 JSON 文件:${filePath}`); + //log.info(`找到文件:${filePath}`); + } + } + } + // 将临时数组中的子文件夹路径按原顺序压入堆栈 + folderStack.push(...subFolders.reverse()); // 反转子文件夹路径 + } + + return files; + } + + // fakeLog 函数,使用方法:将本函数放在主函数前,调用时请务必使用await,否则可能出现v8白框报错 + //在js开头处伪造该js结束运行的日志信息,如 await fakeLog("js脚本", true, true, 0); + //在js结尾处伪造该js开始运行的日志信息,如 await fakeLog("js脚本", true, false, 2333); + //duration项目仅在伪造结束信息时有效,且无实际作用,可以任意填写,当你需要在日志中输出特定值时才需要,单位为毫秒 + //在调用地图追踪前伪造该地图追踪开始运行的日志信息,如 await fakeLog(`地图追踪.json`, false, true, 0); + //在调用地图追踪后伪造该地图追踪结束运行的日志信息,如 await fakeLog(`地图追踪.json`, false, false, 0); + //如此便可以在js运行过程中伪造地图追踪的日志信息,可以在日志分析等中查看 + + async function fakeLog(name, isJs, isStart, duration) { + await sleep(10); + const currentTime = Date.now(); + // 参数检查 + if (typeof name !== 'string') { + log.error("参数 'name' 必须是字符串类型!"); + return; + } + if (typeof isJs !== 'boolean') { + log.error("参数 'isJs' 必须是布尔型!"); + return; + } + if (typeof isStart !== 'boolean') { + log.error("参数 'isStart' 必须是布尔型!"); + return; + } + if (typeof currentTime !== 'number' || !Number.isInteger(currentTime)) { + log.error("参数 'currentTime' 必须是整数!"); + return; + } + if (typeof duration !== 'number' || !Number.isInteger(duration)) { + log.error("参数 'duration' 必须是整数!"); + return; + } + + // 将 currentTime 转换为 Date 对象并格式化为 HH:mm:ss.sss + const date = new Date(currentTime); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + const milliseconds = String(date.getMilliseconds()).padStart(3, '0'); + const formattedTime = `${hours}:${minutes}:${seconds}.${milliseconds}`; + + // 将 duration 转换为分钟和秒,并保留三位小数 + const durationInSeconds = duration / 1000; // 转换为秒 + const durationMinutes = Math.floor(durationInSeconds / 60); + const durationSeconds = (durationInSeconds % 60).toFixed(3); // 保留三位小数 + + // 使用四个独立的 if 语句处理四种情况 + if (isJs && isStart) { + // 处理 isJs = true 且 isStart = true 的情况 + const logMessage = `正在伪造js开始的日志记录\n\n` + + `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + + `------------------------------\n\n` + + `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + + `→ 开始执行JS脚本: "${name}"`; + log.debug(logMessage); + } + if (isJs && !isStart) { + // 处理 isJs = true 且 isStart = false 的情况 + const logMessage = `正在伪造js结束的日志记录\n\n` + + `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + + `→ 脚本执行结束: "${name}", 耗时: ${durationMinutes}分${durationSeconds}秒\n\n` + + `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + + `------------------------------`; + log.debug(logMessage); + } + if (!isJs && isStart) { + // 处理 isJs = false 且 isStart = true 的情况 + const logMessage = `正在伪造地图追踪开始的日志记录\n\n` + + `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + + `------------------------------\n\n` + + `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + + `→ 开始执行地图追踪任务: "${name}"`; + log.debug(logMessage); + } + if (!isJs && !isStart) { + // 处理 isJs = false 且 isStart = false 的情况 + const logMessage = `正在伪造地图追踪结束的日志记录\n\n` + + `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + + `→ 脚本执行结束: "${name}", 耗时: ${durationMinutes}分${durationSeconds}秒\n\n` + + `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + + `------------------------------`; + log.debug(logMessage); + } + } +} + +/** + * 自动联机脚本(整体打包为一个函数) + * @param {Object} autoEnterSettings 配置对象 + * enterMode: "进入他人世界" | "等待他人进入" + * enteringUID: string | null + * permissionMode: "无条件通过" | "白名单" + * nameToPermit1/2/3: string | null + * timeout: 分钟 + * maxEnterCount: number + */ +async function autoEnter(autoEnterSettings) { + // ===== 配置解析 ===== + const enterMode = autoEnterSettings.enterMode || "进入他人世界"; + const enteringUID = autoEnterSettings.enteringUID; + const permissionMode = autoEnterSettings.permissionMode || "无条件通过"; + const timeout = +autoEnterSettings.timeout || 5; + const maxEnterCount = +autoEnterSettings.maxEnterCount || 3; + + // 白名单 + const targetList = []; + [autoEnterSettings.nameToPermit1, autoEnterSettings.nameToPermit2, autoEnterSettings.nameToPermit3] + .forEach(v => v && targetList.push(v)); + + // ===== 模板 / 路径 ===== + const enterUIDRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/enterUID.png")); + const searchRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/search.png")); + const requestEnterRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/requestEnter.png")); + const requestEnter2Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/requestEnter.png"), 1480, 300, 280, 600); + const yUIRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/yUI.png")); + const allowEnterRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/allowEnter.png"), 1250, 300, 150, 130); + const targetsPath = "targets"; + + // ===== 状态 ===== + let enterCount = 0; + let targetsRo = []; + let checkToEnd = false; + let enteredPlayers = []; + + // ===== 初始化 ===== + setGameMetrics(1920, 1080, 1); + const start = new Date(); + log.info(`当前模式为:${enterMode}`); + + // 加载目标 PNG + const targetPngs = await readFolder(targetsPath, false); + for (const f of targetPngs) { + if (!f.fullPath.endsWith('.png')) continue; + const mat = file.ReadImageMatSync(f.fullPath); + const ro = RecognitionObject.TemplateMatch(mat, 650, 320, 350, 60); + const baseName = f.fileName.replace(/\.png$/i, ''); + targetsRo.push({ ro, baseName }); + } + log.info(`加载完成共 ${targetsRo.length} 个目标`); + + // ===== 主循环 ===== + while (new Date() - start < timeout * 60 * 1000) { + if (enterMode === "进入他人世界") { + const playerSign = await getPlayerSign(); + await sleep(500); + if (playerSign !== 0) { + log.info(`加入成功,队伍编号 ${playerSign}`); + break; + } + log.error('不处于多人世界,开始尝试加入'); + await genshin.returnMainUi(); await sleep(500); + + if (!enteringUID) { log.error('未填写有效 UID'); break; } + + await keyPress("F2"); await sleep(2000); + if (!await findAndClick(enterUIDRo)) { await genshin.returnMainUi(); continue; } + await sleep(1000); inputText(enteringUID); + await sleep(1000); + if (!await findAndClick(searchRo)) { await genshin.returnMainUi(); continue; } + if (await confirmSearchResult()) { await genshin.returnMainUi(); continue; } + + await sleep(500); + if (!await findAndClick(requestEnterRo)) { await genshin.returnMainUi(); continue; } + await waitForMainUI(true, 20 * 1000); + + } else { // 等待他人进入 + if (enterCount >= maxEnterCount) break; + + if (await isYUI()) keyPress("VK_ESCAPE"); + await genshin.returnMainUi(); + keyPress("Y"); await sleep(250); + + if (!await isYUI()) continue; + log.info("处于 Y 界面,开始识别"); + + let attempts = 0; + while (attempts++ < 5) { + if (permissionMode === "无条件通过") { + if (await findAndClick(allowEnterRo)) { + await waitForMainUI(true, 20 * 1000); + enterCount++; + break; } } else { - const fileName = filePath.split('\\').pop(); // 提取文件名 - const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组 - files.push({ - fullPath: filePath, - fileName: fileName, - folderPathArray: folderPathArray - }); - //log.info(`找到文件:${filePath}`); + const result = await recognizeRequest(); + if (result) { + if (await findAndClick(allowEnterRo)) { + await waitForMainUI(true, 20 * 1000); + enterCount++; + enteredPlayers = [...new Set([...enteredPlayers, result])]; + log.info(`允许 ${result} 加入`); + if (await isYUI()) { keyPress("VK_ESCAPE"); await genshin.returnMainUi(); } + break; + } else { + if (await isYUI()) { keyPress("VK_ESCAPE"); await genshin.returnMainUi(); } + } + } } + await sleep(500); + } + + if (await isYUI()) { keyPress("VK_ESCAPE"); await genshin.returnMainUi(); } + + if (enterCount >= maxEnterCount || checkToEnd) { + checkToEnd = true; + await sleep(20000); + if (await findTotalNumber() === maxEnterCount + 1) break; + else enterCount--; } } - // 将临时数组中的子文件夹路径按原顺序压入堆栈 - folderStack.push(...subFolders.reverse()); // 反转子文件夹路径 } - return files; -} - -async function runPaths(folderFilePath) { - let Paths = await readFolder(folderFilePath, true); - for (let i = 0; i < Paths.length; i++) { - let skiprecord = false; - const Path = Paths[i]; - let success = true; - if (settings.autoSalvage && autoSalvageCount >= 4) { - autoSalvageCount = 0; - if (settings.decomposeMode === "分解(经验瓶)") { - artifactExperienceDiff += await processArtifacts(1); - } else { - await processArtifacts(1); - } - } else { - autoSalvageCount++; + // ===== 以下工具函数保持不变 ===== + async function waitForMainUI(requirement, timeOut = 60 * 1000) { + const startTime = Date.now(); + while (Date.now() - startTime < timeOut) { + if (await isMainUI() === requirement) return true; + await sleep(1000); } - await fakeLog(Path.fileName, false, true, 0); - const pathInfo = await parsePathing(Path.fullPath); + log.error("超时仍未到达指定状态"); + return false; + } + + async function isMainUI() { + const template = file.ReadImageMatSync("assets/RecognitionObject/MainUI.png"); + const ro = RecognitionObject.TemplateMatch(template, 0, 0, 150, 150); try { - log.info(`当前进度:${Path.fileName}为${folderFilePath}第${i + 1}/${Paths.length}个`); - await pathingScript.runFile(Path.fullPath); - await sleep(1); - } catch (error) { - skiprecord = true; - log.error(`执行路径文件时发生错误:${error.message}`); - if (error.message === "A task was canceled.") { - log.warn("任务取消"); - state.cancel = true; - } - success = false; - break; + const gameRegion = captureGameRegion(); + const result = gameRegion.find(ro); + gameRegion.dispose(); + return result.isExist(); + } catch { return false; } + } + + async function getPlayerSign() { + const picDic = { + "1P": "assets/RecognitionObject/1P.png", + "2P": "assets/RecognitionObject/2P.png", + "3P": "assets/RecognitionObject/3P.png", + "4P": "assets/RecognitionObject/4P.png" + }; + await genshin.returnMainUi(); await sleep(500); + const gameRegion = captureGameRegion(); + const x = 344, y = 22, w = 45, h = 45; + for (let i = 1; i <= 4; i++) { + const ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic[i + "P"]), x, y, w, h); + if (gameRegion.Find(ro).isExist()) { gameRegion.dispose(); return i; } } - await fakeLog(Path.fileName, false, false, 0); - if (pathInfo.ok) { - //回到主界面 - await genshin.returnMainUi(); - await sleep(100); - try { - // 获取当前人物在指定地图上的坐标 - const currentPosition = await genshin.getPositionFromMap(pathInfo.map_name); + gameRegion.dispose(); + return 0; + } - // 计算与最后一个非 orientation 点的距离 - const distToLast = Math.hypot( - currentPosition.x - pathInfo.x, - currentPosition.y - pathInfo.y - ); + async function confirmSearchResult() { + for (let i = 0; i < 5; i++) { + const gameRegion = captureGameRegion(); + const res = gameRegion.find(requestEnter2Ro); + gameRegion.dispose(); + if (res.isExist()) return false; + if (i < 4) await sleep(250); + } + return true; + } - // 距离超过 50 认为路线没有正常完成(卡死或未开图等) - if (distToLast >= 50) { - failcount++; - skiprecord = true; - log.warn(`路线${Path.fileName}没有正常完成`); - await sleep(5000); + async function findAndClick(target, maxAttempts = 20) { + for (let i = 0; i < maxAttempts; i++) { + const gameRegion = captureGameRegion(); + const res = gameRegion.find(target); + if (res.isExist()) { res.click(); return true; } + gameRegion.dispose(); + if (i < maxAttempts - 1) await sleep(250); + } + return false; + } + + async function isYUI() { + for (let i = 0; i < 5; i++) { + const gameRegion = captureGameRegion(); + const res = gameRegion.find(yUIRo); + gameRegion.dispose(); + if (res.isExist()) return true; + await sleep(250); + } + return false; + } + + async function recognizeRequest() { + try { + const gameRegion = captureGameRegion(); + for (const { ro, baseName } of targetsRo) { + if (gameRegion.find(ro).isExist()) { gameRegion.dispose(); return baseName; } + } + gameRegion.dispose(); + } catch { } + try { + const gameRegion = captureGameRegion(); + const resList = gameRegion.findMulti(RecognitionObject.ocr(650, 320, 350, 60)); + gameRegion.dispose(); + let hit = null; + for (const res of resList) { + const txt = res.text.trim(); + if (targetList.includes(txt)) { hit = txt; break; } + } + if (!hit) resList.forEach(r => log.warn(`识别到"${r.text.trim()}",不在白名单`)); + return hit; + } catch { return null; } + } + + async function findTotalNumber() { + await genshin.returnMainUi(); await keyPress("F2"); await sleep(2000); + const template = file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"); + const positions = [ + { x: 1520, y: 277, w: 230, h: 120 }, + { x: 1520, y: 400, w: 230, h: 120 }, + { x: 1520, y: 527, w: 230, h: 120 } + ]; + const gameRegion = captureGameRegion(); + let count = 1; + for (const { x, y, w, h } of positions) { + if (gameRegion.Find(RecognitionObject.TemplateMatch(template, x, y, w, h)).isExist()) count++; + } + gameRegion.dispose(); + log.info(`当前联机世界玩家总数(含自己):${count}`); + return count; + } + + async function readFolder(folderPath, onlyJson) { + const stack = [folderPath]; + const files = []; + while (stack.length) { + const cur = stack.pop(); + const items = file.ReadPathSync(cur); + const folders = []; + for (const p of items) { + if (file.IsFolder(p)) folders.push(p); + else if (!onlyJson || p.endsWith('.json')) { + const fileName = p.split('\\').pop(); + const folderPathArray = p.split('\\').slice(0, -1); + files.push({ fullPath: p, fileName, folderPathArray }); } - } catch (error) { - log.error(`发生错误:${error.message}`); - skiprecord = true; } + stack.push(...folders.reverse()); } + return files; } } -async function parsePathing(pathFilePath) { +//切换队伍 +async function switchPartyIfNeeded(partyName) { + if (!partyName) { + await genshin.returnMainUi(); + return; + } try { - const raw = await file.readText(pathFilePath); - const json = JSON.parse(raw); - - // 只要 positions 不是数组就直接失败 - if (!Array.isArray(json.positions)) { - log.error("文件positions字段异常"); - return { ok: false }; + log.info("正在尝试切换至" + partyName); + if (!await genshin.switchParty(partyName)) { + log.info("切换队伍失败,前往七天神像重试"); + await genshin.tpToStatueOfTheSeven(); + await genshin.switchParty(partyName); } - - // map_name 不存在时兜底为 "Teyvat" - const map_name = - typeof json.map_name === 'string' && json.map_name.trim() !== '' - ? json.map_name - : 'Teyvat'; - - // 从后往前找第一个 type !== "orientation" 的点 - for (let i = json.positions.length - 1; i >= 0; i--) { - const p = json.positions[i]; - if ( - p.type !== 'orientation' && - typeof p.x === 'number' && - typeof p.y === 'number' - ) { - return { - ok: true, - x: p.x, - y: p.y, - map_name, - }; - } - } - return { ok: false }; - } catch (err) { - log.error(`解析路径文件失败: ${err.message}`); - return { ok: false }; - } -} - - -// fakeLog 函数,使用方法:将本函数放在主函数前,调用时请务必使用await,否则可能出现v8白框报错 -//在js开头处伪造该js结束运行的日志信息,如 await fakeLog("js脚本", true, true, 0); -//在js结尾处伪造该js开始运行的日志信息,如 await fakeLog("js脚本", true, false, 2333); -//duration项目仅在伪造结束信息时有效,且无实际作用,可以任意填写,当你需要在日志中输出特定值时才需要,单位为毫秒 -//在调用地图追踪前伪造该地图追踪开始运行的日志信息,如 await fakeLog(`地图追踪.json`, false, true, 0); -//在调用地图追踪后伪造该地图追踪结束运行的日志信息,如 await fakeLog(`地图追踪.json`, false, false, 0); -//如此便可以在js运行过程中伪造地图追踪的日志信息,可以在日志分析等中查看 - -async function fakeLog(name, isJs, isStart, duration) { - await sleep(10); - const currentTime = Date.now(); - // 参数检查 - if (typeof name !== 'string') { - log.error("参数 'name' 必须是字符串类型!"); - return; - } - if (typeof isJs !== 'boolean') { - log.error("参数 'isJs' 必须是布尔型!"); - return; - } - if (typeof isStart !== 'boolean') { - log.error("参数 'isStart' 必须是布尔型!"); - return; - } - if (typeof currentTime !== 'number' || !Number.isInteger(currentTime)) { - log.error("参数 'currentTime' 必须是整数!"); - return; - } - if (typeof duration !== 'number' || !Number.isInteger(duration)) { - log.error("参数 'duration' 必须是整数!"); - return; - } - - // 将 currentTime 转换为 Date 对象并格式化为 HH:mm:ss.sss - const date = new Date(currentTime); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - const milliseconds = String(date.getMilliseconds()).padStart(3, '0'); - const formattedTime = `${hours}:${minutes}:${seconds}.${milliseconds}`; - - // 将 duration 转换为分钟和秒,并保留三位小数 - const durationInSeconds = duration / 1000; // 转换为秒 - const durationMinutes = Math.floor(durationInSeconds / 60); - const durationSeconds = (durationInSeconds % 60).toFixed(3); // 保留三位小数 - - // 使用四个独立的 if 语句处理四种情况 - if (isJs && isStart) { - // 处理 isJs = true 且 isStart = true 的情况 - const logMessage = `正在伪造js开始的日志记录\n\n` + - `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + - `------------------------------\n\n` + - `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + - `→ 开始执行JS脚本: "${name}"`; - log.debug(logMessage); - } - if (isJs && !isStart) { - // 处理 isJs = true 且 isStart = false 的情况 - const logMessage = `正在伪造js结束的日志记录\n\n` + - `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + - `→ 脚本执行结束: "${name}", 耗时: ${durationMinutes}分${durationSeconds}秒\n\n` + - `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + - `------------------------------`; - log.debug(logMessage); - } - if (!isJs && isStart) { - // 处理 isJs = false 且 isStart = true 的情况 - const logMessage = `正在伪造地图追踪开始的日志记录\n\n` + - `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + - `------------------------------\n\n` + - `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + - `→ 开始执行地图追踪任务: "${name}"`; - log.debug(logMessage); - } - if (!isJs && !isStart) { - // 处理 isJs = false 且 isStart = false 的情况 - const logMessage = `正在伪造地图追踪结束的日志记录\n\n` + - `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + - `→ 脚本执行结束: "${name}", 耗时: ${durationMinutes}分${durationSeconds}秒\n\n` + - `[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` + - `------------------------------`; - log.debug(logMessage); + } catch { + log.error("队伍切换失败,可能处于联机模式或其他不可切换状态"); + notification.error(`队伍切换失败,可能处于联机模式或其他不可切换状态`); + await genshin.returnMainUi(); } } \ No newline at end of file diff --git a/repo/js/ArtifactsGroupPurchasing/manifest.json b/repo/js/ArtifactsGroupPurchasing/manifest.json index 550aee1f2..69411d66e 100644 --- a/repo/js/ArtifactsGroupPurchasing/manifest.json +++ b/repo/js/ArtifactsGroupPurchasing/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "AAA狗粮联机团购", - "version": "1.0.7", + "version": "1.1.0", "tags": [ "狗粮" ], diff --git a/repo/js/ArtifactsGroupPurchasing/settings.json b/repo/js/ArtifactsGroupPurchasing/settings.json index ce3dec888..92b5e00dd 100644 --- a/repo/js/ArtifactsGroupPurchasing/settings.json +++ b/repo/js/ArtifactsGroupPurchasing/settings.json @@ -1,8 +1,95 @@ [ + { + "name": "runExtra", + "type": "checkbox", + "label": "运行额外路线,运行完联机收尾后单人运行时勾选\n勾选后处于单人世界时将运行额外路线\n如果选择自动进入模式,则会在任务完成后运行额外路线" + }, + { + "name": "groupMode", + "type": "select", + "label": "####################################################################\n联机运行配置\n\n运行模式", + "options": [ + "手动进入后运行", + "按照下列配置自动进入并运行" + ], + "default": "手动进入后运行" + }, + { + "name": "yourIndex", + "type": "input-text", + "label": "该部分配置请仔细阅读readme后填写\n强烈建议联机运行的所有成员以下配置除你的序号外【完全相同】\n你的序号" + }, + { + "name": "runningOrder", + "type": "input-text", + "label": "运行次序,将依次进入对应序号的成员的世界进行联机狗粮", + "default": "1234" + }, + { + "name": "p1UID", + "type": "input-text", + "label": "1号的uid" + }, + { + "name": "p1Name", + "type": "input-text", + "label": "1号的名称(以游戏内实际显示为准,有备注时填备注名)" + }, + { + "name": "p2UID", + "type": "input-text", + "label": "2号的uid" + }, + { + "name": "p2Name", + "type": "input-text", + "label": "2号的名称(以游戏内实际显示为准,有备注时填备注名)" + }, + { + "name": "p3UID", + "type": "input-text", + "label": "3号的uid" + }, + { + "name": "p3Name", + "type": "input-text", + "label": "3号的名称(以游戏内实际显示为准,有备注时填备注名)" + }, + { + "name": "p4UID", + "type": "input-text", + "label": "4号的uid" + }, + { + "name": "p4Name", + "type": "input-text", + "label": "4号的名称(以游戏内实际显示为准,有备注时填备注名)" + }, + { + "name": "notify", + "type": "checkbox", + "label": "####################################################################\n其他配置\n\n是否通知信息" + }, + { + "name": "forceGroupNumber", + "type": "input-text", + "label": "强制指定自己在队伍中的编号,仅在识别异常时使用,填0不指定", + "default": "0" + }, + { + "name": "partyName", + "type": "input-text", + "label": "运行时使用队伍,将在进入联机模式前切换" + }, + { + "name": "usingCharacter", + "type": "input-text", + "label": "运行时使用角色的序号\n将在进入联机模式前尝试切换到对应序号的角色" + }, { "name": "p1EndingRoute", "type": "select", - "label": "####################################################################\n收尾路线配置\n请确保每个人在该部分的选项【完全一致】\n\n1p收尾路线", + "label": "####################################################################\n收尾路线配置\n请确保每个人在该部分的选项【完全一致】\n非必要不建议更改\n\n1p收尾路线", "options": [ "枫丹高塔", "度假村", @@ -48,46 +135,8 @@ "default": "踏鞴砂" }, { - "name": "runExtra", + "name": "runDebug", "type": "checkbox", - "label": "运行额外路线,运行完联机收尾后单人运行时勾选" - }, - { - "name": "decomposeMode", - "type": "select", - "label": "####################################################################\n狗粮分解配置\n\n狗粮分解模式", - "options": [ - "保留", - "分解(经验瓶)", - "销毁(摩拉)" - ], - "default": "保留" - }, - { - "name": "keep4Star", - "type": "checkbox", - "label": "是否保留四星(默认不保留)" - }, - { - "name": "autoSalvage", - "type": "checkbox", - "label": "是否沿路自动分解(默认:否)" - }, - { - "name": "notify", - "type": "checkbox", - "label": "####################################################################\n其他配置\n\n是否通知信息" - }, - { - "name": "accountName", - "type": "input-text", - "label": "账户名称\n用于多账户运行时区分不同账户\n单账户无需修改", - "default": "默认账户" - }, - { - "name": "forceGroupNumber", - "type": "input-text", - "label": "强制指定自己在队伍中的编号,在识别异常时使用,填0不指定", - "default": "0" + "label": "调试时勾选,跳过路线执行逻辑" } ] \ No newline at end of file diff --git a/repo/js/ArtifactsGroupPurchasing/targets/火山老师.png b/repo/js/ArtifactsGroupPurchasing/targets/火山老师.png new file mode 100644 index 0000000000000000000000000000000000000000..6c047f973161cd714fe784f55664484c5574a353 GIT binary patch literal 7694 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