From e8ac9d79a9a8258b7dc42d81cb35fdf22e1807e5 Mon Sep 17 00:00:00 2001 From: Lingwei Meng Date: Mon, 19 Sep 2022 02:05:04 +0800 Subject: [PATCH 01/39] fix: shop priority bug (#143) --- arknights_mower/solvers/shop.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/arknights_mower/solvers/shop.py b/arknights_mower/solvers/shop.py index fdcdd95e0..e4eb65e49 100644 --- a/arknights_mower/solvers/shop.py +++ b/arknights_mower/solvers/shop.py @@ -23,7 +23,7 @@ def run(self, priority: list[str] = None) -> None: :param priority: list[str], 使用信用点购买东西的优先级, 若无指定则默认购买第一件可购买的物品 """ self.priority = priority - + self.buying = None logger.info('Start: 商店') logger.info('购买期望:%s' % priority if priority else '无,购买到信用点用完为止') super().run() @@ -42,6 +42,11 @@ def transition(self) -> bool: elif self.scene() == Scene.SHOP_CREDIT_CONFIRM: if self.find('shop_credit_not_enough') is None: self.tap_element('shop_cart') + elif len(self.priority) > 0: + # 移除优先级中买不起的物品 + self.priority.remove(self.buying) + logger.info('信用点不足,放弃购买%s,看看别的...' % self.buying) + self.back() else: return True elif self.scene() == Scene.SHOP_ASSIST: @@ -84,4 +89,5 @@ def shop_credit(self) -> bool: if valid[0][1] not in priority: return True logger.info(f'实际购买顺序:{[x[1] for x in valid]}') + self.buying = valid[0][1] self.tap(valid[0][0], interval=3) From a3ed2a690511f18e03444f4173878647a8db3066 Mon Sep 17 00:00:00 2001 From: Lingwei Meng Date: Thu, 22 Sep 2022 00:29:25 +0800 Subject: [PATCH 02/39] fix: a compatibility problem with Python 3.10 & typo (#145) * fix: a compatibility problem with Python 3.10 * fix: typo --- README.md | 2 +- arknights_mower/templates/config.yaml | 2 +- arknights_mower/utils/config.py | 5 ++++- diy.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 618ec8df2..4e1283746 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ plan = { # 阶段 1 'plan_1': { # 控制中枢 - 'contral': ['夕', '令', '凯尔希', '阿米娅', '玛恩纳'], + 'central': ['夕', '令', '凯尔希', '阿米娅', '玛恩纳'], # 办公室 'contact': ['艾雅法拉'], # 宿舍 diff --git a/arknights_mower/templates/config.yaml b/arknights_mower/templates/config.yaml index a0f336579..3d6e15c99 100644 --- a/arknights_mower/templates/config.yaml +++ b/arknights_mower/templates/config.yaml @@ -135,7 +135,7 @@ priority: arrangement: plan_1: # 控制中枢 - contral: + central: - 夕 - 令 - 凯尔希 diff --git a/arknights_mower/utils/config.py b/arknights_mower/utils/config.py index abcac5a94..2c189557e 100644 --- a/arknights_mower/utils/config.py +++ b/arknights_mower/utils/config.py @@ -2,7 +2,10 @@ import shutil import sys -from collections import Mapping +try: + from collections.abc import Mapping +except ImportError: + from collections import Mapping from pathlib import Path from typing import Any diff --git a/diy.py b/diy.py index 4cd978be7..ecf950e74 100644 --- a/diy.py +++ b/diy.py @@ -30,7 +30,7 @@ # 阶段 1 'plan_1': { # 控制中枢 - 'contral': ['夕', '令', '凯尔希', '阿米娅', '玛恩纳'], + 'central': ['夕', '令', '凯尔希', '阿米娅', '玛恩纳'], # 办公室 'contact': ['艾雅法拉'], # 宿舍 From 0cdc012d7fc940a010ddc9dc4aa9e694ed95ddc3 Mon Sep 17 00:00:00 2001 From: Lingwei Meng Date: Thu, 22 Sep 2022 00:31:03 +0800 Subject: [PATCH 03/39] feat: support base arrangement using commandline (#142) --- README.md | 9 ++++++--- arknights_mower/command.py | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4e1283746..86f2b7b2e 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ - 自动登录 - 账户密码需要手动输入 - 自动访友收取信用点 +- 自动前往信用商店,领取信用点并按指定优先级购买商品 - 自动确认任务完成 - 自动刷体力 - 默认进行上一次完成的关卡 @@ -38,8 +39,8 @@ - 自动收取邮件奖励 - 自动收取并安放线索 - 自动消耗无人机加速制造站或贸易站 +- 自动更换基建排班干员(建议搭配配置文件使用, 也可命令行直接输入) - 自动使用菲亚梅塔恢复指定房间心情最低干员的心情并重回岗位(工作位置不变以避免重新暖机) [[参考使用场景](https://www.bilibili.com/video/BV1mZ4y1z7wx)] -- 自动更换基建排班干员(需要搭配配置文件使用) - 支持游戏任意分辨率(低于 1080p 的分辨率可能会有一些问题) ## 安装 @@ -100,7 +101,7 @@ usage: arknights-mower command [command args] [--config filepath] [--debug] commands (prefix abbreviation accepted): base [plan] [-c] [-d[F][N]] [-f[F][N]] 自动处理基建的信赖/货物/订单/线索/无人机 - plan 表示选择的基建干员排班计划(需要搭配配置文件使用) + plan 自动更换基建排班干员(建议搭配配置文件使用,也可命令行直接输入) -c 是否自动收集并使用线索 -d 是否自动消耗无人机,F 表示第几层(1-3),N 表示从左往右第几个房间(1-3) -f 是否自动使用菲亚梅塔恢复指定房间心情最差干员的心情并恢复原位,F、N 含义同上 @@ -157,9 +158,11 @@ arknights-mower recruit 因陀罗 火神 arknights-mower shop 招聘许可 赤金 龙门币 # 在商场使用信用点消费,购买物品的优先级从高到低分别是招聘许可、赤金和龙门币,其余物品不购买 arknights-mower base -f12 plan_2 -# 自动使用菲亚梅塔恢复B102房间心情最差干员的心情,并保持原位;自动进行名为`plan_2`的的基建排班(排班功能须搭配配置文件使用) +# 自动使用菲亚梅塔恢复B102房间心情最差干员的心情,并保持原位;自动进行配置文件中名为`plan_2`的的基建排班 arknights-mower base -c -d33 # 自动收取基建中的信赖/货物/订单;自动放置线索;自动前往 B303 房间(地下 3 层从左往右数第 3 间)使用无人机加速生产或贸易订单; +arknights-mower base room_1_2 柏喙 巫恋 龙舌兰 contact 絮雨 dormitory_1 杜林 Free Free Free Free +# 自动更换基建B102房间干员为 柏喙 巫恋 龙舌兰, 更换办公室干员为 絮雨, 更换1号宿舍干员为 杜林和任意4个空闲干员(房间名请参考 base.json) ``` 命令可使用前缀或首字母缩写,如: diff --git a/arknights_mower/command.py b/arknights_mower/command.py index ad128426e..6c6cf814f 100644 --- a/arknights_mower/command.py +++ b/arknights_mower/command.py @@ -20,15 +20,19 @@ def base(args: list[str] = [], device: Device = None): """ base [plan] [-c] [-d[F][N]] [-f[F][N]] 自动处理基建的信赖/货物/订单/线索/无人机 - plan 表示选择的基建干员排班计划(需要搭配配置文件使用) + plan 表示选择的基建干员排班计划(建议搭配配置文件使用, 也可命令行直接输入) -c 是否自动收集并使用线索 -d 是否自动消耗无人机,F 表示第几层(1-3),N 表示从左往右第几个房间(1-3) -f 是否使用菲亚梅塔恢复特定房间干员心情,恢复后恢复原位且工作位置不变,F、N 含义同上 """ + from .data import base_room_list, agent_list + arrange = None clue_collect = False drone_room = None fia_room = None + any_room = [] + agents = [] try: for p in args: @@ -45,8 +49,17 @@ def base(args: list[str] = [], device: Device = None): fia_room = f'room_{p[2]}_{p[3]}' elif arrange is None: arrange = config.BASE_CONSTRUCT_PLAN.get(p) + if arrange is None: + if p in base_room_list: + any_room.append(p) + agents.append([]) + elif p in agent_list or 'free' == p.lower(): + agents[-1].append(p) except Exception: raise ParamError + + if arrange is None and any_room is not None and len(agents) > 0: + arrange = dict(zip(any_room, agents)) BaseConstructSolver(device).run(arrange, clue_collect, drone_room, fia_room) From 9a4657b549f2d9c118700c2f93ca3478ca4d56ba Mon Sep 17 00:00:00 2001 From: Lingwei Meng Date: Thu, 29 Sep 2022 23:57:58 +0800 Subject: [PATCH 04/39] fix: improve fia-recover performance (#147) * fix: a code redundancy * fix: improve fia-recover performance --- .../resources/agent_in_dormitory.png | Bin 0 -> 6864 bytes arknights_mower/solvers/base_construct.py | 44 +++++++++--------- arknights_mower/utils/character_recognize.py | 6 +-- arknights_mower/utils/segment.py | 2 +- 4 files changed, 24 insertions(+), 28 deletions(-) create mode 100644 arknights_mower/resources/agent_in_dormitory.png diff --git a/arknights_mower/resources/agent_in_dormitory.png b/arknights_mower/resources/agent_in_dormitory.png new file mode 100644 index 0000000000000000000000000000000000000000..1ef999dad0d00e560e0cae01a2adf446edee4544 GIT binary patch literal 6864 zcmV;>8ZYIEP)4Tx04R}dkj*PZVHC!H!yuY`l&^)wETk0TBDMyhW-$#4yF1qyYUX;ob0s?q zb{12lWI=4^FJLjsER_YNEF@W3*@+Fh&NUcc@7wA5^>ohZ^gw}6S1RrW5~gj1hXVeI z$ti!y9i>!JLmlP1kyL%5P*7U<)SvI^5|67Ft?sVOar4f0@#blIEqk9iS-Sk&|KpWS zN0Wx=ieIrv!Yz?+ ze(W13;tR&d4es^6s5w3=S}WDS0D}bO`x!^0hYq?KmW=+N>3W~(XCB287IVay#g^$l zaTIZ)!Xua%w9z80(JrOAS^v&3@6GQ)?;{kZ@?K;M4p*W6D(^M!Ld6OkoGIPXzmoMl zd6P4pT8@1ID7tZ+hbAalhu4(jJnuQq>%Q#2hOGGp!v0NklL4jqt{g_I^NbG(vF&WDyV- zz!e;rw|-gubZ6IePcJk4MKB|hs>;eddGb8xIk|LCv+JXeKH6AcUvHL88yg#QH$8Od zP;=tMiDqqWt$F2@SDI&^eYROyS!sU#_1EV7`SZz4lu3=9_OeM~)mR?{D0=(HuN@ zu+-rYd-m*Ue);8>=8r%AC?MWT9q-xT|3`vX7Gcn$I$;-Jo_zAj0^afC#|!&jc;SWS zi6@?D01M>DAAh`g{`uz{K<(PKt2|@lwQJX!-+ue8xqJ6+b1(H=bX@mS{Q@AP?Nkcu zFb+u1J@;H;+R>v&n`6h0HLd`afCDt(JoeaQ<=rEXJkspnzrW;5mo60$vGMoce{XKz zzWpDNJ)l-R@051~Pxu3X`OSRy^2;wbn0NT_;Q|~+0u3lPZ{BSF{PWKeI)zif9yoBI z)YbO#<;%_4vuB5Mqc;DsaU8({;CRvEPXSIS1OnCp;MA#8&FboE0R+P^9g}|k`RB4| zu~;$BX{Q{B7CKKo^;Ef%1#o5R0QCCx>!q$m&VO@b)9eU#x7c>hjE)GlfxBoq)8`1TLll{MA=qE#L!>JOH}B^wLYE-4|bcQNTAp0{>x8 z3j>(>(lzrHV+)x8c;k&X8jF-?o_VG$QZVJGpMGktUcFij|Mk~jm+KEd{7~jV;oD*c zm{z0eW23N32FH+}e){Rc?tT0A6-IIZ3|5zOVB^CWUiY-d{=a#>7-(`rCgAMp-hTV- z=Kc5IZ{BZVEzQM1fXNugXbg%K<}7hos4KGwIbM3}l*pz|8*!{Un z8$e*6g^hX6RmLv!DK?r5G0`GP{{VHpc=2LctpZ)&oZ(?%wo|}PT~p}A(z~&fa$}bT!mK7#}Oq=Oh|pu030$f zixZ&mY+lXFE#Y~&6(-N;;f&@K#T>=c0r>5=-oSGET3l|?ygfYb@JIoD30wM|WeUJ78CIXmAdl z9b*BaZ!DEHWFVQL&41WHK?>UoG8(S~p!tSTGZz36n>dL1RL*?Lh*+Z!3HbD4dk>=b}C1KH6S*$1%tVQ?w)S9#dtA9j@0 z2O?b9P>zfhhT*Z!FoDQ02E;IsHh%>1EChru3Q>-i`E>o=ci%0mRcw?PT06?i6k$O1 ztZ0Q8hE>kk^zh6nFxNeYa5P6hZ2_oXECq!Ag;2WEN!zSg^&KNJWRCZM7ah|V@MDlI z>^bw5v2a-OjR!H@1J0P(RX_9g#Ua=((a7A-F0xemgDPVJ!aZa1@Nd5PrW_tK1jZ%q zX3n4lPY~iPbQp-i6E4&<7;IonP4MzN>EK7&`z06@`s6kup!jsx&)G6XEKUu0^x(}_$v zh!CHB_F1u~G9b>Aj2R(}RW#v%$ijxAigB(Oyn(2!GhtGP37tBd<~0V74Gc_<=Q`<} zvAvho)fd3@hkcTJ<=(5$v+DZfq;3ftZ9ilvkHe&2&c#SFKzTnVVt{@e0Wt_()1FK@ zju~0oy|No4>)+bpTzf7|r+EQ1HhR(*i9B<1j++p@66GnI!2?oe1Z7(dEbUXzb7a+5 z_c2oHDw~!fchY<9=>}*3F_PzgilNkVkB!E))p+_cw)3nX{pwRB6V4X_FgBpS#KBD` ziFAcDsicC=5{4Kg2JmiDBG}T_T*4!GG-gQT@H}%`;=eNCObF#w6p%7NixUyR{H1^I zh19T(u5=<-pJbT}$eotdk0Ar(w7U1AJkDl}`lstGyY}@kZfl9GC&*!I>?|=)C&>(_ z6VI~}k8)@&2~XOgw|m}hneU9$F&l$t1*P612+iFy$?* z%qjY&!y>76*hSj?lXZg);MtBn*g_hcv{a18Zly3Ih{x*k3Uj8#k1~uP97_P`)CPF! z>DXLgo&z%DlaYmnx>$&T$|Ruk-Yjl_4M4I1ox!-yayoh^)%Pizc8%O}ZT-4+_lZ@F zg&ueBjtHTR+`aSOnqqUfpD1up7`b=WySd$S%~`3dy>~!YA8?X-%7JM`%o%LNfgXUvJaVxzE6-Zbow$#oiqTqN%5@5WbnY0^ zg~<_cqFT2i^?p$RSN5zl>wm8o+0kQzF0C^x1t6p4h)ZZQr)Aa1_y888)id$x6jD-;pzg z+|91t?Xq9pkO#ECnEzVUlAmU;+t+K@qZytf8kdwm01 zCR-Z@TQmVDS)ZdkDqmEl`W<-Y_k(Q|d9Fm4J9p;a2eOl~i#|5+$J>0U9joZcQLo>) zzT6xbFCBU1`0(h?MO}OPy}Ggl0P{BH zSTh_tw?y&H=PX6cOxjJ6#=U0Tgk5xcWnL`TNVu0hkU9dYYlPdYAU|TxZFt zLE8!NGAB4gf`kr=EJU971Cf=a^Tv_M(pHWvwB~7^8K%B>%iv2r}hR|La3<%h~xk-2lN(k4=sQ( zWWWnEQobykYu_h}Q)G25^yz0j4IvvAr^MC)3&{W;Hfpczv3=dQ;~!c8S&M<)Z~dw>JR|`>f!FQTt)% z&1-9G#W9uw&;aY4&imKe5dC+vp2LLHi*fUuYh7)5C-=Ou3TZ8D~^4cO$U6MTTc7*VNm|E7`^aMD_wCMs_-ONL6@s zUH1x%+n&!Dr9%e~mBWyHGC!R{ow$kr5+)q@2#I2pstsY1wQ>R$Bb&U{^9e$~Zry=_ z0b16MOwJ(JNx8ZV!-<^0Fphp0te@1c^JqTXRC6?q4PUWnorf=aHyonQz1L$hdMNd) zR?qZiGa&lR^!$bM<>NHYiX=;gP=*o?fMlOTkT~sLV(`4n?d>j=Kl(ETD021Jn zcE++o%F$N<#6b7Tftz^%Kvq-2F7Rpq$s!lCfvv83-Du0z}3phRa7j(Ht6}-r5u4?H=pq6D00b7mh0@G{6iITgJ#fj&Tn02UT>Ah5To_e1^=o+(O?_8DHqz5Tw|JD>IX3OH##bJ71YFcz~wXwD`PG`t&4w}?QUpWfS zle8;?88B2o(W(1tgXcI_&*yuOr9WjeF;;2$G^yKJ(qbQEFtzMiX+L{ykJ+ScF>qGL z-UrzJbyC|X>Ye*(DeqF>HNd%cKnBcC$`<0f=r^sif4JPq3*f9LtT`;~aQYikCP^WH;X~u&#E)az_Z?W z;Q86jL+w`USR2|$U(B7=)_e0ira2_>#V%j)T3cHyXQK`=d~>h-aZ45gu9p2dMR6}A z#j7WE>YY3(g&}G0d8?HA>eToBHj$G$xsLbKZ9R4mLeReU^D}dYF?HijAKkUz$4)#0 zP=EPKMPcAzOEZuB0BG*ZM@bI0ZO1OP2Oek6o@rK8N=~fRk!)ivi?vb=m(D#0!2Y|` zne{$$YY=K6QXYA^_Uq)?D$>L+1w0;n_;B}~R3P$dsgpfAohgMo_zwUXKc`6yKW#jd zx=jepfr#yrvtQu(1+24Y&o(RBhy%&d7o^2m&zR_B1YK>ivp-`j$yGgy0osOIrxHay zduJ1`bL~AV4-?e&PJ8djll522H3$Zg(pQ`-3m3o*zx>+P<*gjiXJ;o*zEFMxVE_ID zrN8bpsr!{fy6!|Ghc0wyGsb2k4g*BjMfzgP*pr+bHTn`W@Gf2YzFF}(l}XXxqPtq< zbRK)S#8`i7?ZDWFk_1qW5`ATzOHwc$dY?ZP<0JvZVQlIKkN~KU`Xq)!aUc<*51_ha z1w;c(J?(hDdBdM`zxYy{+pA~g#=*G#<2N8~#*Z^G36hzsS|0&U}RpfEdDqd5*nD{#KsR7@0tVJ!`9ejIHx{ zjE;O5$ymyKih)_cV{i%r5Wp172bY`yW7Sa(K=;yLf3`op)qW?^$645{{Lx3cpQWUK zzQ*qZPrsgF^7n~0nK#;YJXc$N)ec#NZ{we^&si>(u&pSj-|eIT$kuQm|7PnXFj(;q z!~2_fq07OvNf(#~?TVEeVj{2%oDgjtBnuaS;`w1!yfKWz6rQOMEC_f2R9Cq=VTE!B z<>W`MGD%%>Eu(+?^|xXqz-6E0@5t9y*P0_oj}$P04=AiPhI8k>YaQv^5@9$%n6a6C ziep?&^Vd1N zn_l!2H2~+{DTUCl9wQ3BqCI2-jw>}0234AX!`ecb1UZK$BsA8 zAL)LHi8Jay^J({2!k1bQFSQ~0TA2gK9c^B1?$3I|*_E+?2mnTQ1qhZgl!buI+Y?5C z30O?c{OHPf!m5zGlL@o*b@If?=G5w`a<6~-U}Tw-FL#l_BIV+xi-S<#t!|Bpv$jL6 z(%3^)p4$Z}k!FitXLZw8b5MT>p#BoW0m&F9WNZ{R!%F1|tJn}gg)oZyFOU zR;*&}JtNPSfU~TvwcCQ$IfP5~fJ3Iam!sGkU_S6fi`%Wv-F%Drn@0b@6qHrytkLjv znQ?`+Y%4Gh8=Z3ab880n4*0+VG^5(oI=ge9xiO5W`_5{ZBiMigZFP0E$mIw^w*Jkr zekaB+SU4ECa^-3(_t)l+-@6~x<8XJ|PvR%_G3;*uY76hMX#8dtHO6btdfFsyQ$bI{ zRJFz%&h@{SF@%gc38}ms11-tjGa7~pr(qz2W2B5#=13tGa{^?&y2LI{AvF8#QY%bO zfsNRdK7~sEtpNbBj~;aM(;Gw3#>d&S&V%x}#IIBYOoq>j)j|c^oSR~ekSFvBp;_1f z#fny)WZ+}9aBE=%Fl@%^oCcW($$Hy!h%^KbKJnlHIe4DVxO)R?x*tm4 zR4{zA^-XKdVu$Ap2KP& zSv*-?e*MkY&FRli7e~mJV3KYRd|7L=q@@9j09PO|wuOyVA7CslFv_p_0umdA(fr<} zcS0iYvmF7Pd5{diVcn&R-7n6cYgd_9u3l+wT<`vSo%QwvrB0(E)uoi{JuiN^)fX_= z^EhAHCYuVTi0SLwYZvB~fwgsa3_yZ`AzQ!z2OB*D788X&fo})yfW8S9yrAe4`^|P?%;Mu0PI@}8v!A_GP;EZ0G*-;lYqoJz{wUU z*ru*_9CL(8;8jEFtMQLK_Xsn;4*h}j1dTC|)WQ3E0w6v`gC6}P!fLCGk0FQ_G8Po?vn%cZ{6yCTB|pn`3>`c*ZhC!wlEpbYjkq}0000< KMNUMnLSTa4=`c|M literal 0 HcmV?d00001 diff --git a/arknights_mower/solvers/base_construct.py b/arknights_mower/solvers/base_construct.py index a76ac1b81..fbf27cda0 100644 --- a/arknights_mower/solvers/base_construct.py +++ b/arknights_mower/solvers/base_construct.py @@ -685,22 +685,28 @@ def choose_agent_in_order(self, agent: list[str], exclude: list[str] = None, exc found = 0 while found == 0: ret = character_recognize.agent(self.recog.img) - ret = np.array(ret, dtype=object).reshape(-1, 2, 2).reshape(-1, 2) # 'Free'代表占位符,选择空闲干员 if agent[idx] == 'Free': for x in ret: - x[1][0, 1] -= 155 - x[1][2, 1] -= 155 - # 不选择已进驻的干员,如果非宿舍则进一步不选择精神涣散的干员 - if not (self.find('agent_on_shift', scope=(x[1][0], x[1][2])) - or self.find('agent_resting', scope=(x[1][0], x[1][2])) - or (not dormitory and self.find('distracted', scope=(x[1][0], x[1][2])))): - if x[0] not in agent and x[0] not in exclude: - self.tap(x[1], x_rate=0.5, y_rate=0.5, interval=0) - agent[idx] = x[0] - _free = x[0] - found = 1 - break + status_coord = x[1].copy() + status_coord[0, 1] -= 0.147*self.recog.h + status_coord[2, 1] -= 0.135*self.recog.h + + room_coord = x[1].copy() + room_coord[0, 1] -= 0.340*self.recog.h + room_coord[2, 1] -= 0.340*self.recog.h + + if x[0] not in agent and x[0] not in exclude: + # 不选择已进驻的干员,如果非宿舍则进一步不选择精神涣散的干员 + if not (self.find('agent_on_shift', scope=(status_coord[0], status_coord[2])) + or self.find('agent_resting', scope=(status_coord[0], status_coord[2])) + or self.find('agent_in_dormitory', scope=(room_coord[0], room_coord[2])) + or (not dormitory and self.find('agent_distracted', scope=(status_coord[0], status_coord[2])))): + self.tap(x[1], x_rate=0.5, y_rate=0.5, interval=0) + agent[idx] = x[0] + _free = x[0] + found = 1 + break elif agent[idx] != 'Free': for x in ret: @@ -795,13 +801,12 @@ def fia(self, room: str): self.tap((self.recog.w*BY_STATUS[0], self.recog.h*BY_STATUS[1]), interval=0.1) # 记录房间中的干员及其工位顺序 ret = character_recognize.agent(self.recog.img) - ret = np.array(ret, dtype=object).reshape(-1, 2, 2).reshape(-1, 2) on_shift_agents = [] for x in ret: - x[1][0, 1] -= 155 - x[1][2, 1] -= 155 + x[1][0, 1] -= 0.147*self.recog.h + x[1][2, 1] -= 0.135*self.recog.h if self.find('agent_on_shift', scope=(x[1][0], x[1][2])) \ - or self.find('distracted', scope=(x[1][0], x[1][2])): + or self.find('agent_distracted', scope=(x[1][0], x[1][2])): self.tap(x[1], x_rate=0.5, y_rate=0.5, interval=0) on_shift_agents.append(x[0]) if len(on_shift_agents) == 0: @@ -817,7 +822,6 @@ def fia(self, room: str): _temp_on_shift_agents = on_shift_agents.copy() while 'Free' not in _temp_on_shift_agents: ret = character_recognize.agent(self.recog.img) - ret = np.array(ret, dtype=object).reshape(-1, 2, 2).reshape(-1, 2) for x in ret: if x[0] in _temp_on_shift_agents: # 用占位符替代on_shift_agents中这个agent @@ -840,10 +844,6 @@ def fia(self, room: str): if not self.find('arrange_check_in_on'): self.tap_element('arrange_check_in', interval=2, rebuild=False) self.tap((self.recog.w*0.82, self.recog.h*0.25), interval=2) - # 确保按心情升序排列 - self.tap((self.recog.w*BY_TRUST[0], self.recog.h*BY_TRUST[1]), interval=0) - self.tap((self.recog.w*BY_EMO[0], self.recog.h*BY_EMO[1]), interval=0) - self.tap((self.recog.w*BY_EMO[0], self.recog.h*BY_EMO[1]), interval=0.1) # 选择待恢复干员和菲亚梅塔 rest_agents = [_recover, '菲亚梅塔'] self.choose_agent_in_order(rest_agents, exclude_checked_in=False) diff --git a/arknights_mower/utils/character_recognize.py b/arknights_mower/utils/character_recognize.py index 592a9c3a2..ca8cd1e9f 100644 --- a/arknights_mower/utils/character_recognize.py +++ b/arknights_mower/utils/character_recognize.py @@ -10,7 +10,6 @@ from .. import __rootdir__ from ..data import agent_list -from ..ocr import ocrhandle from . import segment from .image import saveimg from .log import logger @@ -140,11 +139,8 @@ def agent(img, draw=False): ): x0 += 1 - # ocr 初步识别干员名称 - ocr = ocrhandle.predict(img[:, x0:right]) - # 获取分割结果 - ret = segment.agent(img, draw) + ret, ocr = segment.agent(img, draw) # 确定位置后开始精确识别 ret_succ = [] diff --git a/arknights_mower/utils/segment.py b/arknights_mower/utils/segment.py index c2bc0f3d9..6c7ac0038 100644 --- a/arknights_mower/utils/segment.py +++ b/arknights_mower/utils/segment.py @@ -439,7 +439,7 @@ def agent(img, draw=False): plt.show() logger.debug(f'segment.agent: {[x.tolist() for x in ret]}') - return ret + return ret, ocr except Exception as e: logger.debug(traceback.format_exc()) From 6415b8050d2ea539b36041d096d67f6322c5539b Mon Sep 17 00:00:00 2001 From: Ks-luow <64978950+Ks-luow@users.noreply.github.com> Date: Fri, 10 Mar 2023 14:22:48 +0800 Subject: [PATCH 05/39] dev (#155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat. base scheduler * fix: a code redundancy * fix: improve fia-recover performance * feat: 插拔逻辑 * feat: 优化选择逻辑 * fix: 尝试修复因为干员名字识别问题的报错 * feat: 插拔配适2级贸易站 * feat: 任务queue邮箱 * feat : 菲亚梅塔 * fix: 利用特征点识别干员名 * fix: BUG 和流程优化 * update: 干员列表 * fix: 重构一下逻辑 * fix: 修复因为双站插拔导致时间先后错位 * feat: 断线重连 * feat: 排班计划方程 * feat: 排班自动纠错功能+修复一些BUG * feat: 重写安排Free干员 * feature: 房间心情扫描单独记录 * feature: 完善房间扫描时间逻辑+Add PlanB * enhance: 移动插拔至读取心情之后以解决宿舍安排空人 * feat: 全自动换班 beta * feat: 全自动换班 稳定性优化 * feat: 全自动换班 更新分组规则 * feat: 修复产业合作洽谈会卡死 * feat: 修复产业合作洽谈会卡死 * feat. 全自动换班 * feat: 提高心情读取准确率 * feat: 自动无人机 * fix: 一些BUG * fix: 由于Free type 错误导致干员无法选择 * fix: 新增干员配置 * fix:空位自动安排Free干员 * feat: 新增检测滑动到底 * feat: 许多改动 * fix: 尝试解决移除未进驻无效 * bugfix: comment out 不需要 * bugfix: 逻辑修复 * fix: 纠错的一个逻辑错误。 * update: diy readme * fix: 插拔逻辑 * fix: 修复adb连接问题 * draft: MAA integration * feat:新增逻辑防止死循环 * 连续读取两次时间来提高准确率 * 连续读取两次时间来提高准确率 * fix: multiplelogging * draft: v2 * improve: handel disconnect * draft: v2 * improve: 配置移到 diy * fix: 提高识别准确度 * fix:识图 * fix: fia 换班修复 * fix: fia 24 心情逻辑修复 * fix: fia 24 心情逻辑修复 * fix:识图 * fix: 0心情读取 * fix:error msg * fix:死循环 * fix: 防止休息负数 * fix: fia换班时间滞后 * fix:死循环 * fix: 防止读取心情滑动失败 * bugfix * feat: MAA 4.8 * feat: code clean up * fix: code clean up 不干净 * feat: code clean up * fix : 空房间判定错误 * fix : 暖机组不进宿舍 * remove: task_type * bug fix * improve: 代码优化 * remove:弃用点击double check * fix: key error * feat: 出错不重新扫描心情 * fix: 修复logo 变化导致登录不成功 * fix: 跳过任务忘记返回 * feat: 新干员 * feat: improve accuracy * feat: code clean up * improvement: 部分细节优化 * feat: 2023新年活动 * bugfix: 无人机读取错忽略 * feat: 新增 年 加工站支持 * remove: 年的错误配置 * feat:新增词条 rest in full 用于对齐心情 * update: 153基建配置 * feat: 集成maa生息演算 * feat: 防止maa bug * feat: 优化maa连接逻辑 * feat: 周计划+MAA执行间隔 * improve: comment out 自动连接活跃端口 * feat: 持续出错重启游戏 * feat: 利用飞桨识别 * feat: 宿舍安排流程优化 * fix: 修复填充不执行问题 * fix: 修复部分识别问题 * 编写gui --------- Co-authored-by: Shawnsdaddy Co-authored-by: LingweiMeng Co-authored-by: Shawnsdaddy <33809511+Shawnsdaddy@users.noreply.github.com> --- arknights_mower/data/__init__.py | 3 + arknights_mower/data/agent.json | 26 +- arknights_mower/data/scene.json | 4 + arknights_mower/resources/close_mine.png | Bin 0 -> 3747 bytes arknights_mower/resources/hypergryph.png | Bin 43100 -> 19527 bytes arknights_mower/resources/not_in_dorm.png | Bin 0 -> 11831 bytes arknights_mower/resources/room_detail.png | Bin 0 -> 5636 bytes arknights_mower/solvers/base_schedule.py | 1691 +++++++++++++++++ arknights_mower/strategy.py | 31 +- arknights_mower/utils/asst.py | 265 +++ arknights_mower/utils/character_recognize.py | 101 +- arknights_mower/utils/datetime.py | 13 +- .../utils/device/adb_client/core.py | 11 +- arknights_mower/utils/device/device.py | 4 + arknights_mower/utils/device/utils.py | 4 +- arknights_mower/utils/log.py | 19 +- arknights_mower/utils/recognize.py | 47 + arknights_mower/utils/segment.py | 297 +-- arknights_mower/utils/solver.py | 25 +- diy.py | 354 ++-- menu.py | 363 ++++ requirements.txt | 9 +- 22 files changed, 2964 insertions(+), 303 deletions(-) create mode 100644 arknights_mower/resources/close_mine.png create mode 100644 arknights_mower/resources/not_in_dorm.png create mode 100644 arknights_mower/resources/room_detail.png create mode 100644 arknights_mower/solvers/base_schedule.py create mode 100644 arknights_mower/utils/asst.py create mode 100644 menu.py diff --git a/arknights_mower/data/__init__.py b/arknights_mower/data/__init__.py index b6911a39f..c94c0ae7b 100644 --- a/arknights_mower/data/__init__.py +++ b/arknights_mower/data/__init__.py @@ -7,6 +7,9 @@ agent_list = json.loads( Path(f'{__rootdir__}/data/agent.json').read_text('utf-8')) +# # agents base skills +# agent_base_config = json.loads( +# Path(f'{__rootdir__}/data/agent-base.json').read_text('utf-8')) # name of each room in the basement base_room_list = json.loads( diff --git a/arknights_mower/data/agent.json b/arknights_mower/data/agent.json index 82a46dd2e..975e53411 100644 --- a/arknights_mower/data/agent.json +++ b/arknights_mower/data/agent.json @@ -87,6 +87,7 @@ "芙兰卡", "炎客", "因陀罗", + "石英", "燧石", "拉普兰德", "断崖", @@ -247,5 +248,26 @@ "杰克", "夜魔", "格拉尼", - "斯卡蒂" -] \ No newline at end of file + "斯卡蒂", + "罗小黑", + "海沫", + "铅踝", + "达格达", + "明椒", + "白铁", + "雪绒", + "子月", + "伺夜", + "斥罪", + "缄默德克萨斯", + "焰影苇草", + "和弦", + "谜图", + "重岳", + "林", + "火哨", + "截云", + "麒麟X夜刀", + "火龙S黑角", + "泰拉大陆调查团" +] diff --git a/arknights_mower/data/scene.json b/arknights_mower/data/scene.json index 29e85bf33..9dd47a732 100644 --- a/arknights_mower/data/scene.json +++ b/arknights_mower/data/scene.json @@ -91,6 +91,10 @@ "label": "LOGIN_CADPA_DETAIL", "comment": "游戏适龄提示" }, + "112": { + "label": "CLOSE_MINE", + "comment": "产业合作洽谈会" + }, "201": { "label": "INFRA_MAIN", "comment": "基建全局视角" diff --git a/arknights_mower/resources/close_mine.png b/arknights_mower/resources/close_mine.png new file mode 100644 index 0000000000000000000000000000000000000000..685e3d3dbd145b3da9806d858df8295e3e0d460d GIT binary patch literal 3747 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 literal 0 HcmV?d00001 diff --git a/arknights_mower/resources/hypergryph.png b/arknights_mower/resources/hypergryph.png index 82263eedee16a7799581e16334a098012bdb93f2..3638d09487b87241216ece1c0135fc77952f576d 100644 GIT binary patch literal 19527 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%0AF~4#%YB3d_ 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 NS(?F2t4%y&{{xw<{1gBH literal 43100 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 uSX+<6-go}*4|vU+?sMb+zJbkvu5LG%eXG~{$O diff --git a/arknights_mower/resources/not_in_dorm.png b/arknights_mower/resources/not_in_dorm.png new file mode 100644 index 0000000000000000000000000000000000000000..450c05ea355b21044bf92dfd58821179e44f5920 GIT binary patch literal 11831 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 literal 0 HcmV?d00001 diff --git a/arknights_mower/resources/room_detail.png b/arknights_mower/resources/room_detail.png new file mode 100644 index 0000000000000000000000000000000000000000..7eb50cb744112b8f7fb36725fe705bb10eeec3fb GIT binary patch literal 5636 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 literal 0 HcmV?d00001 diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py new file mode 100644 index 000000000..51a004d1d --- /dev/null +++ b/arknights_mower/solvers/base_schedule.py @@ -0,0 +1,1691 @@ +from __future__ import annotations +import copy +import time +from enum import Enum +from datetime import datetime, timedelta +import numpy as np +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + +from ..data import agent_list +from ..utils import character_recognize, detector, segment +from ..utils import typealias as tp +from ..utils.device import Device +from ..utils.log import logger +from ..utils.recognize import RecognizeError, Recognizer, Scene +from ..utils.solver import BaseSolver +from ..utils.datetime import get_server_weekday +from paddleocr import PaddleOCR +import cv2 + +## Maa +from arknights_mower.utils.asst import Asst, Message +import json + +ocr = None + +class ArrangeOrder(Enum): + STATUS = 1 + SKILL = 2 + FEELING = 3 + TRUST = 4 + + +arrange_order_res = { + ArrangeOrder.STATUS: (1560 / 2496, 96 / 1404), + ArrangeOrder.SKILL: (1720 / 2496, 96 / 1404), + ArrangeOrder.FEELING: (1880 / 2496, 96 / 1404), + ArrangeOrder.TRUST: (2050 / 2496, 96 / 1404), +} + + +class BaseSchedulerSolver(BaseSolver): + """ + 收集基建的产物:物资、赤金、信赖 + """ + + def __init__(self, device: Device = None, recog: Recognizer = None) -> None: + super().__init__(device, recog) + + def run(self) -> None: + """ + :param clue_collect: bool, 是否收取线索 + """ + self.currentPlan = self.global_plan[self.global_plan["default"]] + self.error = False + self.handle_error(True) + if len(self.tasks) > 0: + # 找到时间最近的一次单个任务 + self.task = self.tasks[0] + else: + self.task = None + self.todo_task = False + self.planned = False + if len(self.operators.keys()) == 0: + self.get_agent() + if len(self.scan_time.keys()) == 0: + self.scan_time = {k: None for k, v in self.currentPlan.items()} + return super().run() + + def get_group(self, rest_agent, agent, groupname, name): + for element in agent: + if element['group'] == groupname and name != element["agent"]: + rest_agent.append(element) + # 从大组里删除 + agent.remove(element) + return + + def transition(self) -> None: + self.recog.update() + if self.scene() == Scene.INDEX: + self.tap_element('index_infrastructure') + elif self.scene() == Scene.INFRA_MAIN: + return self.infra_main() + elif self.scene() == Scene.INFRA_TODOLIST: + return self.todo_list() + elif self.scene() == Scene.INFRA_DETAILS: + self.back() + elif self.scene() == Scene.LOADING: + self.sleep(3) + elif self.scene() == Scene.CONNECTING: + self.sleep(3) + elif self.get_navigation(): + self.tap_element('nav_infrastructure') + elif self.scene() == Scene.INFRA_ARRANGE_ORDER: + self.tap_element('arrange_blue_yes') + elif self.scene() != Scene.UNKNOWN: + self.back_to_index() + self.last_room = '' + logger.info("重设上次房间为空") + else: + raise RecognizeError('Unknown scene') + + def overtake_room(self): + name = self.task['type'] + candidate = [] + if self.operators[name]['group'] != '': + candidate.extend([v['name'] for k, v in self.operators.items() if + 'group' in v.keys() and v['group'] == self.operators[name]['group']]) + else: + candidate.append(name) + operators = [] + for i in candidate: + _room = self.operators[name]['current_room'] + idx = [a['agent'] for a in self.current_base[_room]].index(i) + operators.append({'agent': i, 'current_room': _room, 'room_index': idx}) + # if 'resting_priority' in self.operators[i].keys() and self.operators[i]['resting_priority']=='high': + # room_need+=1 + resting_dorm = [] + ignore = [] + if next((e for e in self.tasks if 'type' in e.keys() and e['type'].startswith("dorm")), None) is not None: + # 出去需要休息满的人其他清空 + for task in [e for e in self.tasks if 'type' in e.keys() and e['type'].startswith("dorm")]: + for k, v in task['plan'].items(): + for a in v: + if a == "Current": + continue + elif 'exhaust_require' in self.operators[a].keys() and self.operators[a]['exhaust_require']: + ignore.append(a) + resting_dorm.extend([self.operators[a]['current_room'] for a in ignore]) + # 执行全部任务 + for task in self.tasks: + if 'type' in task.keys() and 'dorm' in task['type'] and 'plan' in task.keys(): + # TODO 移除 resting_room 的干员比如说有巫恋在休息 + self.agent_arrange(task['plan']) + self.tasks.remove(task) + self.get_swap_plan(resting_dorm, operators, False) + def handle_error(self,force = False): + # 如果有任何报错,则生成一个空 + if self.scene() == Scene.UNKNOWN: + self.device.exit('com.hypergryph.arknights') + if self.error or force: + # 如果没有任何时间小于当前时间的任务才生成空任务 + if (next((e for e in self.tasks if e['time'] < datetime.now()), None)) is None: + room = next(iter(self.currentPlan.keys())) + logger.info("由于出现错误情况,生成一次空任务来执行纠错") + self.tasks.append({'time': datetime.now(), 'plan': {room: ['Current'] * len(self.currentPlan[room])}}) + # 如果没有任何时间小于当前时间的任务-10分钟 则清空任务 + if (next((e for e in self.tasks if e['time'] < datetime.now() - timedelta(seconds=600)), None)) is not None: + logger.info("检测到执行超过10分钟的任务,清空全部任务") + self.tasks = [] + self.scan_time = {} + self.operators = {} + return True + + def plan_metadata(self, time_result): + group_info = self.task['metadata']['plan'] + read_time_rooms = self.task['metadata']['room'] + if time_result is None: + time_result = {} + for key, _plan in group_info.items(): + if 'default' != key: + _plan["time"] = time_result[_plan["type"]] + self.tasks.append({"type": _plan['type'], 'plan': _plan['plan'], 'time': _plan['time']}) + else: + # 合在一起则取最小恢复时间 + min_time = datetime.max + __time = datetime.now() + if len(self.task['metadata']['room']) == 0: + # 如果没有读取任何时间,则只休息1小时替换下一组 + _plan["time"] = __time + timedelta(seconds=(3600)) + else: + for dorm in _plan["type"].split(','): + if dorm not in read_time_rooms: + continue + if dorm in time_result.keys() and min_time > time_result[dorm]: + min_time = time_result[dorm] + _plan["time"] = min_time + # 如果有任何已有plan + existing_plan = next( + (e for e in self.tasks if 'type' in e.keys() and e['type'].startswith('dormitory')), None) + if existing_plan is not None and existing_plan['time'] < _plan["time"]: + for k in _plan['plan']: + if k not in existing_plan['plan']: + existing_plan['plan'][k] = _plan['plan'][k] + else: + for idx, _a in enumerate(_plan['plan'][k]): + if _plan['plan'][k][idx] != 'Current': + existing_plan['plan'][k][idx] = _plan['plan'][k][idx] + existing_plan['type'] = existing_plan['type'] + ',' + _plan["type"] + else: + self.tasks.append({"type": _plan['type'], 'plan': _plan['plan'], 'time': _plan['time']}) + + def infra_main(self): + """ 位于基建首页 """ + if self.find('control_central') is None: + self.back() + return + if self.task is not None: + try: + if len(self.task["plan"].keys()) > 0: + read_time_room = [] + if 'metadata' in self.task.keys(): + read_time_room = self.task['metadata']['room'] + time_result = self.agent_arrange(self.task["plan"], read_time_room) + if 'metadata' in self.task.keys(): + self.plan_metadata(time_result) + else: + self.overtake_room() + del self.tasks[0] + except Exception as e: + logger.exception(e) + self.planned = True + self.todo_task = True + self.error = True + self.task = None + elif not self.todo_task: + # 处理基建 Todo + notification = detector.infra_notification(self.recog.img) + if notification is None: + self.sleep(1) + notification = detector.infra_notification(self.recog.img) + if notification is not None: + self.tap(notification) + else: + self.todo_task = True + elif not self.planned: + try: + # 如果有任何type 则会最后修正 + if self.read_mood: + mood_result = self.agent_get_mood(True) + if mood_result is not None: + return True + self.plan_solver() + except Exception as e: + # 重新扫描 + self.error = True + logger.exception({e}) + self.planned = True + else: + return self.handle_error() + + def get_plan(self, room, index=-1): + # -1 就是不换人 + result = [] + for data in self.currentPlan[room]: + result.append(data["agent"]) + return result + + def agent_get_mood(self,skip_dorm = False): + # 如果5分钟之内有任务则跳过心情读取 + if next((k for k in self.tasks if k['time'] ( + datetime.now() - timedelta(seconds=5400))))) + for room in need_read: + error_count = 0 + while True: + try: + self.enter_room(room) + self.current_base[room] = self.get_agent_from_room(room) + logger.info(f'房间 {room} 心情为:{self.current_base[room]}') + break + except Exception as e: + if error_count > 3: raise e + logger.error(e) + error_count += 1 + self.back() + continue + self.back() + if '' in self.operators.keys(): self.operators['']['current_room'] = '' + logger.debug(self.operators) + for room in self.currentPlan.keys(): + for idx, item in enumerate(self.currentPlan[room]): + _name = next((k for k, v in self.operators.items() if + v['current_room'] == room and 'current_index' in v.keys() and v['current_index'] == idx), + None) + if room not in self.current_base.keys(): + self.current_base[room] = [''] * len(self.currentPlan[room]) + if _name is None or _name == '': + self.current_base[room][idx] = {"agent": "", "mood": -1} + else: + self.current_base[room][idx] = {"mood": self.operators[_name]['mood'], "agent": _name} + current_base = copy.deepcopy(self.current_base) + plan = self.currentPlan + self.total_agent = [] + fix_plan = {} + for key in current_base: + if (key == 'train'): continue + need_fix = False + for idx, operator in enumerate(current_base[key]): + data = current_base[key][idx] + # 如果是空房间 + if data["agent"] == '': + if not need_fix: + fix_plan[key] = [''] * len(plan[key]) + need_fix = True + fix_plan[key][idx] = plan[key][idx]["agent"] + continue + if (data["agent"] in self.agent_base_config.keys()): + # 如果有设置下限,则减去下限值 eg: 令 + if ("LowerLimit" in self.agent_base_config[current_base[key][idx]["agent"]]): + data["mood"] = data["mood"] - self.agent_base_config[current_base[key][idx]["agent"]]["LowerLimit"] + # 把额外数据传过去 + data["current_room"] = key + data["room_index"] = idx + # 记录数据 + self.total_agent.append(data) + # 随意人员则跳过 + if plan[key][idx]["agent"] == 'Free': + continue + if not (data['agent'] == plan[key][idx]['agent'] or ( + (data["agent"] in plan[key][idx]["replacement"]) and len(plan[key][idx]["replacement"]) > 0)): + if not need_fix: + fix_plan[key] = [''] * len(plan[key]) + need_fix = True + fix_plan[key][idx] = plan[key][idx]["agent"] + elif need_fix: + fix_plan[key][idx] = data["agent"] + # 检查是否有空名 + if need_fix: + for idx, fix_agent in enumerate(fix_plan[key]): + if fix_plan[key][idx] == '': + # 则使用当前干员 + fix_plan[key][idx] = 'Current' + # 最后如果有任何高效组心情没有记录 或者高效组在宿舍 + miss_list = {k: v for (k, v) in self.operators.items() if + (v['type'] == 'high' and ('mood' not in v.keys() or (v['mood'] == -1 or (v['mood'] == 24) and v['current_room'].startswith('dormitory'))))} + if len(miss_list.keys()) > 0: + # 替换到他应该的位置 + for key in miss_list: + if miss_list[key]['group'] != '': + # 如果还有其他小组成员没满心情则忽略 + if next((k for k, v in self.operators.items() if + v['group'] == miss_list[key]['group'] and v['current_room'].startswith( + 'dormitory') and not (v['mood'] == -1 or v['mood'] == 24)), None) is not None: + continue + if miss_list[key]['room'] not in fix_plan.keys(): + fix_plan[miss_list[key]['room']] = [x['agent'] for x in current_base[miss_list[key]['room']]] + fix_plan[miss_list[key]['room']][miss_list[key]['index']] = key + if len(fix_plan.keys()) > 0: + # 不能在房间里安排同一个人 如果有重复则换成Free + # 还要修复确保同一组在同时上班 + fix_agents = [] + remove_keys = [] + for key in fix_plan: + if skip_dorm and 'dormitory' in key: + remove_keys.append(key) + for idx, fix_agent in enumerate(fix_plan[key]): + if fix_agent not in fix_agents: + fix_agents.append(fix_agent) + else: + fix_plan[key][idx] = "Free" if fix_plan[key][idx] not in ['Free', 'Current'] else \ + fix_plan[key][idx] + if len(remove_keys) > 0: + for item in remove_keys: + del fix_plan[item] + if len(fix_plan.keys()) > 0: + self.tasks.append({"plan": fix_plan, "time": datetime.now()}) + logger.info(f'纠错任务为-->{fix_plan}') + return "self_correction" + + def plan_solver(self): + plan = self.currentPlan + # 如果下个 普通任务 <10 分钟则跳过 plan + if ( + next((e for e in self.tasks if 'type' not in e.keys() and e['time'] < datetime.now() + timedelta(seconds=600)), + None)) is not None: + return + if len(self.check_in_and_out()) > 0: + # 处理龙舌兰和但书的插拔 + for room in self.check_in_and_out(): + if any(room in obj["plan"].keys() and 'type' not in obj.keys() for obj in self.tasks): continue; + in_out_plan = {} + in_out_plan[room] = ['Current'] * len(plan[room]) + for idx, x in enumerate(plan[room]): + if '但书' in x['replacement'] or '龙舌兰' in x['replacement']: + in_out_plan[room][idx] = x['replacement'][0] + self.tasks.append({"time": self.get_in_and_out_time(room), "plan": in_out_plan}) + # 准备数据 + if self.read_mood: + # 根据剩余心情排序 + self.total_agent.sort(key=lambda x: x["mood"], reverse=False) + # 目前有换班的计划后面改 + logger.debug(f'当前基地数据--> {self.total_agent}') + exclude_list = [] + fia_plan, fia_room = self.check_fia() + if fia_room is not None and fia_plan is not None: + exclude_list = copy.deepcopy(fia_plan) + if not any(fia_room in obj["plan"].keys() and len(obj["plan"][fia_room]) == 2 for obj in self.tasks): + fia_idx = self.operators['菲亚梅塔']['index'] + result = [{}]*(fia_idx+1) + result[fia_idx]['time'] =datetime.now() + if self.operators["菲亚梅塔"]["mood"] != 24: + self.enter_room(fia_room) + result = self.get_agent_from_room(fia_room, [fia_idx]) + self.back() + logger.info('下一次进行菲亚梅塔充能:' + result[fia_idx]['time'].strftime("%H:%M:%S")) + self.tasks.append({"time": result[fia_idx]['time'], "plan": {fia_room: [ + next(obj for obj in self.total_agent if obj["agent"] in fia_plan)["agent"], + "菲亚梅塔"]}}) + exclude_list.append("菲亚梅塔") + try: + exaust_rest = [] + if len(self.exaust_agent) > 0: + for _exaust in self.exaust_agent: + i = self.operators[_exaust] + if 'current_room' in i.keys() and i['current_room'].startswith('dormitory') and i[ + 'resting_priority'] == 'high' and next( + (k for k in self.tasks if 'type' in k.keys() and i['current_room'] in k["type"]), + None) is None: + exaust_rest.append(i['name']) + if _exaust not in exclude_list: + exclude_list.append(_exaust) + # 如果exaust_agent<2 则读取 + logger.info(f'安排干员黑名单为:{exclude_list}') + # 先计算需要休息满的人 + total_exaust_plan = [] + for agent in exaust_rest: + error_count = 0 + time_result = None + # 读取时间 + __index = self.operators[agent]['current_index'] + while error_count < 3: + try: + self.enter_room(self.operators[agent]['current_room']) + time_result = self.get_agent_from_room(self.operators[agent]['current_room'],[__index]) + if time_result is None: + raise Exception("读取时间失败") + else: + break + except Exception as e: + self.back_to_index() + if self.scene() == Scene.INDEX: + self.tap_element('index_infrastructure', interval=5) + self.recog.update() + logger.exception(e) + error_count += 1 + continue + # 5分钟gap + time_result = time_result[__index]['time'] - timedelta(seconds=(300)) + # 如果已经有现有plan 则比对时间 + if next((k for k in total_exaust_plan if + next((k for k, v in k['plan'].items() if agent in v), None) is not None), + None) is not None: + _exaust_plan = next( + k for k in total_exaust_plan if next((k for k, v in k['plan'].items() if agent in v), None)) + if self.operators[agent]['current_room'] not in _exaust_plan['type']: + _exaust_plan['type'] += ',' + self.operators[agent]['current_room'] + if time_result > _exaust_plan['time']: + _exaust_plan['time'] = time_result + continue + exaust_plan = {'plan': {}, 'time': time_result, 'type': self.operators[agent]['current_room']} + # 如果有组,则生成小组上班 否则则单人上班 + bundle = [] + if self.operators[agent]['group'] != '': + bundle.extend([v['name'] for k, v in self.operators.items() if + 'group' in v.keys() and v['group'] == self.operators[agent]['group']]) + else: + bundle.append(agent) + for planned in bundle: + if self.operators[planned]['room'] not in exaust_plan['plan']: + exaust_plan['plan'][self.operators[planned]['room']] = [ + 'Current'] * len( + self.currentPlan[self.operators[planned]['room']]) + exaust_plan['plan'][self.operators[planned]['room']][ + self.operators[planned]['index']] = planned + total_exaust_plan.append(exaust_plan) + self.back() + self.tasks.extend(total_exaust_plan) + resting_dorm = [] + for task in self.tasks: + if 'type' in task.keys() and task['type'].startswith("dorm"): + resting_dorm.extend(task["type"].split(',')) + actuall_resting = len(resting_dorm) + + if len(resting_dorm) < self.dorm_count: + need_to_rest = [] + # 根据使用宿舍数量,输出需要休息的干员列表 + number_of_dorm = self.dorm_count + + min_mood = -99 + for agent in self.total_agent: + if actuall_resting >= number_of_dorm: + if min_mood == -99: + min_mood = agent['mood'] + break + if (len([value for value in need_to_rest if value["agent"] == agent["agent"]]) > 0): + continue + # 心情耗尽组如果心情 小于2 则记录时间 + if agent['agent'] in self.exaust_agent and agent['mood'] < 3 and not \ + self.operators[agent['agent']]['current_room'].startswith('dormitory'): + if next((e for e in self.tasks if 'type' in e.keys() and e['type'] == agent['agent']), + None) is None: + __agent = self.operators[agent['agent']] + self.enter_room(__agent['current_room']) + result = self.get_agent_from_room(__agent['current_room'], [__agent['current_index']]) + self.back() + # plan 是空的是因为得动态生成 + self.tasks.append({"time": result[__agent['current_index']]['time'], "plan": {}, "type": __agent['name']}) + else: + continue + # 忽略掉菲亚梅塔充能的干员 + if agent['agent'] in exclude_list: + continue + # 忽略掉低效率的干员 + if agent['agent'] in self.operators.keys() and self.operators[agent['agent']]['type'] != 'high': + continue + if 'resting_priority' in self.operators.keys() and self.operators[agent['agent']]['resting_priority'] != 'high': + continue + # 忽略掉正在休息的 + if agent['current_room'] in resting_dorm or agent['current_room'] in ['factory']: + continue + # 忽略掉心情值没低于上限的8的 + if agent['mood'] > int(self.operators[agent['agent']]["upper_limit"] * self.resting_treshhold): + continue + if agent['agent'] in self.operators.keys() and self.operators[agent['agent']]['group'] != '': + # 如果在group里则同时上下班 + group_resting = [x for x in self.total_agent if + self.operators[x['agent']]['group'] == self.operators[agent['agent']][ + 'group']] + group_restingCount = 0 + for x in group_resting: + if self.operators[x['agent']]['resting_priority'] == 'low': + continue + else: + group_restingCount += 1 + if group_restingCount + actuall_resting <= self.dorm_count: + need_to_rest.extend(group_resting) + actuall_resting += group_restingCount + else: + # 因为人数不够而跳过记录心情 + min_mood = agent['mood'] + continue + else: + need_to_rest.append(agent) + actuall_resting += 1 + # 输出转换后的换班plan + logger.info(f'休息人选为->{need_to_rest}') + if len(need_to_rest) > 0: + self.get_swap_plan(resting_dorm, need_to_rest, min_mood < 3 and min_mood != -99) + # 如果下个 普通任务 >5 分钟则补全宿舍 + if (next((e for e in self.tasks if e['time'] < datetime.now() + timedelta(seconds=300)), + None)) is None: + self.agent_get_mood() + except Exception as e: + logger.exception(f'计算排班计划出错->{e}') + + def get_swap_plan(self, resting_dorm, operators, skip_read_time): + result = {} + agents = copy.deepcopy(operators) + # 替换计划 + for a in operators: + if a['current_room'] not in result.keys(): + result[a['current_room']] = ['Current'] * len(self.currentPlan[a['current_room']]) + # 获取替换组且没有在上班的 排除但书或者龙舌兰 + __replacement = next((obj for obj in self.operators[a['agent']]['replacement'] if (not ( + self.operators[obj]['current_room'] != '' and not self.operators[obj][ + 'current_room'].startswith('dormitory'))) and obj not in ['但书', '龙舌兰']), None) + if __replacement is not None: + self.operators[__replacement]['current_room'] = a['current_room'] + result[a['current_room']][a['room_index']] = __replacement + else: + raise Exception(f"{a['agent']} 没有足够的替换组可用") + group_info = {} + read_time_rooms = [] + need_recover_room = [] + # 从休息计划里 规划出排班计划 并且立刻执行 + for room in [k for k, v in self.currentPlan.items() if (k not in resting_dorm) and k.startswith('dormitory')]: + # 记录房间可以放几个干员: + dorm_plan = [data["agent"] for data in self.currentPlan[room]] + # 塞一个最优组进去 + next_agent = next((obj for obj in agents if self.operators[obj["agent"]]['resting_priority'] == 'high'), + None) + planned_agent = [] + if next_agent is not None: + dorm_plan[dorm_plan.index('Free')] = next_agent["agent"] + planned_agent.append(next_agent["agent"]) + agents.remove(next_agent) + else: + break + if skip_read_time: + if 'rest_in_full' in self.operators[next_agent['agent']].keys() and \ + self.operators[next_agent['agent']]["rest_in_full"]: + need_recover_room.append(room) + read_time_rooms.append((room)) + else: + if 'rest_in_full' in self.operators[next_agent['agent']].keys() and \ + self.operators[next_agent['agent']]["rest_in_full"]: + skip_read_time = True + read_time_rooms.append(room) + free_num = dorm_plan.count('Free') + while free_num > 0: + next_agent_low = next( + (obj for obj in agents if self.operators[obj["agent"]]['resting_priority'] == 'low'), None) + if next_agent_low is not None: + dorm_plan[dorm_plan.index('Free')] = next_agent_low["agent"] + planned_agent.append(next_agent_low["agent"]) + agents.remove(next_agent_low) + free_num -= 1 + else: + break + result[room] = dorm_plan + # 如果有任何正在休息的最优组 + for item in self.current_base[room]: + if 'agent' in item.keys(): + _item_name = item['agent'] + if _item_name in self.operators.keys() and self.operators[_item_name]['type'] == 'high' and not \ + self.operators[_item_name]['room'].startswith( + 'dormitory'): + # 如果是高效干员 + _room = self.operators[_item_name]['room'] + if _room not in result.keys(): + result[_room] = ['Current'] * len(self.currentPlan[_room]) + result[_room][self.operators[_item_name]['index']] = _item_name + if room in need_recover_room: + group_name = self.operators[next_agent['agent']]["name"] + else: + # 未分组则强制分组 + group_name = 'default' + if not group_name in group_info.keys(): + group_info[group_name] = {'type': room, 'plan': {}} + else: + group_info[group_name]['type'] += ',' + room + for planned in planned_agent: + if self.operators[planned]['current_room'] not in group_info[group_name]['plan']: + group_info[group_name]['plan'][self.operators[planned]['current_room']] = ['Current'] * len( + self.currentPlan[self.operators[planned]['room']]) + group_info[group_name]['plan'][self.operators[planned]['current_room']][ + self.operators[planned]['index']] = planned + # group_info[group_name]['plan'][room]=[x['agent'] for x in self.currentPlan[room]] + logger.info(f'生成的分组计划:{group_info}') + logger.info(f'生成的排班计划为->{result}') + self.tasks.append( + {'plan': result, 'time': datetime.now(), 'metadata': {'plan': group_info, 'room': read_time_rooms}}) + + def get_agent(self): + plan = self.currentPlan + high_production = [] + replacements = [] + for room in plan.keys(): + for idx, data in enumerate(plan[room]): + __high = {"name": data["agent"], "room": room, "index": idx, "group": data["group"], + 'replacement': data["replacement"], 'resting_priority': 'high', 'current_room': '', + 'exhaust_require': False, "upper_limit": 24,"rest_in_full":False} + if __high['name'] in self.agent_base_config.keys() and 'RestingPriority' in self.agent_base_config[ + __high['name']].keys() and self.agent_base_config[__high['name']]['RestingPriority'] == 'low': + __high["resting_priority"] = 'low' + if __high['name'] in self.agent_base_config.keys() and 'ExhaustRequire' in self.agent_base_config[ + __high['name']].keys() and self.agent_base_config[__high['name']]['ExhaustRequire'] == True: + __high["exhaust_require"] = True + if __high['name'] in self.agent_base_config.keys() and 'UpperLimit' in self.agent_base_config[ + __high['name']].keys(): + __high["upper_limit"] = self.agent_base_config[__high['name']]['UpperLimit'] + if __high['name'] in self.agent_base_config.keys() and 'RestInFull' in self.agent_base_config[ + __high['name']].keys() and self.agent_base_config[__high['name']]['RestInFull'] == True: + __high["rest_in_full"] = True + high_production.append(__high) + if "replacement" in data.keys() and data["agent"] != '菲亚梅塔': + replacements.extend(data["replacement"]) + for agent in high_production: + if agent["room"].startswith('dormitory'): + agent["type"] = "low" + else: + agent["type"] = "high" + self.operators[agent["name"]] = agent + for agent in replacements: + if agent in self.operators.keys(): + if 'type' in self.operators[agent].keys() and self.operators[agent]['type'] == 'high': + continue + else: + self.operators[agent] = {"type": "low", "name": agent, "group": '', 'resting_priority': 'low', + "index": -1, 'current_room': '', 'mood': 24, "upper_limit": 24,"rest_in_full":False} + else: + self.operators[agent] = {"type": "low", "name": agent, "group": '', 'current_room': '', + 'resting_priority': 'low', "index": -1, 'mood': 24, "upper_limit": 24,"rest_in_full":False} + self.exaust_agent = [] + if next((k for k, v in self.operators.items() if 'exhaust_require' in v.keys() and v["exhaust_require"]), + None) is not None: + exhaust_require = [v for k, v in self.operators.items() if + 'exhaust_require' in v.keys() and v["exhaust_require"]] + for i in exhaust_require: + if i['name'] in self.exaust_agent: continue + if 'group' in i.keys() and i['group'] != '': + self.exaust_agent.extend([v['name'] for k, v in self.operators.items() if + 'group' in v.keys() and v['group'] == i['group']]) + else: + self.exaust_agent.append(i['name']) + logger.info(f'需要用尽心情的干员为: {self.exaust_agent}') + + def check_in_and_out(self): + res = {} + for x, y in self.currentPlan.items(): + if not x.startswith('room'): continue + if any(('但书' in obj['replacement'] or '龙舌兰' in obj['replacement']) for obj in y): + res[x] = y + return res + + def check_fia(self): + res = {} + if '菲亚梅塔' in self.operators.keys() and self.operators['菲亚梅塔']['room'].startswith('dormitory'): + if 'replacement' in self.operators['菲亚梅塔'].keys(): + return self.operators['菲亚梅塔']['replacement'], self.operators['菲亚梅塔']['room'] + return None, None + + def get_in_and_out_time(self, room): + logger.info('基建:读取插拔时间') + # 点击进入该房间 + self.enter_room(room) + # 进入房间详情 + error_count = 0 + while self.find('bill_accelerate') is None: + if error_count > 5: + raise Exception('未成功进入无人机界面') + self.tap((self.recog.w * 0.05, self.recog.h * 0.95), interval=1) + error_count += 1 + execute_time = self.double_read_time((int(self.recog.w * 650 / 2496), int(self.recog.h * 660 / 1404), + int(self.recog.w * 815 / 2496), int(self.recog.h * 710 / 1404))) + execute_time = execute_time - timedelta(seconds=(600)) + logger.info('下一次进行插拔的时间为:' + execute_time.strftime("%H:%M:%S")) + logger.info('返回基建主界面') + self.back(interval=2, rebuild=False) + self.back(interval=2) + return execute_time + + def double_read_time(self, cord, upperLimit=36000, error_count=0,): + if upperLimit < 36000: + upperLimit = 36000 + self.recog.update() + time_in_seconds = self.read_time(cord, upperLimit) + execute_time = datetime.now() + timedelta(seconds=(time_in_seconds)) + return execute_time + + + def initialize_paddle(self): + global ocr + if ocr is None: + ocr = PaddleOCR(use_angle_cls=True, lang='en') + + def read_screen(self,img, type="mood",limit=24, cord=None, change_color=False): + if cord is not None: + img = img[cord[1]:cord[3], cord[0]:cord[2]] + if 'mood' in type or type == "time": + # 心情图片太小,复制8次提高准确率 + for x in range(0, 4): + img = cv2.vconcat([img, img]) + if change_color: img[img == 137] = 255 + try: + self.initialize_paddle() + rets = ocr.ocr(img, cls=True) + line_conf = [] + for idx in range(len(rets[0])): + res = rets[0][idx] + if 'mood' in type : + # filter 掉不符合规范的结果 + if ('/' + str(limit)) in res[1][0]: + line_conf.append(res[1]) + else: + line_conf.append(res[1]) + logger.debug(line_conf) + if len(line_conf) == 0 and 'mood' in type: return -1 + x = [i[0] for i in line_conf] + __str = max(set(x), key=x.count) + print(__str) + if "mood" in type: + number = int(__str[0:__str.index('/')]) + return number + elif 'time' in type: + if '.' in __str: + __str = __str.replace(".", ":") + return __str + except Exception as e : + logger.exception(e) + return limit + + def read_time(self, cord, upperlimit, error_count=0): + # 刷新图片 + self.recog.update() + time_str = segment.read_screen(self.recog.img, type='time', cord=cord) + logger.debug(str(time_str)) + try: + h, m, s = str(time_str).split(':') + if int(m)>60 or int(s)>60: + raise Exception(f"读取错误") + res = int(h) * 3600 + int(m) * 60 + int(s) + if res>upperlimit: + raise Exception(f"超过读取上限") + else :return res + except: + logger.error("读取失败" + "--> " + str(time_str)) + if error_count > 50: + raise Exception(f"读取失败{error_count}次超过上限") + else: + return self.read_time(cord,upperlimit, error_count + 1) + + def todo_list(self) -> None: + """ 处理基建 Todo 列表 """ + tapped = False + trust = self.find('infra_collect_trust') + if trust is not None: + logger.info('基建:干员信赖') + self.tap(trust) + tapped = True + bill = self.find('infra_collect_bill') + if bill is not None: + logger.info('基建:订单交付') + self.tap(bill) + tapped = True + factory = self.find('infra_collect_factory') + if factory is not None: + logger.info('基建:可收获') + self.tap(factory) + tapped = True + if not tapped: + self.tap((self.recog.w * 0.05, self.recog.h * 0.95)) + self.todo_task = True + + def clue(self) -> None: + # 一些识别时会用到的参数 + global x1, x2, x3, x4, y0, y1, y2 + x1, x2, x3, x4 = 0, 0, 0, 0 + y0, y1, y2 = 0, 0, 0 + + logger.info('基建:线索') + + # 进入会客室 + self.enter_room('meeting') + + # 点击线索详情 + self.tap((self.recog.w * 0.1, self.recog.h * 0.9), interval=3) + + # 如果是线索交流的报告则返回 + self.find('clue_summary') and self.back() + + # 识别右侧按钮 + (x0, y0), (x1, y1) = self.find('clue_func', strict=True) + + logger.info('接收赠送线索') + self.tap(((x0 + x1) // 2, (y0 * 3 + y1) // 4), interval=3, rebuild=False) + self.tap((self.recog.w - 10, self.recog.h - 10), interval=3, rebuild=False) + self.tap((self.recog.w * 0.05, self.recog.h * 0.95), interval=3, rebuild=False) + + logger.info('领取会客室线索') + self.tap(((x0 + x1) // 2, (y0 * 5 - y1) // 4), interval=3) + obtain = self.find('clue_obtain') + if obtain is not None and self.get_color(self.get_pos(obtain, 0.25, 0.5))[0] < 20: + self.tap(obtain, interval=2) + if self.find('clue_full') is not None: + self.back() + else: + self.back() + + logger.info('放置线索') + clue_unlock = self.find('clue_unlock') + if clue_unlock is not None: + # 当前线索交流未开启 + self.tap_element('clue', interval=3) + + # 识别阵营切换栏 + self.recog_bar() + + # 点击总览 + self.tap(((x1 * 7 + x2) // 8, y0 // 2), rebuild=False) + + # 获得和线索视图相关的数据 + self.recog_view(only_y2=False) + + # 检测是否拥有全部线索 + get_all_clue = True + for i in range(1, 8): + # 切换阵营 + self.tap(self.switch_camp(i), rebuild=False) + + # 清空界面内被选中的线索 + self.clear_clue_mask() + + # 获得和线索视图有关的数据 + self.recog_view() + + # 检测该阵营线索数量为 0 + if len(self.ori_clue()) == 0: + logger.info(f'无线索 {i}') + get_all_clue = False + break + + # 检测是否拥有全部线索 + if get_all_clue: + for i in range(1, 8): + # 切换阵营 + self.tap(self.switch_camp(i), rebuild=False) + + # 获得和线索视图有关的数据 + self.recog_view() + + # 放置线索 + logger.info(f'放置线索 {i}') + self.tap(((x1 + x2) // 2, y1 + 3), rebuild=False) + + # 返回线索主界面 + self.tap((self.recog.w * 0.05, self.recog.h * 0.95), interval=3, rebuild=False) + + # 线索交流开启 + if clue_unlock is not None and get_all_clue: + self.tap(clue_unlock) + else: + self.back(interval=2, rebuild=False) + + logger.info('返回基建主界面') + self.back(interval=2) + + def switch_camp(self, id: int) -> tuple[int, int]: + """ 切换阵营 """ + x = ((id + 0.5) * x2 + (8 - id - 0.5) * x1) // 8 + y = (y0 + y1) // 2 + return x, y + + def recog_bar(self) -> None: + """ 识别阵营选择栏 """ + global x1, x2, y0, y1 + + (x1, y0), (x2, y1) = self.find('clue_nav', strict=True) + while int(self.recog.img[y0, x1 - 1].max()) - int(self.recog.img[y0, x1].max()) <= 1: + x1 -= 1 + while int(self.recog.img[y0, x2].max()) - int(self.recog.img[y0, x2 - 1].max()) <= 1: + x2 += 1 + while abs(int(self.recog.img[y1 + 1, x1].max()) - int(self.recog.img[y1, x1].max())) <= 1: + y1 += 1 + y1 += 1 + + logger.debug(f'recog_bar: x1:{x1}, x2:{x2}, y0:{y0}, y1:{y1}') + + def recog_view(self, only_y2: bool = True) -> None: + """ 识别另外一些和线索视图有关的数据 """ + global x1, x2, x3, x4, y0, y1, y2 + + # y2: 线索底部 + y2 = self.recog.h + while self.recog.img[y2 - 1, x1:x2].ptp() <= 24: + y2 -= 1 + if only_y2: + logger.debug(f'recog_view: y2:{y2}') + return y2 + # x3: 右边黑色 mask 边缘 + x3 = self.recog_view_mask_right() + # x4: 用来区分单个线索 + x4 = (54 * x1 + 25 * x2) // 79 + + logger.debug(f'recog_view: y2:{y2}, x3:{x3}, x4:{x4}') + + def recog_view_mask_right(self) -> int: + """ 识别线索视图中右边黑色 mask 边缘的位置 """ + x3 = x2 + while True: + max_abs = 0 + for y in range(y1, y2): + max_abs = max(max_abs, + abs(int(self.recog.img[y, x3 - 1, 0]) - int(self.recog.img[y, x3 - 2, 0]))) + if max_abs <= 5: + x3 -= 1 + else: + break + flag = False + for y in range(y1, y2): + if int(self.recog.img[y, x3 - 1, 0]) - int(self.recog.img[y, x3 - 2, 0]) == max_abs: + flag = True + if not flag: + self.tap(((x1 + x2) // 2, y1 + 10), rebuild=False) + x3 = x2 + while True: + max_abs = 0 + for y in range(y1, y2): + max_abs = max(max_abs, + abs(int(self.recog.img[y, x3 - 1, 0]) - int(self.recog.img[y, x3 - 2, 0]))) + if max_abs <= 5: + x3 -= 1 + else: + break + flag = False + for y in range(y1, y2): + if int(self.recog.img[y, x3 - 1, 0]) - int(self.recog.img[y, x3 - 2, 0]) == max_abs: + flag = True + if not flag: + x3 = None + return x3 + + def get_clue_mask(self) -> None: + """ 界面内是否有被选中的线索 """ + try: + mask = [] + for y in range(y1, y2): + if int(self.recog.img[y, x3 - 1, 0]) - int(self.recog.img[y, x3 - 2, 0]) > 20 and np.ptp( + self.recog.img[y, x3 - 2]) == 0: + mask.append(y) + if len(mask) > 0: + logger.debug(np.average(mask)) + return np.average(mask) + else: + return None + except Exception as e: + raise RecognizeError(e) + + def clear_clue_mask(self) -> None: + """ 清空界面内被选中的线索 """ + try: + while True: + mask = False + for y in range(y1, y2): + if int(self.recog.img[y, x3 - 1, 0]) - int(self.recog.img[y, x3 - 2, 0]) > 20 and np.ptp( + self.recog.img[y, x3 - 2]) == 0: + self.tap((x3 - 2, y + 1), rebuild=True) + mask = True + break + if mask: + continue + break + except Exception as e: + raise RecognizeError(e) + + def ori_clue(self): + """ 获取界面内有多少线索 """ + clues = [] + y3 = y1 + status = -2 + for y in range(y1, y2): + if self.recog.img[y, x4 - 5:x4 + 5].max() < 192: + if status == -1: + status = 20 + if status > 0: + status -= 1 + if status == 0: + status = -2 + clues.append(segment.get_poly(x1, x2, y3, y - 20)) + y3 = y - 20 + 5 + else: + status = -1 + if status != -2: + clues.append(segment.get_poly(x1, x2, y3, y2)) + + # 忽视一些只有一半的线索 + clues = [x.tolist() for x in clues if x[1][1] - x[0][1] >= self.recog.h / 5] + logger.debug(clues) + return clues + + def enter_room(self, room: str) -> tp.Rectangle: + """ 获取房间的位置并进入 """ + + # 获取基建各个房间的位置 + base_room = segment.base(self.recog.img, self.find('control_central', strict=True)) + + # 将画面外的部分删去 + _room = base_room[room] + for i in range(4): + _room[i, 0] = max(_room[i, 0], 0) + _room[i, 0] = min(_room[i, 0], self.recog.w) + _room[i, 1] = max(_room[i, 1], 0) + _room[i, 1] = min(_room[i, 1], self.recog.h) + + # 点击进入 + self.tap(_room[0], interval=3) + while self.find('control_central') is not None: + self.tap(_room[0], interval=3) + + def drone(self, room: str, one_time=False, not_return=False): + logger.info('基建:无人机加速') + + # 点击进入该房间 + self.enter_room(room) + # 进入房间详情 + + self.tap((self.recog.w * 0.05, self.recog.h * 0.95), interval=3) + # 关闭掉房间总览 + error_count = 0 + while self.find('factory_accelerate') is None and self.find('bill_accelerate') is None: + if error_count > 5: + raise Exception('未成功进入无人机界面') + self.tap((self.recog.w * 0.05, self.recog.h * 0.95), interval=3) + error_count += 1 + + accelerate = self.find('factory_accelerate') + if accelerate: + logger.info('制造站加速') + self.tap(accelerate) + self.tap_element('all_in') + self.tap(accelerate, y_rate=1) + + else: + accelerate = self.find('bill_accelerate') + while accelerate: + logger.info('贸易站加速') + self.tap(accelerate) + self.tap_element('all_in') + self.tap((self.recog.w * 0.75, self.recog.h * 0.8)) + while self.get_infra_scene() == Scene.CONNECTING: + self.sleep(3) + if one_time: + drone_count = self.read_screen(self.recog.img, type='drone_mood', cord=( + int(self.recog.w * 1150 / 1920), int(self.recog.h * 35 / 1080), int(self.recog.w * 1295 / 1920), + int(self.recog.h * 72 / 1080)), limit=200) + logger.info(f'当前无人机数量为:{drone_count}') + self.recog.update() + self.recog.save_screencap('run_order') + # 200 为识别错误 + if drone_count < 100 or drone_count ==200: + logger.info(f"无人机数量小于92->停止") + break + st = accelerate[1] # 起点 + ed = accelerate[0] # 终点 + # 0.95, 1.05 are offset compensations + self.swipe_noinertia(st, (ed[0] * 0.95 - st[0] * 1.05, 0), rebuild=True) + accelerate = self.find('bill_accelerate') + if not_return: return + logger.info('返回基建主界面') + self.back(interval=2, rebuild=False) + self.back(interval=2) + + def get_arrange_order(self) -> ArrangeOrder: + best_score, best_order = 0, None + for order in ArrangeOrder: + score = self.recog.score(arrange_order_res[order][0]) + if score is not None and score[0] > best_score: + best_score, best_order = score[0], order + # if best_score < 0.6: + # raise RecognizeError + logger.debug((best_score, best_order)) + return best_order + + def switch_arrange_order(self, index: int, asc="false") -> None: + self.tap((self.recog.w * arrange_order_res[ArrangeOrder(index)][0], + self.recog.h * arrange_order_res[ArrangeOrder(index)][1]), interval=0, rebuild=False) + # 点个不需要的 + if index < 4: + self.tap((self.recog.w * arrange_order_res[ArrangeOrder(index + 1)][0], + self.recog.h * arrange_order_res[ArrangeOrder(index)][1]), interval=0, rebuild=False) + else: + self.tap((self.recog.w * arrange_order_res[ArrangeOrder(index - 1)][0], + self.recog.h * arrange_order_res[ArrangeOrder(index)][1]), interval=0, rebuild=False) + # 切回来 + self.tap((self.recog.w * arrange_order_res[ArrangeOrder(index)][0], + self.recog.h * arrange_order_res[ArrangeOrder(index)][1]), interval=0.2, rebuild=True) + # 倒序 + if asc != "false": + self.tap((self.recog.w * arrange_order_res[ArrangeOrder(index)][0], + self.recog.h * arrange_order_res[ArrangeOrder(index)][1]), interval=0.2, rebuild=True) + + def scan_agant(self, agent: list[str], error_count=0, max_agent_count=-1): + try: + # 识别干员 + self.recog.update() + ret = character_recognize.agent(self.recog.img) # 返回的顺序是从左往右从上往下 + # 提取识别出来的干员的名字 + agent_name = set([x[0] for x in ret]) + agent_size = len(agent) + select_name = [] + for y in ret: + name = y[0] + if name in agent: + select_name.append(name) + # self.get_agent_detail((y[1][0])) + self.tap((y[1][0])) + agent.remove(name) + # 如果是按照个数选择 Free + if max_agent_count != -1: + if len(select_name) >= max_agent_count: + return select_name, ret + return select_name, ret + except Exception as e: + error_count += 1 + if error_count < 3: + logger.exception(e) + self.sleep(3) + return self.scan_agant(agent, error_count, max_agent_count) + else: + raise e + + def get_order(self, name): + if (name in self.agent_base_config.keys()): + if "ArrangeOrder" in self.agent_base_config[name].keys(): + return True, self.agent_base_config[name]["ArrangeOrder"] + else: + return False, self.agent_base_config["Default"]["ArrangeOrder"] + return False, self.agent_base_config["Default"]["ArrangeOrder"] + + def detail_filter(self, turn_on, type="not_in_dorm"): + logger.info(f'开始 {("打开" if turn_on else "关闭")} {type} 筛选') + self.tap((self.recog.w * 0.95, self.recog.h * 0.05), interval=1) + if type == "not_in_dorm": + not_in_dorm = self.find('not_in_dorm') + if turn_on ^ (not_in_dorm is not None): + self.tap((self.recog.w * 0.3, self.recog.h * 0.5), interval=0.5) + # 确认 + self.tap((self.recog.w * 0.8, self.recog.h * 0.8), interval=0.5) + + def choose_agent(self, agents: list[str], room: str) -> None: + """ + :param order: ArrangeOrder, 选择干员时右上角的排序功能 + """ + first_name = '' + max_swipe = 50 + for idx, n in enumerate(agents): + if n == '': + agents[idx] = 'Free' + agent = copy.deepcopy(agents) + logger.info(f'安排干员 :{agent}') + # 若不是空房间,则清空工作中的干员 + is_dorm = room.startswith("dorm") + h, w = self.recog.h, self.recog.w + first_time = True + # 在 agent 中 'Free' 表示任意空闲干员 + free_num = agent.count('Free') + for i in range(agent.count("Free")): + agent.remove("Free") + index_change = False + pre_order = [2, False] + right_swipe = 0 + retry_count = 0 + # 如果重复进入宿舍则需要排序 + selected = [] + logger.info(f'上次进入房间为:{self.last_room},本次房间为:{room}') + if self.last_room.startswith('dorm') and is_dorm: + self.detail_filter(False) + while len(agent) > 0: + if retry_count > 3: raise Exception(f"到达最大尝试次数 3次") + if right_swipe > max_swipe: + # 到底了则返回再来一次 + for _ in range(right_swipe): + self.swipe_only((w // 2, h // 2), (w // 2, 0), interval=0.5) + right_swipe = 0 + max_swipe = 50 + retry_count += 1 + self.detail_filter(False) + if first_time: + # 清空 + if is_dorm: + self.switch_arrange_order(3, "true") + pre_order = [3, 'true'] + self.tap((self.recog.w * 0.38, self.recog.h * 0.95), interval=0.5) + changed, ret = self.scan_agant(agent) + if changed: + selected.extend(changed) + if len(agent) == 0: break + index_change = True + + # 如果选中了人,则可能需要重新排序 + if index_change or first_time: + # 第一次则调整 + is_custom, arrange_type = self.get_order(agent[0]) + if is_dorm and not (agent[0] in self.operators.keys() and + 'room' in self.operators[agent[0]].keys() and self.operators[agent[0]]['room'].startswith( + 'dormitory')): + arrange_type = (3, 'true') + # 如果重新排序则滑到最左边 + if pre_order[0] != arrange_type[0] or pre_order[1] != arrange_type[1]: + self.switch_arrange_order(arrange_type[0], arrange_type[1]) + # 滑倒最左边 + self.sleep(interval=0.5, rebuild=True) + right_swipe = self.swipe_left(right_swipe, w, h) + pre_order = arrange_type + first_time = False + + changed, ret = self.scan_agant(agent) + if changed: + selected.extend(changed) + # 如果找到了 + index_change = True + else: + # 如果没找到 而且右移次数大于5 + if ret[0][0] == first_name and right_swipe > 5: + max_swipe = right_swipe + else: + first_name = ret[0][0] + index_change = False + st = ret[-2][1][2] # 起点 + ed = ret[0][1][1] # 终点 + self.swipe_noinertia(st, (ed[0] - st[0], 0)) + right_swipe += 1 + if len(agent) == 0: break; + + # 安排空闲干员 + if free_num: + if free_num == len(agents): + self.tap((self.recog.w * 0.38, self.recog.h * 0.95), interval=0.5) + if not first_time: + # 滑动到最左边 + self.sleep(interval=0.5, rebuild=False) + right_swipe = self.swipe_left(right_swipe, w, h) + self.detail_filter(True) + self.switch_arrange_order(3, "true") + # 只选择在列表里面的 + # 替换组小于20才休息,防止进入就满心情进行网络连接 + free_list = [v["name"] for k, v in self.operators.items() if + v["name"] not in agents and v["type"] != 'high'] + free_list.extend([_name for _name in agent_list if _name not in self.operators.keys()]) + free_list = list(set(free_list) - set(self.free_blacklist)) + while free_num: + selected_name, ret = self.scan_agant(free_list, max_agent_count=free_num) + selected.extend(selected_name) + free_num -= len(selected_name) + while len(selected_name) > 0: + agents[agents.index('Free')] = selected_name[0] + selected_name.remove(selected_name[0]) + if free_num == 0: + break + else: + st = ret[-2][1][2] # 起点 + ed = ret[0][1][1] # 终点 + self.swipe_noinertia(st, (ed[0] - st[0], 0)) + right_swipe += 1 + # 排序 + if len(agents) != 1: + # 左移 + self.swipe_left(right_swipe, w, h) + self.tap((self.recog.w * arrange_order_res[ArrangeOrder.SKILL][0], + self.recog.h * arrange_order_res[ArrangeOrder.SKILL][1]), interval=0.5, rebuild=False) + position = [(0.35, 0.35), (0.35, 0.75), (0.45, 0.35), (0.45, 0.75), (0.55, 0.35)] + not_match = False + for idx, item in enumerate(agents): + if agents[idx] != selected[idx] or not_match: + not_match = True + p_idx = selected.index(agents[idx]) + self.tap((self.recog.w * position[p_idx][0], self.recog.h * position[p_idx][1]), interval=0, + rebuild=False) + self.tap((self.recog.w * position[p_idx][0], self.recog.h * position[p_idx][1]), interval=0, + rebuild=False) + self.last_room = room + logger.info(f"设置上次房间为{self.last_room}") + + def swipe_left(self, right_swipe, w, h): + for _ in range(right_swipe): + self.swipe_only((w // 2, h // 2), (w // 2, 0), interval=0.5) + return 0 + + def get_agent_from_room(self, room, read_time_index=[]): + error_count = 0 + if room == 'meeting': + time.sleep(3) + while self.find('room_detail') is None: + if error_count > 3: + raise Exception('未成功进入房间') + self.tap((self.recog.w * 0.05, self.recog.h * 0.4), interval=0.5) + error_count += 1 + length = len(self.currentPlan[room]) + if length > 3: self.swipe((self.recog.w * 0.8, self.recog.h * 0.8), (0, self.recog.h * 0.4), interval=1, + rebuild=True) + name_p = [((1460, 155), (1700, 210)), ((1460, 370), (1700, 420)), ((1460, 585), (1700, 630)), + ((1460, 560), (1700, 610)), ((1460, 775), (1700, 820))] + time_p = [((1650, 270, 1780, 305)), ((1650, 480, 1780, 515)), ((1650, 690, 1780, 725)), + ((1650, 665, 1780, 700)), ((1650, 875, 1780, 910))] + mood_p = [((1685, 213, 1780, 256)), ((1685, 422, 1780, 465)), ((1685, 632, 1780, 675)), + ((1685, 612, 1780, 655)), ((1685, 822, 1780, 865))] + result = [] + swiped = False + for i in range(0, length): + if i >= 3 and not swiped: + self.swipe((self.recog.w * 0.8, self.recog.h * 0.8), (0, -self.recog.h * 0.4), interval=1, rebuild=True) + swiped = True + data = {} + data['agent'] = character_recognize.agent_name( + self.recog.img[name_p[i][0][1]:name_p[i][1][1], name_p[i][0][0]:name_p[i][1][0]], self.recog.h*1.1) + error_count = 0 + while i>=3 and data['agent'] !='' and (next((e for e in result if e['agent'] == data['agent']), None)) is not None: + logger.warning("检测到滑动可能失败") + self.swipe((self.recog.w * 0.8, self.recog.h * 0.8), (0, -self.recog.h * 0.4), interval=1, rebuild=True) + data['agent'] = character_recognize.agent_name( + self.recog.img[name_p[i][0][1]:name_p[i][1][1], name_p[i][0][0]:name_p[i][1][0]], self.recog.h*1.1) + error_count+=1 + if error_count>4: + raise Exception("超过出错上限") + data['mood'] = self.read_screen(self.recog.img, cord=mood_p[i], change_color=True) + if data['agent'] not in self.operators.keys(): + self.operators[data['agent']] = {"type": "low", "name": data['agent'], "group": '', 'current_room': '', + 'resting_priority': 'low', "index": -1, 'mood': data['mood'], + "upper_limit": 24} + else: + self.operators[data['agent']]['mood'] = data['mood'] + self.operators[data['agent']]['current_index'] = i + self.operators[data['agent']]['current_room'] = room + if i in read_time_index: + if data['mood'] in [24] or (data['mood'] == 0 and not room.startswith('dorm')): + data['time'] = datetime.now() + else: + upperLimit = 21600 + if data['agent']in ['菲亚梅塔','刻俄柏']: + upperLimit = 43200 + data['time'] = self.double_read_time(time_p[i],upperLimit=upperLimit) + result.append(data) + self.scan_time[room] = datetime.now() + # update current_room + for item in result: + operator = item['agent'] + if operator in self.operators.keys(): + self.operators[operator]['current_room'] = room + for _operator in self.operators.keys(): + if 'current_room' in self.operators[_operator].keys() and self.operators[_operator][ + 'current_room'] == room and _operator not in [res['agent'] for res in result] : + self.operators[_operator]['current_room'] = '' + logger.info(f'重设 {_operator} 至空闲') + return result + + def agent_arrange(self, plan: tp.BasePlan, read_time_room=[]): + logger.info('基建:排班') + in_and_out = [] + fia_room = "" + rooms = list(plan.keys()) + # 优先替换工作站再替换宿舍 + rooms.sort(key=lambda x: x.startswith('dorm'), reverse=False) + time_result = {} + for room in rooms: + finished = False + choose_error = 0 + while not finished: + try: + error_count = 0 + self.enter_room(room) + while self.find('room_detail') is None: + if error_count > 3: + raise Exception('未成功进入房间') + self.tap((self.recog.w * 0.05, self.recog.h * 0.4), interval=0.5) + error_count += 1 + error_count = 0 + update_base = False + if ('但书' in plan[room] or '龙舌兰' in plan[room]) and not \ + room.startswith('dormitory') and room not in in_and_out: + in_and_out.append(room) + update_base = True + if '菲亚梅塔' in plan[room] and len(plan[room]) == 2: + fia_room = room + update_base = True + # 如果需要更新当前阵容 + self.scan_time[room] = None + # 是否该干员变动影响其他房间 + update_room = [] + for operator in (plan[room]): + if operator in self.operators.keys() and self.operators[operator]['current_room'] != '' and \ + self.operators[operator]['current_room'] != room: + update_room.append(self.operators[operator]['current_room']) + for __room in update_room: + self.scan_time[__room] = None + if update_base or 'Current' in plan[room]: + self.current_base[room] = self.get_agent_from_room(room) + # 纠错 因为网络连接导致房间移位 + if 'Current' in plan[room]: + # replace current + for current_idx, _name in enumerate(plan[room]): + if _name == 'Current': + current_name = self.current_base[room][current_idx]["agent"] + if current_name in agent_list: + plan[room][current_idx] = current_name + else: + # 如果空房间或者名字错误,则使用default干员 + plan[room][current_idx] = \ + self.currentPlan[room][current_idx]["agent"] + self.scan_time[room] = None + while self.find('arrange_order_options') is None: + if error_count > 3: + raise Exception('未成功进入干员选择界面') + self.tap((self.recog.w * 0.82, self.recog.h * 0.2), interval=1) + error_count += 1 + error_count = 0 + self.choose_agent(plan[room], room) + self.recog.update() + self.tap_element('confirm_blue', detected=True, judge=False, interval=3) + if self.get_infra_scene() == Scene.INFRA_ARRANGE_CONFIRM: + x0 = self.recog.w // 3 * 2 # double confirm + y0 = self.recog.h - 10 + self.tap((x0, y0), rebuild=True) + time_index = [] + # 如果需要读取时间 + if room in read_time_room: + time_index = [[data["agent"] for data in self.currentPlan[room]].index('Free')] + current = self.get_agent_from_room(room, time_index) + for idx, name in enumerate(plan[room]): + if current[idx]['agent'] != name: + logger.error(f'检测到的干员{current[idx]["agent"]},需要安排的干员{name}') + raise Exception('检测到安排干员未成功') + # 如果不匹配,则退出主界面再重新进房间一次 + if room in read_time_room: + __name = plan[room][[data["agent"] for data in self.currentPlan[room]].index('Free')] + time_result[room] = current[time_index[0]]['time'] + if not self.operators[__name]['exhaust_require']: + time_result[room] = time_result[room] - timedelta(seconds=600) + finished = True + # back to 基地主界面 + while self.scene() == Scene.CONNECTING: + self.sleep(3) + except Exception as e: + logger.exception(e) + choose_error += 1 + self.recog.update() + back_count = 0 + while self.get_infra_scene() != Scene.INFRA_MAIN: + self.back() + self.recog.update() + back_count+=1 + if back_count>3: + raise e + if choose_error > 3: + raise e + else: + continue + self.back(0.5) + if len(in_and_out) > 0: + replace_plan = {} + for room in in_and_out: + logger.info("开始插拔") + self.drone(room, True, True) + in_and_out_plan = [data["agent"] for data in self.current_base[room]] + # 防止由于意外导致的死循环 + if '但书' in in_and_out_plan or '龙舌兰' in in_and_out_plan: + in_and_out_plan = [data["agent"] for data in self.currentPlan[room]] + replace_plan[room] = in_and_out_plan + self.back(interval=0.5) + self.back(interval=0.5) + self.tasks.append({'time': self.tasks[0]['time'], 'plan': replace_plan}) + # 急速换班 + self.todo_task = True + self.planned = True + if fia_room != '': + replace_agent = plan[fia_room][0] + fia_change_room = self.operators[replace_agent]["room"] + fia_room_plan = [data["agent"] for data in self.current_base[fia_room]] + fia_change_room_plan = ['Current']*len(self.currentPlan[fia_change_room]) + fia_change_room_plan[self.operators[replace_agent]["index"]] = replace_agent + self.tasks.append( + {'time': self.tasks[0]['time'], 'plan': {fia_room: fia_room_plan, fia_change_room: fia_change_room_plan}}) + # 急速换班 + self.todo_task = True + self.planned = True + logger.info('返回基建主界面') + if len(read_time_room) > 0: + return time_result + + @Asst.CallBackType + def log_maa(msg, details, arg): + m = Message(msg) + d = json.loads(details.decode('utf-8')) + logger.debug(d) + logger.debug(m) + logger.debug(arg) + + def inialize_maa(self): + # 若需要获取详细执行信息,请传入 callback 参数 + # 例如 asst = Asst(callback=my_callback) + Asst.load(path=self.maa_config['maa_path']) + self.MAA = Asst(callback=self.log_maa) + # self.MAA.set_instance_option(2, 'maatouch') + # 请自行配置 adb 环境变量,或修改为 adb 可执行程序的路径 + if self.MAA.connect(self.maa_config['maa_adb_path'], self.ADB_CONNECT): + logger.info("MAA 连接成功") + else: + logger.info("MAA 连接失败") + raise Exception("MAA 连接失败") + + def maa_plan_solver(self): + try: + if self.maa_config['last_execution'] is not None and datetime.now() - timedelta(seconds=self.maa_config['maa_execution_gap']*3600)< self.maa_config['last_execution']: + logger.info("间隔未超过设定时间,不启动maa") + else: + self.send_email('休息时长超过9分钟,启动MAA') + self.back_to_index() + # 任务及参数请参考 docs/集成文档.md + self.inialize_maa() + self.MAA.append_task('StartUp') + _plan= self.maa_config['weekly_plan'][get_server_weekday()] + logger.info(f"现在服务器是{_plan['weekday']}") + fights = [] + for stage in _plan["stage"]: + logger.info(f"添加关卡:{stage}") + self.MAA.append_task('Fight', { + # 空值表示上一次 + # 'stage': '', + 'stage': stage, + 'medicine': _plan["medicine"], + 'stone': 0, + 'times': 999, + 'report_to_penguin': True, + 'client_type': '', + 'penguin_id': '', + 'DrGrandet': False, + 'server': 'CN' + }) + fights.append(stage) + self.MAA.append_task('Recruit', { + 'select': [4], + 'confirm': [3, 4], + 'times': 4, + 'refresh': True, + "recruitment_time": { + "3": 460, + "4": 540 + } + }) + self.MAA.append_task('Visit') + self.MAA.append_task('Mall', { + 'shopping': True, + 'buy_first': ['龙门币', '赤金'], + 'blacklist': ['家具', '碳', '加急'], + 'credit_fight':fights[len(fights)-1]!='' + }) + self.MAA.append_task('Award') + # asst.append_task('Copilot', { + # 'stage_name': '千层蛋糕', + # 'filename': './GA-EX8-raid.json', + # 'formation': False + + # }) + self.MAA.start() + logger.info(f"MAA 启动") + hard_stop = False + while self.MAA.running(): + # 5分钟之前就停止 + if (self.tasks[0]["time"] - datetime.now()).total_seconds() < 300: + self.MAA.stop() + hard_stop = True + else: + time.sleep(0) + self.send_email('MAA停止') + if hard_stop: + logger.info(f"由于maa任务并未完成,等待3分钟重启软件") + time.sleep(180) + self.device.exit('com.hypergryph.arknights') + else: + logger.info(f"记录MAA 本次执行时间") + self.maa_config['last_execution'] = datetime.now() + logger.info(self.maa_config['last_execution']) + if self.maa_config['roguelike'] or self.maa_config['reclamation_algorithm'] or self.maa_config[ + 'stationary_security_service']: + while (self.tasks[0]["time"] - datetime.now()).total_seconds() > 30: + self.MAA = None + self.inialize_maa() + if self.maa_config['roguelike']: + self.MAA.append_task('Roguelike', { + 'mode': 0, + 'starts_count': 9999999, + 'investment_enabled': True, + 'investments_count': 9999999, + 'stop_when_investment_full': False, + 'squad': '指挥分队', + 'roles': '取长补短', + 'theme': 'Mizuki', + 'core_char': '海沫' + }) + elif self.maa_config['reclamation_algorithm']: + self.back_to_maa_config['reclamation_algorithm']() + self.MAA.append_task('ReclamationAlgorithm') + # elif self.maa_config['stationary_security_service'] : + # self.MAA.append_task('SSSCopilot', { + # 'filename': "F:\\MAA-v4.10.5-win-x64\\resource\\copilot\\SSS_阿卡胡拉丛林.json", + # 'formation': False, + # 'loop_times':99 + # }) + self.MAA.start() + while self.MAA.running(): + if (self.tasks[0]["time"] - datetime.now()).total_seconds() < 30: + self.MAA.stop() + break + else: + time.sleep(0) + self.device.exit('com.hypergryph.arknights') + # 生息演算逻辑 结束 + remaining_time = (self.tasks[0]["time"] - datetime.now()).total_seconds() + logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") + self.send_email("脚本停止") + time.sleep(remaining_time) + self.MAA = None + except Exception as e: + logger.error(e) + self.MAA = None + remaining_time = (self.tasks[0]["time"] - datetime.now()).total_seconds() + if remaining_time > 0: + logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") + time.sleep(remaining_time) + self.device.exit('com.hypergryph.arknights') + + def send_email(self, tasks): + return + try: + msg = MIMEMultipart() + conntent = str(tasks) + msg.attach(MIMEText(conntent, 'plain', 'utf-8')) + msg['Subject'] = self.email_config['subject'] + msg['From'] = self.email_config['account'] + s = smtplib.SMTP_SSL("smtp.qq.com", 465) + # 登录邮箱 + s.login(self.email_config['account'], self.email_config['pass_code']) + # 开始发送 + s.sendmail(self.email_config['account'], self.email_config['receipts'], msg.as_string()) + logger.info("邮件发送成功") + except Exception as e: + logger.error("邮件发送失败") diff --git a/arknights_mower/strategy.py b/arknights_mower/strategy.py index 1a4ec58ae..c6c2194e2 100644 --- a/arknights_mower/strategy.py +++ b/arknights_mower/strategy.py @@ -1,12 +1,11 @@ from __future__ import annotations import functools -import signal from .solvers import * +from .solvers.base_schedule import BaseSchedulerSolver from .utils import typealias as tp from .utils.device import Device -from .utils.log import logger from .utils.recognize import Recognizer from .utils.solver import BaseSolver @@ -22,20 +21,11 @@ def __init__(self, device: Device = None, recog: Recognizer = None, timeout: int self.recog = recog if recog is not None else Recognizer(self.device) self.timeout = timeout - def timer(f): - @functools.wraps(f) - def inner(self: Solver, *args, **kwargs): - def handler(signum, frame): - logger.warning('Operation timed out') - raise KeyboardInterrupt - signal.signal(signal.SIGALRM, handler) - signal.alarm(self.timeout * 3600) - ret = f(self, *args, **kwargs) - signal.alarm(0) - return ret - return inner - - @timer + + def base_scheduler (self,tasks=[],plan={},current_base={},)-> None: + # 返还所有排班计划以及 当前基建干员位置 + return BaseSchedulerSolver(self.device, self.recog).run(tasks,plan,current_base) + def base(self, arrange: tp.BasePlan = None, clue_collect: bool = False, drone_room: str = None, fia_room: str = None) -> None: """ :param arrange: dict(room_name: [agent,...]), 基建干员安排 @@ -43,25 +33,21 @@ def base(self, arrange: tp.BasePlan = None, clue_collect: bool = False, drone_ro :param drone_room: str, 是否使用无人机加速 :param fia_room: str, 是否使用菲亚梅塔恢复心情 """ - BaseConstructSolver(self.device, self.recog).run( + BaseSolver(self.device, self.recog).run( arrange, clue_collect, drone_room, fia_room) - @timer def credit(self) -> None: CreditSolver(self.device, self.recog).run() - @timer def mission(self) -> None: MissionSolver(self.device, self.recog).run() - @timer def recruit(self, priority: list[str] = None) -> None: """ :param priority: list[str], 优先考虑的公招干员,默认为高稀有度优先 """ RecruitSolver(self.device, self.recog).run(priority) - @timer def ope(self, level: str = None, times: int = -1, potion: int = 0, originite: int = 0, eliminate: int = 0, plan: list[tp.OpePlan] = None) -> list[tp.OpePlan]: """ :param level: str, 指定关卡,默认为前往上一次关卡或当前界面关卡 @@ -75,17 +61,14 @@ def ope(self, level: str = None, times: int = -1, potion: int = 0, originite: in """ return OpeSolver(self.device, self.recog).run(level, times, potion, originite, eliminate, plan) - @timer def shop(self, priority: bool = None) -> None: """ :param priority: list[str], 使用信用点购买东西的优先级, 若无指定则默认购买第一件可购买的物品 """ ShopSolver(self.device, self.recog).run(priority) - @timer def mail(self) -> None: MailSolver(self.device, self.recog).run() - @timer def index(self) -> None: BaseSolver(self.device, self.recog).back_to_index() diff --git a/arknights_mower/utils/asst.py b/arknights_mower/utils/asst.py new file mode 100644 index 000000000..e12eb266d --- /dev/null +++ b/arknights_mower/utils/asst.py @@ -0,0 +1,265 @@ +import ctypes +import os +import pathlib +import platform +import json + +from typing import Union, Dict, List, Any, Type, Optional +from enum import Enum, IntEnum, unique, auto + +JSON = Union[Dict[str, Any], List[Any], int, str, float, bool, Type[None]] + + +class InstanceOptionType(IntEnum): + touch_type = 2 + deployment_with_pause = 3 + + +class Asst: + CallBackType = ctypes.CFUNCTYPE( + None, ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p) + """ + 回调函数,使用实例可参照 my_callback + :params: + ``param1 message``: 消息类型 + ``param2 details``: json string + ``param3 arg``: 自定义参数 + """ + + @staticmethod + def load(path: Union[pathlib.Path, str], incremental_path: Optional[Union[pathlib.Path, str]] = None, user_dir: Optional[Union[pathlib.Path, str]] = None) -> bool: + """ + 加载 dll 及资源 + :params: + ``path``: DLL及资源所在文件夹路径 + ``incremental_path``: 增量资源所在文件夹路径 + ``user_dir``: 用户数据(日志、调试图片等)写入文件夹路径 + """ + + platform_values = { + 'windows': { + 'libpath': 'MaaCore.dll', + 'environ_var': 'PATH' + }, + 'darwin': { + 'libpath': 'libMaaCore.dylib', + 'environ_var': 'DYLD_LIBRARY_PATH' + }, + 'linux': { + 'libpath': 'libMaaCore.so', + 'environ_var': 'LD_LIBRARY_PATH' + } + } + lib_import_func = None + + platform_type = platform.system().lower() + if platform_type == 'windows': + lib_import_func = ctypes.WinDLL + else: + lib_import_func = ctypes.CDLL + + Asst.__libpath = pathlib.Path(path) / platform_values[platform_type]['libpath'] + try: + os.environ[platform_values[platform_type]['environ_var']] += os.pathsep + str(path) + except KeyError: + os.environ[platform_values[platform_type]['environ_var']] = os.pathsep + str(path) + Asst.__lib = lib_import_func(str(Asst.__libpath)) + Asst.__set_lib_properties() + + ret: bool = True + if user_dir: + ret &= Asst.__lib.AsstSetUserDir(str(user_dir).encode('utf-8')) + + ret &= Asst.__lib.AsstLoadResource(str(path).encode('utf-8')) + if incremental_path: + ret &= Asst.__lib.AsstLoadResource( + str(incremental_path).encode('utf-8')) + + return ret + + def __init__(self, callback: CallBackType = None, arg=None): + """ + :params: + ``callback``: 回调函数 + ``arg``: 自定义参数 + """ + + if callback: + self.__ptr = Asst.__lib.AsstCreateEx(callback, arg) + else: + self.__ptr = Asst.__lib.AsstCreate() + + def __del__(self): + Asst.__lib.AsstDestroy(self.__ptr) + self.__ptr = None + + def set_instance_option(self, option_type: InstanceOptionType, option_value: str): + """ + 设置额外配置 + 参见${MaaAssistantArknights}/src/MaaCore/Assistant.cpp#set_instance_option + :params: + ``externa_config``: 额外配置类型 + ``config_value``: 额外配置的值 + :return: 是否设置成功 + """ + return Asst.__lib.AsstSetInstanceOption(self.__ptr, + int(option_type), option_value.encode('utf-8')) + + + def connect(self, adb_path: str, address: str, config: str = 'General'): + """ + 连接设备 + :params: + ``adb_path``: adb 程序的路径 + ``address``: adb 地址+端口 + ``config``: adb 配置,可参考 resource/config.json + :return: 是否连接成功 + """ + return Asst.__lib.AsstConnect(self.__ptr, + adb_path.encode('utf-8'), address.encode('utf-8'), config.encode('utf-8')) + + TaskId = int + + def append_task(self, type_name: str, params: JSON = {}) -> TaskId: + """ + 添加任务 + :params: + ``type_name``: 任务类型,请参考 docs/集成文档.md + ``params``: 任务参数,请参考 docs/集成文档.md + :return: 任务 ID, 可用于 set_task_params 接口 + """ + return Asst.__lib.AsstAppendTask(self.__ptr, type_name.encode('utf-8'), json.dumps(params, ensure_ascii=False).encode('utf-8')) + + def set_task_params(self, task_id: TaskId, params: JSON) -> bool: + """ + 动态设置任务参数 + :params: + ``task_id``: 任务 ID, 使用 append_task 接口的返回值 + ``params``: 任务参数,同 append_task 接口,请参考 docs/集成文档.md + :return: 是否成功 + """ + return Asst.__lib.AsstSetTaskParams(self.__ptr, task_id, json.dumps(params, ensure_ascii=False).encode('utf-8')) + + def start(self) -> bool: + """ + 开始任务 + :return: 是否成功 + """ + return Asst.__lib.AsstStart(self.__ptr) + + def stop(self) -> bool: + """ + 停止并清空所有任务 + :return: 是否成功 + """ + return Asst.__lib.AsstStop(self.__ptr) + + def running(self) -> bool: + """ + 是否正在运行 + :return: 是否正在运行 + """ + return Asst.__lib.AsstRunning(self.__ptr) + + @staticmethod + def log(level: str, message: str) -> None: + ''' + 打印日志 + :params: + ``level``: 日志等级标签 + ``message``: 日志内容 + ''' + + Asst.__lib.AsstLog(level.encode('utf-8'), message.encode('utf-8')) + + def get_version(self) -> str: + """ + 获取DLL版本号 + : return: 版本号 + """ + return Asst.__lib.AsstGetVersion().decode('utf-8') + + @staticmethod + def __set_lib_properties(): + Asst.__lib.AsstSetUserDir.restype = ctypes.c_bool + Asst.__lib.AsstSetUserDir.argtypes = ( + ctypes.c_char_p,) + + Asst.__lib.AsstLoadResource.restype = ctypes.c_bool + Asst.__lib.AsstLoadResource.argtypes = ( + ctypes.c_char_p,) + + Asst.__lib.AsstCreate.restype = ctypes.c_void_p + Asst.__lib.AsstCreate.argtypes = () + + Asst.__lib.AsstCreateEx.restype = ctypes.c_void_p + Asst.__lib.AsstCreateEx.argtypes = ( + ctypes.c_void_p, ctypes.c_void_p,) + + Asst.__lib.AsstDestroy.argtypes = (ctypes.c_void_p,) + + Asst.__lib.AsstSetInstanceOption.restype = ctypes.c_bool + Asst.__lib.AsstSetInstanceOption.argtypes = ( + ctypes.c_void_p, ctypes.c_int, ctypes.c_char_p,) + + Asst.__lib.AsstConnect.restype = ctypes.c_bool + Asst.__lib.AsstConnect.argtypes = ( + ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p,) + + Asst.__lib.AsstAppendTask.restype = ctypes.c_int + Asst.__lib.AsstAppendTask.argtypes = ( + ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p) + + Asst.__lib.AsstSetTaskParams.restype = ctypes.c_bool + Asst.__lib.AsstSetTaskParams.argtypes = ( + ctypes.c_void_p, ctypes.c_int, ctypes.c_char_p) + + Asst.__lib.AsstStart.restype = ctypes.c_bool + Asst.__lib.AsstStart.argtypes = (ctypes.c_void_p,) + + Asst.__lib.AsstStop.restype = ctypes.c_bool + Asst.__lib.AsstStop.argtypes = (ctypes.c_void_p,) + + Asst.__lib.AsstRunning.restype = ctypes.c_bool + Asst.__lib.AsstRunning.argtypes = (ctypes.c_void_p,) + + Asst.__lib.AsstGetVersion.restype = ctypes.c_char_p + + Asst.__lib.AsstLog.restype = None + Asst.__lib.AsstLog.argtypes = ( + ctypes.c_char_p, ctypes.c_char_p) + + +@unique +class Message(Enum): + """ + 回调消息 + 请参考 docs/回调消息.md + """ + InternalError = 0 + + InitFailed = auto() + + ConnectionInfo = auto() + + AllTasksCompleted = auto() + + TaskChainError = 10000 + + TaskChainStart = auto() + + TaskChainCompleted = auto() + + TaskChainExtraInfo = auto() + + TaskChainStopped = auto() + + SubTaskError = 20000 + + SubTaskStart = auto() + + SubTaskCompleted = auto() + + SubTaskExtraInfo = auto() + + SubTaskStopped = auto() \ No newline at end of file diff --git a/arknights_mower/utils/character_recognize.py b/arknights_mower/utils/character_recognize.py index ca8cd1e9f..7b11d7ab1 100644 --- a/arknights_mower/utils/character_recognize.py +++ b/arknights_mower/utils/character_recognize.py @@ -14,6 +14,7 @@ from .image import saveimg from .log import logger from .recognize import RecognizeError +from ..ocr import ocrhandle def poly_center(poly): @@ -62,14 +63,15 @@ def agent_sift_init(): origin_kp, origin_des = SIFT.detectAndCompute(origin, None) -def sift_recog(query, resolution, draw=False): +def sift_recog(query, resolution, draw=False,reverse = False): """ 使用 SIFT 提取特征点识别干员名称 """ agent_sift_init() - query = cv2.cvtColor(np.array(query), cv2.COLOR_RGB2GRAY) - + if reverse : + # 干员总览界面图像色度反转 + query = 255 -query # the height & width of query image height, width = query.shape @@ -153,37 +155,50 @@ def agent(img, draw=False): if in_poly(poly, (cx + x0, cy)) and cx > fx: fx = cx found_ocr = x - - if found_ocr is not None: - x = found_ocr - if x[1] in agent_list and x[1] not in ['砾', '陈']: # ocr 经常会把这两个搞错 - ret_agent.append(x[1]) - ret_succ.append(poly) - continue - __img = img[poly[0, 1] : poly[2, 1], poly[0, 0] : poly[2, 0]] - res = sift_recog(__img, resolution, draw) - if res is not None: - logger.debug(f'干员名称识别修正:{x[1]} -> {res}') - ret_agent.append(res) - ret_succ.append(poly) - continue - logger.warning( - f'干员名称识别异常:{x[1]} 为不存在的数据,请报告至 https://github.com/Konano/arknights-mower/issues' - ) - saveimg(__img, 'failure_agent') - else: - __img = img[poly[0, 1] : poly[2, 1], poly[0, 0] : poly[2, 0]] - if 80 <= np.min(__img): - continue - res = sift_recog(__img, resolution, draw) - if res is not None: - ret_agent.append(res) + __img = img[poly[0, 1]: poly[2, 1], poly[0, 0]: poly[2, 0]] + try: + if found_ocr is not None: + x = found_ocr + if x[1] in agent_list and x[1] not in ['砾', '陈']: # ocr 经常会把这两个搞错 + ret_agent.append(x[1]) + ret_succ.append(poly) + continue + res = sift_recog(__img, resolution, draw) + if (res is not None) and res in agent_list: + ret_agent.append(res) + ret_succ.append(poly) + continue + logger.warning( + f'干员名称识别异常:{x[1]} 为不存在的数据,请报告至 https://github.com/Konano/arknights-mower/issues' + ) + saveimg(__img, 'failure_agent') + raise Exception("启动 Plan B") + else: + if 80 <= np.min(__img): + continue + res = sift_recog(__img, resolution, draw) + if res is not None: + ret_agent.append(res) + ret_succ.append(poly) + continue + logger.warning(f'干员名称识别异常:区域 {poly.tolist()}') + saveimg(__img, 'failure_agent') + raise Exception("启动 Plan B") + ret_fail.append(poly) + raise Exception("启动 Plan B") + except Exception as e: + # 大哥不行了,二哥上! + name = segment.read_screen(__img, + langurage="chi_sim", + type="text") + logger.warning(f'备选方案识别结果: {name}') + ret_fail.append(poly) + if name in ocr_error.keys(): + name = ocr_error[name] + else: + ret_agent.append(name) ret_succ.append(poly) continue - logger.warning(f'干员名称识别异常:区域 {poly.tolist()}') - saveimg(__img, 'failure_agent') - ret_fail.append(poly) - if len(ret_fail): saveimg(img, 'failure') if draw: @@ -198,4 +213,26 @@ def agent(img, draw=False): except Exception as e: logger.debug(traceback.format_exc()) + saveimg(img, 'failure_agent') raise RecognizeError(e) + +def agent_name(__img, height,reverse = False, draw: bool = False): + query = cv2.cvtColor(np.array(__img), cv2.COLOR_RGB2GRAY) + h, w= query.shape + dim = (w*4, h*4) + # resize image + resized = cv2.resize(__img, dim, interpolation=cv2.INTER_AREA) + ocr = ocrhandle.predict(resized) + name = '' + try: + if len(ocr) > 0 and ocr[0][1] in agent_list and ocr[0][1] not in ['砾', '陈']: + name = ocr[0][1] + else: + res = sift_recog(__img, height, draw, reverse) + if (res is not None) and res in agent_list: + name = res + else: + raise Exception("识别错误") + except Exception as e: + saveimg(__img, 'failure_agent') + return name diff --git a/arknights_mower/utils/datetime.py b/arknights_mower/utils/datetime.py index d19001044..735844cc7 100644 --- a/arknights_mower/utils/datetime.py +++ b/arknights_mower/utils/datetime.py @@ -1,7 +1,14 @@ -import datetime +from datetime import datetime +import pytz - -def the_same_day(a: datetime.datetime = None, b: datetime.datetime = None) -> bool: +def the_same_day(a: datetime = None, b: datetime = None) -> bool: if a is None or b is None: return False return a.year == b.year and a.month == b.month and a.day == b.day + +def the_same_time(a: datetime = None, b: datetime = None) -> bool: + if a is None or b is None: + return False + return a.year == b.year and a.month == b.month and a.day == b.day and a.hour ==b.hour and a.minute==b.minute and a.second == b.second +def get_server_weekday(): + return datetime.now(pytz.timezone('Asia/Dubai')).weekday() \ No newline at end of file diff --git a/arknights_mower/utils/device/adb_client/core.py b/arknights_mower/utils/device/adb_client/core.py index 630fe1689..2eed6d64a 100644 --- a/arknights_mower/utils/device/adb_client/core.py +++ b/arknights_mower/utils/device/adb_client/core.py @@ -41,10 +41,10 @@ def __init_adb(self) -> None: def __init_device(self) -> None: # wait for the newly started ADB server to probe emulators time.sleep(1) - if self.device_id is None: + if self.device_id is None or self.device_id not in config.ADB_DEVICE: self.device_id = self.__choose_devices() - if self.device_id is None: - if self.connect is None: + if self.device_id is None or self.device_id not in config.ADB_DEVICE: + if self.connect is None or self.device_id not in config.ADB_CONNECT: for connect in config.ADB_CONNECT: Session().connect(connect) else: @@ -60,8 +60,9 @@ def __choose_devices(self) -> Optional[str]: for device in config.ADB_DEVICE: if device in devices: return device - if len(devices) > 0: - return devices[0] + # if len(devices) > 0: + # logger.debug(devices[0]) + # return devices[0] def __available_devices(self) -> list[str]: """ return available devices """ diff --git a/arknights_mower/utils/device/device.py b/arknights_mower/utils/device/device.py index df7a373ab..f406c57f6 100644 --- a/arknights_mower/utils/device/device.py +++ b/arknights_mower/utils/device/device.py @@ -82,6 +82,10 @@ def launch(self, app: str) -> None: """ launch the application """ self.run(f'am start -n {app}') + def exit(self, app: str) -> None: + """ launch the application """ + self.run(f'am force-stop {app}') + def send_keyevent(self, keycode: int) -> None: """ send a key event """ logger.debug(f'keyevent: {keycode}') diff --git a/arknights_mower/utils/device/utils.py b/arknights_mower/utils/device/utils.py index b412d2d43..2a203b80e 100644 --- a/arknights_mower/utils/device/utils.py +++ b/arknights_mower/utils/device/utils.py @@ -1,5 +1,6 @@ from __future__ import annotations +import http import socket import tempfile @@ -12,13 +13,12 @@ def download_file(target_url: str) -> str: """ download file to temp path, and return its file path for further usage """ logger.debug(f'downloading: {target_url}') - resp = requests.get(target_url) + resp = requests.get(target_url, verify=False) with tempfile.NamedTemporaryFile('wb+', delete=False) as f: file_name = f.name f.write(resp.content) return file_name - # def is_port_using(host: str, port: int) -> bool: # """ if port is using by others, return True. else return False """ # s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) diff --git a/arknights_mower/utils/log.py b/arknights_mower/utils/log.py index bce9e1d17..2ef280eb8 100644 --- a/arknights_mower/utils/log.py +++ b/arknights_mower/utils/log.py @@ -7,7 +7,6 @@ from pathlib import Path import colorlog - from . import config BASIC_FORMAT = '%(asctime)s - %(levelname)s - %(relativepath)s:%(lineno)d - %(funcName)s - %(message)s' @@ -40,6 +39,16 @@ def filter(self, record: logging.LogRecord) -> bool: return True +class Handler(logging.StreamHandler): + def __init__(self, pipe): + logging.StreamHandler.__init__(self) + self.pipe = pipe + + def emit(self, record): + record = f'{record.message}' + self.pipe.send(record) + + chlr = logging.StreamHandler(stream=sys.stdout) chlr.setFormatter(color_formatter) chlr.setLevel('INFO') @@ -57,7 +66,7 @@ def filter(self, record: logging.LogRecord) -> bool: logger.addHandler(ehlr) -def init_fhlr() -> None: +def init_fhlr(pipe) -> None: """ initialize log file """ if config.LOGFILE_PATH is None: return @@ -73,6 +82,10 @@ def init_fhlr() -> None: fhlr.setLevel('DEBUG') fhlr.addFilter(PackagePathFilter()) logger.addHandler(fhlr) + if pipe is not None: + wh = Handler(pipe) + wh.setLevel(logging.INFO) + logger.addHandler(wh) def set_debug_mode() -> None: @@ -113,3 +126,5 @@ def run(self) -> None: while True: line = self.pipe.readline().strip() logger.debug(f'{self.process}: {line}') + + diff --git a/arknights_mower/utils/recognize.py b/arknights_mower/utils/recognize.py index 6e275a077..8bd2fca39 100644 --- a/arknights_mower/utils/recognize.py +++ b/arknights_mower/utils/recognize.py @@ -69,6 +69,8 @@ def get_scene(self) -> int: self.scene = Scene.INDEX elif self.find('nav_index') is not None: self.scene = Scene.NAVIGATION_BAR + elif self.find('close_mine') is not None: + self.scene = Scene.CLOSE_MINE elif self.find('materiel_ico') is not None: self.scene = Scene.MATERIEL elif self.find('read_mail') is not None: @@ -215,6 +217,51 @@ def get_scene(self) -> int: logger.info(f'Scene: {self.scene}: {SceneComment[self.scene]}') return self.scene + def get_infra_scene(self)-> int: + if self.scene != Scene.UNDEFINED: + return self.scene + if self.find('connecting', scope=((self.w//2, self.h//10*8), (self.w//4*3, self.h))) is not None: + self.scene = Scene.CONNECTING + elif self.find('double_confirm') is not None: + if self.find('network_check') is not None: + self.scene = Scene.NETWORK_CHECK + else: + self.scene = Scene.DOUBLE_CONFIRM + elif self.find('infra_overview') is not None: + self.scene = Scene.INFRA_MAIN + elif self.find('infra_todo') is not None: + self.scene = Scene.INFRA_TODOLIST + elif self.find('clue') is not None: + self.scene = Scene.INFRA_CONFIDENTIAL + elif self.find('arrange_check_in') or self.find('arrange_check_in_on') is not None: + self.scene = Scene.INFRA_DETAILS + elif self.find('infra_overview_in') is not None: + self.scene = Scene.INFRA_ARRANGE + elif self.find('arrange_confirm') is not None: + self.scene = Scene.INFRA_ARRANGE_CONFIRM + elif self.find('arrange_order_options_scene') is not None: + self.scene = Scene.INFRA_ARRANGE_ORDER + elif self.find('loading') is not None: + self.scene = Scene.LOADING + elif self.find('loading2') is not None: + self.scene = Scene.LOADING + elif self.find('loading3') is not None: + self.scene = Scene.LOADING + elif self.find('loading4') is not None: + self.scene = Scene.LOADING + elif self.find('index_nav', thres=250, scope=((0, 0), (100+self.w//4, self.h//10))) is not None: + self.scene = Scene.INDEX + elif self.is_black(): + self.scene = Scene.LOADING + else: + self.scene = Scene.UNKNOWN + self.device.check_current_focus() + # save screencap to analyse + if config.SCREENSHOT_PATH is not None: + self.save_screencap(self.scene) + logger.info(f'Scene: {self.scene}: {SceneComment[self.scene]}') + return self.scene + def is_black(self) -> None: """ check if the current scene is all black """ return np.max(self.gray[:, 105:-105]) < 16 diff --git a/arknights_mower/utils/segment.py b/arknights_mower/utils/segment.py index 6c7ac0038..c6b6b28fb 100644 --- a/arknights_mower/utils/segment.py +++ b/arknights_mower/utils/segment.py @@ -5,14 +5,14 @@ import cv2 import numpy as np from matplotlib import pyplot as plt - -from .. import __rootdir__ +from .image import saveimg from ..data import agent_list from ..ocr import ocrhandle from . import detector from . import typealias as tp from .log import logger from .recognize import RecognizeError +import pytesseract class FloodCheckFailed(Exception): @@ -22,10 +22,10 @@ class FloodCheckFailed(Exception): def get_poly(x1: int, x2: int, y1: int, y2: int) -> tp.Rectangle: x1, x2 = int(x1), int(x2) y1, y2 = int(y1), int(y2) - return np.array([[x1, y1], [x1, y2], [x2, y2], [x2, y1]]) + return np.array([ [ x1, y1 ], [ x1, y2 ], [ x2, y2 ], [ x2, y1 ] ]) -def credit(img: tp.Image, draw: bool = False) -> list[tp.Scope]: +def credit(img: tp.Image, draw: bool = False) -> list[ tp.Scope ]: """ 信用交易所特供的图像分割算法 """ @@ -33,25 +33,25 @@ def credit(img: tp.Image, draw: bool = False) -> list[tp.Scope]: height, width, _ = img.shape left, right = 0, width - while np.max(img[:, right-1]) < 100: + while np.max(img[ :, right - 1 ]) < 100: right -= 1 - while np.max(img[:, left]) < 100: + while np.max(img[ :, left ]) < 100: left += 1 def average(i: int) -> int: num, sum = 0, 0 for j in range(left, right): - if img[i, j, 0] == img[i, j, 1] and img[i, j, 1] == img[i, j, 2]: + if img[ i, j, 0 ] == img[ i, j, 1 ] and img[ i, j, 1 ] == img[ i, j, 2 ]: num += 1 - sum += img[i, j, 0] + sum += img[ i, j, 0 ] return sum // num def ptp(j: int) -> int: maxval = -999999 minval = 999999 for i in range(up_1, up_2): - minval = min(minval, img[i, j, 0]) - maxval = max(maxval, img[i, j, 0]) + minval = min(minval, img[ i, j, 0 ]) + maxval = max(maxval, img[ i, j, 0 ]) return maxval - minval up_1 = 0 @@ -78,18 +78,18 @@ def ptp(j: int) -> int: while ptp(left) < 50: left += 1 - split_x = [left + (right - left) // 5 * i for i in range(0, 6)] - split_y = [up_1, (up_1 + down) // 2, down] + split_x = [ left + (right - left) // 5 * i for i in range(0, 6) ] + split_y = [ up_1, (up_1 + down) // 2, down ] - ret = [] - for y1, y2 in zip(split_y[:-1], split_y[1:]): - for x1, x2 in zip(split_x[:-1], split_x[1:]): + ret = [ ] + for y1, y2 in zip(split_y[ :-1 ], split_y[ 1: ]): + for x1, x2 in zip(split_x[ :-1 ], split_x[ 1: ]): ret.append(((x1, y1), (x2, y2))) if draw: - for y1, y2 in zip(split_y[:-1], split_y[1:]): - for x1, x2 in zip(split_x[:-1], split_x[1:]): - cv2.polylines(img, [get_poly(x1, x2, y1, y2)], + for y1, y2 in zip(split_y[ :-1 ], split_y[ 1: ]): + for x1, x2 in zip(split_x[ :-1 ], split_x[ 1: ]): + cv2.polylines(img, [ get_poly(x1, x2, y1, y2) ], True, 0, 10, cv2.LINE_AA) plt.imshow(img) plt.show() @@ -102,13 +102,13 @@ def ptp(j: int) -> int: raise RecognizeError(e) -def recruit(img: tp.Image, draw: bool = False) -> list[tp.Scope]: +def recruit(img: tp.Image, draw: bool = False) -> list[ tp.Scope ]: """ 公招特供的图像分割算法 """ try: height, width, _ = img.shape - left, right = width//2-100, width//2-50 + left, right = width // 2 - 100, width // 2 - 50 def adj_x(i: int) -> int: if i == 0: @@ -116,8 +116,8 @@ def adj_x(i: int) -> int: sum = 0 for j in range(left, right): for k in range(3): - sum += abs(int(img[i, j, k]) - int(img[i-1, j, k])) - return sum // (right-left) + sum += abs(int(img[ i, j, k ]) - int(img[ i - 1, j, k ])) + return sum // (right - left) def adj_y(j: int) -> int: if j == 0: @@ -125,54 +125,54 @@ def adj_y(j: int) -> int: sum = 0 for i in range(up_2, down_2): for k in range(3): - sum += abs(int(img[i, j, k]) - int(img[i, j-1, k])) - return int(sum / (down_2-up_2)) + sum += abs(int(img[ i, j, k ]) - int(img[ i, j - 1, k ])) + return int(sum / (down_2 - up_2)) def average(i: int) -> int: sum = 0 for j in range(left, right): - sum += np.sum(img[i, j, :3]) - return sum // (right-left) // 3 + sum += np.sum(img[ i, j, :3 ]) + return sum // (right - left) // 3 def minus(i: int) -> int: s = 0 for j in range(left, right): - s += int(img[i, j, 2]) - int(img[i, j, 0]) - return s // (right-left) + s += int(img[ i, j, 2 ]) - int(img[ i, j, 0 ]) + return s // (right - left) up = 0 while minus(up) > -100: up += 1 while not (adj_x(up) > 80 and minus(up) > -10 and average(up) > 210): up += 1 - up_2, down_2 = up-90, up-40 + up_2, down_2 = up - 90, up - 40 left = 0 - while np.max(img[:, left]) < 100: + while np.max(img[ :, left ]) < 100: left += 1 left += 1 while adj_y(left) < 50: left += 1 right = width - 1 - while np.max(img[:, right]) < 100: + while np.max(img[ :, right ]) < 100: right -= 1 while adj_y(right) < 50: right -= 1 - split_x = [left, (left + right) // 2, right] + split_x = [ left, (left + right) // 2, right ] down = height - 1 - split_y = [up, (up + down) // 2, down] + split_y = [ up, (up + down) // 2, down ] - ret = [] - for y1, y2 in zip(split_y[:-1], split_y[1:]): - for x1, x2 in zip(split_x[:-1], split_x[1:]): + ret = [ ] + for y1, y2 in zip(split_y[ :-1 ], split_y[ 1: ]): + for x1, x2 in zip(split_x[ :-1 ], split_x[ 1: ]): ret.append(((x1, y1), (x2, y2))) if draw: - for y1, y2 in zip(split_y[:-1], split_y[1:]): - for x1, x2 in zip(split_x[:-1], split_x[1:]): - cv2.polylines(img, [get_poly(x1, x2, y1, y2)], + for y1, y2 in zip(split_y[ :-1 ], split_y[ 1: ]): + for x1, x2 in zip(split_x[ :-1 ], split_x[ 1: ]): + cv2.polylines(img, [ get_poly(x1, x2, y1, y2) ], True, 0, 10, cv2.LINE_AA) plt.imshow(img) plt.show() @@ -185,22 +185,22 @@ def minus(i: int) -> int: raise RecognizeError(e) -def base(img: tp.Image, central: tp.Scope, draw: bool = False) -> dict[str, tp.Rectangle]: +def base(img: tp.Image, central: tp.Scope, draw: bool = False) -> dict[ str, tp.Rectangle ]: """ 基建布局的图像分割算法 """ try: ret = {} - x1, y1 = central[0] - x2, y2 = central[1] + x1, y1 = central[ 0 ] + x2, y2 = central[ 1 ] alpha = (y2 - y1) / 160 x1 -= 170 * alpha x2 += 182 * alpha y1 -= 67 * alpha y2 += 67 * alpha central = get_poly(x1, x2, y1, y2) - ret['central'] = central + ret[ 'central' ] = central for i in range(1, 5): y1 = y2 + 25 * alpha @@ -209,51 +209,51 @@ def base(img: tp.Image, central: tp.Scope, draw: bool = False) -> dict[str, tp.R dormitory = get_poly(x1, x2 - 158 * alpha, y1, y2) else: dormitory = get_poly(x1 + 158 * alpha, x2, y1, y2) - ret[f'dormitory_{i}'] = dormitory + ret[ f'dormitory_{i}' ] = dormitory - x1, y1 = ret['dormitory_1'][0] - x2, y2 = ret['dormitory_1'][2] + x1, y1 = ret[ 'dormitory_1' ][ 0 ] + x2, y2 = ret[ 'dormitory_1' ][ 2 ] x1 = x2 + 419 * alpha x2 = x1 + 297 * alpha factory = get_poly(x1, x2, y1, y2) - ret[f'factory'] = factory + ret[ f'factory' ] = factory y2 = y1 - 25 * alpha y1 = y2 - 134 * alpha meeting = get_poly(x1 - 158 * alpha, x2, y1, y2) - ret[f'meeting'] = meeting + ret[ f'meeting' ] = meeting y1 = y2 + 25 * alpha y2 = y1 + 134 * alpha y1 = y2 + 25 * alpha y2 = y1 + 134 * alpha contact = get_poly(x1, x2, y1, y2) - ret[f'contact'] = contact + ret[ f'contact' ] = contact y1 = y2 + 25 * alpha y2 = y1 + 134 * alpha train = get_poly(x1, x2, y1, y2) - ret[f'train'] = train + ret[ f'train' ] = train for floor in range(1, 4): - x1, y1 = ret[f'dormitory_{floor}'][0] - x2, y2 = ret[f'dormitory_{floor}'][2] + x1, y1 = ret[ f'dormitory_{floor}' ][ 0 ] + x2, y2 = ret[ f'dormitory_{floor}' ][ 2 ] x2 = x1 - 102 * alpha x1 = x2 - 295 * alpha if floor & 1 == 0: x2 = x1 - 24 * alpha x1 = x2 - 295 * alpha room = get_poly(x1, x2, y1, y2) - ret[f'room_{floor}_3'] = room + ret[ f'room_{floor}_3' ] = room x2 = x1 - 24 * alpha x1 = x2 - 295 * alpha room = get_poly(x1, x2, y1, y2) - ret[f'room_{floor}_2'] = room + ret[ f'room_{floor}_2' ] = room x2 = x1 - 24 * alpha x1 = x2 - 295 * alpha room = get_poly(x1, x2, y1, y2) - ret[f'room_{floor}_1'] = room + ret[ f'room_{floor}_1' ] = room if draw: polys = list(ret.values()) @@ -267,9 +267,70 @@ def base(img: tp.Image, central: tp.Scope, draw: bool = False) -> dict[str, tp.R except Exception as e: logger.debug(traceback.format_exc()) raise RecognizeError(e) - - -def worker(img: tp.Image, draw: bool = False) -> tuple[list[tp.Rectangle], tp.Rectangle, bool]: +def read_screen(img, type="mood", langurage="eng", limit=24, cord=None, change_color=False, draw=False) -> int: + if cord is not None : + img = img[ cord[1]:cord[3], cord[0]:cord[2] ] + if 'mood' in type or type=="time": + # 心情图片太小,复制8次提高准确率 + for x in range(0, 4): + img = cv2.vconcat([img, img]) + if change_color: img[img == 137] = 255 + if draw : plt.imshow(img) + if "mood" in type or type=='time': + try: + _config = r'-c tessedit_char_whitelist=0123456789: --psm 6' + if type=='mood':_config = r'-c tessedit_char_whitelist=0123456789/- --psm 6' + text = pytesseract.image_to_data(img,lang=langurage, config=_config, output_type='data.frame' ) + if type =='time': saveimg(img, 'data') + text = text[text.conf != -1] + lines = text.groupby(['page_num', 'block_num', 'par_num', 'line_num'], group_keys=True)['text'] \ + .apply(lambda x: ' '.join(map(str,list(x)))).tolist() + confs = text.groupby(['page_num', 'block_num', 'par_num', 'line_num'], group_keys=True)['conf'].mean().tolist() + line_conf = [] + for i in range(len(lines)): + if lines[i].strip(): + line_conf.append((lines[i], round(confs[i], 3))) + logger.debug(str(line_conf)) + __str = '' + if 'mood' in type: + if line_conf[len(line_conf) - 1][1] > 0.0 or (max(line_conf, key=lambda tup: tup[1])) == 0.0 or limit == 200: + __str=line_conf[len(line_conf) - 1][0] + else: + __str = (max(line_conf, key=lambda tup: tup[1]))[0] + else: + _data = (max(line_conf, key=lambda tup: tup[1])) + if _data[1]<70.0: + __str = line_conf[len(line_conf) - 1][0] + else: + __str = _data[0] + if '.0' in __str: + __str = __str[0:__str.index('.0')] + # else: + # # 时间就找最大出现次数 + # x = [i[0] for i in line_conf] + # __str = max(set(x), key=x.count) + if "mood" in type: + idx = 4 + if type =='mood' : idx = 3 + __str = __str[0:len(__str)-idx] + if '/' in __str: + __str= __str[0:__str.index('/')] + if ''==__str: + return 0 + number = int(__str) + if number>limit: + saveimg(img, 'error_mood') + return limit + return number + else: + return __str + except Exception as e: + # 空的时候是没人在基建 + logger.warning(f'读取错误:{__str}') + saveimg(img, 'error_mood') + return -1 + +def worker(img: tp.Image, draw: bool = False) -> tuple[ list[ tp.Rectangle ], tp.Rectangle, bool ]: """ 进驻总览的图像分割算法 """ @@ -277,23 +338,23 @@ def worker(img: tp.Image, draw: bool = False) -> tuple[list[tp.Rectangle], tp.Re height, width, _ = img.shape left, right = 0, width - while np.max(img[:, right-1]) < 100: + while np.max(img[ :, right - 1 ]) < 100: right -= 1 - while np.max(img[:, left]) < 100: + while np.max(img[ :, left ]) < 100: left += 1 - x0 = right-1 - while np.average(img[:, x0, 1]) >= 100: + x0 = right - 1 + while np.average(img[ :, x0, 1 ]) >= 100: x0 -= 1 x0 -= 2 - seg = [] + seg = [ ] remove_mode = False - pre, st = int(img[0, x0, 1]), 0 + pre, st = int(img[ 0, x0, 1 ]), 0 for y in range(1, height): - remove_mode |= int(img[y, x0, 0]) - int(img[y, x0, 1]) > 40 - if np.ptp(img[y, x0]) <= 1 or int(img[y, x0, 0]) - int(img[y, x0, 1]) > 40: - now = int(img[y, x0, 1]) + remove_mode |= int(img[ y, x0, 0 ]) - int(img[ y, x0, 1 ]) > 40 + if np.ptp(img[ y, x0 ]) <= 1 or int(img[ y, x0, 0 ]) - int(img[ y, x0, 1 ]) > 40: + now = int(img[ y, x0, 1 ]) if abs(now - pre) > 20: if now < pre and st == 0: st = y @@ -308,27 +369,27 @@ def worker(img: tp.Image, draw: bool = False) -> tuple[list[tp.Rectangle], tp.Re # seg.append((st, height)) logger.debug(f'segment.worker: seg {seg}') - remove_button = get_poly(x0-10, x0, seg[0][0], seg[0][1]) - seg = seg[1:] + remove_button = get_poly(x0 - 10, x0, seg[ 0 ][ 0 ], seg[ 0 ][ 1 ]) + seg = seg[ 1: ] for i in range(1, len(seg)): - if seg[i][1] - seg[i][0] > 9: + if seg[ i ][ 1 ] - seg[ i ][ 0 ] > 9: x1 = x0 - while img[seg[i][1]-3, x1-1, 2] < 100: + while img[ seg[ i ][ 1 ] - 3, x1 - 1, 2 ] < 100: x1 -= 1 break - ret = [] + ret = [ ] for i in range(len(seg)): - if seg[i][1] - seg[i][0] > 9: - ret.append(get_poly(x1, x0, seg[i][0], seg[i][1])) + if seg[ i ][ 1 ] - seg[ i ][ 0 ] > 9: + ret.append(get_poly(x1, x0, seg[ i ][ 0 ], seg[ i ][ 1 ])) if draw: cv2.polylines(img, ret, True, (255, 0, 0), 10, cv2.LINE_AA) plt.imshow(img) plt.show() - logger.debug(f'segment.worker: {[x.tolist() for x in ret]}') + logger.debug(f'segment.worker: {[ x.tolist() for x in ret ]}') return ret, remove_button, remove_mode except Exception as e: @@ -346,35 +407,38 @@ def agent(img, draw=False): left, right = 0, width # 异形屏适配 - while np.max(img[:, right-1]) < 100: + while np.max(img[ :, right - 1 ]) < 100: right -= 1 - while np.max(img[:, left]) < 100: + while np.max(img[ :, left ]) < 100: left += 1 # 去除左侧干员详情 x0 = left + 1 - while not (img[height-1, x0-1, 0] > img[height-1, x0, 0] + 10 and abs(int(img[height-1, x0, 0]) - int(img[height-1, x0+1, 0])) < 5): + while not (img[ height - 1, x0 - 1, 0 ] > img[ height - 1, x0, 0 ] + 10 and abs( + int(img[ height - 1, x0, 0 ]) - int(img[ height - 1, x0 + 1, 0 ])) < 5): x0 += 1 # ocr 初步识别干员名称 - ocr = ocrhandle.predict(img[:, x0:right]) + ocr = ocrhandle.predict(img[ :, x0:right ]) # 收集成功识别出来的干员名称识别结果,提取 y 范围,并将重叠的范围进行合并 - segs = [(min(x[2][0][1], x[2][1][1]), max(x[2][2][1], x[2][3][1])) - for x in ocr if x[1] in agent_list] + segs = [ (min(x[ 2 ][ 0 ][ 1 ], x[ 2 ][ 1 ][ 1 ]), max(x[ 2 ][ 2 ][ 1 ], x[ 2 ][ 3 ][ 1 ])) + for x in ocr if x[ 1 ] in agent_list ] while True: _a, _b = None, None for i in range(len(segs)): for j in range(len(segs)): - if i != j and (segs[i][0] <= segs[j][0] <= segs[i][1] or segs[i][0] <= segs[j][1] <= segs[i][1]): - _a, _b = segs[i], segs[j] + if i != j and ( + segs[ i ][ 0 ] <= segs[ j ][ 0 ] <= segs[ i ][ 1 ] or segs[ i ][ 0 ] <= segs[ j ][ 1 ] <= + segs[ i ][ 1 ]): + _a, _b = segs[ i ], segs[ j ] break if _b is not None: break if _b is not None: segs.remove(_a) segs.remove(_b) - segs.append((min(_a[0], _b[0]), max(_a[1], _b[1]))) + segs.append((min(_a[ 0 ], _b[ 0 ]), max(_a[ 1 ], _b[ 1 ]))) else: break segs = sorted(segs) @@ -382,41 +446,41 @@ def agent(img, draw=False): # 计算纵向的四个高度,[y0, y1] 是第一行干员名称的纵向坐标范围,[y2, y3] 是第二行干员名称的纵向坐标范围 y0 = y1 = y2 = y3 = None for x in segs: - if x[1] < height // 2: # FIXME 是否需要改成 x[0] + if x[ 1 ] < height // 2: y0, y1 = x else: y2, y3 = x if y0 is None or y2 is None: raise RecognizeError hpx = y1 - y0 # 卡片上干员名称的高度 - logger.debug((segs, [y0, y1, y2, y3])) + logger.debug((segs, [ y0, y1, y2, y3 ])) # 预计算:横向坐标范围集合 x_set = set() for x in ocr: - if x[1] in agent_list and (y0 <= x[2][0][1] <= y1 or y2 <= x[2][0][1] <= y3): + if x[ 1 ] in agent_list and (y0 <= x[ 2 ][ 0 ][ 1 ] <= y1 or y2 <= x[ 2 ][ 0 ][ 1 ] <= y3): # 只考虑矩形右边端点 - x_set.add(x[2][1][0]) - x_set.add(x[2][2][0]) + x_set.add(x[ 2 ][ 1 ][ 0 ]) + x_set.add(x[ 2 ][ 2 ][ 0 ]) x_set = sorted(x_set) logger.debug(x_set) # 排除掉一些重叠的范围,获得最终的横向坐标范围 gap = 160 * (resolution / 1080) # 卡片宽度下限 - x_set = [x_set[0]] + \ - [y for x, y in zip(x_set[:-1], x_set[1:]) if y - x > gap] - gap = [y - x for x, y in zip(x_set[:-1], x_set[1:])] + x_set = [ x_set[ 0 ] ] + \ + [ y for x, y in zip(x_set[ :-1 ], x_set[ 1: ]) if y - x > gap ] + gap = [ y - x for x, y in zip(x_set[ :-1 ], x_set[ 1: ]) ] logger.debug(sorted(gap)) gap = int(np.median(gap)) # 干员卡片宽度 - for x, y in zip(x_set[:-1], x_set[1:]): + for x, y in zip(x_set[ :-1 ], x_set[ 1: ]): if y - x > gap: gap_num = round((y - x) / gap) for i in range(1, gap_num): x_set.append(int(x + (y - x) / gap_num * i)) x_set = sorted(x_set) - if x_set[-1] - x_set[-2] < gap: + if x_set[ -1 ] - x_set[ -2 ] < gap: # 如果最后一个间隔不足宽度则丢弃,避免出现「梅尔」只露出一半识别成「梅」算作成功识别的情况 - x_set = x_set[:-1] + x_set = x_set[ :-1 ] while np.min(x_set) > 0: x_set.append(np.min(x_set) - gap) while np.max(x_set) < right - x0: @@ -425,11 +489,11 @@ def agent(img, draw=False): logger.debug(x_set) # 获得所有的干员名称对应位置 - ret = [] - for x1, x2 in zip(x_set[:-1], x_set[1:]): - if 0 <= x1+hpx and x0+x2+5 <= right: - ret += [get_poly(x0+x1+hpx, x0+x2+5, y0, y1), - get_poly(x0+x1+hpx, x0+x2+5, y2, y3)] + ret = [ ] + for x1, x2 in zip(x_set[ :-1 ], x_set[ 1: ]): + if 0 <= x1 + hpx and x0 + x2 + 5 <= right: + ret += [ get_poly(x0 + x1 + hpx, x0 + x2 + 5, y0, y1), + get_poly(x0 + x1 + hpx, x0 + x2 + 5, y2, y3) ] # draw for debug if draw: @@ -438,7 +502,7 @@ def agent(img, draw=False): plt.imshow(__img) plt.show() - logger.debug(f'segment.agent: {[x.tolist() for x in ret]}') + logger.debug(f'segment.agent: {[ x.tolist() for x in ret ]}') return ret, ocr except Exception as e: @@ -456,34 +520,35 @@ def free_agent(img, draw=False): left, right = 0, width # 异形屏适配 - while np.max(img[:, right-1]) < 100: + while np.max(img[ :, right - 1 ]) < 100: right -= 1 - while np.max(img[:, left]) < 100: + while np.max(img[ :, left ]) < 100: left += 1 # 去除左侧干员详情 x0 = left + 1 - while not (img[height-1, x0-1, 0] > img[height-1, x0, 0] + 10 and abs(int(img[height-1, x0, 0]) - int(img[height-1, x0+1, 0])) < 5): + while not (img[ height - 1, x0 - 1, 0 ] > img[ height - 1, x0, 0 ] + 10 and abs( + int(img[ height - 1, x0, 0 ]) - int(img[ height - 1, x0 + 1, 0 ])) < 5): x0 += 1 # 获取分割结果 ret = agent(img, draw) - st = ret[-2][2] # 起点 - ed = ret[0][1] # 终点 + st = ret[ -2 ][ 2 ] # 起点 + ed = ret[ 0 ][ 1 ] # 终点 # 收集 y 坐标并初步筛选 y_set = set() - __ret = [] + __ret = [ ] for poly in ret: - __img = img[poly[0, 1]:poly[2, 1], poly[0, 0]:poly[2, 0]] - y_set.add(poly[0, 1]) - y_set.add(poly[2, 1]) + __img = img[ poly[ 0, 1 ]:poly[ 2, 1 ], poly[ 0, 0 ]:poly[ 2, 0 ] ] + y_set.add(poly[ 0, 1 ]) + y_set.add(poly[ 2, 1 ]) # 去除空白的干员框 if 80 <= np.min(__img): logger.debug(f'drop(empty): {poly.tolist()}') continue # 去除被选中的蓝框 - elif np.count_nonzero(__img[:, :, 0] >= 224) == 0 or np.count_nonzero(__img[:, :, 0] == 0) > 0: + elif np.count_nonzero(__img[ :, :, 0 ] >= 224) == 0 or np.count_nonzero(__img[ :, :, 0 ] == 0) > 0: logger.debug(f'drop(selected): {poly.tolist()}') continue __ret.append(poly) @@ -493,11 +558,11 @@ def free_agent(img, draw=False): y0 = height - y5 y3 = y0 - y2 + y5 - ret_free = [] + ret_free = [ ] for poly in ret: - poly[:, 1][poly[:, 1] == y1] = y0 - poly[:, 1][poly[:, 1] == y4] = y3 - __img = img[poly[0, 1]:poly[2, 1], poly[0, 0]:poly[2, 0]] + poly[ :, 1 ][ poly[ :, 1 ] == y1 ] = y0 + poly[ :, 1 ][ poly[ :, 1 ] == y4 ] = y3 + __img = img[ poly[ 0, 1 ]:poly[ 2, 1 ], poly[ 0, 0 ]:poly[ 2, 0 ] ] if not detector.is_on_shift(__img): ret_free.append(poly) @@ -507,7 +572,7 @@ def free_agent(img, draw=False): plt.imshow(__img) plt.show() - logger.debug(f'segment.free_agent: {[x.tolist() for x in ret_free]}') + logger.debug(f'segment.free_agent: {[ x.tolist() for x in ret_free ]}') return ret_free, st, ed except Exception as e: diff --git a/arknights_mower/utils/solver.py b/arknights_mower/utils/solver.py index d3346ca60..9f7491534 100644 --- a/arknights_mower/utils/solver.py +++ b/arknights_mower/utils/solver.py @@ -27,12 +27,14 @@ def __init__(self, device: Device = None, recog: Recognizer = None) -> None: self.recog = recog if recog is not None else Recognizer(self.device) self.device.check_current_focus() - def run(self) -> None: + def run(self)-> None: retry_times = config.MAX_RETRYTIME + result =None while retry_times > 0: try: - if self.transition(): - break + result = self.transition() + if result: + return result except RecognizeError as e: logger.warning(f'识别出了点小差错 qwq: {e}') self.recog.save_screencap('failure') @@ -167,6 +169,10 @@ def scene(self) -> int: """ get the current scene in the game """ return self.recog.get_scene() + def get_infra_scene(self) -> int: + """ get the current scene in the infra """ + return self.recog.get_infra_scene() + def is_login(self): """ check if you are logged in """ return not (self.scene() // 100 == 1 or self.scene() // 100 == 99 or self.scene() == -1) @@ -255,6 +261,8 @@ def back_to_index(self): try: if self.get_navigation(): self.tap_element('nav_index') + elif self.scene() == Scene.CLOSE_MINE: + self.tap_element('close_mine') elif self.scene() == Scene.ANNOUNCEMENT: self.tap(detector.announcement_close(self.recog.img)) elif self.scene() == Scene.MATERIEL: @@ -304,3 +312,14 @@ def back_to_index(self): if self.scene() != Scene.INDEX: raise StrategyError + + def back_to_reclamation_algorithm(self): + self.recog.update() + while self.find('index_terminal') is None: + if self.scene() == Scene.UNKNOWN: + self.device.exit('com.hypergryph.arknights') + self.back_to_index() + logger.info('导航至生息演算') + self.tap_element('index_terminal', 0.5) + self.tap((self.recog.w*0.2, self.recog.h*0.8),interval=0.5) + diff --git a/diy.py b/diy.py index ecf950e74..84e1ebe29 100644 --- a/diy.py +++ b/diy.py @@ -1,111 +1,184 @@ import time -import schedule +from datetime import datetime + +from arknights_mower.solvers.base_schedule import BaseSchedulerSolver from arknights_mower.strategy import Solver +from arknights_mower.utils.device import Device from arknights_mower.utils.log import logger, init_fhlr from arknights_mower.utils import config -# 指定无人机加速第三层第三个房间的制造或贸易订单 -drone_room = 'room_3_3' - -# 指定使用菲亚梅塔恢复第一层第二个房间心情最差的干员的心情 -# 恢复后回到原工作岗位,工作顺序不变,以保证最大效率 -fia_room = 'room_1_2' +email_config= { + 'account':"xxx@qq.com", + 'pass_code':'从QQ邮箱帐户设置—>生成授权码', + 'receipts':['任何邮箱'], + 'notify':False +} +maa_config = { + # 请设置为存放 dll 文件及资源的路径 + "maa_path":'F:\\MAA-v4.10.5-win-x64', + # 请设置为存放 dll 文件及资源的路径 + "maa_adb_path":"D:\\Program Files\\Nox\\bin\\adb.exe", + # adb 地址 + "maa_adb":['127.0.0.1:62001'], + # maa 运行的时间间隔,以小时计 + "maa_execution_gap":4, + # 以下配置,第一个设置为true的首先生效 + # 是否启动肉鸽 + "roguelike":False, + # 是否启动生息演算 + "reclamation_algorithm":False, + # 是否启动保全派驻 + "stationary_security_service":False, + "last_execution": None, + "weekly_plan":[{"weekday":"周一","stage":['AP-5'],"medicine":0}, + {"weekday":"周二","stage":['CE-6'],"medicine":0}, + {"weekday":"周三","stage":['1-7'],"medicine":0}, + {"weekday":"周四","stage":['AP-5'],"medicine":0}, + {"weekday":"周五","stage":['1-7'],"medicine":0}, + {"weekday":"周六","stage":['AP-5'],"medicine":0}, + {"weekday":"周日","stage":['AP-5'],"medicine":0}] +} -# 指定关卡序列的作战计划 -ope_lists = [['AP-5', 1], ['1-7', -1]] +# Free (宿舍填充)干员安排黑名单 +free_blacklist= [] -# 使用信用点购买东西的优先级(从高到低) -shop_priority = ['招聘许可', '赤金', '龙门币', '初级作战记录', '技巧概要·卷2', '基础作战记录', '技巧概要·卷1'] +# 干员宿舍回复阈值 + # 高效组心情低于 UpperLimit * 阈值 (向下取整)的时候才会会安排休息 + # UpperLimit:默认24,特殊技能干员如夕,令可能会有所不同(设置在 agent-base.json 文件可以自行更改) +resting_treshhold = 0.5 -# 公招选取标签时优先选择的干员的优先级(从高到低) -recruit_priority = ['因陀罗', '火神'] +# 全自动基建排班计划: +# 这里定义了一套全自动基建的排班计划 plan_1 +# agent 为常驻高效组的干员名 -# 自定义基建排班 -# 这里自定义了一套排班策略,实现的是两班倒,分为四个阶段 -# 阶段 1 和 2 为第一班,阶段 3 和 4 为第二班 -# 第一班的干员在阶段 3 和 4 分两批休息,第二班同理 -# 每个阶段耗时 6 小时 +# group 为干员编队,你希望任何编队的人一起上下班则给他们编一样的名字 + # 编队最大数不支持超过4个干员 否则可能会在计算自动排班的时候报错 +# replacement 为替换组干员备选 + # 暖机干员的自动换班 + # 目前只支持一个暖机干员休息 + # !! 会吧其他正在休息的暖机干员赶出宿舍 + # 请尽量安排多的替换干员,且尽量不同干员的替换人员不冲突 + # 龙舌兰和但书默认为插拔干员 必须放在 replacement的第一位 + # 请把你所安排的替换组 写入replacement 否则程序可能报错 + # 替换组会按照从左到右的优先级选择可以编排的干员 + # 宿舍常驻干员不会被替换所以不需要设置替换组 + # 宿舍空余位置请编写为Free,请至少安排一个群补和一个单补 以达到最大恢复效率 + # 宿管必须安排靠左,后面为填充干员 + # 宿舍恢复速率务必1-4从高到低排列 + # 如果有菲亚梅塔则需要安排replacement 建议干员至少为三 + # 菲亚梅塔会从replacment里找最低心情的进行充能 plan = { # 阶段 1 - 'plan_1': { - # 控制中枢 - 'central': ['夕', '令', '凯尔希', '阿米娅', '玛恩纳'], - # 办公室 - 'contact': ['艾雅法拉'], + "default": "plan_1", + "plan_1": { + # 中枢 + 'central': [{'agent': '焰尾', 'group': '红松骑士', 'replacement': ["凯尔希","诗怀雅"]}, + {'agent': '琴柳', 'group': '', 'replacement': ["凯尔希","阿米娅"]}, + {'agent': '重岳', 'group': '夕', 'replacement': ["玛恩纳", "清道夫", "凯尔希", "阿米娅", '坚雷']}, + {'agent': '夕', 'group': '夕', 'replacement': ["玛恩纳", "清道夫", "凯尔希", "阿米娅", '坚雷']}, + {'agent': '令', 'group': '夕', 'replacement': ["玛恩纳", "清道夫", "凯尔希", "阿米娅", '坚雷']}, + ], + 'contact': [{'agent': '絮雨', 'group': '絮雨', 'replacement': []}], # 宿舍 - 'dormitory_1': ['杜林', '闪灵', '安比尔', '空弦', '缠丸'], - 'dormitory_2': ['推进之王', '琴柳', '赫默', '杰西卡', '调香师'], - 'dormitory_3': ['夜莺', '波登可', '夜刀', '古米', '空爆'], - 'dormitory_4': ['空', 'Lancet-2', '香草', '史都华德', '刻俄柏'], + 'dormitory_1': [{'agent': '流明', 'group': '', 'replacement': []}, + {'agent': '闪灵', 'group': '', 'replacement': []}, + {'agent': 'Free', 'group': '', 'replacement': []}, + {'agent': 'Free', 'group': '', 'replacement': []}, + {'agent': 'Free', 'group': '', 'replacement': []} + ], + 'dormitory_2': [{'agent': '杜林', 'group': '', 'replacement': []}, + {'agent': '蜜莓', 'group': '', 'replacement': []}, + {'agent': '褐果', 'group': '', 'replacement': []}, + {'agent': 'Free', 'group': '', 'replacement': []}, + {'agent': 'Free', 'group': '', 'replacement': []} + ], + 'dormitory_3': [{'agent': '车尔尼', 'group': '', 'replacement': []}, + {'agent': '斥罪', 'group': '', 'replacement': []}, + {'agent': '爱丽丝', 'group': '', 'replacement': []}, + {'agent': '桃金娘', 'group': '', 'replacement': []}, + {'agent': 'Free', 'group': '', 'replacement': []} + ], + 'dormitory_4': [{'agent': '波登可', 'group': '', 'replacement': []}, + {'agent': '夜莺', 'group': '', 'replacement': []}, + {'agent': '菲亚梅塔', 'group': '', 'replacement': ['迷迭香', '黑键', '絮雨','至简']}, + {'agent': 'Free', 'group': '', 'replacement': []}, + {'agent': 'Free', 'group': '', 'replacement': []}], + 'factory':[{'agent': '年', 'replacement': ['九色鹿','芳汀'], 'group': '夕'}], # 会客室 - 'meeting': ['陈', '红'], - # 制造站 + 贸易站 + 发电站 - 'room_1_1': ['德克萨斯', '能天使', '拉普兰德'], - 'room_1_2': ['断罪者', '食铁兽', '槐琥'], - 'room_1_3': ['阿消'], - 'room_2_1': ['巫恋', '柏喙', '慕斯'], - 'room_2_2': ['红豆', '霜叶', '白雪'], - 'room_2_3': ['雷蛇'], - 'room_3_1': ['Castle-3', '梅尔', '白面鸮'], - 'room_3_2': ['格雷伊'], - 'room_3_3': ['砾', '夜烟', '斑点'] - }, - # 阶段 2 - 'plan_2': { - # 注释掉了部分和阶段 1 一样排班计划的房间,加快排班速度 - # 'contact': ['艾雅法拉'], - 'dormitory_1': ['杜林', '闪灵', '芬', '稀音', '克洛丝'], - 'dormitory_2': ['推进之王', '琴柳', '清流', '森蚺', '温蒂'], - 'dormitory_3': ['夜莺', '波登可', '伊芙利特', '深靛', '炎熔'], - 'dormitory_4': ['空', 'Lancet-2', '远山', '星极', '普罗旺斯'], - # 'meeting': ['陈', '红'], - # 'room_1_1': ['德克萨斯', '能天使', '拉普兰德'], - # 'room_1_2': ['断罪者', '食铁兽', '槐琥'], - # 'room_1_3': ['阿消'], - # 'room_2_1': ['巫恋', '柏喙', '慕斯'], - # 'room_2_2': ['红豆', '霜叶', '白雪'], - # 'room_2_3': ['雷蛇'], - # 'room_3_1': ['Castle-3', '梅尔', '白面鸮'], - # 'room_3_2': ['格雷伊'], - # 'room_3_3': ['砾', '夜烟', '斑点'] - }, - 'plan_3': { - 'contact': ['普罗旺斯'], - 'dormitory_1': ['杜林', '闪灵', '格雷伊', '雷蛇', '阿消'], - 'dormitory_2': ['推进之王', '琴柳', '德克萨斯', '能天使', '拉普兰德'], - 'dormitory_3': ['夜莺', '波登可', '巫恋', '柏喙', '慕斯'], - 'dormitory_4': ['空', 'Lancet-2', '艾雅法拉', '陈', '红'], - 'meeting': ['远山', '星极'], - 'room_1_1': ['安比尔', '空弦', '缠丸'], - 'room_1_2': ['赫默', '杰西卡', '调香师'], - 'room_1_3': ['伊芙利特'], - 'room_2_1': ['夜刀', '古米', '空爆'], - 'room_2_2': ['香草', '史都华德', '刻俄柏'], - 'room_2_3': ['深靛'], - 'room_3_1': ['芬', '稀音', '克洛丝'], - 'room_3_2': ['炎熔'], - 'room_3_3': ['清流', '森蚺', '温蒂'] - }, - 'plan_4': { - # 'contact': ['普罗旺斯'], - 'dormitory_1': ['杜林', '闪灵', '断罪者', '食铁兽', '槐琥'], - 'dormitory_2': ['推进之王', '琴柳', '红豆', '霜叶', '白雪'], - 'dormitory_3': ['夜莺', '波登可', 'Castle-3', '梅尔', '白面鸮'], - 'dormitory_4': ['空', 'Lancet-2', '砾', '夜烟', '斑点'], - # 'meeting': ['远山', '星极'], - # 'room_1_1': ['安比尔', '空弦', '缠丸'], - # 'room_1_2': ['赫默', '杰西卡', '调香师'], - # 'room_1_3': ['伊芙利特'], - # 'room_2_1': ['夜刀', '古米', '空爆'], - # 'room_2_2': ['香草', '史都华德', '刻俄柏'], - # 'room_2_3': ['深靛'], - # 'room_3_1': ['芬', '稀音', '克洛丝'], - # 'room_3_2': ['炎熔'], - # 'room_3_3': ['清流', '森蚺', '温蒂'] + 'meeting': [{'agent': '陈', 'replacement': ['星极','远山'], 'group': ''}, + {'agent': '红', 'replacement': ['远山','星极'], 'group': ''} ], + 'room_1_1': [{'agent': '黑键', 'group': '', 'replacement': []}, + {'agent': '乌有', 'group': '夕', 'replacement': ['但书','图耶']}, + {'agent': '空弦', 'group': '夕', 'replacement': ['龙舌兰', '鸿雪']} + # {'agent': '伺夜', 'group': '图耶', 'replacement': ['但书','能天使']}, + # {'agent': '空弦', 'group': '图耶', 'replacement': ['龙舌兰', '雪雉']} + ], + 'room_1_2': [{'agent': '迷迭香', 'group': '', 'replacement': []}, + {'agent': '砾', 'group': '', 'Type': '', 'replacement': ['斑点','夜烟']}, + {'agent': '至简', 'group': '', 'replacement': []}], + 'room_1_3': [{'agent': '承曦格雷伊', 'group': '异客', 'replacement': ['炎狱炎熔','格雷伊']}], + 'room_2_2': [{'agent': '温蒂', 'group': '异客', 'replacement': ['火神']}, + # {'agent': '异客', 'group': '异客', 'Type': '', 'replacement': ['贝娜']}, + {'agent': '异客', 'group': '异客', 'Type': '', 'replacement': ['贝娜']}, + {'agent': '森蚺', 'group': '异客', 'replacement': ['泡泡']}], + 'room_3_1': [{'agent': '稀音', 'group': '稀音', 'replacement': ['贝娜']}, + {'agent': '帕拉斯', 'group': '稀音', 'Type': '', 'replacement': ['泡泡']}, + {'agent': '红云', 'group': '稀音', 'replacement': ['火神']}], + 'room_2_3': [{'agent': '澄闪', 'group': '', 'replacement': ['炎狱炎熔', '格雷伊']}], + 'room_2_1': [{'agent': '食铁兽', 'group': '食铁兽', 'replacement': ['泡泡']}, + {'agent': '断罪者', 'group': '食铁兽', 'replacement': ['火神']}, + {'agent': '槐琥', 'group': '食铁兽', 'replacement': ['贝娜']}], + 'room_3_2': [{'agent': '灰毫', 'group': '红松骑士', 'replacement': ['贝娜']}, + {'agent': '远牙', 'group': '红松骑士', 'Type': '', 'replacement': ['泡泡']}, + {'agent': '野鬃', 'group': '红松骑士', 'replacement': ['火神']}], + 'room_3_3': [{'agent': '雷蛇', 'group': '', 'replacement': ['炎狱炎熔','格雷伊']}] } } +agent_base_config = { + "Default":{"UpperLimit": 24,"LowerLimit": 0,"ExhaustRequire": False,"ArrangeOrder":[2,"false"],"RestInFull": False}, + "令":{"ArrangeOrder":[2,"true"]}, + "夕": {"ArrangeOrder":[2,"true"]}, + "稀音":{"ExhaustRequire": True,"ArrangeOrder":[2,"true"],"RestInFull": True}, + "巫恋":{"ArrangeOrder":[2,"true"]}, + "柏喙":{"ExhaustRequire": True,"ArrangeOrder":[2,"true"]}, + "龙舌兰":{"ArrangeOrder":[2,"true"]}, + "空弦":{"ArrangeOrder":[2,"true"],"RestingPriority": "low"}, + "伺夜":{"ArrangeOrder":[2,"true"]}, + "绮良":{"ArrangeOrder":[2,"true"]}, + "但书":{"ArrangeOrder":[2,"true"]}, + "泡泡":{"ArrangeOrder":[2,"true"]}, + "火神":{"ArrangeOrder":[2,"true"]}, + "黑键":{"ArrangeOrder":[2,"true"]}, + "波登可":{"ArrangeOrder":[ 2, "false" ]}, + "夜莺":{"ArrangeOrder":[ 2, "false" ]}, + "菲亚梅塔":{"ArrangeOrder":[ 2, "false" ]}, + "流明":{"ArrangeOrder":[ 2, "false" ]}, + "蜜莓":{"ArrangeOrder":[ 2, "false" ]}, + "闪灵":{"ArrangeOrder":[ 2, "false" ]}, + "杜林":{"ArrangeOrder":[ 2, "false" ]}, + "褐果":{"ArrangeOrder":[ 2, "false" ]}, + "车尔尼":{"ArrangeOrder":[ 2, "false" ]}, + "安比尔":{"ArrangeOrder":[ 2, "false" ]}, + "爱丽丝":{"ArrangeOrder":[ 2, "false" ]}, + "桃金娘":{"ArrangeOrder":[ 2, "false" ]}, + "帕拉斯": {"RestingPriority": "low"}, + "红云": {"RestingPriority": "low","ArrangeOrder":[2,"true"]}, + "承曦格雷伊": {"ArrangeOrder":[2,"true"]}, + "乌有":{"ArrangeOrder":[2,"true"],"RestingPriority": "low"}, + "图耶":{"ArrangeOrder":[2,"true"]}, + "鸿雪": {"ArrangeOrder":[2,"true"]}, + "孑":{"ArrangeOrder":[2,"true"]}, + "清道夫":{"ArrangeOrder":[2,"true"]}, + "临光":{"ArrangeOrder":[2,"true"]}, + "杜宾":{"ArrangeOrder":[2,"true"]}, + "焰尾":{"RestInFull": True}, + "重岳":{"ArrangeOrder":[2,"true"]}, + "坚雷":{"ArrangeOrder":[2,"true"]}, + "年":{"RestingPriority": "low"} +} + def debuglog(): ''' @@ -121,37 +194,92 @@ def savelog(): ''' config.LOGFILE_PATH = './log' config.SCREENSHOT_PATH = './screenshot' - config.SCREENSHOT_MAXNUM = 100 + config.SCREENSHOT_MAXNUM = 1000 + config.ADB_DEVICE = maa_config['maa_adb'] + config.ADB_CONNECT = maa_config['maa_adb'] + config.PASSWORD = '你的密码' init_fhlr() +def inialize(tasks,scheduler=None): + device = Device() + cli = Solver(device) + if scheduler is None: + base_scheduler = BaseSchedulerSolver(cli.device, cli.recog) + base_scheduler.operators = {} + base_scheduler.global_plan = plan + base_scheduler.current_base = {} + base_scheduler.resting=[] + base_scheduler.dorm_count=4 + base_scheduler.tasks = tasks + # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true + base_scheduler.read_mood = True + base_scheduler.scan_time = {} + base_scheduler.last_room = '' + base_scheduler.free_blacklist = free_blacklist + base_scheduler.resting_treshhold=resting_treshhold + base_scheduler.MAA = None + base_scheduler.email_config = email_config + base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] + base_scheduler.maa_config = maa_config + base_scheduler.error = False + base_scheduler.agent_base_config = agent_base_config + return base_scheduler + else : + scheduler.device=cli.device + scheduler.recog=cli.recog + scheduler.handle_error(True) + return scheduler def simulate(): ''' 具体调用方法可见各个函数的参数说明 ''' - global ope_lists - cli = Solver() - cli.mail() # 邮件 - cli.base(clue_collect=True, drone_room=drone_room, fia_room=fia_room, arrange=plan) # 基建 - cli.credit() # 信用 - ope_lists = cli.ope(eliminate=True, plan=ope_lists) # 行动,返回未完成的作战计划 - cli.shop(shop_priority) # 商店 - cli.recruit() # 公招 - cli.mission() # 任务 - - -def schedule_task(): - """ - 定期运行任务 - """ - schedule.every().day.at('07:00').do(simulate) - schedule.every().day.at('19:00').do(simulate) + global ope_list + # 第一次执行任务 + # tasks = [{"plan": {'room_1_1': ['能天使','但书','龙舌兰']}, "time": datetime.now()}] + tasks =[] + reconnect_max_tries = 10 + reconnect_tries = 0 + base_scheduler = inialize(tasks) + while True: - schedule.run_pending() - time.sleep(60) + try: + if len(base_scheduler.tasks) > 0: + (base_scheduler.tasks.sort(key=lambda x: x["time"], reverse=False)) + sleep_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() + logger.info(base_scheduler.tasks) + base_scheduler.send_email(base_scheduler.tasks) + # 如果任务间隔时间超过9分钟则启动MAA + if sleep_time > 540: + base_scheduler.maa_plan_solver() + elif sleep_time > 0 : time.sleep(sleep_time) + base_scheduler.run() + reconnect_tries = 0 + except ConnectionError as e: + reconnect_tries +=1 + if reconnect_tries < reconnect_max_tries: + logger.warning(f'连接端口断开....正在重连....') + connected = False + while not connected: + try: + base_scheduler = inialize([],base_scheduler) + break + except Exception as ce: + logger.error(ce) + time.sleep(5) + continue + continue + else: + raise Exception(e) + except Exception as E: + logger.exception(f"程序出错--->{E}") + # cli.credit() # 信用 + # ope_lists = cli.ope(eliminate=True, plan=ope_lists) # 行动,返回未完成的作战计划 + # cli.shop(shop_priority) # 商店 + # cli.recruit() # 公招 + # cli.mission() # 任务 -debuglog() +# debuglog() savelog() simulate() -schedule_task() diff --git a/menu.py b/menu.py new file mode 100644 index 000000000..7957b8ae4 --- /dev/null +++ b/menu.py @@ -0,0 +1,363 @@ +import json +from multiprocessing import Pipe, Process, freeze_support +import time +from datetime import datetime +import PySimpleGUI as sg +import os +from ruamel.yaml import YAML +from arknights_mower.solvers.base_schedule import BaseSchedulerSolver +from arknights_mower.strategy import Solver +from arknights_mower.utils.device import Device +from arknights_mower.utils.log import logger, init_fhlr +from arknights_mower.utils import config +from arknights_mower.data import agent_list + +yaml = YAML() +confUrl = './conf.yml'; +conf = {} +plan = {} +global window +buffer = '' +line = 0 +half_line_index = 0 + + +def inialize(tasks, scheduler=None): + device = Device() + cli = Solver(device) + if scheduler is None: + base_scheduler = BaseSchedulerSolver(cli.device, cli.recog) + base_scheduler.operators = {} + plan1 = {} + for key in plan: + plan1[key] = plan[key]['plans'] + base_scheduler.global_plan = {'default': "plan_1", "plan_1": plan1} + base_scheduler.current_base = {} + base_scheduler.resting = [] + base_scheduler.dorm_count = 4 + base_scheduler.tasks = tasks + # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true + base_scheduler.read_mood = True + base_scheduler.scan_time = {} + base_scheduler.last_room = '' + base_scheduler.free_blacklist = [] + base_scheduler.resting_treshhold = 0.5 + base_scheduler.MAA = None + base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] + base_scheduler.error = False + return base_scheduler + else: + scheduler.device = cli.device + scheduler.recog = cli.recog + scheduler.handle_error(True) + return scheduler + + +def simulate(): + ''' + 具体调用方法可见各个函数的参数说明 + ''' + tasks = [] + reconnect_max_tries = 10 + reconnect_tries = 0 + base_scheduler = inialize(tasks) + while True: + try: + if len(base_scheduler.tasks) > 0: + (base_scheduler.tasks.sort(key=lambda x: x["time"], reverse=False)) + sleep_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() + logger.info(base_scheduler.tasks) + if sleep_time >= 0: + remaining_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() + logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}") + time.sleep(sleep_time) + base_scheduler.run() + reconnect_tries = 0 + except ConnectionError as e: + reconnect_tries += 1 + if reconnect_tries < reconnect_max_tries: + logger.warning(f'连接端口断开....正在重连....') + connected = False + while not connected: + try: + base_scheduler = inialize([], base_scheduler) + break + except Exception as ce: + logger.error(ce) + time.sleep(5) + continue + continue + else: + raise Exception(e) + except Exception as E: + logger.exception(f"程序出错--->{E}") + + +# 读取写入配置文件 +def loadConf(): + global conf + global confUrl + if not os.path.isfile(confUrl): + open(confUrl, 'w') # 创建空配置文件 + conf['planFile'] = './plan.json' # 默认排班表地址 + return + with open(confUrl, 'r', encoding='utf8') as c: + conf = yaml.load(c) + if conf is None: + conf = {} + + +def writeConf(): + global conf + global confUrl + with open(confUrl, 'w', encoding='utf8') as c: + yaml.default_flow_style = False + yaml.dump(conf, c) + + +# 读取写入排班表 +def loadPlan(url): + global plan + if not os.path.isfile(url): + with open(url, 'w') as f: + json.dump(plan, f) # 创建空json文件 + return + try: + with open(url, 'r', encoding='utf8') as fp: + plan = json.loads(fp.read()) + conf['planFile'] = url + for i in range(1, 4): + for j in range(1, 4): + window[f'btn_room_{str(i)}_{str(j)}'].update('待建造', button_color=('white', '#4f4945')) + for key in plan: + if type(plan[key]).__name__ == 'list': # 兼容旧版格式 + plan[key] = {'plans': plan[key], 'name': ''} + elif plan[key]['name'] == '贸易站': + window['btn_' + key].update('贸易站', button_color=('#4f4945', '#33ccff')) + elif plan[key]['name'] == '制造站': + window['btn_' + key].update('制造站', button_color=('#4f4945', '#ffcc00')) + elif plan[key]['name'] == '发电站': + window['btn_' + key].update('发电站', button_color=('#4f4945', '#ccff66')) + except Exception as e: + logger.error(e) + println('json格式错误!') + + +def writePlan(): + with open(conf['planFile'], 'w', encoding='utf8') as c: + json.dump(plan, c, ensure_ascii=False) + + +# 执行自动排班 +def start(c, p, child_conn): + global plan + global conf + conf = c + plan = p + config.LOGFILE_PATH = './log' + config.SCREENSHOT_PATH = './screenshot' + config.SCREENSHOT_MAXNUM = 1000 + config.ADB_DEVICE = [conf['adb']] + config.ADB_CONNECT = [conf['adb']] + init_fhlr(child_conn) + logger.info('开始运行Mower') + simulate() + + +# 主页面 +def menu(): + global window + global buffer + loadConf() + sg.theme('LightBlue2') + # maa_title = sg.Text('MAA:') + # maa_select = sg.InputCombo(['启用', '停用'], default_value='停用', size=(20, 3)) + # adb + adb_title = sg.Text('adb连接地址:', size=10) + adb = sg.InputText(conf['adb'] if 'adb' in conf.keys() else '', size=60) + # 排班表json + plan_title = sg.Text('排班表:', size=10) + planFile = sg.InputText(conf['planFile'], readonly=True, size=60, key='planFile', enable_events=True) + plan_select = sg.FileBrowse('...', size=(3, 1), file_types=(("JSON files", "*.json"),)) + # 总开关 + on_btn = sg.Button('开始执行', key='on') + off_btn = sg.Button('立即停止', key='off', visible=False, button_color='red') + # 日志栏 + output = sg.Output(size=(150, 25), key='log', text_color='#808069', font=('微软雅黑', 9)) + + # 宿舍区 + central = sg.Button('控制中枢', key='btn_central', size=(18, 3), button_color='#303030') + dormitory_1 = sg.Button('宿舍', key='btn_dormitory_1', size=(18, 2), button_color='#303030') + dormitory_2 = sg.Button('宿舍', key='btn_dormitory_2', size=(18, 2), button_color='#303030') + dormitory_3 = sg.Button('宿舍', key='btn_dormitory_3', size=(18, 2), button_color='#303030') + dormitory_4 = sg.Button('宿舍', key='btn_dormitory_4', size=(18, 2), button_color='#303030') + centralArea = sg.Column([[central], [dormitory_1], [dormitory_2], [dormitory_3], [dormitory_4]]) + # 制造站区 + room_1_1 = sg.Button('待建造', key='btn_room_1_1', size=(12, 2), button_color='#4f4945') + room_1_2 = sg.Button('待建造', key='btn_room_1_2', size=(12, 2), button_color='#4f4945') + room_1_3 = sg.Button('待建造', key='btn_room_1_3', size=(12, 2), button_color='#4f4945') + room_2_1 = sg.Button('待建造', key='btn_room_2_1', size=(12, 2), button_color='#4f4945') + room_2_2 = sg.Button('待建造', key='btn_room_2_2', size=(12, 2), button_color='#4f4945') + room_2_3 = sg.Button('待建造', key='btn_room_2_3', size=(12, 2), button_color='#4f4945') + room_3_1 = sg.Button('待建造', key='btn_room_3_1', size=(12, 2), button_color='#4f4945') + room_3_2 = sg.Button('待建造', key='btn_room_3_2', size=(12, 2), button_color='#4f4945') + room_3_3 = sg.Button('待建造', key='btn_room_3_3', size=(12, 2), button_color='#4f4945') + leftArea = sg.Column([[room_1_1, room_1_2, room_1_3], + [room_2_1, room_2_2, room_2_3], + [room_3_1, room_3_2, room_3_3]]) + # 功能区 + meeting = sg.Button('会客室', key='btn_meeting', size=(24, 2), button_color='#303030') + factory = sg.Button('加工站', key='btn_factory', size=(24, 2), button_color='#303030') + contact = sg.Button('办公室', key='btn_contact', size=(24, 2), button_color='#303030') + rightArea = sg.Column([[meeting], [factory], [contact]]) + + settingLayout = [[sg.Column([[sg.Text('设施类别:'), sg.InputCombo(['贸易站', '制造站', '发电站'], size=12, key='station_type')]], + key='station_type_col', visible=False)]] + # 排班表设置标签 + for i in range(1, 6): + setArea = sg.Column([[sg.Text('干员:'), + sg.InputCombo(['Free'] + agent_list, size=20, key='agent' + str(i)), + sg.Text('组:'), + sg.InputText('', size=15, key='group' + str(i)), + sg.Text('替换:'), + sg.InputText('', size=30, key='replacement' + str(i)) + ]], key='setArea' + str(i), visible=False) + settingLayout.append([setArea]) + settingLayout.append([sg.Button('保存', key='savePlan', visible=False)]) + settingArea = sg.Column(settingLayout, element_justification="center", + vertical_alignment="bottom", + expand_x=True) + # 组装页面 + mainTab = sg.Tab(' 主页 ', [[adb_title, adb], + [plan_title, planFile, plan_select], + [output], + [on_btn, off_btn]]) + + planTab = sg.Tab(' 排班表 ', [[leftArea, centralArea, rightArea], [settingArea]], element_justification="center") + window = sg.Window('Mower', [[sg.TabGroup([[mainTab, planTab]], border_width=0, + tab_border_width=0, focus_color='#bcc8e5', + selected_background_color='#d4dae8', background_color='#aab6d3', + tab_background_color='#aab6d3')]], font='微软雅黑', finalize=True) + + loadPlan(conf['planFile']) + while True: + event, value = window.Read() + if event == sg.WIN_CLOSED: + conf['adb'] = adb.get() + break + elif event == 'planFile' and planFile.get() != conf['planFile']: + writePlan() + loadPlan(planFile.get()) + planFile.update(conf['planFile']) + elif event.startswith('btn_'): + btn = event + initBtn(event) + elif event == 'savePlan': + saveBtn(btn) + elif event == 'on': + if adb.get() == '': + println('adb未设置!') + continue + conf['adb'] = adb.get() + + on_btn.update(visible=False) + off_btn.update(visible=True) + clear() + parent_conn, child_conn = Pipe() + main_thread = Process(target=start, args=(conf, plan, child_conn), daemon=True) + main_thread.start() + window.perform_long_operation(lambda: log(parent_conn), 'log') + elif event == 'off': + println('停止运行') + child_conn.close() + main_thread.terminate() + on_btn.update(visible=True) + off_btn.update(visible=False) + + window.close() + writeConf() + writePlan() + + +def initBtn(event): + room_key = event[4:] + station_name = plan[room_key]['name'] if room_key in plan.keys() else '' + plans = plan[room_key]['plans'] if room_key in plan.keys() else [] + if room_key.startswith('room'): + window['station_type_col'].update(visible=True) + window['station_type'].update(station_name) + visibleCnt = 3 # 设施干员需求数量 + else: + if room_key == 'meeting': + visibleCnt = 2 + elif room_key == 'factory' or room_key == 'contact': + visibleCnt = 1 + else: + visibleCnt = 5 + window['station_type_col'].update(visible=False) + window['station_type'].update('') + window['savePlan'].update(visible=True) + for i in range(1, 6): + if i > visibleCnt: + window['setArea' + str(i)].update(visible=False) + window['agent' + str(i)].update('') + window['group' + str(i)].update('') + window['replacement' + str(i)].update('') + else: + window['setArea' + str(i)].update(visible=True) + window['agent' + str(i)].update(plans[i - 1]['agent'] if len(plans) >= i else '') + window['group' + str(i)].update(plans[i - 1]['group'] if len(plans) >= i else '') + window['replacement' + str(i)].update(','.join(plans[i - 1]['replacement']) if len(plans) >= i else '') + + +def saveBtn(btn): + plan1 = {'name': window['station_type'].get(), 'plans': []} + for i in range(1, 6): + agent = window['agent' + str(i)].get() + group = window['group' + str(i)].get() + replacement = list(filter(None, window['replacement' + str(i)].get().split(','))) + if agent != '': + plan1['plans'].append({'agent': agent, 'group': group, 'replacement': replacement}) + plan[btn[4:]] = plan1 + writePlan() + loadPlan(conf['planFile']) + + +# 输出日志 +def log(pipe): + try: + while True: + msg = pipe.recv() + println(msg) + except EOFError: + pipe.close() + + +def println(msg): + global buffer + global line + global half_line_index + maxLen = 500 # 最大行数 + buffer = f'{buffer}\n{time.strftime("%m-%d %H:%M:%S")} {msg}'.strip() + window['log'].update(value=buffer) + if line == maxLen // 2: + half_line_index = len(buffer) + if line >= maxLen: + buffer = buffer[half_line_index:] + line = maxLen // 2 + else: + line += 1 + + +# 清空输出栏 +def clear(): + global buffer + global line + buffer = '' + window['log'].update(value=buffer) + line = 0 + + +if __name__ == '__main__': + freeze_support() + menu() diff --git a/requirements.txt b/requirements.txt index d7e5cdade..1957f00ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,5 +9,12 @@ pyclipper==1.3.0 shapely==1.7.1 tornado==6.1 requests==2.22.0 -ruamel.yaml==0.17.17 schedule~=1.1.0 +setuptools~=60.2.0 +Pillow~=9.2.0 +pytesseract~=0.3.10 +pandas==1.5.2 +pyyaml==6.0 +PySimpleGUI~=4.60.4 +pytz~=2022.6 +paddleocr==2.6.1.3 From 37a91335c7d35556eeaeda74b9576a191a532ba7 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Fri, 10 Mar 2023 10:22:45 -0800 Subject: [PATCH 06/39] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=8A=A5?= =?UTF-8?q?=E9=94=99=E5=AE=BF=E8=88=8D=E4=B8=8D=E6=89=A7=E8=A1=8C=E7=BA=A0?= =?UTF-8?q?=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 51a004d1d..52c3da89b 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -541,12 +541,12 @@ def plan_solver(self): logger.info(f'休息人选为->{need_to_rest}') if len(need_to_rest) > 0: self.get_swap_plan(resting_dorm, need_to_rest, min_mood < 3 and min_mood != -99) - # 如果下个 普通任务 >5 分钟则补全宿舍 - if (next((e for e in self.tasks if e['time'] < datetime.now() + timedelta(seconds=300)), - None)) is None: - self.agent_get_mood() except Exception as e: logger.exception(f'计算排班计划出错->{e}') + # 如果下个 普通任务 >5 分钟则补全宿舍 + if (next((e for e in self.tasks if e['time'] < datetime.now() + timedelta(seconds=300)), + None)) is None: + self.agent_get_mood() def get_swap_plan(self, resting_dorm, operators, skip_read_time): result = {} @@ -725,11 +725,11 @@ def get_in_and_out_time(self, room): self.back(interval=2) return execute_time - def double_read_time(self, cord, upperLimit=36000, error_count=0,): + def double_read_time(self, cord,upperLimit = 36000): if upperLimit < 36000: upperLimit = 36000 self.recog.update() - time_in_seconds = self.read_time(cord, upperLimit) + time_in_seconds = self.read_time(cord,upperLimit) execute_time = datetime.now() + timedelta(seconds=(time_in_seconds)) return execute_time @@ -738,8 +738,8 @@ def initialize_paddle(self): global ocr if ocr is None: ocr = PaddleOCR(use_angle_cls=True, lang='en') - - def read_screen(self,img, type="mood",limit=24, cord=None, change_color=False): + + def read_screen(self, img, type="mood", limit=24, cord=None, change_color=False): if cord is not None: img = img[cord[1]:cord[3], cord[0]:cord[2]] if 'mood' in type or type == "time": @@ -774,11 +774,11 @@ def read_screen(self,img, type="mood",limit=24, cord=None, change_color=False): except Exception as e : logger.exception(e) return limit - - def read_time(self, cord, upperlimit, error_count=0): + + def read_time(self, cord,upperlimit, error_count=0): # 刷新图片 self.recog.update() - time_str = segment.read_screen(self.recog.img, type='time', cord=cord) + time_str = self.read_screen(self.recog.img, type='time', cord=cord) logger.debug(str(time_str)) try: h, m, s = str(time_str).split(':') @@ -1381,7 +1381,7 @@ def get_agent_from_room(self, room, read_time_index=[]): data['time'] = datetime.now() else: upperLimit = 21600 - if data['agent']in ['菲亚梅塔','刻俄柏']: + if data['agent']in ['菲亚梅塔'] or data['agent'] in self.exaust_agent: upperLimit = 43200 data['time'] = self.double_read_time(time_p[i],upperLimit=upperLimit) result.append(data) @@ -1674,7 +1674,6 @@ def maa_plan_solver(self): self.device.exit('com.hypergryph.arknights') def send_email(self, tasks): - return try: msg = MIMEMultipart() conntent = str(tasks) @@ -1689,3 +1688,4 @@ def send_email(self, tasks): logger.info("邮件发送成功") except Exception as e: logger.error("邮件发送失败") + logger.exception(e) From e5d57adfcc9faac2998898a777853a043d2404e2 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Fri, 10 Mar 2023 10:32:50 -0800 Subject: [PATCH 07/39] =?UTF-8?q?feat:=E7=A7=BB=E9=99=A4tesseract=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/utils/log.py | 2 +- arknights_mower/utils/segment.py | 64 -------------------------------- requirements.txt | 1 - 3 files changed, 1 insertion(+), 66 deletions(-) diff --git a/arknights_mower/utils/log.py b/arknights_mower/utils/log.py index 2ef280eb8..20e627044 100644 --- a/arknights_mower/utils/log.py +++ b/arknights_mower/utils/log.py @@ -66,7 +66,7 @@ def emit(self, record): logger.addHandler(ehlr) -def init_fhlr(pipe) -> None: +def init_fhlr(pipe=None) -> None: """ initialize log file """ if config.LOGFILE_PATH is None: return diff --git a/arknights_mower/utils/segment.py b/arknights_mower/utils/segment.py index c6b6b28fb..7c522cbac 100644 --- a/arknights_mower/utils/segment.py +++ b/arknights_mower/utils/segment.py @@ -5,14 +5,12 @@ import cv2 import numpy as np from matplotlib import pyplot as plt -from .image import saveimg from ..data import agent_list from ..ocr import ocrhandle from . import detector from . import typealias as tp from .log import logger from .recognize import RecognizeError -import pytesseract class FloodCheckFailed(Exception): @@ -267,68 +265,6 @@ def base(img: tp.Image, central: tp.Scope, draw: bool = False) -> dict[ str, tp. except Exception as e: logger.debug(traceback.format_exc()) raise RecognizeError(e) -def read_screen(img, type="mood", langurage="eng", limit=24, cord=None, change_color=False, draw=False) -> int: - if cord is not None : - img = img[ cord[1]:cord[3], cord[0]:cord[2] ] - if 'mood' in type or type=="time": - # 心情图片太小,复制8次提高准确率 - for x in range(0, 4): - img = cv2.vconcat([img, img]) - if change_color: img[img == 137] = 255 - if draw : plt.imshow(img) - if "mood" in type or type=='time': - try: - _config = r'-c tessedit_char_whitelist=0123456789: --psm 6' - if type=='mood':_config = r'-c tessedit_char_whitelist=0123456789/- --psm 6' - text = pytesseract.image_to_data(img,lang=langurage, config=_config, output_type='data.frame' ) - if type =='time': saveimg(img, 'data') - text = text[text.conf != -1] - lines = text.groupby(['page_num', 'block_num', 'par_num', 'line_num'], group_keys=True)['text'] \ - .apply(lambda x: ' '.join(map(str,list(x)))).tolist() - confs = text.groupby(['page_num', 'block_num', 'par_num', 'line_num'], group_keys=True)['conf'].mean().tolist() - line_conf = [] - for i in range(len(lines)): - if lines[i].strip(): - line_conf.append((lines[i], round(confs[i], 3))) - logger.debug(str(line_conf)) - __str = '' - if 'mood' in type: - if line_conf[len(line_conf) - 1][1] > 0.0 or (max(line_conf, key=lambda tup: tup[1])) == 0.0 or limit == 200: - __str=line_conf[len(line_conf) - 1][0] - else: - __str = (max(line_conf, key=lambda tup: tup[1]))[0] - else: - _data = (max(line_conf, key=lambda tup: tup[1])) - if _data[1]<70.0: - __str = line_conf[len(line_conf) - 1][0] - else: - __str = _data[0] - if '.0' in __str: - __str = __str[0:__str.index('.0')] - # else: - # # 时间就找最大出现次数 - # x = [i[0] for i in line_conf] - # __str = max(set(x), key=x.count) - if "mood" in type: - idx = 4 - if type =='mood' : idx = 3 - __str = __str[0:len(__str)-idx] - if '/' in __str: - __str= __str[0:__str.index('/')] - if ''==__str: - return 0 - number = int(__str) - if number>limit: - saveimg(img, 'error_mood') - return limit - return number - else: - return __str - except Exception as e: - # 空的时候是没人在基建 - logger.warning(f'读取错误:{__str}') - saveimg(img, 'error_mood') - return -1 def worker(img: tp.Image, draw: bool = False) -> tuple[ list[ tp.Rectangle ], tp.Rectangle, bool ]: """ diff --git a/requirements.txt b/requirements.txt index 1957f00ae..4dee94da6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,6 @@ requests==2.22.0 schedule~=1.1.0 setuptools~=60.2.0 Pillow~=9.2.0 -pytesseract~=0.3.10 pandas==1.5.2 pyyaml==6.0 PySimpleGUI~=4.60.4 From a77769f9b0c1be19f6c6d60fe5b4e35afe8879f2 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Fri, 10 Mar 2023 11:26:19 -0800 Subject: [PATCH 08/39] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9E=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E9=98=88=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/resources/not_in_dorm.png | Bin 11831 -> 0 bytes arknights_mower/solvers/base_schedule.py | 10 +++++++--- arknights_mower/utils/matcher.py | 5 ++++- arknights_mower/utils/recognize.py | 7 ++++--- arknights_mower/utils/solver.py | 4 ++-- 5 files changed, 17 insertions(+), 9 deletions(-) delete mode 100644 arknights_mower/resources/not_in_dorm.png diff --git a/arknights_mower/resources/not_in_dorm.png b/arknights_mower/resources/not_in_dorm.png deleted file mode 100644 index 450c05ea355b21044bf92dfd58821179e44f5920..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11831 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 diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 52c3da89b..20d03620f 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -1184,10 +1184,14 @@ def get_order(self, name): def detail_filter(self, turn_on, type="not_in_dorm"): logger.info(f'开始 {("打开" if turn_on else "关闭")} {type} 筛选') - self.tap((self.recog.w * 0.95, self.recog.h * 0.05), interval=1) + error_count = 0 + while self.find("arrange_order_options_scene") is None and error_count<4: + self.tap((self.recog.w * 0.95, self.recog.h * 0.05), interval=1) + error_count+=1 + if error_count>=4: raise Exception("进入筛选界面失败") if type == "not_in_dorm": - not_in_dorm = self.find('not_in_dorm') - if turn_on ^ (not_in_dorm is not None): + not_in_dorm = self.find('arrange_non_check_in',score=0.85) + if turn_on ^ (not_in_dorm is None): self.tap((self.recog.w * 0.3, self.recog.h * 0.5), interval=0.5) # 确认 self.tap((self.recog.w * 0.8, self.recog.h * 0.8), interval=0.5) diff --git a/arknights_mower/utils/matcher.py b/arknights_mower/utils/matcher.py index 0a5ffae5d..3f3f56c11 100644 --- a/arknights_mower/utils/matcher.py +++ b/arknights_mower/utils/matcher.py @@ -55,7 +55,7 @@ def init_sift(self) -> None: """ get SIFT feature points """ self.kp, self.des = SIFT.detectAndCompute(self.origin, None) - def match(self, query: tp.GrayImage, draw: bool = False, scope: tp.Scope = None, judge: bool = True) -> Optional(tp.Scope): + def match(self, query: tp.GrayImage, draw: bool = False, scope: tp.Scope = None, judge: bool = True,prescore = 0.0) -> Optional(tp.Scope): """ check if the image can be matched """ rect_score = self.score(query, draw, scope) # get matching score if rect_score is None: @@ -68,6 +68,9 @@ def match(self, query: tp.GrayImage, draw: bool = False, scope: tp.Scope = None, logger.debug(f'match fail: {score}') return None # failed in matching else: + if prescore>0 and score[3] tp.Scope: + def find(self, res: str, draw: bool = False, scope: tp.Scope = None, thres: int = None, judge: bool = True, strict: bool = False,score = 0.0) -> tp.Scope: """ 查找元素是否出现在画面中 @@ -280,6 +280,7 @@ def find(self, res: str, draw: bool = False, scope: tp.Scope = None, thres: int :param thres: 是否在匹配前对图像进行二值化处理 :param judge: 是否加入更加精确的判断 :param strict: 是否启用严格模式,未找到时报错 + :param strict: 是否启用分数限制,有些图片精确识别需要提高分数阈值 :return ret: 若匹配成功,则返回元素在游戏界面中出现的位置,否则返回 None """ @@ -291,11 +292,11 @@ def find(self, res: str, draw: bool = False, scope: tp.Scope = None, thres: int res_img = thres2(loadimg(res, True), thres) gray_img = cropimg(self.gray, scope) matcher = Matcher(thres2(gray_img, thres)) - ret = matcher.match(res_img, draw=draw, judge=judge) + ret = matcher.match(res_img, draw=draw, judge=judge,prescore=score) else: res_img = loadimg(res, True) matcher = self.matcher - ret = matcher.match(res_img, draw=draw, scope=scope, judge=judge) + ret = matcher.match(res_img, draw=draw, scope=scope, judge=judge,prescore=score) if strict and ret is None: raise RecognizeError(f"Can't find '{res}'") return ret diff --git a/arknights_mower/utils/solver.py b/arknights_mower/utils/solver.py index 9f7491534..2ad143306 100644 --- a/arknights_mower/utils/solver.py +++ b/arknights_mower/utils/solver.py @@ -92,8 +92,8 @@ def input(self, referent: str, input_area: tp.Scope, text: str = None) -> None: self.device.send_text(str(text)) self.device.tap((0, 0)) - def find(self, res: str, draw: bool = False, scope: tp.Scope = None, thres: int = None, judge: bool = True, strict: bool = False) -> tp.Scope: - return self.recog.find(res, draw, scope, thres, judge, strict) + def find(self, res: str, draw: bool = False, scope: tp.Scope = None, thres: int = None, judge: bool = True, strict: bool = False, score = 0.0) -> tp.Scope: + return self.recog.find(res, draw, scope, thres, judge, strict,score) def tap(self, poly: tp.Location, x_rate: float = 0.5, y_rate: float = 0.5, interval: float = 1, rebuild: bool = True) -> None: """ tap """ From b92152519bd740bfdd0adf7ac59bce63d5329aaa Mon Sep 17 00:00:00 2001 From: Ksnow <1048879349@qq.com> Date: Sun, 12 Mar 2023 18:44:57 +0800 Subject: [PATCH 09/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=AB=98=E7=BA=A7?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/utils/log.py | 2 +- main.spec | 1 + menu.py | 272 ++++++++++++++++++++++++----------- 3 files changed, 187 insertions(+), 88 deletions(-) diff --git a/arknights_mower/utils/log.py b/arknights_mower/utils/log.py index 20e627044..9766e17b6 100644 --- a/arknights_mower/utils/log.py +++ b/arknights_mower/utils/log.py @@ -61,7 +61,7 @@ def emit(self, record): ehlr.addFilter(PackagePathFilter()) logger = logging.getLogger(__name__) -logger.setLevel('DEBUG') +logger.setLevel('INFO') logger.addHandler(chlr) logger.addHandler(ehlr) diff --git a/main.spec b/main.spec index da6c8b840..f4f43f59d 100644 --- a/main.spec +++ b/main.spec @@ -15,6 +15,7 @@ a = Analysis( ('arknights_mower/resources', 'arknights_mower/__init__/resources'), ('arknights_mower/data', 'arknights_mower/__init__/data'), ('arknights_mower/vendor', 'arknights_mower/__init__/vendor'), + ('arknights_mower/paddleocr', '.'), ('venv64/Lib/site-packages/onnxruntime/capi/onnxruntime_providers_shared.dll', 'onnxruntime/capi/'), ('venv64/Lib/site-packages/shapely/DLLs/geos.dll', '.'), ('venv64/Lib/site-packages/shapely/DLLs/geos_c.dll', '.') diff --git a/menu.py b/menu.py index 7957b8ae4..1be714185 100644 --- a/menu.py +++ b/menu.py @@ -12,6 +12,7 @@ from arknights_mower.utils import config from arknights_mower.data import agent_list + yaml = YAML() confUrl = './conf.yml'; conf = {} @@ -21,6 +22,80 @@ line = 0 half_line_index = 0 +agent_base_config = { + "Default": {"UpperLimit": 24, "LowerLimit": 0, "ExhaustRequire": False, "ArrangeOrder": [2, "false"], + "RestInFull": False}, + "令": {"ArrangeOrder": [2, "true"]}, + "夕": {"ArrangeOrder": [2, "true"]}, + "稀音": {"ExhaustRequire": True, "ArrangeOrder": [2, "true"], "RestInFull": True}, + "巫恋": {"ArrangeOrder": [2, "true"]}, + "柏喙": {"ExhaustRequire": True, "ArrangeOrder": [2, "true"]}, + "龙舌兰": {"ArrangeOrder": [2, "true"]}, + "空弦": {"ArrangeOrder": [2, "true"], "RestingPriority": "low"}, + "伺夜": {"ArrangeOrder": [2, "true"]}, + "绮良": {"ArrangeOrder": [2, "true"]}, + "但书": {"ArrangeOrder": [2, "true"]}, + "泡泡": {"ArrangeOrder": [2, "true"]}, + "火神": {"ArrangeOrder": [2, "true"]}, + "黑键": {"ArrangeOrder": [2, "true"]}, + "波登可": {"ArrangeOrder": [2, "false"]}, + "夜莺": {"ArrangeOrder": [2, "false"]}, + "菲亚梅塔": {"ArrangeOrder": [2, "false"]}, + "流明": {"ArrangeOrder": [2, "false"]}, + "蜜莓": {"ArrangeOrder": [2, "false"]}, + "闪灵": {"ArrangeOrder": [2, "false"]}, + "杜林": {"ArrangeOrder": [2, "false"]}, + "褐果": {"ArrangeOrder": [2, "false"]}, + "车尔尼": {"ArrangeOrder": [2, "false"]}, + "安比尔": {"ArrangeOrder": [2, "false"]}, + "爱丽丝": {"ArrangeOrder": [2, "false"]}, + "桃金娘": {"ArrangeOrder": [2, "false"]}, + "帕拉斯": {"RestingPriority": "low"}, + "红云": {"RestingPriority": "low", "ArrangeOrder": [2, "true"]}, + "承曦格雷伊": {"ArrangeOrder": [2, "true"]}, + "乌有": {"ArrangeOrder": [2, "true"], "RestingPriority": "low"}, + "图耶": {"ArrangeOrder": [2, "true"]}, + "鸿雪": {"ArrangeOrder": [2, "true"]}, + "孑": {"ArrangeOrder": [2, "true"]}, + "清道夫": {"ArrangeOrder": [2, "true"]}, + "临光": {"ArrangeOrder": [2, "true"]}, + "杜宾": {"ArrangeOrder": [2, "true"]}, + "焰尾": {"RestInFull": True}, + "重岳": {"ArrangeOrder": [2, "true"]}, + "坚雷": {"ArrangeOrder": [2, "true"]}, + "年": {"RestingPriority": "low"} +} + + +# 执行自动排班 +def start(c, p, child_conn): + global plan + global conf + conf = c + plan = p + config.LOGFILE_PATH = './log' + config.SCREENSHOT_PATH = './screenshot' + config.SCREENSHOT_MAXNUM = 1000 + config.ADB_DEVICE = [conf['adb']] + config.ADB_CONNECT = [conf['adb']] + config.ADB_CONNECT = [conf['adb']] + init_fhlr(child_conn) + if conf['ling_xi'] == 1: + agent_base_config['令']['UpperLimit'] = 11 + agent_base_config['夕']['UpperLimit'] = 11 + agent_base_config['夕']['LowerLimit'] = 13 + elif conf['ling_xi'] == 2: + agent_base_config['夕']['UpperLimit'] = 11 + agent_base_config['令']['UpperLimit'] = 11 + agent_base_config['令']['LowerLimit'] = 13 + for key in list(filter(None, conf['rest_in_full'].replace(',', ',').split(','))): + if key in agent_base_config.keys(): + agent_base_config[key]['RestInFull'] = True + else: + agent_base_config[key] = {'RestInFull': True} + logger.info('开始运行Mower') + simulate() + def inialize(tasks, scheduler=None): device = Device() @@ -40,11 +115,12 @@ def inialize(tasks, scheduler=None): base_scheduler.read_mood = True base_scheduler.scan_time = {} base_scheduler.last_room = '' - base_scheduler.free_blacklist = [] + base_scheduler.free_blacklist = list(filter(None, conf['free_blacklist'].replace(',', ',').split(','))) base_scheduler.resting_treshhold = 0.5 base_scheduler.MAA = None base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] base_scheduler.error = False + base_scheduler.agent_base_config = agent_base_config return base_scheduler else: scheduler.device = cli.device @@ -69,7 +145,8 @@ def simulate(): logger.info(base_scheduler.tasks) if sleep_time >= 0: remaining_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() - logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}") + logger.info( + f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}") time.sleep(sleep_time) base_scheduler.run() reconnect_tries = 0 @@ -94,20 +171,24 @@ def simulate(): # 读取写入配置文件 -def loadConf(): +def load_conf(): global conf global confUrl if not os.path.isfile(confUrl): open(confUrl, 'w') # 创建空配置文件 - conf['planFile'] = './plan.json' # 默认排班表地址 - return - with open(confUrl, 'r', encoding='utf8') as c: - conf = yaml.load(c) - if conf is None: - conf = {} - - -def writeConf(): + else: + with open(confUrl, 'r', encoding='utf8') as c: + conf = yaml.load(c) + if conf is None: + conf = {} + conf['adb'] = conf['adb'] if 'adb' in conf.keys() else '' + conf['planFile'] = conf['planFile'] if 'planFile' in conf.keys() else './plan.json' # 默认排班表地址 + conf['free_blacklist'] = conf['free_blacklist'] if 'free_blacklist' in conf.keys() else '' + conf['ling_xi'] = conf['ling_xi'] if 'ling_xi' in conf.keys() else 1 + conf['rest_in_full'] = conf['rest_in_full'] if 'rest_in_full' in conf.keys() else '' + + +def write_conf(): global conf global confUrl with open(confUrl, 'w', encoding='utf8') as c: @@ -115,8 +196,8 @@ def writeConf(): yaml.dump(conf, c) -# 读取写入排班表 -def loadPlan(url): +# 读取排班表 +def load_plan(url): global plan if not os.path.isfile(url): with open(url, 'w') as f: @@ -143,41 +224,27 @@ def loadPlan(url): println('json格式错误!') -def writePlan(): +# 写入排班表 +def write_plan(): with open(conf['planFile'], 'w', encoding='utf8') as c: json.dump(plan, c, ensure_ascii=False) -# 执行自动排班 -def start(c, p, child_conn): - global plan - global conf - conf = c - plan = p - config.LOGFILE_PATH = './log' - config.SCREENSHOT_PATH = './screenshot' - config.SCREENSHOT_MAXNUM = 1000 - config.ADB_DEVICE = [conf['adb']] - config.ADB_CONNECT = [conf['adb']] - init_fhlr(child_conn) - logger.info('开始运行Mower') - simulate() - - # 主页面 def menu(): global window global buffer - loadConf() + load_conf() sg.theme('LightBlue2') - # maa_title = sg.Text('MAA:') - # maa_select = sg.InputCombo(['启用', '停用'], default_value='停用', size=(20, 3)) - # adb + # --------主页 adb_title = sg.Text('adb连接地址:', size=10) - adb = sg.InputText(conf['adb'] if 'adb' in conf.keys() else '', size=60) + adb = sg.InputText(conf['adb'], size=60, key='conf_adb', enable_events=True) + # 黑名单 + free_blacklist_title = sg.Text('宿舍黑名单:', size=10) + free_blacklist = sg.InputText(conf['free_blacklist'], size=60, key='conf_free_blacklist', enable_events=True) # 排班表json plan_title = sg.Text('排班表:', size=10) - planFile = sg.InputText(conf['planFile'], readonly=True, size=60, key='planFile', enable_events=True) + plan_file = sg.InputText(conf['planFile'], readonly=True, size=60, key='planFile', enable_events=True) plan_select = sg.FileBrowse('...', size=(3, 1), file_types=(("JSON files", "*.json"),)) # 总开关 on_btn = sg.Button('开始执行', key='on') @@ -185,13 +252,14 @@ def menu(): # 日志栏 output = sg.Output(size=(150, 25), key='log', text_color='#808069', font=('微软雅黑', 9)) + # --------排班表设置页面 # 宿舍区 central = sg.Button('控制中枢', key='btn_central', size=(18, 3), button_color='#303030') dormitory_1 = sg.Button('宿舍', key='btn_dormitory_1', size=(18, 2), button_color='#303030') dormitory_2 = sg.Button('宿舍', key='btn_dormitory_2', size=(18, 2), button_color='#303030') dormitory_3 = sg.Button('宿舍', key='btn_dormitory_3', size=(18, 2), button_color='#303030') dormitory_4 = sg.Button('宿舍', key='btn_dormitory_4', size=(18, 2), button_color='#303030') - centralArea = sg.Column([[central], [dormitory_1], [dormitory_2], [dormitory_3], [dormitory_4]]) + central_area = sg.Column([[central], [dormitory_1], [dormitory_2], [dormitory_3], [dormitory_4]]) # 制造站区 room_1_1 = sg.Button('待建造', key='btn_room_1_1', size=(12, 2), button_color='#4f4945') room_1_2 = sg.Button('待建造', key='btn_room_1_2', size=(12, 2), button_color='#4f4945') @@ -202,63 +270,90 @@ def menu(): room_3_1 = sg.Button('待建造', key='btn_room_3_1', size=(12, 2), button_color='#4f4945') room_3_2 = sg.Button('待建造', key='btn_room_3_2', size=(12, 2), button_color='#4f4945') room_3_3 = sg.Button('待建造', key='btn_room_3_3', size=(12, 2), button_color='#4f4945') - leftArea = sg.Column([[room_1_1, room_1_2, room_1_3], - [room_2_1, room_2_2, room_2_3], - [room_3_1, room_3_2, room_3_3]]) + left_area = sg.Column([[room_1_1, room_1_2, room_1_3], + [room_2_1, room_2_2, room_2_3], + [room_3_1, room_3_2, room_3_3]]) # 功能区 meeting = sg.Button('会客室', key='btn_meeting', size=(24, 2), button_color='#303030') factory = sg.Button('加工站', key='btn_factory', size=(24, 2), button_color='#303030') contact = sg.Button('办公室', key='btn_contact', size=(24, 2), button_color='#303030') - rightArea = sg.Column([[meeting], [factory], [contact]]) + right_area = sg.Column([[meeting], [factory], [contact]]) - settingLayout = [[sg.Column([[sg.Text('设施类别:'), sg.InputCombo(['贸易站', '制造站', '发电站'], size=12, key='station_type')]], - key='station_type_col', visible=False)]] + setting_layout = [ + [sg.Column([[sg.Text('设施类别:'), sg.InputCombo(['贸易站', '制造站', '发电站'], size=12, key='station_type')]], + key='station_type_col', visible=False)]] # 排班表设置标签 for i in range(1, 6): - setArea = sg.Column([[sg.Text('干员:'), - sg.InputCombo(['Free'] + agent_list, size=20, key='agent' + str(i)), - sg.Text('组:'), - sg.InputText('', size=15, key='group' + str(i)), - sg.Text('替换:'), - sg.InputText('', size=30, key='replacement' + str(i)) - ]], key='setArea' + str(i), visible=False) - settingLayout.append([setArea]) - settingLayout.append([sg.Button('保存', key='savePlan', visible=False)]) - settingArea = sg.Column(settingLayout, element_justification="center", - vertical_alignment="bottom", - expand_x=True) - # 组装页面 - mainTab = sg.Tab(' 主页 ', [[adb_title, adb], - [plan_title, planFile, plan_select], - [output], - [on_btn, off_btn]]) - - planTab = sg.Tab(' 排班表 ', [[leftArea, centralArea, rightArea], [settingArea]], element_justification="center") - window = sg.Window('Mower', [[sg.TabGroup([[mainTab, planTab]], border_width=0, + set_area = sg.Column([[sg.Text('干员:'), + sg.InputCombo(['Free'] + agent_list, size=20, key='agent' + str(i)), + sg.Text('组:'), + sg.InputText('', size=15, key='group' + str(i)), + sg.Text('替换:'), + sg.InputText('', size=30, key='replacement' + str(i)) + ]], key='setArea' + str(i), visible=False) + setting_layout.append([set_area]) + setting_layout.append([sg.Button('保存', key='savePlan', visible=False)]) + setting_area = sg.Column(setting_layout, element_justification="center", + vertical_alignment="bottom", + expand_x=True) + + # --------高级设置页面 + ling_xi_title = sg.Text('令夕模式(令夕上班时起作用):', size=25) + ling_xi_1 = sg.Radio('感知信息', 'ling_xi', default=conf['ling_xi'] == 1, + key='radio_ling_xi_1', enable_events=True) + ling_xi_2 = sg.Radio('人间烟火', 'ling_xi', default=conf['ling_xi'] == 2, + key='radio_ling_xi_2', enable_events=True) + ling_xi_3 = sg.Radio('均衡模式', 'ling_xi', default=conf['ling_xi'] == 3, + key='radio_ling_xi_3', enable_events=True) + rest_in_full_title = sg.Text('需要回满心情的干员:', size=25) + rest_in_full = sg.InputText(conf['rest_in_full'] if 'rest_in_full' in conf.keys() else '', size=60, + key='conf_rest_in_full', enable_events=True) + # --------组装页面 + main_tab = sg.Tab(' 主页 ', [[adb_title, adb], + [free_blacklist_title, free_blacklist], + [plan_title, plan_file, plan_select], + [output], + [on_btn, off_btn]]) + + plan_tab = sg.Tab(' 排班表 ', [[left_area, central_area, right_area], [setting_area]], element_justification="center") + + setting_tab = sg.Tab(' 高级设置 ', + [[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full]]) + window = sg.Window('Mower', [[sg.TabGroup([[main_tab, plan_tab, setting_tab]], border_width=0, tab_border_width=0, focus_color='#bcc8e5', selected_background_color='#d4dae8', background_color='#aab6d3', tab_background_color='#aab6d3')]], font='微软雅黑', finalize=True) - loadPlan(conf['planFile']) + load_plan(conf['planFile']) + btn = None while True: event, value = window.Read() + if event == sg.WIN_CLOSED: - conf['adb'] = adb.get() break - elif event == 'planFile' and planFile.get() != conf['planFile']: - writePlan() - loadPlan(planFile.get()) - planFile.update(conf['planFile']) - elif event.startswith('btn_'): + elif event.startswith('conf_'): + key = event[5:] + conf[key] = window[event].get() + elif event.startswith('radio_'): + v_index = event.rindex('_') + conf[event[6:v_index]] = int(event[v_index + 1:]) + elif event == 'planFile' and plan_file.get() != conf['planFile']: # 排班表 + btn = None + write_plan() + load_plan(plan_file.get()) + plan_file.update(conf['planFile']) + elif event.startswith('btn_'): # 设施按钮 + if btn is not None: + save_btn(btn) btn = event - initBtn(event) - elif event == 'savePlan': - saveBtn(btn) + init_btn(event) + elif event == 'savePlan': # 保存设施信息 + save_btn(btn) + elif event == 'on': if adb.get() == '': println('adb未设置!') continue - conf['adb'] = adb.get() on_btn.update(visible=False) off_btn.update(visible=True) @@ -275,30 +370,30 @@ def menu(): off_btn.update(visible=False) window.close() - writeConf() - writePlan() + write_conf() + write_plan() -def initBtn(event): +def init_btn(event): room_key = event[4:] station_name = plan[room_key]['name'] if room_key in plan.keys() else '' plans = plan[room_key]['plans'] if room_key in plan.keys() else [] if room_key.startswith('room'): window['station_type_col'].update(visible=True) window['station_type'].update(station_name) - visibleCnt = 3 # 设施干员需求数量 + visible_cnt = 3 # 设施干员需求数量 else: if room_key == 'meeting': - visibleCnt = 2 + visible_cnt = 2 elif room_key == 'factory' or room_key == 'contact': - visibleCnt = 1 + visible_cnt = 1 else: - visibleCnt = 5 + visible_cnt = 5 window['station_type_col'].update(visible=False) window['station_type'].update('') window['savePlan'].update(visible=True) for i in range(1, 6): - if i > visibleCnt: + if i > visible_cnt: window['setArea' + str(i)].update(visible=False) window['agent' + str(i)].update('') window['group' + str(i)].update('') @@ -310,17 +405,19 @@ def initBtn(event): window['replacement' + str(i)].update(','.join(plans[i - 1]['replacement']) if len(plans) >= i else '') -def saveBtn(btn): +def save_btn(btn): plan1 = {'name': window['station_type'].get(), 'plans': []} for i in range(1, 6): agent = window['agent' + str(i)].get() group = window['group' + str(i)].get() - replacement = list(filter(None, window['replacement' + str(i)].get().split(','))) + replacement = list(filter(None, window['replacement' + str(i)].get().replace(',', ',').split(','))) if agent != '': plan1['plans'].append({'agent': agent, 'group': group, 'replacement': replacement}) + elif btn.startswith('btn_dormitory'): # 宿舍 + plan1['plans'].append({'agent': 'Free', 'group': '', 'replacement': []}) plan[btn[4:]] = plan1 - writePlan() - loadPlan(conf['planFile']) + write_plan() + load_plan(conf['planFile']) # 输出日志 @@ -359,5 +456,6 @@ def clear(): if __name__ == '__main__': + # logger.info(123) freeze_support() menu() From bb7e0b6d709c8d32ddf04fa5be24b06c84d911a2 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Sun, 12 Mar 2023 21:38:04 -0700 Subject: [PATCH 10/39] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dmater=20None=20t?= =?UTF-8?q?ype=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 20d03620f..bd3015b65 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -1184,11 +1184,7 @@ def get_order(self, name): def detail_filter(self, turn_on, type="not_in_dorm"): logger.info(f'开始 {("打开" if turn_on else "关闭")} {type} 筛选') - error_count = 0 - while self.find("arrange_order_options_scene") is None and error_count<4: - self.tap((self.recog.w * 0.95, self.recog.h * 0.05), interval=1) - error_count+=1 - if error_count>=4: raise Exception("进入筛选界面失败") + self.tap((self.recog.w * 0.95, self.recog.h * 0.05), interval=1) if type == "not_in_dorm": not_in_dorm = self.find('arrange_non_check_in',score=0.85) if turn_on ^ (not_in_dorm is None): From 6790c9f172e2bc625151be00558827ccb6dddb8b Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Mon, 13 Mar 2023 20:46:08 -0700 Subject: [PATCH 11/39] fix: diy Missing subject --- diy.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/diy.py b/diy.py index 84e1ebe29..29047dcbd 100644 --- a/diy.py +++ b/diy.py @@ -12,7 +12,8 @@ 'account':"xxx@qq.com", 'pass_code':'从QQ邮箱帐户设置—>生成授权码', 'receipts':['任何邮箱'], - 'notify':False + 'notify':False, + 'subject': '任务数据' } maa_config = { # 请设置为存放 dll 文件及资源的路径 @@ -138,8 +139,12 @@ agent_base_config = { "Default":{"UpperLimit": 24,"LowerLimit": 0,"ExhaustRequire": False,"ArrangeOrder":[2,"false"],"RestInFull": False}, - "令":{"ArrangeOrder":[2,"true"]}, - "夕": {"ArrangeOrder":[2,"true"]}, + # 卡贸易站 + "令":{"UpperLimit": 11,"LowerLimit": 13,"ArrangeOrder":[2,"true"]}, + "夕": {"UpperLimit": 11,"ArrangeOrder":[2,"true"]}, + # 卡制造站 + #"令": {"UpperLimit": 11, "LowerLimit": 13, "ArrangeOrder": [2, "true"]}, + #"夕": {"UpperLimit": 11, "ArrangeOrder": [2, "true"]}, "稀音":{"ExhaustRequire": True,"ArrangeOrder":[2,"true"],"RestInFull": True}, "巫恋":{"ArrangeOrder":[2,"true"]}, "柏喙":{"ExhaustRequire": True,"ArrangeOrder":[2,"true"]}, From 5cd1e8fbb09c87c60ae29ab15e5e6b32407cf119 Mon Sep 17 00:00:00 2001 From: shawnsdaddy Date: Tue, 14 Mar 2023 21:35:14 -0700 Subject: [PATCH 12/39] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=E7=A1=AC?= =?UTF-8?q?=E7=BC=96package=5Fname?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 9 +++++---- diy.py | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index bd3015b65..6a9a1acbb 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -44,6 +44,7 @@ class BaseSchedulerSolver(BaseSolver): """ 收集基建的产物:物资、赤金、信赖 """ + package_name = '' def __init__(self, device: Device = None, recog: Recognizer = None) -> None: super().__init__(device, recog) @@ -138,7 +139,7 @@ def overtake_room(self): def handle_error(self,force = False): # 如果有任何报错,则生成一个空 if self.scene() == Scene.UNKNOWN: - self.device.exit('com.hypergryph.arknights') + self.device.exit(self.package_name) if self.error or force: # 如果没有任何时间小于当前时间的任务才生成空任务 if (next((e for e in self.tasks if e['time'] < datetime.now()), None)) is None: @@ -1619,7 +1620,7 @@ def maa_plan_solver(self): if hard_stop: logger.info(f"由于maa任务并未完成,等待3分钟重启软件") time.sleep(180) - self.device.exit('com.hypergryph.arknights') + self.device.exit(self.package_name) else: logger.info(f"记录MAA 本次执行时间") self.maa_config['last_execution'] = datetime.now() @@ -1657,7 +1658,7 @@ def maa_plan_solver(self): break else: time.sleep(0) - self.device.exit('com.hypergryph.arknights') + self.device.exit(self.package_name) # 生息演算逻辑 结束 remaining_time = (self.tasks[0]["time"] - datetime.now()).total_seconds() logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") @@ -1671,7 +1672,7 @@ def maa_plan_solver(self): if remaining_time > 0: logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") time.sleep(remaining_time) - self.device.exit('com.hypergryph.arknights') + self.device.exit(self.package_name) def send_email(self, tasks): try: diff --git a/diy.py b/diy.py index 29047dcbd..038e7d91c 100644 --- a/diy.py +++ b/diy.py @@ -210,6 +210,8 @@ def inialize(tasks,scheduler=None): cli = Solver(device) if scheduler is None: base_scheduler = BaseSchedulerSolver(cli.device, cli.recog) + base_scheduler.package_name= 'com.hypergryph.arknights' # 官服 + # com.hypergryph.arknights.bilibili # Bilibili 服 base_scheduler.operators = {} base_scheduler.global_plan = plan base_scheduler.current_base = {} From 9dc98e46e986b06dd2be913fe5d9dab645996838 Mon Sep 17 00:00:00 2001 From: Ksnow <1048879349@qq.com> Date: Wed, 15 Mar 2023 12:53:51 +0800 Subject: [PATCH 13/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E6=8F=90=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 10 +++-- arknights_mower/utils/log.py | 2 +- menu.py | 54 +++++++++++++++++++----- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 20d03620f..b3ca6553b 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -1677,12 +1677,14 @@ def maa_plan_solver(self): time.sleep(remaining_time) self.device.exit('com.hypergryph.arknights') - def send_email(self, tasks): + def send_email(self, context,subject): + if 'mail_enable' in self.email_config.keys() and self.email_config['mail_enable'] == 0: + logger.info('邮件功能未开启') + return try: msg = MIMEMultipart() - conntent = str(tasks) - msg.attach(MIMEText(conntent, 'plain', 'utf-8')) - msg['Subject'] = self.email_config['subject'] + msg.attach(MIMEText(str(context), 'plain', 'utf-8')) + msg['Subject'] = self.email_config['subject']+(str(subject)if subject else '') msg['From'] = self.email_config['account'] s = smtplib.SMTP_SSL("smtp.qq.com", 465) # 登录邮箱 diff --git a/arknights_mower/utils/log.py b/arknights_mower/utils/log.py index 9766e17b6..20e627044 100644 --- a/arknights_mower/utils/log.py +++ b/arknights_mower/utils/log.py @@ -61,7 +61,7 @@ def emit(self, record): ehlr.addFilter(PackagePathFilter()) logger = logging.getLogger(__name__) -logger.setLevel('INFO') +logger.setLevel('DEBUG') logger.addHandler(chlr) logger.addHandler(ehlr) diff --git a/menu.py b/menu.py index 1be714185..6dfb2e4ef 100644 --- a/menu.py +++ b/menu.py @@ -12,7 +12,6 @@ from arknights_mower.utils import config from arknights_mower.data import agent_list - yaml = YAML() confUrl = './conf.yml'; conf = {} @@ -116,8 +115,17 @@ def inialize(tasks, scheduler=None): base_scheduler.scan_time = {} base_scheduler.last_room = '' base_scheduler.free_blacklist = list(filter(None, conf['free_blacklist'].replace(',', ',').split(','))) + logger.info('宿舍黑名单:' + str(base_scheduler.free_blacklist)) base_scheduler.resting_treshhold = 0.5 base_scheduler.MAA = None + base_scheduler.email_config = { + 'mail_enable': conf['mail_enable'], + 'subject': '[Mower通知]', + 'account': conf['account'], + 'pass_code': conf['pass_code'], + 'receipts': [conf['account']], + 'notify': False + } base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] base_scheduler.error = False base_scheduler.agent_base_config = agent_base_config @@ -142,11 +150,14 @@ def simulate(): if len(base_scheduler.tasks) > 0: (base_scheduler.tasks.sort(key=lambda x: x["time"], reverse=False)) sleep_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() - logger.info(base_scheduler.tasks) - if sleep_time >= 0: + logger.debug(base_scheduler.tasks) + if sleep_time > 0: remaining_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() - logger.info( - f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}") + subject = f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}" + context = f"下一次任务:{base_scheduler.tasks[0]['plan']}" + logger.info(context) + logger.info(subject) + base_scheduler.send_email(context, subject) time.sleep(sleep_time) base_scheduler.run() reconnect_tries = 0 @@ -186,6 +197,11 @@ def load_conf(): conf['free_blacklist'] = conf['free_blacklist'] if 'free_blacklist' in conf.keys() else '' conf['ling_xi'] = conf['ling_xi'] if 'ling_xi' in conf.keys() else 1 conf['rest_in_full'] = conf['rest_in_full'] if 'rest_in_full' in conf.keys() else '' + conf['mail_enable'] = conf['mail_enable'] if 'mail_enable' in conf.keys() else 0 + conf['account'] = conf['account'] if 'account' in conf.keys() else '' + conf['pass_code'] = conf['pass_code'] if 'pass_code' in conf.keys() else '' + conf['maa_enable'] = conf['maa_enable'] if 'maa_enable' in conf.keys() else '' + conf['maa_path'] = conf['maa_path'] if 'maa_path' in conf.keys() else '' def write_conf(): @@ -306,8 +322,28 @@ def menu(): ling_xi_3 = sg.Radio('均衡模式', 'ling_xi', default=conf['ling_xi'] == 3, key='radio_ling_xi_3', enable_events=True) rest_in_full_title = sg.Text('需要回满心情的干员:', size=25) - rest_in_full = sg.InputText(conf['rest_in_full'] if 'rest_in_full' in conf.keys() else '', size=60, + rest_in_full = sg.InputText(conf['rest_in_full'], size=60, key='conf_rest_in_full', enable_events=True) + # mail + mail_enable_1 = sg.Radio('启用', 'mail_enable', default=conf['mail_enable'] == 1, + key='radio_mail_enable_1', enable_events=True) + mail_enable_0 = sg.Radio('禁用', 'mail_enable', default=conf['mail_enable'] == 0, + key='radio_mail_enable_0', enable_events=True) + account_title = sg.Text('QQ邮箱', size=25) + account = sg.InputText(conf['account'], size=60, key='conf_account', enable_events=True) + pass_code_title = sg.Text('授权码', size=25) + pass_code = sg.Input(conf['pass_code'], size=60, key='conf_pass_code', enable_events=True, password_char='*') + mail_frame = sg.Frame('邮件提醒', + [[mail_enable_1, mail_enable_0], [account_title, account], [pass_code_title, pass_code]]) + # maa + maa_enable_1 = sg.Radio('启用', 'maa_enable', default=conf['maa_enable'] == 1, + key='radio_maa_enable_1', enable_events=True) + maa_enable_0 = sg.Radio('禁用', 'maa_enable', default=conf['maa_enable'] == 0, + key='radio_maa_enable_0', enable_events=True) + maa_path_title = sg.Text('MAA地址', size=25) + maa_path = sg.InputText(conf['maa_path'], size=60, key='conf_maa_path', enable_events=True) + maa_frame = sg.Frame('MAA', + [[maa_enable_1, maa_enable_0], [maa_path_title, maa_path]]) # --------组装页面 main_tab = sg.Tab(' 主页 ', [[adb_title, adb], [free_blacklist_title, free_blacklist], @@ -318,7 +354,8 @@ def menu(): plan_tab = sg.Tab(' 排班表 ', [[left_area, central_area, right_area], [setting_area]], element_justification="center") setting_tab = sg.Tab(' 高级设置 ', - [[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full]]) + [[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full], + [mail_frame],[maa_frame]]) window = sg.Window('Mower', [[sg.TabGroup([[main_tab, plan_tab, setting_tab]], border_width=0, tab_border_width=0, focus_color='#bcc8e5', selected_background_color='#d4dae8', background_color='#aab6d3', @@ -338,13 +375,10 @@ def menu(): v_index = event.rindex('_') conf[event[6:v_index]] = int(event[v_index + 1:]) elif event == 'planFile' and plan_file.get() != conf['planFile']: # 排班表 - btn = None write_plan() load_plan(plan_file.get()) plan_file.update(conf['planFile']) elif event.startswith('btn_'): # 设施按钮 - if btn is not None: - save_btn(btn) btn = event init_btn(event) elif event == 'savePlan': # 保存设施信息 From f423c6dbb9213ca75cc20ce90777ce88940458ed Mon Sep 17 00:00:00 2001 From: shawnsdaddy Date: Tue, 14 Mar 2023 22:28:00 -0700 Subject: [PATCH 14/39] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Eappname?= =?UTF-8?q?=E5=88=B0diy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/diy.py b/diy.py index 038e7d91c..813f2c026 100644 --- a/diy.py +++ b/diy.py @@ -203,6 +203,8 @@ def savelog(): config.ADB_DEVICE = maa_config['maa_adb'] config.ADB_CONNECT = maa_config['maa_adb'] config.PASSWORD = '你的密码' + config.APPNAME = 'com.hypergryph.arknights' # 官服 + # com.hypergryph.arknights.bilibili # Bilibili 服 init_fhlr() def inialize(tasks,scheduler=None): @@ -210,8 +212,7 @@ def inialize(tasks,scheduler=None): cli = Solver(device) if scheduler is None: base_scheduler = BaseSchedulerSolver(cli.device, cli.recog) - base_scheduler.package_name= 'com.hypergryph.arknights' # 官服 - # com.hypergryph.arknights.bilibili # Bilibili 服 + base_scheduler.package_name= config.APPNAME base_scheduler.operators = {} base_scheduler.global_plan = plan base_scheduler.current_base = {} From 7e968617594b77171f9ad4a52df5e792e0ea558a Mon Sep 17 00:00:00 2001 From: Ksnow <1048879349@qq.com> Date: Sun, 19 Mar 2023 02:32:24 +0800 Subject: [PATCH 15/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0MAA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/__main__.py | 252 +++++++++++++++++------ arknights_mower/solvers/base_schedule.py | 13 +- menu.py | 221 +++++--------------- 3 files changed, 248 insertions(+), 238 deletions(-) diff --git a/arknights_mower/__main__.py b/arknights_mower/__main__.py index a43835188..c998503ee 100644 --- a/arknights_mower/__main__.py +++ b/arknights_mower/__main__.py @@ -1,72 +1,200 @@ -import sys -import traceback -from pathlib import Path +import time +from datetime import datetime -from . import __pyinstall__, __rootdir__ -from .command import * -from .utils import config -from .utils.device import Device -from .utils.log import logger, set_debug_mode +conf = {} +plan = {} -def main(module: bool = True) -> None: - args = sys.argv[1:] - if not args and __pyinstall__: - logger.info('参数为空,默认执行 schedule 模式,按下 Ctrl+C 以结束脚本运行') - args.append('schedule') - config_path = None - debug_mode = False - while True: - if len(args) > 1 and args[-2] == '--config': - config_path = Path(args[-1]) - args = args[:-2] - continue - if len(args) > 0 and args[-1] == '--debug': - debug_mode = True - args = args[:-1] - continue - break - - if config_path is None: - if __pyinstall__: - config_path = Path(sys.executable).parent.joinpath('config.yaml') - elif module: - config_path = Path.home().joinpath('.ark_mower.yaml') +# 执行自动排班 +def main(c, p, child_conn): + __init_params__() + from arknights_mower.utils.log import logger, init_fhlr + from arknights_mower.utils import config + global plan + global conf + conf = c + plan = p + config.LOGFILE_PATH = './log' + config.SCREENSHOT_PATH = './screenshot' + config.SCREENSHOT_MAXNUM = 1000 + config.ADB_DEVICE = [conf['adb']] + config.ADB_CONNECT = [conf['adb']] + config.ADB_CONNECT = [conf['adb']] + config.APPNAME = 'com.hypergryph.arknights' if conf[ + 'package_type'] == 1 else 'com.hypergryph.arknights.bilibili' # 服务器 + init_fhlr(child_conn) + if conf['ling_xi'] == 1: + agent_base_config['令']['UpperLimit'] = 11 + agent_base_config['夕']['UpperLimit'] = 11 + agent_base_config['夕']['LowerLimit'] = 13 + elif conf['ling_xi'] == 2: + agent_base_config['夕']['UpperLimit'] = 11 + agent_base_config['令']['UpperLimit'] = 11 + agent_base_config['令']['LowerLimit'] = 13 + for key in list(filter(None, conf['rest_in_full'].replace(',', ',').split(','))): + if key in agent_base_config.keys(): + agent_base_config[key]['RestInFull'] = True else: - config_path = __rootdir__.parent.joinpath('config.yaml') - if not config_path.exists(): - config.build_config(config_path, module) - else: - if not config_path.exists(): - logger.error(f'The configuration file does not exist: {config_path}') - return - try: - logger.info(f'Loading the configuration file: {config_path}') - config.load_config(config_path) - except Exception as e: - logger.error('An error occurred when loading the configuration file') - raise e + agent_base_config[key] = {'RestInFull': True} + logger.info('开始运行Mower') + simulate() - if debug_mode and not config.DEBUG_MODE: - config.DEBUG_MODE = True - set_debug_mode() +def inialize(tasks, scheduler=None): + from arknights_mower.utils.log import logger + from arknights_mower.solvers.base_schedule import BaseSchedulerSolver + from arknights_mower.strategy import Solver + from arknights_mower.utils.device import Device + from arknights_mower.utils import config device = Device() - - logger.debug(args) - if len(args) == 0: - help() + cli = Solver(device) + if scheduler is None: + base_scheduler = BaseSchedulerSolver(cli.device, cli.recog) + base_scheduler.operators = {} + plan1 = {} + for key in plan: + plan1[key] = plan[key]['plans'] + base_scheduler.package_name = config.APPNAME # 服务器 + base_scheduler.global_plan = {'default': "plan_1", "plan_1": plan1} + base_scheduler.current_base = {} + base_scheduler.resting = [] + base_scheduler.dorm_count = 4 + base_scheduler.tasks = tasks + # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true + base_scheduler.read_mood = True + base_scheduler.scan_time = {} + base_scheduler.last_room = '' + base_scheduler.free_blacklist = list(filter(None, conf['free_blacklist'].replace(',', ',').split(','))) + logger.info('宿舍黑名单:' + str(base_scheduler.free_blacklist)) + base_scheduler.resting_treshhold = 0.5 + base_scheduler.MAA = None + base_scheduler.email_config = { + 'mail_enable': conf['mail_enable'], + 'subject': '[Mower通知]', + 'account': conf['account'], + 'pass_code': conf['pass_code'], + 'receipts': [conf['account']], + 'notify': False + } + maa_config['maa_path'] = conf['maa_path'] + maa_config['maa_adb_path'] = conf['maa_adb_path'] + maa_config['maa_adb'] = conf['adb'] + maa_config['weekly_plan'] = conf['maa_weekly_plan'] + base_scheduler.maa_config = maa_config + base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] + base_scheduler.error = False + base_scheduler.agent_base_config = agent_base_config + return base_scheduler else: - target_cmd = match_cmd(args[0]) - if target_cmd is not None: - try: - target_cmd(args[1:], device) - except ParamError: - logger.debug(traceback.format_exc()) - help() - else: - help() + scheduler.device = cli.device + scheduler.recog = cli.recog + scheduler.handle_error(True) + return scheduler + +def simulate(): + from arknights_mower.utils.log import logger + ''' + 具体调用方法可见各个函数的参数说明 + ''' + tasks = [] + reconnect_max_tries = 10 + reconnect_tries = 0 + base_scheduler = inialize(tasks) + while True: + try: + if len(base_scheduler.tasks) > 0: + (base_scheduler.tasks.sort(key=lambda x: x["time"], reverse=False)) + sleep_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() + logger.debug(base_scheduler.tasks) + if sleep_time > 540 and conf['maa_enable'] == 1: + base_scheduler.maa_plan_solver() + elif sleep_time > 0: + remaining_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() + subject = f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}" + context = f"下一次任务:{base_scheduler.tasks[0]['plan']}" + logger.info(context) + logger.info(subject) + base_scheduler.send_email(context, subject) + time.sleep(sleep_time) + base_scheduler.run() + reconnect_tries = 0 + except ConnectionError as e: + reconnect_tries += 1 + if reconnect_tries < reconnect_max_tries: + logger.warning(f'连接端口断开....正在重连....') + connected = False + while not connected: + try: + base_scheduler = inialize([], base_scheduler) + break + except Exception as ce: + logger.error(ce) + time.sleep(5) + continue + continue + else: + raise Exception(e) + except Exception as E: + logger.exception(f"程序出错--->{E}") -if __name__ == '__main__': - main(module=True) +agent_base_config = {} +maa_config = {} +def __init_params__(): + global agent_base_config + global maa_config + agent_base_config = { + "Default": {"UpperLimit": 24, "LowerLimit": 0, "ExhaustRequire": False, "ArrangeOrder": [2, "false"], + "RestInFull": False}, + "令": {"ArrangeOrder": [2, "true"]}, + "夕": {"ArrangeOrder": [2, "true"]}, + "稀音": {"ExhaustRequire": True, "ArrangeOrder": [2, "true"], "RestInFull": True}, + "巫恋": {"ArrangeOrder": [2, "true"]}, + "柏喙": {"ExhaustRequire": True, "ArrangeOrder": [2, "true"]}, + "龙舌兰": {"ArrangeOrder": [2, "true"]}, + "空弦": {"ArrangeOrder": [2, "true"], "RestingPriority": "low"}, + "伺夜": {"ArrangeOrder": [2, "true"]}, + "绮良": {"ArrangeOrder": [2, "true"]}, + "但书": {"ArrangeOrder": [2, "true"]}, + "泡泡": {"ArrangeOrder": [2, "true"]}, + "火神": {"ArrangeOrder": [2, "true"]}, + "黑键": {"ArrangeOrder": [2, "true"]}, + "波登可": {"ArrangeOrder": [2, "false"]}, + "夜莺": {"ArrangeOrder": [2, "false"]}, + "菲亚梅塔": {"ArrangeOrder": [2, "false"]}, + "流明": {"ArrangeOrder": [2, "false"]}, + "蜜莓": {"ArrangeOrder": [2, "false"]}, + "闪灵": {"ArrangeOrder": [2, "false"]}, + "杜林": {"ArrangeOrder": [2, "false"]}, + "褐果": {"ArrangeOrder": [2, "false"]}, + "车尔尼": {"ArrangeOrder": [2, "false"]}, + "安比尔": {"ArrangeOrder": [2, "false"]}, + "爱丽丝": {"ArrangeOrder": [2, "false"]}, + "桃金娘": {"ArrangeOrder": [2, "false"]}, + "帕拉斯": {"RestingPriority": "low"}, + "红云": {"RestingPriority": "low", "ArrangeOrder": [2, "true"]}, + "承曦格雷伊": {"ArrangeOrder": [2, "true"]}, + "乌有": {"ArrangeOrder": [2, "true"], "RestingPriority": "low"}, + "图耶": {"ArrangeOrder": [2, "true"]}, + "鸿雪": {"ArrangeOrder": [2, "true"]}, + "孑": {"ArrangeOrder": [2, "true"]}, + "清道夫": {"ArrangeOrder": [2, "true"]}, + "临光": {"ArrangeOrder": [2, "true"]}, + "杜宾": {"ArrangeOrder": [2, "true"]}, + "焰尾": {"RestInFull": True}, + "重岳": {"ArrangeOrder": [2, "true"]}, + "坚雷": {"ArrangeOrder": [2, "true"]}, + "年": {"RestingPriority": "low"} + } + maa_config = { + # maa 运行的时间间隔,以小时计 + "maa_execution_gap": 4, + # 以下配置,第一个设置为true的首先生效 + # 是否启动肉鸽 + "roguelike": False, + # 是否启动生息演算 + "reclamation_algorithm": False, + # 是否启动保全派驻 + "stationary_security_service": False, + "last_execution": None + } diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 0bd4d168c..710a45de5 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -8,6 +8,7 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart + from ..data import agent_list from ..utils import character_recognize, detector, segment from ..utils import typealias as tp @@ -738,7 +739,8 @@ def double_read_time(self, cord,upperLimit = 36000): def initialize_paddle(self): global ocr if ocr is None: - ocr = PaddleOCR(use_angle_cls=True, lang='en') + ocr = PaddleOCR(enable_mkldnn=True,use_angle_cls=False) + def read_screen(self, img, type="mood", limit=24, cord=None, change_color=False): if cord is not None: @@ -1661,8 +1663,11 @@ def maa_plan_solver(self): self.device.exit(self.package_name) # 生息演算逻辑 结束 remaining_time = (self.tasks[0]["time"] - datetime.now()).total_seconds() - logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") - self.send_email("脚本停止") + subject = f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}" + context = f"下一次任务:{self.tasks[0]['plan']}" + logger.info(context) + logger.info(subject) + self.send_email(context, subject) time.sleep(remaining_time) self.MAA = None except Exception as e: @@ -1674,7 +1679,7 @@ def maa_plan_solver(self): time.sleep(remaining_time) self.device.exit(self.package_name) - def send_email(self, context,subject): + def send_email(self, context,subject=''): if 'mail_enable' in self.email_config.keys() and self.email_config['mail_enable'] == 0: logger.info('邮件功能未开启') return diff --git a/menu.py b/menu.py index 6dfb2e4ef..d5671181f 100644 --- a/menu.py +++ b/menu.py @@ -1,16 +1,14 @@ +import importlib import json +import sys from multiprocessing import Pipe, Process, freeze_support import time -from datetime import datetime import PySimpleGUI as sg import os from ruamel.yaml import YAML -from arknights_mower.solvers.base_schedule import BaseSchedulerSolver -from arknights_mower.strategy import Solver -from arknights_mower.utils.device import Device -from arknights_mower.utils.log import logger, init_fhlr -from arknights_mower.utils import config +from arknights_mower.utils.log import logger from arknights_mower.data import agent_list +from arknights_mower.__main__ import main yaml = YAML() confUrl = './conf.yml'; @@ -21,165 +19,6 @@ line = 0 half_line_index = 0 -agent_base_config = { - "Default": {"UpperLimit": 24, "LowerLimit": 0, "ExhaustRequire": False, "ArrangeOrder": [2, "false"], - "RestInFull": False}, - "令": {"ArrangeOrder": [2, "true"]}, - "夕": {"ArrangeOrder": [2, "true"]}, - "稀音": {"ExhaustRequire": True, "ArrangeOrder": [2, "true"], "RestInFull": True}, - "巫恋": {"ArrangeOrder": [2, "true"]}, - "柏喙": {"ExhaustRequire": True, "ArrangeOrder": [2, "true"]}, - "龙舌兰": {"ArrangeOrder": [2, "true"]}, - "空弦": {"ArrangeOrder": [2, "true"], "RestingPriority": "low"}, - "伺夜": {"ArrangeOrder": [2, "true"]}, - "绮良": {"ArrangeOrder": [2, "true"]}, - "但书": {"ArrangeOrder": [2, "true"]}, - "泡泡": {"ArrangeOrder": [2, "true"]}, - "火神": {"ArrangeOrder": [2, "true"]}, - "黑键": {"ArrangeOrder": [2, "true"]}, - "波登可": {"ArrangeOrder": [2, "false"]}, - "夜莺": {"ArrangeOrder": [2, "false"]}, - "菲亚梅塔": {"ArrangeOrder": [2, "false"]}, - "流明": {"ArrangeOrder": [2, "false"]}, - "蜜莓": {"ArrangeOrder": [2, "false"]}, - "闪灵": {"ArrangeOrder": [2, "false"]}, - "杜林": {"ArrangeOrder": [2, "false"]}, - "褐果": {"ArrangeOrder": [2, "false"]}, - "车尔尼": {"ArrangeOrder": [2, "false"]}, - "安比尔": {"ArrangeOrder": [2, "false"]}, - "爱丽丝": {"ArrangeOrder": [2, "false"]}, - "桃金娘": {"ArrangeOrder": [2, "false"]}, - "帕拉斯": {"RestingPriority": "low"}, - "红云": {"RestingPriority": "low", "ArrangeOrder": [2, "true"]}, - "承曦格雷伊": {"ArrangeOrder": [2, "true"]}, - "乌有": {"ArrangeOrder": [2, "true"], "RestingPriority": "low"}, - "图耶": {"ArrangeOrder": [2, "true"]}, - "鸿雪": {"ArrangeOrder": [2, "true"]}, - "孑": {"ArrangeOrder": [2, "true"]}, - "清道夫": {"ArrangeOrder": [2, "true"]}, - "临光": {"ArrangeOrder": [2, "true"]}, - "杜宾": {"ArrangeOrder": [2, "true"]}, - "焰尾": {"RestInFull": True}, - "重岳": {"ArrangeOrder": [2, "true"]}, - "坚雷": {"ArrangeOrder": [2, "true"]}, - "年": {"RestingPriority": "low"} -} - - -# 执行自动排班 -def start(c, p, child_conn): - global plan - global conf - conf = c - plan = p - config.LOGFILE_PATH = './log' - config.SCREENSHOT_PATH = './screenshot' - config.SCREENSHOT_MAXNUM = 1000 - config.ADB_DEVICE = [conf['adb']] - config.ADB_CONNECT = [conf['adb']] - config.ADB_CONNECT = [conf['adb']] - init_fhlr(child_conn) - if conf['ling_xi'] == 1: - agent_base_config['令']['UpperLimit'] = 11 - agent_base_config['夕']['UpperLimit'] = 11 - agent_base_config['夕']['LowerLimit'] = 13 - elif conf['ling_xi'] == 2: - agent_base_config['夕']['UpperLimit'] = 11 - agent_base_config['令']['UpperLimit'] = 11 - agent_base_config['令']['LowerLimit'] = 13 - for key in list(filter(None, conf['rest_in_full'].replace(',', ',').split(','))): - if key in agent_base_config.keys(): - agent_base_config[key]['RestInFull'] = True - else: - agent_base_config[key] = {'RestInFull': True} - logger.info('开始运行Mower') - simulate() - - -def inialize(tasks, scheduler=None): - device = Device() - cli = Solver(device) - if scheduler is None: - base_scheduler = BaseSchedulerSolver(cli.device, cli.recog) - base_scheduler.operators = {} - plan1 = {} - for key in plan: - plan1[key] = plan[key]['plans'] - base_scheduler.global_plan = {'default': "plan_1", "plan_1": plan1} - base_scheduler.current_base = {} - base_scheduler.resting = [] - base_scheduler.dorm_count = 4 - base_scheduler.tasks = tasks - # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true - base_scheduler.read_mood = True - base_scheduler.scan_time = {} - base_scheduler.last_room = '' - base_scheduler.free_blacklist = list(filter(None, conf['free_blacklist'].replace(',', ',').split(','))) - logger.info('宿舍黑名单:' + str(base_scheduler.free_blacklist)) - base_scheduler.resting_treshhold = 0.5 - base_scheduler.MAA = None - base_scheduler.email_config = { - 'mail_enable': conf['mail_enable'], - 'subject': '[Mower通知]', - 'account': conf['account'], - 'pass_code': conf['pass_code'], - 'receipts': [conf['account']], - 'notify': False - } - base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] - base_scheduler.error = False - base_scheduler.agent_base_config = agent_base_config - return base_scheduler - else: - scheduler.device = cli.device - scheduler.recog = cli.recog - scheduler.handle_error(True) - return scheduler - - -def simulate(): - ''' - 具体调用方法可见各个函数的参数说明 - ''' - tasks = [] - reconnect_max_tries = 10 - reconnect_tries = 0 - base_scheduler = inialize(tasks) - while True: - try: - if len(base_scheduler.tasks) > 0: - (base_scheduler.tasks.sort(key=lambda x: x["time"], reverse=False)) - sleep_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() - logger.debug(base_scheduler.tasks) - if sleep_time > 0: - remaining_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() - subject = f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}" - context = f"下一次任务:{base_scheduler.tasks[0]['plan']}" - logger.info(context) - logger.info(subject) - base_scheduler.send_email(context, subject) - time.sleep(sleep_time) - base_scheduler.run() - reconnect_tries = 0 - except ConnectionError as e: - reconnect_tries += 1 - if reconnect_tries < reconnect_max_tries: - logger.warning(f'连接端口断开....正在重连....') - connected = False - while not connected: - try: - base_scheduler = inialize([], base_scheduler) - break - except Exception as ce: - logger.error(ce) - time.sleep(5) - continue - continue - else: - raise Exception(e) - except Exception as E: - logger.exception(f"程序出错--->{E}") - # 读取写入配置文件 def load_conf(): @@ -192,6 +31,7 @@ def load_conf(): conf = yaml.load(c) if conf is None: conf = {} + conf['package_type'] = conf['package_type'] if 'package_type' in conf.keys() else 1 conf['adb'] = conf['adb'] if 'adb' in conf.keys() else '' conf['planFile'] = conf['planFile'] if 'planFile' in conf.keys() else './plan.json' # 默认排班表地址 conf['free_blacklist'] = conf['free_blacklist'] if 'free_blacklist' in conf.keys() else '' @@ -202,6 +42,16 @@ def load_conf(): conf['pass_code'] = conf['pass_code'] if 'pass_code' in conf.keys() else '' conf['maa_enable'] = conf['maa_enable'] if 'maa_enable' in conf.keys() else '' conf['maa_path'] = conf['maa_path'] if 'maa_path' in conf.keys() else '' + conf['maa_adb_path'] = conf['maa_adb_path'] if 'maa_adb_path' in conf.keys() else '' + conf['maa_weekly_plan'] = conf['maa_weekly_plan'] if 'maa_weekly_plan' in conf.keys() else [ + {"weekday": "周一", "stage": ['AP-5'], "medicine": 0}, + {"weekday": "周二", "stage": ['CE-6'], "medicine": 0}, + {"weekday": "周三", "stage": ['1-7'], "medicine": 0}, + {"weekday": "周四", "stage": ['AP-5'], "medicine": 0}, + {"weekday": "周五", "stage": ['1-7'], "medicine": 0}, + {"weekday": "周六", "stage": ['AP-5'], "medicine": 0}, + {"weekday": "周日", "stage": ['AP-5'], "medicine": 0} + ] def write_conf(): @@ -253,6 +103,11 @@ def menu(): load_conf() sg.theme('LightBlue2') # --------主页 + package_type_title = sg.Text('服务器:', size=10) + package_type_1 = sg.Radio('官服', 'package_type', default=conf['package_type'] == 1, + key='radio_package_type_1', enable_events=True) + package_type_2 = sg.Radio('BiliBili服', 'package_type', default=conf['package_type'] == 2, + key='radio_package_type_2', enable_events=True) adb_title = sg.Text('adb连接地址:', size=10) adb = sg.InputText(conf['adb'], size=60, key='conf_adb', enable_events=True) # 黑名单 @@ -336,16 +191,32 @@ def menu(): mail_frame = sg.Frame('邮件提醒', [[mail_enable_1, mail_enable_0], [account_title, account], [pass_code_title, pass_code]]) # maa + maa_enable_1 = sg.Radio('启用', 'maa_enable', default=conf['maa_enable'] == 1, key='radio_maa_enable_1', enable_events=True) maa_enable_0 = sg.Radio('禁用', 'maa_enable', default=conf['maa_enable'] == 0, key='radio_maa_enable_0', enable_events=True) maa_path_title = sg.Text('MAA地址', size=25) maa_path = sg.InputText(conf['maa_path'], size=60, key='conf_maa_path', enable_events=True) - maa_frame = sg.Frame('MAA', - [[maa_enable_1, maa_enable_0], [maa_path_title, maa_path]]) + maa_adb_path_title = sg.Text('adb地址', size=25) + maa_adb_path = sg.InputText(conf['maa_adb_path'], size=60, key='conf_maa_adb_path', enable_events=True) + maa_weekly_plan_title = sg.Text('周计划', size=25) + maa_layout = [[maa_enable_1, maa_enable_0], [maa_path_title, maa_path], [maa_adb_path_title, maa_adb_path], + [maa_weekly_plan_title]] + for i, v in enumerate(conf['maa_weekly_plan']): + maa_layout.append([ + sg.Text(f"-- {v['weekday']}:", size=15), + sg.Text('关卡:', size=5), + sg.InputText(",".join(v['stage']), size=15, key='maa_weekly_plan_stage_' + str(i), enable_events=True), + sg.Text('理智药:', size=10), + sg.Spin([l for l in range(0, 999)], initial_value=v['medicine'], size=5, + key='maa_weekly_plan_medicine_' + str(i), enable_events=True, readonly=True) + ]) + + maa_frame = sg.Frame('MAA', maa_layout) # --------组装页面 - main_tab = sg.Tab(' 主页 ', [[adb_title, adb], + main_tab = sg.Tab(' 主页 ', [[package_type_title, package_type_1, package_type_2], + [adb_title, adb], [free_blacklist_title, free_blacklist], [plan_title, plan_file, plan_select], [output], @@ -355,11 +226,12 @@ def menu(): setting_tab = sg.Tab(' 高级设置 ', [[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full], - [mail_frame],[maa_frame]]) + [mail_frame], [maa_frame]]) window = sg.Window('Mower', [[sg.TabGroup([[main_tab, plan_tab, setting_tab]], border_width=0, tab_border_width=0, focus_color='#bcc8e5', selected_background_color='#d4dae8', background_color='#aab6d3', - tab_background_color='#aab6d3')]], font='微软雅黑', finalize=True) + tab_background_color='#aab6d3')]], font='微软雅黑', finalize=True, + resizable=True) load_plan(conf['planFile']) btn = None @@ -378,6 +250,12 @@ def menu(): write_plan() load_plan(plan_file.get()) plan_file.update(conf['planFile']) + elif event.startswith('maa_weekly_plan_stage_'): # 关卡名 + v_index = event.rindex('_') + conf['maa_weekly_plan'][int(event[v_index + 1:])]['stage'] = [window[event].get()] + elif event.startswith('maa_weekly_plan_medicine_'): # 体力药 + v_index = event.rindex('_') + conf['maa_weekly_plan'][int(event[v_index + 1:])]['medicine'] = int(window[event].get()) elif event.startswith('btn_'): # 设施按钮 btn = event init_btn(event) @@ -393,7 +271,7 @@ def menu(): off_btn.update(visible=True) clear() parent_conn, child_conn = Pipe() - main_thread = Process(target=start, args=(conf, plan, child_conn), daemon=True) + main_thread = Process(target=main, args=(conf, plan, child_conn), daemon=True) main_thread.start() window.perform_long_operation(lambda: log(parent_conn), 'log') elif event == 'off': @@ -490,6 +368,5 @@ def clear(): if __name__ == '__main__': - # logger.info(123) freeze_support() menu() From f2e799bd1fdb192be72789b144b6b5ac362459c7 Mon Sep 17 00:00:00 2001 From: Ksnow <1048879349@qq.com> Date: Sun, 19 Mar 2023 20:36:03 +0800 Subject: [PATCH 16/39] =?UTF-8?q?app=E6=97=A0=E6=B3=95=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=BC=80=E5=90=AFBug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/utils/config.py | 6 ++++-- arknights_mower/utils/device/device.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/arknights_mower/utils/config.py b/arknights_mower/utils/config.py index 2c189557e..057ebd6d0 100644 --- a/arknights_mower/utils/config.py +++ b/arknights_mower/utils/config.py @@ -135,8 +135,10 @@ def init_config() -> None: PASSWORD = __get('account/password', None) global APPNAME - APPNAME = __get('app/package_name', 'com.hypergryph.arknights') + \ - '/' + __get('app/activity_name', 'com.u8.sdk.U8UnityContext') + APPNAME = __get('app/package_name', 'com.hypergryph.arknights') + + global APP_ACTIVITY_NAME + APP_ACTIVITY_NAME = __get('app/activity_name','com.u8.sdk.U8UnityContext') global DEBUG_MODE, LOGFILE_PATH, LOGFILE_AMOUNT, SCREENSHOT_PATH, SCREENSHOT_MAXNUM DEBUG_MODE = __get('debug/enabled', False) diff --git a/arknights_mower/utils/device/device.py b/arknights_mower/utils/device/device.py index f406c57f6..1299520d5 100644 --- a/arknights_mower/utils/device/device.py +++ b/arknights_mower/utils/device/device.py @@ -142,7 +142,7 @@ def swipe_ext(self, points: list[tuple[int, int]], durations: list[int], up_wait def check_current_focus(self): """ check if the application is in the foreground """ - if self.current_focus() != config.APPNAME: - self.launch(config.APPNAME) + if self.current_focus() != f"{config.APPNAME}/{config.APP_ACTIVITY_NAME}": + self.launch(f"{config.APPNAME}/{config.APP_ACTIVITY_NAME}") # wait for app to finish launching time.sleep(10) From 685c8c265de533b35562c528319c4316a064381e Mon Sep 17 00:00:00 2001 From: Ks-luow <64978950+Ks-luow@users.noreply.github.com> Date: Sun, 19 Mar 2023 23:11:40 +0800 Subject: [PATCH 17/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=AB=98=E7=BA=A7?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=80=89=E9=A1=B9=20(#156)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加高级设置选项 * 添加邮件提醒 * 添加MAA * app无法自动开启Bug修复 --- arknights_mower/__main__.py | 252 ++++++++++++----- arknights_mower/solvers/base_schedule.py | 21 +- arknights_mower/utils/config.py | 6 +- arknights_mower/utils/device/device.py | 4 +- main.spec | 1 + menu.py | 337 ++++++++++++----------- 6 files changed, 384 insertions(+), 237 deletions(-) diff --git a/arknights_mower/__main__.py b/arknights_mower/__main__.py index a43835188..c998503ee 100644 --- a/arknights_mower/__main__.py +++ b/arknights_mower/__main__.py @@ -1,72 +1,200 @@ -import sys -import traceback -from pathlib import Path +import time +from datetime import datetime -from . import __pyinstall__, __rootdir__ -from .command import * -from .utils import config -from .utils.device import Device -from .utils.log import logger, set_debug_mode +conf = {} +plan = {} -def main(module: bool = True) -> None: - args = sys.argv[1:] - if not args and __pyinstall__: - logger.info('参数为空,默认执行 schedule 模式,按下 Ctrl+C 以结束脚本运行') - args.append('schedule') - config_path = None - debug_mode = False - while True: - if len(args) > 1 and args[-2] == '--config': - config_path = Path(args[-1]) - args = args[:-2] - continue - if len(args) > 0 and args[-1] == '--debug': - debug_mode = True - args = args[:-1] - continue - break - - if config_path is None: - if __pyinstall__: - config_path = Path(sys.executable).parent.joinpath('config.yaml') - elif module: - config_path = Path.home().joinpath('.ark_mower.yaml') +# 执行自动排班 +def main(c, p, child_conn): + __init_params__() + from arknights_mower.utils.log import logger, init_fhlr + from arknights_mower.utils import config + global plan + global conf + conf = c + plan = p + config.LOGFILE_PATH = './log' + config.SCREENSHOT_PATH = './screenshot' + config.SCREENSHOT_MAXNUM = 1000 + config.ADB_DEVICE = [conf['adb']] + config.ADB_CONNECT = [conf['adb']] + config.ADB_CONNECT = [conf['adb']] + config.APPNAME = 'com.hypergryph.arknights' if conf[ + 'package_type'] == 1 else 'com.hypergryph.arknights.bilibili' # 服务器 + init_fhlr(child_conn) + if conf['ling_xi'] == 1: + agent_base_config['令']['UpperLimit'] = 11 + agent_base_config['夕']['UpperLimit'] = 11 + agent_base_config['夕']['LowerLimit'] = 13 + elif conf['ling_xi'] == 2: + agent_base_config['夕']['UpperLimit'] = 11 + agent_base_config['令']['UpperLimit'] = 11 + agent_base_config['令']['LowerLimit'] = 13 + for key in list(filter(None, conf['rest_in_full'].replace(',', ',').split(','))): + if key in agent_base_config.keys(): + agent_base_config[key]['RestInFull'] = True else: - config_path = __rootdir__.parent.joinpath('config.yaml') - if not config_path.exists(): - config.build_config(config_path, module) - else: - if not config_path.exists(): - logger.error(f'The configuration file does not exist: {config_path}') - return - try: - logger.info(f'Loading the configuration file: {config_path}') - config.load_config(config_path) - except Exception as e: - logger.error('An error occurred when loading the configuration file') - raise e + agent_base_config[key] = {'RestInFull': True} + logger.info('开始运行Mower') + simulate() - if debug_mode and not config.DEBUG_MODE: - config.DEBUG_MODE = True - set_debug_mode() +def inialize(tasks, scheduler=None): + from arknights_mower.utils.log import logger + from arknights_mower.solvers.base_schedule import BaseSchedulerSolver + from arknights_mower.strategy import Solver + from arknights_mower.utils.device import Device + from arknights_mower.utils import config device = Device() - - logger.debug(args) - if len(args) == 0: - help() + cli = Solver(device) + if scheduler is None: + base_scheduler = BaseSchedulerSolver(cli.device, cli.recog) + base_scheduler.operators = {} + plan1 = {} + for key in plan: + plan1[key] = plan[key]['plans'] + base_scheduler.package_name = config.APPNAME # 服务器 + base_scheduler.global_plan = {'default': "plan_1", "plan_1": plan1} + base_scheduler.current_base = {} + base_scheduler.resting = [] + base_scheduler.dorm_count = 4 + base_scheduler.tasks = tasks + # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true + base_scheduler.read_mood = True + base_scheduler.scan_time = {} + base_scheduler.last_room = '' + base_scheduler.free_blacklist = list(filter(None, conf['free_blacklist'].replace(',', ',').split(','))) + logger.info('宿舍黑名单:' + str(base_scheduler.free_blacklist)) + base_scheduler.resting_treshhold = 0.5 + base_scheduler.MAA = None + base_scheduler.email_config = { + 'mail_enable': conf['mail_enable'], + 'subject': '[Mower通知]', + 'account': conf['account'], + 'pass_code': conf['pass_code'], + 'receipts': [conf['account']], + 'notify': False + } + maa_config['maa_path'] = conf['maa_path'] + maa_config['maa_adb_path'] = conf['maa_adb_path'] + maa_config['maa_adb'] = conf['adb'] + maa_config['weekly_plan'] = conf['maa_weekly_plan'] + base_scheduler.maa_config = maa_config + base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] + base_scheduler.error = False + base_scheduler.agent_base_config = agent_base_config + return base_scheduler else: - target_cmd = match_cmd(args[0]) - if target_cmd is not None: - try: - target_cmd(args[1:], device) - except ParamError: - logger.debug(traceback.format_exc()) - help() - else: - help() + scheduler.device = cli.device + scheduler.recog = cli.recog + scheduler.handle_error(True) + return scheduler + +def simulate(): + from arknights_mower.utils.log import logger + ''' + 具体调用方法可见各个函数的参数说明 + ''' + tasks = [] + reconnect_max_tries = 10 + reconnect_tries = 0 + base_scheduler = inialize(tasks) + while True: + try: + if len(base_scheduler.tasks) > 0: + (base_scheduler.tasks.sort(key=lambda x: x["time"], reverse=False)) + sleep_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() + logger.debug(base_scheduler.tasks) + if sleep_time > 540 and conf['maa_enable'] == 1: + base_scheduler.maa_plan_solver() + elif sleep_time > 0: + remaining_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() + subject = f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}" + context = f"下一次任务:{base_scheduler.tasks[0]['plan']}" + logger.info(context) + logger.info(subject) + base_scheduler.send_email(context, subject) + time.sleep(sleep_time) + base_scheduler.run() + reconnect_tries = 0 + except ConnectionError as e: + reconnect_tries += 1 + if reconnect_tries < reconnect_max_tries: + logger.warning(f'连接端口断开....正在重连....') + connected = False + while not connected: + try: + base_scheduler = inialize([], base_scheduler) + break + except Exception as ce: + logger.error(ce) + time.sleep(5) + continue + continue + else: + raise Exception(e) + except Exception as E: + logger.exception(f"程序出错--->{E}") -if __name__ == '__main__': - main(module=True) +agent_base_config = {} +maa_config = {} +def __init_params__(): + global agent_base_config + global maa_config + agent_base_config = { + "Default": {"UpperLimit": 24, "LowerLimit": 0, "ExhaustRequire": False, "ArrangeOrder": [2, "false"], + "RestInFull": False}, + "令": {"ArrangeOrder": [2, "true"]}, + "夕": {"ArrangeOrder": [2, "true"]}, + "稀音": {"ExhaustRequire": True, "ArrangeOrder": [2, "true"], "RestInFull": True}, + "巫恋": {"ArrangeOrder": [2, "true"]}, + "柏喙": {"ExhaustRequire": True, "ArrangeOrder": [2, "true"]}, + "龙舌兰": {"ArrangeOrder": [2, "true"]}, + "空弦": {"ArrangeOrder": [2, "true"], "RestingPriority": "low"}, + "伺夜": {"ArrangeOrder": [2, "true"]}, + "绮良": {"ArrangeOrder": [2, "true"]}, + "但书": {"ArrangeOrder": [2, "true"]}, + "泡泡": {"ArrangeOrder": [2, "true"]}, + "火神": {"ArrangeOrder": [2, "true"]}, + "黑键": {"ArrangeOrder": [2, "true"]}, + "波登可": {"ArrangeOrder": [2, "false"]}, + "夜莺": {"ArrangeOrder": [2, "false"]}, + "菲亚梅塔": {"ArrangeOrder": [2, "false"]}, + "流明": {"ArrangeOrder": [2, "false"]}, + "蜜莓": {"ArrangeOrder": [2, "false"]}, + "闪灵": {"ArrangeOrder": [2, "false"]}, + "杜林": {"ArrangeOrder": [2, "false"]}, + "褐果": {"ArrangeOrder": [2, "false"]}, + "车尔尼": {"ArrangeOrder": [2, "false"]}, + "安比尔": {"ArrangeOrder": [2, "false"]}, + "爱丽丝": {"ArrangeOrder": [2, "false"]}, + "桃金娘": {"ArrangeOrder": [2, "false"]}, + "帕拉斯": {"RestingPriority": "low"}, + "红云": {"RestingPriority": "low", "ArrangeOrder": [2, "true"]}, + "承曦格雷伊": {"ArrangeOrder": [2, "true"]}, + "乌有": {"ArrangeOrder": [2, "true"], "RestingPriority": "low"}, + "图耶": {"ArrangeOrder": [2, "true"]}, + "鸿雪": {"ArrangeOrder": [2, "true"]}, + "孑": {"ArrangeOrder": [2, "true"]}, + "清道夫": {"ArrangeOrder": [2, "true"]}, + "临光": {"ArrangeOrder": [2, "true"]}, + "杜宾": {"ArrangeOrder": [2, "true"]}, + "焰尾": {"RestInFull": True}, + "重岳": {"ArrangeOrder": [2, "true"]}, + "坚雷": {"ArrangeOrder": [2, "true"]}, + "年": {"RestingPriority": "low"} + } + maa_config = { + # maa 运行的时间间隔,以小时计 + "maa_execution_gap": 4, + # 以下配置,第一个设置为true的首先生效 + # 是否启动肉鸽 + "roguelike": False, + # 是否启动生息演算 + "reclamation_algorithm": False, + # 是否启动保全派驻 + "stationary_security_service": False, + "last_execution": None + } diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 6a9a1acbb..710a45de5 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -8,6 +8,7 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart + from ..data import agent_list from ..utils import character_recognize, detector, segment from ..utils import typealias as tp @@ -738,7 +739,8 @@ def double_read_time(self, cord,upperLimit = 36000): def initialize_paddle(self): global ocr if ocr is None: - ocr = PaddleOCR(use_angle_cls=True, lang='en') + ocr = PaddleOCR(enable_mkldnn=True,use_angle_cls=False) + def read_screen(self, img, type="mood", limit=24, cord=None, change_color=False): if cord is not None: @@ -1661,8 +1663,11 @@ def maa_plan_solver(self): self.device.exit(self.package_name) # 生息演算逻辑 结束 remaining_time = (self.tasks[0]["time"] - datetime.now()).total_seconds() - logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") - self.send_email("脚本停止") + subject = f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}" + context = f"下一次任务:{self.tasks[0]['plan']}" + logger.info(context) + logger.info(subject) + self.send_email(context, subject) time.sleep(remaining_time) self.MAA = None except Exception as e: @@ -1674,12 +1679,14 @@ def maa_plan_solver(self): time.sleep(remaining_time) self.device.exit(self.package_name) - def send_email(self, tasks): + def send_email(self, context,subject=''): + if 'mail_enable' in self.email_config.keys() and self.email_config['mail_enable'] == 0: + logger.info('邮件功能未开启') + return try: msg = MIMEMultipart() - conntent = str(tasks) - msg.attach(MIMEText(conntent, 'plain', 'utf-8')) - msg['Subject'] = self.email_config['subject'] + msg.attach(MIMEText(str(context), 'plain', 'utf-8')) + msg['Subject'] = self.email_config['subject']+(str(subject)if subject else '') msg['From'] = self.email_config['account'] s = smtplib.SMTP_SSL("smtp.qq.com", 465) # 登录邮箱 diff --git a/arknights_mower/utils/config.py b/arknights_mower/utils/config.py index 2c189557e..057ebd6d0 100644 --- a/arknights_mower/utils/config.py +++ b/arknights_mower/utils/config.py @@ -135,8 +135,10 @@ def init_config() -> None: PASSWORD = __get('account/password', None) global APPNAME - APPNAME = __get('app/package_name', 'com.hypergryph.arknights') + \ - '/' + __get('app/activity_name', 'com.u8.sdk.U8UnityContext') + APPNAME = __get('app/package_name', 'com.hypergryph.arknights') + + global APP_ACTIVITY_NAME + APP_ACTIVITY_NAME = __get('app/activity_name','com.u8.sdk.U8UnityContext') global DEBUG_MODE, LOGFILE_PATH, LOGFILE_AMOUNT, SCREENSHOT_PATH, SCREENSHOT_MAXNUM DEBUG_MODE = __get('debug/enabled', False) diff --git a/arknights_mower/utils/device/device.py b/arknights_mower/utils/device/device.py index f406c57f6..1299520d5 100644 --- a/arknights_mower/utils/device/device.py +++ b/arknights_mower/utils/device/device.py @@ -142,7 +142,7 @@ def swipe_ext(self, points: list[tuple[int, int]], durations: list[int], up_wait def check_current_focus(self): """ check if the application is in the foreground """ - if self.current_focus() != config.APPNAME: - self.launch(config.APPNAME) + if self.current_focus() != f"{config.APPNAME}/{config.APP_ACTIVITY_NAME}": + self.launch(f"{config.APPNAME}/{config.APP_ACTIVITY_NAME}") # wait for app to finish launching time.sleep(10) diff --git a/main.spec b/main.spec index da6c8b840..f4f43f59d 100644 --- a/main.spec +++ b/main.spec @@ -15,6 +15,7 @@ a = Analysis( ('arknights_mower/resources', 'arknights_mower/__init__/resources'), ('arknights_mower/data', 'arknights_mower/__init__/data'), ('arknights_mower/vendor', 'arknights_mower/__init__/vendor'), + ('arknights_mower/paddleocr', '.'), ('venv64/Lib/site-packages/onnxruntime/capi/onnxruntime_providers_shared.dll', 'onnxruntime/capi/'), ('venv64/Lib/site-packages/shapely/DLLs/geos.dll', '.'), ('venv64/Lib/site-packages/shapely/DLLs/geos_c.dll', '.') diff --git a/menu.py b/menu.py index 7957b8ae4..d5671181f 100644 --- a/menu.py +++ b/menu.py @@ -1,16 +1,14 @@ +import importlib import json +import sys from multiprocessing import Pipe, Process, freeze_support import time -from datetime import datetime import PySimpleGUI as sg import os from ruamel.yaml import YAML -from arknights_mower.solvers.base_schedule import BaseSchedulerSolver -from arknights_mower.strategy import Solver -from arknights_mower.utils.device import Device -from arknights_mower.utils.log import logger, init_fhlr -from arknights_mower.utils import config +from arknights_mower.utils.log import logger from arknights_mower.data import agent_list +from arknights_mower.__main__ import main yaml = YAML() confUrl = './conf.yml'; @@ -22,92 +20,41 @@ half_line_index = 0 -def inialize(tasks, scheduler=None): - device = Device() - cli = Solver(device) - if scheduler is None: - base_scheduler = BaseSchedulerSolver(cli.device, cli.recog) - base_scheduler.operators = {} - plan1 = {} - for key in plan: - plan1[key] = plan[key]['plans'] - base_scheduler.global_plan = {'default': "plan_1", "plan_1": plan1} - base_scheduler.current_base = {} - base_scheduler.resting = [] - base_scheduler.dorm_count = 4 - base_scheduler.tasks = tasks - # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true - base_scheduler.read_mood = True - base_scheduler.scan_time = {} - base_scheduler.last_room = '' - base_scheduler.free_blacklist = [] - base_scheduler.resting_treshhold = 0.5 - base_scheduler.MAA = None - base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] - base_scheduler.error = False - return base_scheduler - else: - scheduler.device = cli.device - scheduler.recog = cli.recog - scheduler.handle_error(True) - return scheduler - - -def simulate(): - ''' - 具体调用方法可见各个函数的参数说明 - ''' - tasks = [] - reconnect_max_tries = 10 - reconnect_tries = 0 - base_scheduler = inialize(tasks) - while True: - try: - if len(base_scheduler.tasks) > 0: - (base_scheduler.tasks.sort(key=lambda x: x["time"], reverse=False)) - sleep_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() - logger.info(base_scheduler.tasks) - if sleep_time >= 0: - remaining_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() - logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}") - time.sleep(sleep_time) - base_scheduler.run() - reconnect_tries = 0 - except ConnectionError as e: - reconnect_tries += 1 - if reconnect_tries < reconnect_max_tries: - logger.warning(f'连接端口断开....正在重连....') - connected = False - while not connected: - try: - base_scheduler = inialize([], base_scheduler) - break - except Exception as ce: - logger.error(ce) - time.sleep(5) - continue - continue - else: - raise Exception(e) - except Exception as E: - logger.exception(f"程序出错--->{E}") - - # 读取写入配置文件 -def loadConf(): +def load_conf(): global conf global confUrl if not os.path.isfile(confUrl): open(confUrl, 'w') # 创建空配置文件 - conf['planFile'] = './plan.json' # 默认排班表地址 - return - with open(confUrl, 'r', encoding='utf8') as c: - conf = yaml.load(c) - if conf is None: - conf = {} - - -def writeConf(): + else: + with open(confUrl, 'r', encoding='utf8') as c: + conf = yaml.load(c) + if conf is None: + conf = {} + conf['package_type'] = conf['package_type'] if 'package_type' in conf.keys() else 1 + conf['adb'] = conf['adb'] if 'adb' in conf.keys() else '' + conf['planFile'] = conf['planFile'] if 'planFile' in conf.keys() else './plan.json' # 默认排班表地址 + conf['free_blacklist'] = conf['free_blacklist'] if 'free_blacklist' in conf.keys() else '' + conf['ling_xi'] = conf['ling_xi'] if 'ling_xi' in conf.keys() else 1 + conf['rest_in_full'] = conf['rest_in_full'] if 'rest_in_full' in conf.keys() else '' + conf['mail_enable'] = conf['mail_enable'] if 'mail_enable' in conf.keys() else 0 + conf['account'] = conf['account'] if 'account' in conf.keys() else '' + conf['pass_code'] = conf['pass_code'] if 'pass_code' in conf.keys() else '' + conf['maa_enable'] = conf['maa_enable'] if 'maa_enable' in conf.keys() else '' + conf['maa_path'] = conf['maa_path'] if 'maa_path' in conf.keys() else '' + conf['maa_adb_path'] = conf['maa_adb_path'] if 'maa_adb_path' in conf.keys() else '' + conf['maa_weekly_plan'] = conf['maa_weekly_plan'] if 'maa_weekly_plan' in conf.keys() else [ + {"weekday": "周一", "stage": ['AP-5'], "medicine": 0}, + {"weekday": "周二", "stage": ['CE-6'], "medicine": 0}, + {"weekday": "周三", "stage": ['1-7'], "medicine": 0}, + {"weekday": "周四", "stage": ['AP-5'], "medicine": 0}, + {"weekday": "周五", "stage": ['1-7'], "medicine": 0}, + {"weekday": "周六", "stage": ['AP-5'], "medicine": 0}, + {"weekday": "周日", "stage": ['AP-5'], "medicine": 0} + ] + + +def write_conf(): global conf global confUrl with open(confUrl, 'w', encoding='utf8') as c: @@ -115,8 +62,8 @@ def writeConf(): yaml.dump(conf, c) -# 读取写入排班表 -def loadPlan(url): +# 读取排班表 +def load_plan(url): global plan if not os.path.isfile(url): with open(url, 'w') as f: @@ -143,41 +90,32 @@ def loadPlan(url): println('json格式错误!') -def writePlan(): +# 写入排班表 +def write_plan(): with open(conf['planFile'], 'w', encoding='utf8') as c: json.dump(plan, c, ensure_ascii=False) -# 执行自动排班 -def start(c, p, child_conn): - global plan - global conf - conf = c - plan = p - config.LOGFILE_PATH = './log' - config.SCREENSHOT_PATH = './screenshot' - config.SCREENSHOT_MAXNUM = 1000 - config.ADB_DEVICE = [conf['adb']] - config.ADB_CONNECT = [conf['adb']] - init_fhlr(child_conn) - logger.info('开始运行Mower') - simulate() - - # 主页面 def menu(): global window global buffer - loadConf() + load_conf() sg.theme('LightBlue2') - # maa_title = sg.Text('MAA:') - # maa_select = sg.InputCombo(['启用', '停用'], default_value='停用', size=(20, 3)) - # adb + # --------主页 + package_type_title = sg.Text('服务器:', size=10) + package_type_1 = sg.Radio('官服', 'package_type', default=conf['package_type'] == 1, + key='radio_package_type_1', enable_events=True) + package_type_2 = sg.Radio('BiliBili服', 'package_type', default=conf['package_type'] == 2, + key='radio_package_type_2', enable_events=True) adb_title = sg.Text('adb连接地址:', size=10) - adb = sg.InputText(conf['adb'] if 'adb' in conf.keys() else '', size=60) + adb = sg.InputText(conf['adb'], size=60, key='conf_adb', enable_events=True) + # 黑名单 + free_blacklist_title = sg.Text('宿舍黑名单:', size=10) + free_blacklist = sg.InputText(conf['free_blacklist'], size=60, key='conf_free_blacklist', enable_events=True) # 排班表json plan_title = sg.Text('排班表:', size=10) - planFile = sg.InputText(conf['planFile'], readonly=True, size=60, key='planFile', enable_events=True) + plan_file = sg.InputText(conf['planFile'], readonly=True, size=60, key='planFile', enable_events=True) plan_select = sg.FileBrowse('...', size=(3, 1), file_types=(("JSON files", "*.json"),)) # 总开关 on_btn = sg.Button('开始执行', key='on') @@ -185,13 +123,14 @@ def menu(): # 日志栏 output = sg.Output(size=(150, 25), key='log', text_color='#808069', font=('微软雅黑', 9)) + # --------排班表设置页面 # 宿舍区 central = sg.Button('控制中枢', key='btn_central', size=(18, 3), button_color='#303030') dormitory_1 = sg.Button('宿舍', key='btn_dormitory_1', size=(18, 2), button_color='#303030') dormitory_2 = sg.Button('宿舍', key='btn_dormitory_2', size=(18, 2), button_color='#303030') dormitory_3 = sg.Button('宿舍', key='btn_dormitory_3', size=(18, 2), button_color='#303030') dormitory_4 = sg.Button('宿舍', key='btn_dormitory_4', size=(18, 2), button_color='#303030') - centralArea = sg.Column([[central], [dormitory_1], [dormitory_2], [dormitory_3], [dormitory_4]]) + central_area = sg.Column([[central], [dormitory_1], [dormitory_2], [dormitory_3], [dormitory_4]]) # 制造站区 room_1_1 = sg.Button('待建造', key='btn_room_1_1', size=(12, 2), button_color='#4f4945') room_1_2 = sg.Button('待建造', key='btn_room_1_2', size=(12, 2), button_color='#4f4945') @@ -202,69 +141,137 @@ def menu(): room_3_1 = sg.Button('待建造', key='btn_room_3_1', size=(12, 2), button_color='#4f4945') room_3_2 = sg.Button('待建造', key='btn_room_3_2', size=(12, 2), button_color='#4f4945') room_3_3 = sg.Button('待建造', key='btn_room_3_3', size=(12, 2), button_color='#4f4945') - leftArea = sg.Column([[room_1_1, room_1_2, room_1_3], - [room_2_1, room_2_2, room_2_3], - [room_3_1, room_3_2, room_3_3]]) + left_area = sg.Column([[room_1_1, room_1_2, room_1_3], + [room_2_1, room_2_2, room_2_3], + [room_3_1, room_3_2, room_3_3]]) # 功能区 meeting = sg.Button('会客室', key='btn_meeting', size=(24, 2), button_color='#303030') factory = sg.Button('加工站', key='btn_factory', size=(24, 2), button_color='#303030') contact = sg.Button('办公室', key='btn_contact', size=(24, 2), button_color='#303030') - rightArea = sg.Column([[meeting], [factory], [contact]]) + right_area = sg.Column([[meeting], [factory], [contact]]) - settingLayout = [[sg.Column([[sg.Text('设施类别:'), sg.InputCombo(['贸易站', '制造站', '发电站'], size=12, key='station_type')]], - key='station_type_col', visible=False)]] + setting_layout = [ + [sg.Column([[sg.Text('设施类别:'), sg.InputCombo(['贸易站', '制造站', '发电站'], size=12, key='station_type')]], + key='station_type_col', visible=False)]] # 排班表设置标签 for i in range(1, 6): - setArea = sg.Column([[sg.Text('干员:'), - sg.InputCombo(['Free'] + agent_list, size=20, key='agent' + str(i)), - sg.Text('组:'), - sg.InputText('', size=15, key='group' + str(i)), - sg.Text('替换:'), - sg.InputText('', size=30, key='replacement' + str(i)) - ]], key='setArea' + str(i), visible=False) - settingLayout.append([setArea]) - settingLayout.append([sg.Button('保存', key='savePlan', visible=False)]) - settingArea = sg.Column(settingLayout, element_justification="center", - vertical_alignment="bottom", - expand_x=True) - # 组装页面 - mainTab = sg.Tab(' 主页 ', [[adb_title, adb], - [plan_title, planFile, plan_select], - [output], - [on_btn, off_btn]]) - - planTab = sg.Tab(' 排班表 ', [[leftArea, centralArea, rightArea], [settingArea]], element_justification="center") - window = sg.Window('Mower', [[sg.TabGroup([[mainTab, planTab]], border_width=0, + set_area = sg.Column([[sg.Text('干员:'), + sg.InputCombo(['Free'] + agent_list, size=20, key='agent' + str(i)), + sg.Text('组:'), + sg.InputText('', size=15, key='group' + str(i)), + sg.Text('替换:'), + sg.InputText('', size=30, key='replacement' + str(i)) + ]], key='setArea' + str(i), visible=False) + setting_layout.append([set_area]) + setting_layout.append([sg.Button('保存', key='savePlan', visible=False)]) + setting_area = sg.Column(setting_layout, element_justification="center", + vertical_alignment="bottom", + expand_x=True) + + # --------高级设置页面 + ling_xi_title = sg.Text('令夕模式(令夕上班时起作用):', size=25) + ling_xi_1 = sg.Radio('感知信息', 'ling_xi', default=conf['ling_xi'] == 1, + key='radio_ling_xi_1', enable_events=True) + ling_xi_2 = sg.Radio('人间烟火', 'ling_xi', default=conf['ling_xi'] == 2, + key='radio_ling_xi_2', enable_events=True) + ling_xi_3 = sg.Radio('均衡模式', 'ling_xi', default=conf['ling_xi'] == 3, + key='radio_ling_xi_3', enable_events=True) + rest_in_full_title = sg.Text('需要回满心情的干员:', size=25) + rest_in_full = sg.InputText(conf['rest_in_full'], size=60, + key='conf_rest_in_full', enable_events=True) + # mail + mail_enable_1 = sg.Radio('启用', 'mail_enable', default=conf['mail_enable'] == 1, + key='radio_mail_enable_1', enable_events=True) + mail_enable_0 = sg.Radio('禁用', 'mail_enable', default=conf['mail_enable'] == 0, + key='radio_mail_enable_0', enable_events=True) + account_title = sg.Text('QQ邮箱', size=25) + account = sg.InputText(conf['account'], size=60, key='conf_account', enable_events=True) + pass_code_title = sg.Text('授权码', size=25) + pass_code = sg.Input(conf['pass_code'], size=60, key='conf_pass_code', enable_events=True, password_char='*') + mail_frame = sg.Frame('邮件提醒', + [[mail_enable_1, mail_enable_0], [account_title, account], [pass_code_title, pass_code]]) + # maa + + maa_enable_1 = sg.Radio('启用', 'maa_enable', default=conf['maa_enable'] == 1, + key='radio_maa_enable_1', enable_events=True) + maa_enable_0 = sg.Radio('禁用', 'maa_enable', default=conf['maa_enable'] == 0, + key='radio_maa_enable_0', enable_events=True) + maa_path_title = sg.Text('MAA地址', size=25) + maa_path = sg.InputText(conf['maa_path'], size=60, key='conf_maa_path', enable_events=True) + maa_adb_path_title = sg.Text('adb地址', size=25) + maa_adb_path = sg.InputText(conf['maa_adb_path'], size=60, key='conf_maa_adb_path', enable_events=True) + maa_weekly_plan_title = sg.Text('周计划', size=25) + maa_layout = [[maa_enable_1, maa_enable_0], [maa_path_title, maa_path], [maa_adb_path_title, maa_adb_path], + [maa_weekly_plan_title]] + for i, v in enumerate(conf['maa_weekly_plan']): + maa_layout.append([ + sg.Text(f"-- {v['weekday']}:", size=15), + sg.Text('关卡:', size=5), + sg.InputText(",".join(v['stage']), size=15, key='maa_weekly_plan_stage_' + str(i), enable_events=True), + sg.Text('理智药:', size=10), + sg.Spin([l for l in range(0, 999)], initial_value=v['medicine'], size=5, + key='maa_weekly_plan_medicine_' + str(i), enable_events=True, readonly=True) + ]) + + maa_frame = sg.Frame('MAA', maa_layout) + # --------组装页面 + main_tab = sg.Tab(' 主页 ', [[package_type_title, package_type_1, package_type_2], + [adb_title, adb], + [free_blacklist_title, free_blacklist], + [plan_title, plan_file, plan_select], + [output], + [on_btn, off_btn]]) + + plan_tab = sg.Tab(' 排班表 ', [[left_area, central_area, right_area], [setting_area]], element_justification="center") + + setting_tab = sg.Tab(' 高级设置 ', + [[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full], + [mail_frame], [maa_frame]]) + window = sg.Window('Mower', [[sg.TabGroup([[main_tab, plan_tab, setting_tab]], border_width=0, tab_border_width=0, focus_color='#bcc8e5', selected_background_color='#d4dae8', background_color='#aab6d3', - tab_background_color='#aab6d3')]], font='微软雅黑', finalize=True) + tab_background_color='#aab6d3')]], font='微软雅黑', finalize=True, + resizable=True) - loadPlan(conf['planFile']) + load_plan(conf['planFile']) + btn = None while True: event, value = window.Read() + if event == sg.WIN_CLOSED: - conf['adb'] = adb.get() break - elif event == 'planFile' and planFile.get() != conf['planFile']: - writePlan() - loadPlan(planFile.get()) - planFile.update(conf['planFile']) - elif event.startswith('btn_'): + elif event.startswith('conf_'): + key = event[5:] + conf[key] = window[event].get() + elif event.startswith('radio_'): + v_index = event.rindex('_') + conf[event[6:v_index]] = int(event[v_index + 1:]) + elif event == 'planFile' and plan_file.get() != conf['planFile']: # 排班表 + write_plan() + load_plan(plan_file.get()) + plan_file.update(conf['planFile']) + elif event.startswith('maa_weekly_plan_stage_'): # 关卡名 + v_index = event.rindex('_') + conf['maa_weekly_plan'][int(event[v_index + 1:])]['stage'] = [window[event].get()] + elif event.startswith('maa_weekly_plan_medicine_'): # 体力药 + v_index = event.rindex('_') + conf['maa_weekly_plan'][int(event[v_index + 1:])]['medicine'] = int(window[event].get()) + elif event.startswith('btn_'): # 设施按钮 btn = event - initBtn(event) - elif event == 'savePlan': - saveBtn(btn) + init_btn(event) + elif event == 'savePlan': # 保存设施信息 + save_btn(btn) + elif event == 'on': if adb.get() == '': println('adb未设置!') continue - conf['adb'] = adb.get() on_btn.update(visible=False) off_btn.update(visible=True) clear() parent_conn, child_conn = Pipe() - main_thread = Process(target=start, args=(conf, plan, child_conn), daemon=True) + main_thread = Process(target=main, args=(conf, plan, child_conn), daemon=True) main_thread.start() window.perform_long_operation(lambda: log(parent_conn), 'log') elif event == 'off': @@ -275,30 +282,30 @@ def menu(): off_btn.update(visible=False) window.close() - writeConf() - writePlan() + write_conf() + write_plan() -def initBtn(event): +def init_btn(event): room_key = event[4:] station_name = plan[room_key]['name'] if room_key in plan.keys() else '' plans = plan[room_key]['plans'] if room_key in plan.keys() else [] if room_key.startswith('room'): window['station_type_col'].update(visible=True) window['station_type'].update(station_name) - visibleCnt = 3 # 设施干员需求数量 + visible_cnt = 3 # 设施干员需求数量 else: if room_key == 'meeting': - visibleCnt = 2 + visible_cnt = 2 elif room_key == 'factory' or room_key == 'contact': - visibleCnt = 1 + visible_cnt = 1 else: - visibleCnt = 5 + visible_cnt = 5 window['station_type_col'].update(visible=False) window['station_type'].update('') window['savePlan'].update(visible=True) for i in range(1, 6): - if i > visibleCnt: + if i > visible_cnt: window['setArea' + str(i)].update(visible=False) window['agent' + str(i)].update('') window['group' + str(i)].update('') @@ -310,17 +317,19 @@ def initBtn(event): window['replacement' + str(i)].update(','.join(plans[i - 1]['replacement']) if len(plans) >= i else '') -def saveBtn(btn): +def save_btn(btn): plan1 = {'name': window['station_type'].get(), 'plans': []} for i in range(1, 6): agent = window['agent' + str(i)].get() group = window['group' + str(i)].get() - replacement = list(filter(None, window['replacement' + str(i)].get().split(','))) + replacement = list(filter(None, window['replacement' + str(i)].get().replace(',', ',').split(','))) if agent != '': plan1['plans'].append({'agent': agent, 'group': group, 'replacement': replacement}) + elif btn.startswith('btn_dormitory'): # 宿舍 + plan1['plans'].append({'agent': 'Free', 'group': '', 'replacement': []}) plan[btn[4:]] = plan1 - writePlan() - loadPlan(conf['planFile']) + write_plan() + load_plan(conf['planFile']) # 输出日志 From 4b94d09f0d61a87426f6fddc547bbd475a9f1406 Mon Sep 17 00:00:00 2001 From: Ksnow <1048879349@qq.com> Date: Wed, 22 Mar 2023 22:57:53 +0800 Subject: [PATCH 18/39] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E5=BF=83=E6=83=85=E6=8C=89=E9=92=AE=20=E6=89=93=E5=BC=80adb?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=90=9C=E7=B4=A2=EF=BC=8C=E7=9B=AE=E5=89=8D?= =?UTF-8?q?=E4=BC=9A=E5=85=88=E6=89=BE=E8=AE=BE=E5=AE=9A=E7=9A=84adb?= =?UTF-8?q?=EF=BC=8C=E8=8B=A5=E6=B2=A1=E6=9C=89=E5=88=99=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=AC=AC=E4=B8=80=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/__main__.py | 5 ++++- arknights_mower/utils/device/adb_client/core.py | 9 +++++---- menu.py | 8 +++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/arknights_mower/__main__.py b/arknights_mower/__main__.py index c998503ee..dd8b45c61 100644 --- a/arknights_mower/__main__.py +++ b/arknights_mower/__main__.py @@ -61,7 +61,7 @@ def inialize(tasks, scheduler=None): base_scheduler.dorm_count = 4 base_scheduler.tasks = tasks # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true - base_scheduler.read_mood = True + base_scheduler.read_mood = conf['run_mode'] == 1 base_scheduler.scan_time = {} base_scheduler.last_room = '' base_scheduler.free_blacklist = list(filter(None, conf['free_blacklist'].replace(',', ',').split(','))) @@ -138,8 +138,11 @@ def simulate(): except Exception as E: logger.exception(f"程序出错--->{E}") + agent_base_config = {} maa_config = {} + + def __init_params__(): global agent_base_config global maa_config diff --git a/arknights_mower/utils/device/adb_client/core.py b/arknights_mower/utils/device/adb_client/core.py index 2eed6d64a..05393d481 100644 --- a/arknights_mower/utils/device/adb_client/core.py +++ b/arknights_mower/utils/device/adb_client/core.py @@ -60,9 +60,10 @@ def __choose_devices(self) -> Optional[str]: for device in config.ADB_DEVICE: if device in devices: return device - # if len(devices) > 0: - # logger.debug(devices[0]) - # return devices[0] + if len(devices) > 0: + logger.debug(devices[0]) + return devices[0] + def __available_devices(self) -> list[str]: """ return available devices """ @@ -164,7 +165,7 @@ def push(self, target_path: str, target: bytes) -> None: def stream(self, cmd: str) -> Socket: """ run adb command, return socket """ return self.session().request(cmd, True).sock - + def stream_shell(self, cmd: str) -> Socket: """ run adb shell command, return socket """ return self.stream('shell:' + cmd) diff --git a/menu.py b/menu.py index d5671181f..a21c73cf1 100644 --- a/menu.py +++ b/menu.py @@ -35,6 +35,7 @@ def load_conf(): conf['adb'] = conf['adb'] if 'adb' in conf.keys() else '' conf['planFile'] = conf['planFile'] if 'planFile' in conf.keys() else './plan.json' # 默认排班表地址 conf['free_blacklist'] = conf['free_blacklist'] if 'free_blacklist' in conf.keys() else '' + conf['run_mode'] = conf['run_mode'] if 'run_mode' in conf.keys() else 1 conf['ling_xi'] = conf['ling_xi'] if 'ling_xi' in conf.keys() else 1 conf['rest_in_full'] = conf['rest_in_full'] if 'rest_in_full' in conf.keys() else '' conf['mail_enable'] = conf['mail_enable'] if 'mail_enable' in conf.keys() else 0 @@ -169,6 +170,11 @@ def menu(): expand_x=True) # --------高级设置页面 + run_mode_title = sg.Text('运行模式:', size=25) + run_mode_1 = sg.Radio('换班模式', 'run_mode', default=conf['run_mode'] == 1, + key='radio_run_mode_1', enable_events=True) + run_mode_2 = sg.Radio('仅跑单模式', 'run_mode', default=conf['run_mode'] == 2, + key='radio_run_mode_2', enable_events=True) ling_xi_title = sg.Text('令夕模式(令夕上班时起作用):', size=25) ling_xi_1 = sg.Radio('感知信息', 'ling_xi', default=conf['ling_xi'] == 1, key='radio_ling_xi_1', enable_events=True) @@ -225,7 +231,7 @@ def menu(): plan_tab = sg.Tab(' 排班表 ', [[left_area, central_area, right_area], [setting_area]], element_justification="center") setting_tab = sg.Tab(' 高级设置 ', - [[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full], + [[run_mode_title,run_mode_1,run_mode_2],[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full], [mail_frame], [maa_frame]]) window = sg.Window('Mower', [[sg.TabGroup([[main_tab, plan_tab, setting_tab]], border_width=0, tab_border_width=0, focus_color='#bcc8e5', From cdc64380235f2716a0455072ff35376e05e3de45 Mon Sep 17 00:00:00 2001 From: Ks-luow <64978950+Ks-luow@users.noreply.github.com> Date: Wed, 29 Mar 2023 08:52:46 +0800 Subject: [PATCH 19/39] Dev shawn (#158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加高级设置选项 * 添加邮件提醒 * 添加MAA * app无法自动开启Bug修复 * 增加读取心情按钮 打开adb自动搜索,目前会先找设定的adb,若没有则自动搜索第一个 --- arknights_mower/__main__.py | 3 ++- arknights_mower/utils/device/adb_client/core.py | 9 +++++---- menu.py | 8 +++++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/arknights_mower/__main__.py b/arknights_mower/__main__.py index c998503ee..c1123c908 100644 --- a/arknights_mower/__main__.py +++ b/arknights_mower/__main__.py @@ -61,7 +61,8 @@ def inialize(tasks, scheduler=None): base_scheduler.dorm_count = 4 base_scheduler.tasks = tasks # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true - base_scheduler.read_mood = True + base_scheduler.read_mood = conf['run_mode'] == 1 + base_scheduler.scan_time = {} base_scheduler.last_room = '' base_scheduler.free_blacklist = list(filter(None, conf['free_blacklist'].replace(',', ',').split(','))) diff --git a/arknights_mower/utils/device/adb_client/core.py b/arknights_mower/utils/device/adb_client/core.py index 2eed6d64a..05393d481 100644 --- a/arknights_mower/utils/device/adb_client/core.py +++ b/arknights_mower/utils/device/adb_client/core.py @@ -60,9 +60,10 @@ def __choose_devices(self) -> Optional[str]: for device in config.ADB_DEVICE: if device in devices: return device - # if len(devices) > 0: - # logger.debug(devices[0]) - # return devices[0] + if len(devices) > 0: + logger.debug(devices[0]) + return devices[0] + def __available_devices(self) -> list[str]: """ return available devices """ @@ -164,7 +165,7 @@ def push(self, target_path: str, target: bytes) -> None: def stream(self, cmd: str) -> Socket: """ run adb command, return socket """ return self.session().request(cmd, True).sock - + def stream_shell(self, cmd: str) -> Socket: """ run adb shell command, return socket """ return self.stream('shell:' + cmd) diff --git a/menu.py b/menu.py index d5671181f..a21c73cf1 100644 --- a/menu.py +++ b/menu.py @@ -35,6 +35,7 @@ def load_conf(): conf['adb'] = conf['adb'] if 'adb' in conf.keys() else '' conf['planFile'] = conf['planFile'] if 'planFile' in conf.keys() else './plan.json' # 默认排班表地址 conf['free_blacklist'] = conf['free_blacklist'] if 'free_blacklist' in conf.keys() else '' + conf['run_mode'] = conf['run_mode'] if 'run_mode' in conf.keys() else 1 conf['ling_xi'] = conf['ling_xi'] if 'ling_xi' in conf.keys() else 1 conf['rest_in_full'] = conf['rest_in_full'] if 'rest_in_full' in conf.keys() else '' conf['mail_enable'] = conf['mail_enable'] if 'mail_enable' in conf.keys() else 0 @@ -169,6 +170,11 @@ def menu(): expand_x=True) # --------高级设置页面 + run_mode_title = sg.Text('运行模式:', size=25) + run_mode_1 = sg.Radio('换班模式', 'run_mode', default=conf['run_mode'] == 1, + key='radio_run_mode_1', enable_events=True) + run_mode_2 = sg.Radio('仅跑单模式', 'run_mode', default=conf['run_mode'] == 2, + key='radio_run_mode_2', enable_events=True) ling_xi_title = sg.Text('令夕模式(令夕上班时起作用):', size=25) ling_xi_1 = sg.Radio('感知信息', 'ling_xi', default=conf['ling_xi'] == 1, key='radio_ling_xi_1', enable_events=True) @@ -225,7 +231,7 @@ def menu(): plan_tab = sg.Tab(' 排班表 ', [[left_area, central_area, right_area], [setting_area]], element_justification="center") setting_tab = sg.Tab(' 高级设置 ', - [[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full], + [[run_mode_title,run_mode_1,run_mode_2],[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full], [mail_frame], [maa_frame]]) window = sg.Window('Mower', [[sg.TabGroup([[main_tab, plan_tab, setting_tab]], border_width=0, tab_border_width=0, focus_color='#bcc8e5', From 05fb15164e3161acd0e87a24d199648818bd915d Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Tue, 28 Mar 2023 21:39:43 -0700 Subject: [PATCH 20/39] =?UTF-8?q?feat:=20=E9=80=82=E9=85=8D252=E5=9F=BA?= =?UTF-8?q?=E5=BB=BA=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 963 +++++++++++------------ arknights_mower/utils/operators.py | 201 +++++ diy.py | 3 +- 3 files changed, 648 insertions(+), 519 deletions(-) create mode 100644 arknights_mower/utils/operators.py diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index bd3015b65..6e709c6cb 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -10,6 +10,7 @@ from ..data import agent_list from ..utils import character_recognize, detector, segment +from ..utils.operators import Operators, Operator, Dormitory from ..utils import typealias as tp from ..utils.device import Device from ..utils.log import logger @@ -25,6 +26,7 @@ ocr = None + class ArrangeOrder(Enum): STATUS = 1 SKILL = 2 @@ -47,6 +49,8 @@ class BaseSchedulerSolver(BaseSolver): def __init__(self, device: Device = None, recog: Recognizer = None) -> None: super().__init__(device, recog) + self.op_data = None + self.max_resting_count = 4 def run(self) -> None: """ @@ -62,10 +66,8 @@ def run(self) -> None: self.task = None self.todo_task = False self.planned = False - if len(self.operators.keys()) == 0: - self.get_agent() - if len(self.scan_time.keys()) == 0: - self.scan_time = {k: None for k, v in self.currentPlan.items()} + if self.op_data is None: + self.initialize_operators() return super().run() def get_group(self, rest_agent, agent, groupname, name): @@ -102,40 +104,96 @@ def transition(self) -> None: raise RecognizeError('Unknown scene') def overtake_room(self): - name = self.task['type'] - candidate = [] - if self.operators[name]['group'] != '': - candidate.extend([v['name'] for k, v in self.operators.items() if - 'group' in v.keys() and v['group'] == self.operators[name]['group']]) + candidates = self.task['type'].split(',') + # 在candidate 中,计算出需要的high free 和 Low free 数量 + _high_free = 0 + _low_free = 0 + for x in candidates: + if self.op_data.operators[x].resting_priority == 'high': + _high_free += 1 + else: + _low_free += 1 + self.agent_get_mood(force=True) + # 剩余高效组位置 + current_high = self.op_data.available_free(count=self.max_resting_count) + # 剩余低效位置 + current_low = self.op_data.available_free('low', count=self.max_resting_count) + if current_high >= _high_free and current_low >= _low_free: + # 检查是否目前宿舍满足 low free 和high free 的数量需求,如果满足,则直接安排 + _plan = {} + _replacement = [] + _replacement, _plan, current_high, current_low = self.get_resting_plan(candidates, + _replacement, + _plan, + current_high, + current_low) + if len(_plan.items()) > 0: + self.tasks.append({'time': datetime.now(), 'plan': _plan}) else: - candidate.append(name) - operators = [] - for i in candidate: - _room = self.operators[name]['current_room'] - idx = [a['agent'] for a in self.current_base[_room]].index(i) - operators.append({'agent': i, 'current_room': _room, 'room_index': idx}) - # if 'resting_priority' in self.operators[i].keys() and self.operators[i]['resting_priority']=='high': - # room_need+=1 - resting_dorm = [] - ignore = [] - if next((e for e in self.tasks if 'type' in e.keys() and e['type'].startswith("dorm")), None) is not None: - # 出去需要休息满的人其他清空 - for task in [e for e in self.tasks if 'type' in e.keys() and e['type'].startswith("dorm")]: - for k, v in task['plan'].items(): - for a in v: - if a == "Current": - continue - elif 'exhaust_require' in self.operators[a].keys() and self.operators[a]['exhaust_require']: - ignore.append(a) - resting_dorm.extend([self.operators[a]['current_room'] for a in ignore]) - # 执行全部任务 - for task in self.tasks: - if 'type' in task.keys() and 'dorm' in task['type'] and 'plan' in task.keys(): - # TODO 移除 resting_room 的干员比如说有巫恋在休息 - self.agent_arrange(task['plan']) - self.tasks.remove(task) - self.get_swap_plan(resting_dorm, operators, False) - def handle_error(self,force = False): + # 如果不满足,则找到并且执行最近一个type 包含 超过数量的high free 和low free 的 任务并且 干员没有 exaust_require 词条 + task_index = -1 + current_high, current_low = 0, 0 + for idx, task in enumerate(self.tasks): + if "type" in task.keys() and 'dorm' in task['type']: + # 检查数量 + ids = [int(w[4:]) for w in task['type'].split(',')] + is_exhaust_require = False + for _id in ids: + if not is_exhaust_require: + if self.op_data.dorm[_id].name in self.op_data.exhaust_agent: + is_exhaust_require = True + + if _id > self.max_resting_count - 1: + current_low += 1 + else: + current_high += 1 + # 休息满需要则跳过 + if current_high >= _high_free and current_low >= _low_free and not is_exhaust_require: + task_index = idx + else: + current_low, current_high = 0, 0 + if task_index > -1: + # 修改执行时间 + self.tasks[task_index]['time'] = datetime.now() + # 执行完提前换班任务再次执行本任务 + self.tasks.append({'time': datetime.now(), 'plan': self.task['plan']}) + else: + # 任务全清 + rooms = [] + remove_idx = [] + for idx, task in enumerate(self.tasks): + if "type" in task.keys() and 'dorm' in task['type']: + # 检查数量 + ids = [int(w[4:]) for w in task['type'].split(',')] + is_exhaust_require = False + _rooms = [] + for _id in ids: + if not is_exhaust_require: + if self.op_data.dorm[_id].name in self.op_data.exhaust_agent: + is_exhaust_require = True + __room = self.op_data.operators[self.op_data.dorm[_id].name].room + if __room not in _rooms: + _rooms.append(__room) + # 跳过需要休息满 + if not is_exhaust_require: + rooms.extend(__room) + remove_idx.append(idx) + for idx in remove_idx: + del self.tasks[idx] + plan = {} + for room in rooms: + if room not in plan.keys(): + plan[room] = [data["agent"] for data in self.currentPlan[room]] + if len(plan.keys()) > 0: + self.tasks.append({'time': datetime.now(), 'plan': plan}) + # 执行完提前换班任务再次执行本任务 + self.tasks.append({'time': datetime.now(), 'plan': self.task['plan']}) + # 急速换班 + self.todo_task = True + self.planned = True + return + + def handle_error(self, force=False): # 如果有任何报错,则生成一个空 if self.scene() == Scene.UNKNOWN: self.device.exit('com.hypergryph.arknights') @@ -144,52 +202,87 @@ def handle_error(self,force = False): if (next((e for e in self.tasks if e['time'] < datetime.now()), None)) is None: room = next(iter(self.currentPlan.keys())) logger.info("由于出现错误情况,生成一次空任务来执行纠错") - self.tasks.append({'time': datetime.now(), 'plan': {room: ['Current'] * len(self.currentPlan[room])}}) + self.tasks.append({'time': datetime.now(), 'plan': {}}) # 如果没有任何时间小于当前时间的任务-10分钟 则清空任务 if (next((e for e in self.tasks if e['time'] < datetime.now() - timedelta(seconds=600)), None)) is not None: logger.info("检测到执行超过10分钟的任务,清空全部任务") self.tasks = [] - self.scan_time = {} - self.operators = {} + self.op_data = None return True - def plan_metadata(self, time_result): - group_info = self.task['metadata']['plan'] - read_time_rooms = self.task['metadata']['room'] - if time_result is None: - time_result = {} - for key, _plan in group_info.items(): - if 'default' != key: - _plan["time"] = time_result[_plan["type"]] - self.tasks.append({"type": _plan['type'], 'plan': _plan['plan'], 'time': _plan['time']}) - else: - # 合在一起则取最小恢复时间 - min_time = datetime.max - __time = datetime.now() - if len(self.task['metadata']['room']) == 0: - # 如果没有读取任何时间,则只休息1小时替换下一组 - _plan["time"] = __time + timedelta(seconds=(3600)) + def plan_metadata(self): + planned_index = [] + for t in self.tasks: + if 'type' in t.keys() and 'dorm' in t['type']: + planned_index.extend([int(w[4:]) for w in t['type'].split(',')]) + _time = datetime.max + _plan = {} + _type = [] + # 第一个心情低的且小于3 则只休息半小时 + short_rest = False + low_priority = [] + for idx, dorm in enumerate(self.op_data.dorm): + # Filter out resting priority low + if idx >= self.max_resting_count: + break + # 如果已经plan了,则跳过 + if idx in planned_index or idx in low_priority: + continue + _name = dorm.name + if _name == '': + continue + # 如果是rest in full,则新增单独任务.. + if _name in self.op_data.operators.keys() and self.op_data.operators[_name].rest_in_full: + __plan = {} + __rest_agent = [] + __type = [] + if self.op_data.operators[dorm.name].group == "": + __rest_agent.append(dorm.name) else: - for dorm in _plan["type"].split(','): - if dorm not in read_time_rooms: - continue - if dorm in time_result.keys() and min_time > time_result[dorm]: - min_time = time_result[dorm] - _plan["time"] = min_time - # 如果有任何已有plan - existing_plan = next( - (e for e in self.tasks if 'type' in e.keys() and e['type'].startswith('dormitory')), None) - if existing_plan is not None and existing_plan['time'] < _plan["time"]: - for k in _plan['plan']: - if k not in existing_plan['plan']: - existing_plan['plan'][k] = _plan['plan'][k] - else: - for idx, _a in enumerate(_plan['plan'][k]): - if _plan['plan'][k][idx] != 'Current': - existing_plan['plan'][k][idx] = _plan['plan'][k][idx] - existing_plan['type'] = existing_plan['type'] + ',' + _plan["type"] + __rest_agent.extend(self.op_data.groups[self.op_data.operators[dorm.name].group]) + __time = dorm.time + for x in __rest_agent: + # 如果同小组也是rest_in_full则取最大休息时间 否则忽略 + _idx, __dorm = self.op_data.get_dorm_by_name(x) + if x in self.op_data.operators.keys() and self.op_data.operators[x].rest_in_full: + if __dorm is not None and __dorm.time is not None: + if __dorm.time > _time: + _time = __dorm.time + __type.append('dorm' + str(_idx)) + planned_index.append(_idx) + __room = self.op_data.operators[x].room + if __room not in __plan.keys(): + __plan[__room] = ['Current'] * len(self.currentPlan[__room]) + __plan[__room][self.op_data.operators[x].index] = x + self.tasks.append({"type": ','.join(__type), 'plan': __plan, 'time': __time}) + # 如果非 rest in full, 则同组取时间最小值 + else: + if dorm.time is not None and dorm.time < _time: + _time = dorm.time + __room = self.op_data.operators[_name].room + __rest_agent = [] + if self.op_data.operators[_name].group == "": + __rest_agent.append(_name) else: - self.tasks.append({"type": _plan['type'], 'plan': _plan['plan'], 'time': _plan['time']}) + __rest_agent.extend(self.op_data.groups[self.op_data.operators[_name].group]) + for x in __rest_agent: + if x in low_priority: + continue + __room = self.op_data.operators[x].room + if __room not in _plan.keys(): + _plan[__room] = ['Current'] * len(self.currentPlan[__room]) + _plan[__room][self.op_data.operators[x].index] = x + _dorm_idx, __dorm = self.op_data.get_dorm_by_name(x) + if __dorm is not None: + _type.append('dorm' + str(_dorm_idx)) + planned_index.append(_dorm_idx) + if x not in low_priority: + low_priority.append(x) + # 生成单个任务 + if len(_plan.items()) > 0: + _time -= timedelta(minutes=8) + self.tasks.append({"type": ','.join(_type), 'plan': _plan, + 'time': _time if not short_rest else (datetime.now() + timedelta(hours=0.5))}) def infra_main(self): """ 位于基建首页 """ @@ -199,13 +292,14 @@ def infra_main(self): if self.task is not None: try: if len(self.task["plan"].keys()) > 0: - read_time_room = [] - if 'metadata' in self.task.keys(): - read_time_room = self.task['metadata']['room'] - time_result = self.agent_arrange(self.task["plan"], read_time_room) + metadata = None if 'metadata' in self.task.keys(): - self.plan_metadata(time_result) - else: + metadata = self.task['metadata'] + self.agent_arrange(self.task["plan"], metadata) + if metadata is not None: + self.plan_metadata() + # 如果任务名称包含干员名,则为动态生成的 + elif 'type' in self.task.keys() and self.task['type'].split(',')[0] in agent_list: self.overtake_room() del self.tasks[0] except Exception as e: @@ -240,21 +334,14 @@ def infra_main(self): else: return self.handle_error() - def get_plan(self, room, index=-1): - # -1 就是不换人 - result = [] - for data in self.currentPlan[room]: - result.append(data["agent"]) - return result - - def agent_get_mood(self,skip_dorm = False): + def agent_get_mood(self, skip_dorm=False, force=False): # 如果5分钟之内有任务则跳过心情读取 - if next((k for k in self.tasks if k['time'] ( - datetime.now() - timedelta(seconds=5400))))) + need_read = set(v.room for k, v in self.op_data.operators.items() if v.need_to_refresh()) for room in need_read: error_count = 0 while True: @@ -270,84 +357,72 @@ def agent_get_mood(self,skip_dorm = False): self.back() continue self.back() - if '' in self.operators.keys(): self.operators['']['current_room'] = '' - logger.debug(self.operators) + logger.debug(self.op_data.print()) for room in self.currentPlan.keys(): for idx, item in enumerate(self.currentPlan[room]): - _name = next((k for k, v in self.operators.items() if - v['current_room'] == room and 'current_index' in v.keys() and v['current_index'] == idx), + _name = next((k for k, v in self.op_data.operators.items() if + v.current_room == room and v.current_index == idx), None) if room not in self.current_base.keys(): self.current_base[room] = [''] * len(self.currentPlan[room]) if _name is None or _name == '': self.current_base[room][idx] = {"agent": "", "mood": -1} else: - self.current_base[room][idx] = {"mood": self.operators[_name]['mood'], "agent": _name} + self.current_base[room][idx] = {"mood": self.op_data.operators[_name].mood, "agent": _name} current_base = copy.deepcopy(self.current_base) plan = self.currentPlan - self.total_agent = [] fix_plan = {} for key in current_base: - if (key == 'train'): continue + if key == 'train': continue need_fix = False for idx, operator in enumerate(current_base[key]): data = current_base[key][idx] # 如果是空房间 if data["agent"] == '': if not need_fix: - fix_plan[key] = [''] * len(plan[key]) + fix_plan[key] = ['Current'] * len(plan[key]) need_fix = True fix_plan[key][idx] = plan[key][idx]["agent"] continue - if (data["agent"] in self.agent_base_config.keys()): - # 如果有设置下限,则减去下限值 eg: 令 - if ("LowerLimit" in self.agent_base_config[current_base[key][idx]["agent"]]): - data["mood"] = data["mood"] - self.agent_base_config[current_base[key][idx]["agent"]]["LowerLimit"] - # 把额外数据传过去 - data["current_room"] = key - data["room_index"] = idx - # 记录数据 - self.total_agent.append(data) # 随意人员则跳过 + _name = data['agent'] if plan[key][idx]["agent"] == 'Free': continue - if not (data['agent'] == plan[key][idx]['agent'] or ( - (data["agent"] in plan[key][idx]["replacement"]) and len(plan[key][idx]["replacement"]) > 0)): + if not (_name == plan[key][idx]['agent'] or ( + (_name in plan[key][idx]["replacement"]) and len(plan[key][idx]["replacement"]) > 0) or not + self.op_data.operators[_name].need_to_refresh()): if not need_fix: - fix_plan[key] = [''] * len(plan[key]) + fix_plan[key] = ['Current'] * len(plan[key]) need_fix = True fix_plan[key][idx] = plan[key][idx]["agent"] - elif need_fix: - fix_plan[key][idx] = data["agent"] - # 检查是否有空名 - if need_fix: - for idx, fix_agent in enumerate(fix_plan[key]): - if fix_plan[key][idx] == '': - # 则使用当前干员 - fix_plan[key][idx] = 'Current' # 最后如果有任何高效组心情没有记录 或者高效组在宿舍 - miss_list = {k: v for (k, v) in self.operators.items() if - (v['type'] == 'high' and ('mood' not in v.keys() or (v['mood'] == -1 or (v['mood'] == 24) and v['current_room'].startswith('dormitory'))))} + miss_list = {k: v for (k, v) in self.op_data.operators.items() if v.not_valid()} if len(miss_list.keys()) > 0: # 替换到他应该的位置 + logger.debug(f"高效组心情没有记录 或者高效组在宿舍{str(miss_list)}") for key in miss_list: - if miss_list[key]['group'] != '': - # 如果还有其他小组成员没满心情则忽略 - if next((k for k, v in self.operators.items() if - v['group'] == miss_list[key]['group'] and v['current_room'].startswith( - 'dormitory') and not (v['mood'] == -1 or v['mood'] == 24)), None) is not None: + if miss_list[key].group != '' and miss_list[key].current_room.startswith("dorm"): + # 如果还有其他小组成员在休息且没满心情则忽略 + if next((k for k, v in self.op_data.operators.items() if + v.group == miss_list[key].group and not v.not_valid() and v.current_room.startswith( + "dorm")), None) is not None: continue - if miss_list[key]['room'] not in fix_plan.keys(): - fix_plan[miss_list[key]['room']] = [x['agent'] for x in current_base[miss_list[key]['room']]] - fix_plan[miss_list[key]['room']][miss_list[key]['index']] = key + if miss_list[key].room not in fix_plan.keys(): + fix_plan[miss_list[key].room] = [x['agent'] for x in current_base[miss_list[key].room]] + fix_plan[miss_list[key].room][miss_list[key].index] = key if len(fix_plan.keys()) > 0: # 不能在房间里安排同一个人 如果有重复则换成Free # 还要修复确保同一组在同时上班 fix_agents = [] remove_keys = [] + logger.debug(f"Fix_plan {str(fix_plan)}") for key in fix_plan: - if skip_dorm and 'dormitory' in key: - remove_keys.append(key) + if 'dormitory' in key: + # 如果宿舍差Free干员 则跳过 + if next((e for e in fix_plan[key] if e not in ['Free', 'Current']), + None) is None and skip_dorm: + remove_keys.append(key) + continue for idx, fix_agent in enumerate(fix_plan[key]): if fix_agent not in fix_agents: fix_agents.append(fix_agent) @@ -366,8 +441,8 @@ def plan_solver(self): plan = self.currentPlan # 如果下个 普通任务 <10 分钟则跳过 plan if ( - next((e for e in self.tasks if 'type' not in e.keys() and e['time'] < datetime.now() + timedelta(seconds=600)), - None)) is not None: + next((e for e in self.tasks if e['time'] < datetime.now() + timedelta(seconds=600)), + None)) is not None: return if len(self.check_in_and_out()) > 0: # 处理龙舌兰和但书的插拔 @@ -378,317 +453,169 @@ def plan_solver(self): for idx, x in enumerate(plan[room]): if '但书' in x['replacement'] or '龙舌兰' in x['replacement']: in_out_plan[room][idx] = x['replacement'][0] - self.tasks.append({"time": self.get_in_and_out_time(room), "plan": in_out_plan}) + self.tasks.append({"time": self.get_run_roder_time(room), "plan": in_out_plan}) # 准备数据 + logger.debug(self.op_data.print()) if self.read_mood: # 根据剩余心情排序 - self.total_agent.sort(key=lambda x: x["mood"], reverse=False) + self.total_agent = list( + v for k, v in self.op_data.operators.items() if v.is_high() and not v.room.startswith('dorm')) + self.total_agent.sort(key=lambda x: x.mood - x.lower_limit, reverse=False) # 目前有换班的计划后面改 logger.debug(f'当前基地数据--> {self.total_agent}') - exclude_list = [] fia_plan, fia_room = self.check_fia() if fia_room is not None and fia_plan is not None: - exclude_list = copy.deepcopy(fia_plan) if not any(fia_room in obj["plan"].keys() and len(obj["plan"][fia_room]) == 2 for obj in self.tasks): - fia_idx = self.operators['菲亚梅塔']['index'] - result = [{}]*(fia_idx+1) - result[fia_idx]['time'] =datetime.now() - if self.operators["菲亚梅塔"]["mood"] != 24: + fia_idx = self.op_data.operators['菲亚梅塔'].current_index if self.op_data.operators[ + '菲亚梅塔'].current_index != -1 else \ + self.op_data.operators['菲亚梅塔'].index + result = [{}] * (fia_idx + 1) + result[fia_idx]['time'] = datetime.now() + if self.op_data.operators["菲亚梅塔"].mood != 24: self.enter_room(fia_room) result = self.get_agent_from_room(fia_room, [fia_idx]) self.back() logger.info('下一次进行菲亚梅塔充能:' + result[fia_idx]['time'].strftime("%H:%M:%S")) self.tasks.append({"time": result[fia_idx]['time'], "plan": {fia_room: [ - next(obj for obj in self.total_agent if obj["agent"] in fia_plan)["agent"], + next(obj for obj in self.total_agent if obj.name in fia_plan).name, "菲亚梅塔"]}}) - exclude_list.append("菲亚梅塔") try: - exaust_rest = [] - if len(self.exaust_agent) > 0: - for _exaust in self.exaust_agent: - i = self.operators[_exaust] - if 'current_room' in i.keys() and i['current_room'].startswith('dormitory') and i[ - 'resting_priority'] == 'high' and next( - (k for k in self.tasks if 'type' in k.keys() and i['current_room'] in k["type"]), - None) is None: - exaust_rest.append(i['name']) - if _exaust not in exclude_list: - exclude_list.append(_exaust) - # 如果exaust_agent<2 则读取 - logger.info(f'安排干员黑名单为:{exclude_list}') - # 先计算需要休息满的人 - total_exaust_plan = [] - for agent in exaust_rest: - error_count = 0 - time_result = None - # 读取时间 - __index = self.operators[agent]['current_index'] - while error_count < 3: - try: - self.enter_room(self.operators[agent]['current_room']) - time_result = self.get_agent_from_room(self.operators[agent]['current_room'],[__index]) - if time_result is None: - raise Exception("读取时间失败") - else: - break - except Exception as e: - self.back_to_index() - if self.scene() == Scene.INDEX: - self.tap_element('index_infrastructure', interval=5) - self.recog.update() - logger.exception(e) - error_count += 1 - continue - # 5分钟gap - time_result = time_result[__index]['time'] - timedelta(seconds=(300)) - # 如果已经有现有plan 则比对时间 - if next((k for k in total_exaust_plan if - next((k for k, v in k['plan'].items() if agent in v), None) is not None), - None) is not None: - _exaust_plan = next( - k for k in total_exaust_plan if next((k for k, v in k['plan'].items() if agent in v), None)) - if self.operators[agent]['current_room'] not in _exaust_plan['type']: - _exaust_plan['type'] += ',' + self.operators[agent]['current_room'] - if time_result > _exaust_plan['time']: - _exaust_plan['time'] = time_result + # 自动生成任务 + self.plan_metadata() + # 剩余高效组位置 + high_free = self.op_data.available_free(count=self.max_resting_count) + # 剩余低效位置 + low_free = self.op_data.available_free('low', count=self.max_resting_count) + _replacement = [] + _plan = {} + for op in self.total_agent: + # 忽略掉菲亚梅塔充能的干员 + if high_free == 0 or low_free == 0: + break + if fia_room is not None and op.name in self.op_data.operators['菲亚梅塔'].replacement: continue - exaust_plan = {'plan': {}, 'time': time_result, 'type': self.operators[agent]['current_room']} - # 如果有组,则生成小组上班 否则则单人上班 - bundle = [] - if self.operators[agent]['group'] != '': - bundle.extend([v['name'] for k, v in self.operators.items() if - 'group' in v.keys() and v['group'] == self.operators[agent]['group']]) - else: - bundle.append(agent) - for planned in bundle: - if self.operators[planned]['room'] not in exaust_plan['plan']: - exaust_plan['plan'][self.operators[planned]['room']] = [ - 'Current'] * len( - self.currentPlan[self.operators[planned]['room']]) - exaust_plan['plan'][self.operators[planned]['room']][ - self.operators[planned]['index']] = planned - total_exaust_plan.append(exaust_plan) - self.back() - self.tasks.extend(total_exaust_plan) - resting_dorm = [] - for task in self.tasks: - if 'type' in task.keys() and task['type'].startswith("dorm"): - resting_dorm.extend(task["type"].split(',')) - actuall_resting = len(resting_dorm) - - if len(resting_dorm) < self.dorm_count: - need_to_rest = [] - # 根据使用宿舍数量,输出需要休息的干员列表 - number_of_dorm = self.dorm_count - - min_mood = -99 - for agent in self.total_agent: - if actuall_resting >= number_of_dorm: - if min_mood == -99: - min_mood = agent['mood'] - break - if (len([value for value in need_to_rest if value["agent"] == agent["agent"]]) > 0): - continue - # 心情耗尽组如果心情 小于2 则记录时间 - if agent['agent'] in self.exaust_agent and agent['mood'] < 3 and not \ - self.operators[agent['agent']]['current_room'].startswith('dormitory'): - if next((e for e in self.tasks if 'type' in e.keys() and e['type'] == agent['agent']), + # 忽略掉正在休息的 + if op.current_room.startswith("dorm") or op.current_room in ['factory']: + continue + # 忽略掉心情值没低于上限的的 + if op.mood > int((op.upper_limit - op.lower_limit) * self.resting_treshhold + op.lower_limit): + continue + if op.name in self.op_data.exhaust_agent: + if op.mood <= 2: + if next((e for e in self.tasks if 'type' in e.keys() and op.name in e['type']), None) is None: - __agent = self.operators[agent['agent']] - self.enter_room(__agent['current_room']) - result = self.get_agent_from_room(__agent['current_room'], [__agent['current_index']]) + self.enter_room(op.current_room) + result = self.get_agent_from_room(op.current_room, [op.current_index]) self.back() # plan 是空的是因为得动态生成 - self.tasks.append({"time": result[__agent['current_index']]['time'], "plan": {}, "type": __agent['name']}) - else: - continue - # 忽略掉菲亚梅塔充能的干员 - if agent['agent'] in exclude_list: - continue - # 忽略掉低效率的干员 - if agent['agent'] in self.operators.keys() and self.operators[agent['agent']]['type'] != 'high': - continue - if 'resting_priority' in self.operators.keys() and self.operators[agent['agent']]['resting_priority'] != 'high': - continue - # 忽略掉正在休息的 - if agent['current_room'] in resting_dorm or agent['current_room'] in ['factory']: - continue - # 忽略掉心情值没低于上限的8的 - if agent['mood'] > int(self.operators[agent['agent']]["upper_limit"] * self.resting_treshhold): - continue - if agent['agent'] in self.operators.keys() and self.operators[agent['agent']]['group'] != '': - # 如果在group里则同时上下班 - group_resting = [x for x in self.total_agent if - self.operators[x['agent']]['group'] == self.operators[agent['agent']][ - 'group']] - group_restingCount = 0 - for x in group_resting: - if self.operators[x['agent']]['resting_priority'] == 'low': - continue - else: - group_restingCount += 1 - if group_restingCount + actuall_resting <= self.dorm_count: - need_to_rest.extend(group_resting) - actuall_resting += group_restingCount - else: - # 因为人数不够而跳过记录心情 - min_mood = agent['mood'] - continue - else: - need_to_rest.append(agent) - actuall_resting += 1 - # 输出转换后的换班plan - logger.info(f'休息人选为->{need_to_rest}') - if len(need_to_rest) > 0: - self.get_swap_plan(resting_dorm, need_to_rest, min_mood < 3 and min_mood != -99) + exhaust_type = op.name + if op.group != '': + exhaust_type = ','.join(self.op_data.groups[op.group]) + self.tasks.append({"time": result[op.current_index]['time'] if result[op.current_index][ + 'time'] is not None else datetime.now(), + "plan": {}, "type": exhaust_type}) + continue + if op.group != '': + # 如果在group里则同时上下班 + group_resting = self.op_data.groups[op.group] + _replacement, _plan, high_free, low_free = self.get_resting_plan( + group_resting, _replacement, _plan, high_free, low_free) + else: + _replacement, _plan, high_free, low_free = self.get_resting_plan([op.name], + _replacement, + _plan, + high_free, + low_free) + if len(_plan.keys()) > 0: + self.tasks.append({'plan': _plan, 'time': datetime.now(), + 'metadata': {}}) except Exception as e: - logger.exception(f'计算排班计划出错->{e}') + logger.exception(e) # 如果下个 普通任务 >5 分钟则补全宿舍 if (next((e for e in self.tasks if e['time'] < datetime.now() + timedelta(seconds=300)), None)) is None: self.agent_get_mood() - def get_swap_plan(self, resting_dorm, operators, skip_read_time): - result = {} - agents = copy.deepcopy(operators) - # 替换计划 - for a in operators: - if a['current_room'] not in result.keys(): - result[a['current_room']] = ['Current'] * len(self.currentPlan[a['current_room']]) - # 获取替换组且没有在上班的 排除但书或者龙舌兰 - __replacement = next((obj for obj in self.operators[a['agent']]['replacement'] if (not ( - self.operators[obj]['current_room'] != '' and not self.operators[obj][ - 'current_room'].startswith('dormitory'))) and obj not in ['但书', '龙舌兰']), None) - if __replacement is not None: - self.operators[__replacement]['current_room'] = a['current_room'] - result[a['current_room']][a['room_index']] = __replacement - else: - raise Exception(f"{a['agent']} 没有足够的替换组可用") - group_info = {} - read_time_rooms = [] - need_recover_room = [] - # 从休息计划里 规划出排班计划 并且立刻执行 - for room in [k for k, v in self.currentPlan.items() if (k not in resting_dorm) and k.startswith('dormitory')]: - # 记录房间可以放几个干员: - dorm_plan = [data["agent"] for data in self.currentPlan[room]] - # 塞一个最优组进去 - next_agent = next((obj for obj in agents if self.operators[obj["agent"]]['resting_priority'] == 'high'), - None) - planned_agent = [] - if next_agent is not None: - dorm_plan[dorm_plan.index('Free')] = next_agent["agent"] - planned_agent.append(next_agent["agent"]) - agents.remove(next_agent) - else: - break - if skip_read_time: - if 'rest_in_full' in self.operators[next_agent['agent']].keys() and \ - self.operators[next_agent['agent']]["rest_in_full"]: - need_recover_room.append(room) - read_time_rooms.append((room)) + def get_resting_plan(self, agents, exist_replacement, plan, high_free, low_free): + _low, _high = 0, 0 + __replacement = [] + __plan = {} + for x in agents: + if self.op_data.operators[x].resting_priority == 'low': + _low += 1 else: - if 'rest_in_full' in self.operators[next_agent['agent']].keys() and \ - self.operators[next_agent['agent']]["rest_in_full"]: - skip_read_time = True - read_time_rooms.append(room) - free_num = dorm_plan.count('Free') - while free_num > 0: - next_agent_low = next( - (obj for obj in agents if self.operators[obj["agent"]]['resting_priority'] == 'low'), None) - if next_agent_low is not None: - dorm_plan[dorm_plan.index('Free')] = next_agent_low["agent"] - planned_agent.append(next_agent_low["agent"]) - agents.remove(next_agent_low) - free_num -= 1 - else: + _high += 1 + # 排序 + agents.sort(key=lambda x: self.op_data.operators[x].mood) + # 进行位置数量的初步判定 + # 对于252可能需要进行额外判定,由于 low_free 性质等同于 high_free + if high_free - _high >= 0 and low_free - _low >= 0: + success = True + for agent in agents: + if not success: break - result[room] = dorm_plan - # 如果有任何正在休息的最优组 - for item in self.current_base[room]: - if 'agent' in item.keys(): - _item_name = item['agent'] - if _item_name in self.operators.keys() and self.operators[_item_name]['type'] == 'high' and not \ - self.operators[_item_name]['room'].startswith( - 'dormitory'): - # 如果是高效干员 - _room = self.operators[_item_name]['room'] - if _room not in result.keys(): - result[_room] = ['Current'] * len(self.currentPlan[_room]) - result[_room][self.operators[_item_name]['index']] = _item_name - if room in need_recover_room: - group_name = self.operators[next_agent['agent']]["name"] - else: - # 未分组则强制分组 - group_name = 'default' - if not group_name in group_info.keys(): - group_info[group_name] = {'type': room, 'plan': {}} + x = self.op_data.operators[agent] + _rep = next((obj for obj in x.replacement if (not ( + self.op_data.operators[obj].current_room != '' and not self.op_data.operators[ + obj].current_room.startswith('dormitory'))) and obj not in ['但书', + '龙舌兰'] and obj not in exist_replacement and obj not in __replacement), + None) + if _rep is not None: + __replacement.append(_rep) + if x.room not in __plan.keys(): + __plan[x.room] = ['Current'] * len(self.currentPlan[x.room]) + __plan[x.room][x.index] = _rep + else: + success = False + if success: + # 记录替换组 + exist_replacement.extend(__replacement) + for x in agents: + _dorm = self.op_data.assign_dorm(x) + if _dorm.position[0] not in plan.keys(): + plan[_dorm.position[0]] = ['Current'] * 5 + plan[_dorm.position[0]][_dorm.position[1]] = _dorm.name + for k, v in __plan.items(): + if k not in plan.keys(): + plan[k] = __plan[k] + for idx, name in enumerate(__plan[k]): + if plan[k][idx] == 'Current' and name != 'Current': + plan[k][idx] = name else: - group_info[group_name]['type'] += ',' + room - for planned in planned_agent: - if self.operators[planned]['current_room'] not in group_info[group_name]['plan']: - group_info[group_name]['plan'][self.operators[planned]['current_room']] = ['Current'] * len( - self.currentPlan[self.operators[planned]['room']]) - group_info[group_name]['plan'][self.operators[planned]['current_room']][ - self.operators[planned]['index']] = planned - # group_info[group_name]['plan'][room]=[x['agent'] for x in self.currentPlan[room]] - logger.info(f'生成的分组计划:{group_info}') - logger.info(f'生成的排班计划为->{result}') - self.tasks.append( - {'plan': result, 'time': datetime.now(), 'metadata': {'plan': group_info, 'room': read_time_rooms}}) - - def get_agent(self): + _low, _high = 0, 0 + return exist_replacement, plan, high_free - _high, low_free - _low + + def initialize_operators(self): plan = self.currentPlan - high_production = [] - replacements = [] + self.op_data = Operators(self.agent_base_config, self.max_resting_count) for room in plan.keys(): for idx, data in enumerate(plan[room]): - __high = {"name": data["agent"], "room": room, "index": idx, "group": data["group"], - 'replacement': data["replacement"], 'resting_priority': 'high', 'current_room': '', - 'exhaust_require': False, "upper_limit": 24,"rest_in_full":False} - if __high['name'] in self.agent_base_config.keys() and 'RestingPriority' in self.agent_base_config[ - __high['name']].keys() and self.agent_base_config[__high['name']]['RestingPriority'] == 'low': - __high["resting_priority"] = 'low' - if __high['name'] in self.agent_base_config.keys() and 'ExhaustRequire' in self.agent_base_config[ - __high['name']].keys() and self.agent_base_config[__high['name']]['ExhaustRequire'] == True: - __high["exhaust_require"] = True - if __high['name'] in self.agent_base_config.keys() and 'UpperLimit' in self.agent_base_config[ - __high['name']].keys(): - __high["upper_limit"] = self.agent_base_config[__high['name']]['UpperLimit'] - if __high['name'] in self.agent_base_config.keys() and 'RestInFull' in self.agent_base_config[ - __high['name']].keys() and self.agent_base_config[__high['name']]['RestInFull'] == True: - __high["rest_in_full"] = True - high_production.append(__high) + self.op_data.add(Operator(data["agent"], room, idx, data["group"], data["replacement"], 'high', + operator_type="high")) + # 菲亚梅塔替换组做特例判断 if "replacement" in data.keys() and data["agent"] != '菲亚梅塔': - replacements.extend(data["replacement"]) - for agent in high_production: - if agent["room"].startswith('dormitory'): - agent["type"] = "low" - else: - agent["type"] = "high" - self.operators[agent["name"]] = agent - for agent in replacements: - if agent in self.operators.keys(): - if 'type' in self.operators[agent].keys() and self.operators[agent]['type'] == 'high': - continue - else: - self.operators[agent] = {"type": "low", "name": agent, "group": '', 'resting_priority': 'low', - "index": -1, 'current_room': '', 'mood': 24, "upper_limit": 24,"rest_in_full":False} - else: - self.operators[agent] = {"type": "low", "name": agent, "group": '', 'current_room': '', - 'resting_priority': 'low', "index": -1, 'mood': 24, "upper_limit": 24,"rest_in_full":False} - self.exaust_agent = [] - if next((k for k, v in self.operators.items() if 'exhaust_require' in v.keys() and v["exhaust_require"]), - None) is not None: - exhaust_require = [v for k, v in self.operators.items() if - 'exhaust_require' in v.keys() and v["exhaust_require"]] - for i in exhaust_require: - if i['name'] in self.exaust_agent: continue - if 'group' in i.keys() and i['group'] != '': - self.exaust_agent.extend([v['name'] for k, v in self.operators.items() if - 'group' in v.keys() and v['group'] == i['group']]) - else: - self.exaust_agent.append(i['name']) - logger.info(f'需要用尽心情的干员为: {self.exaust_agent}') + for _replacement in data["replacement"]: + self.op_data.add(Operator(_replacement, "")) + dorm_names = [k for k in plan.keys() if k.startswith("dorm")] + dorm_names.sort(key=lambda x: x, reverse=False) + added = [] + # 竖向遍历出效率高到低 + for dorm in dorm_names: + for _idx, _dorm in enumerate(plan[dorm]): + if _dorm['agent'] == 'Free' and (dorm + str(_idx)) not in added and len(added)60 or int(s)>60: + if int(m) > 60 or int(s) > 60: raise Exception(f"读取错误") - res = int(h) * 3600 + int(m) * 60 + int(s) - if res>upperlimit: + res = int(h) * 3600 + int(m) * 60 + int(s) + if upperlimit is not None and res > upperlimit: raise Exception(f"超过读取上限") - else :return res + else: + return res except: - logger.error("读取失败" + "--> " + str(time_str)) - if error_count > 50: - raise Exception(f"读取失败{error_count}次超过上限") + logger.error("读取失败") + if error_count > 3: + logger.exception(f"读取失败{error_count}次超过上限") + return None else: - return self.read_time(cord,upperlimit, error_count + 1) + return self.read_time(cord, upperlimit, error_count + 1) def todo_list(self) -> None: """ 处理基建 Todo 列表 """ @@ -837,7 +764,7 @@ def clue(self) -> None: # 识别右侧按钮 (x0, y0), (x1, y1) = self.find('clue_func', strict=True) - logger.info('接收赠送线索') + logger.info('接收线索') self.tap(((x0 + x1) // 2, (y0 * 3 + y1) // 4), interval=3, rebuild=False) self.tap((self.recog.w - 10, self.recog.h - 10), interval=3, rebuild=False) self.tap((self.recog.w * 0.05, self.recog.h * 0.95), interval=3, rebuild=False) @@ -905,6 +832,8 @@ def clue(self) -> None: if clue_unlock is not None and get_all_clue: self.tap(clue_unlock) else: + # 记录趴体时间 + self.back(interval=2, rebuild=False) logger.info('返回基建主界面') @@ -1102,7 +1031,7 @@ def drone(self, room: str, one_time=False, not_return=False): self.recog.update() self.recog.save_screencap('run_order') # 200 为识别错误 - if drone_count < 100 or drone_count ==200: + if drone_count < 100 or drone_count == 200: logger.info(f"无人机数量小于92->停止") break st = accelerate[1] # 起点 @@ -1121,8 +1050,6 @@ def get_arrange_order(self) -> ArrangeOrder: score = self.recog.score(arrange_order_res[order][0]) if score is not None and score[0] > best_score: best_score, best_order = score[0], order - # if best_score < 0.6: - # raise RecognizeError logger.debug((best_score, best_order)) return best_order @@ -1150,8 +1077,6 @@ def scan_agant(self, agent: list[str], error_count=0, max_agent_count=-1): self.recog.update() ret = character_recognize.agent(self.recog.img) # 返回的顺序是从左往右从上往下 # 提取识别出来的干员的名字 - agent_name = set([x[0] for x in ret]) - agent_size = len(agent) select_name = [] for y in ret: name = y[0] @@ -1186,7 +1111,7 @@ def detail_filter(self, turn_on, type="not_in_dorm"): logger.info(f'开始 {("打开" if turn_on else "关闭")} {type} 筛选') self.tap((self.recog.w * 0.95, self.recog.h * 0.05), interval=1) if type == "not_in_dorm": - not_in_dorm = self.find('arrange_non_check_in',score=0.85) + not_in_dorm = self.find('arrange_non_check_in', score=0.85) if turn_on ^ (not_in_dorm is None): self.tap((self.recog.w * 0.3, self.recog.h * 0.5), interval=0.5) # 确认 @@ -1201,6 +1126,12 @@ def choose_agent(self, agents: list[str], room: str) -> None: for idx, n in enumerate(agents): if n == '': agents[idx] = 'Free' + # 如果是宿舍且干员不为高效组,则改为Free 加速换班时间 + elif room.startswith('dorm'): + if n not in self.op_data.operators.keys(): + agents[idx] = 'Free' + elif not self.op_data.operators[n].is_high(): + agents[idx] = 'Free' agent = copy.deepcopy(agents) logger.info(f'安排干员 :{agent}') # 若不是空房间,则清空工作中的干员 @@ -1246,8 +1177,8 @@ def choose_agent(self, agents: list[str], room: str) -> None: if index_change or first_time: # 第一次则调整 is_custom, arrange_type = self.get_order(agent[0]) - if is_dorm and not (agent[0] in self.operators.keys() and - 'room' in self.operators[agent[0]].keys() and self.operators[agent[0]]['room'].startswith( + if is_dorm and not ( + agent[0] in self.op_data.operators.keys() and self.op_data.operators[agent[0]].room.startswith( 'dormitory')): arrange_type = (3, 'true') # 如果重新排序则滑到最左边 @@ -1289,9 +1220,9 @@ def choose_agent(self, agents: list[str], room: str) -> None: self.switch_arrange_order(3, "true") # 只选择在列表里面的 # 替换组小于20才休息,防止进入就满心情进行网络连接 - free_list = [v["name"] for k, v in self.operators.items() if - v["name"] not in agents and v["type"] != 'high'] - free_list.extend([_name for _name in agent_list if _name not in self.operators.keys()]) + free_list = [v.name for k, v in self.op_data.operators.items() if + v.name not in agents and v.operator_type != 'high'] + free_list.extend([_name for _name in agent_list if _name not in self.op_data.operators.keys()]) free_list = list(set(free_list) - set(self.free_blacklist)) while free_num: selected_name, ret = self.scan_agant(free_list, max_agent_count=free_num) @@ -1331,7 +1262,9 @@ def swipe_left(self, right_swipe, w, h): self.swipe_only((w // 2, h // 2), (w // 2, 0), interval=0.5) return 0 - def get_agent_from_room(self, room, read_time_index=[]): + def get_agent_from_room(self, room, read_time_index=None): + if read_time_index is None: + read_time_index = [] error_count = 0 if room == 'meeting': time.sleep(3) @@ -1356,56 +1289,62 @@ def get_agent_from_room(self, room, read_time_index=[]): self.swipe((self.recog.w * 0.8, self.recog.h * 0.8), (0, -self.recog.h * 0.4), interval=1, rebuild=True) swiped = True data = {} - data['agent'] = character_recognize.agent_name( - self.recog.img[name_p[i][0][1]:name_p[i][1][1], name_p[i][0][0]:name_p[i][1][0]], self.recog.h*1.1) + _name = character_recognize.agent_name( + self.recog.img[name_p[i][0][1]:name_p[i][1][1], name_p[i][0][0]:name_p[i][1][0]], self.recog.h * 1.1) error_count = 0 - while i>=3 and data['agent'] !='' and (next((e for e in result if e['agent'] == data['agent']), None)) is not None: + while i >= 3 and _name != '' and ( + next((e for e in result if e['agent'] == _name), None)) is not None: logger.warning("检测到滑动可能失败") self.swipe((self.recog.w * 0.8, self.recog.h * 0.8), (0, -self.recog.h * 0.4), interval=1, rebuild=True) - data['agent'] = character_recognize.agent_name( - self.recog.img[name_p[i][0][1]:name_p[i][1][1], name_p[i][0][0]:name_p[i][1][0]], self.recog.h*1.1) - error_count+=1 - if error_count>4: + _name = character_recognize.agent_name( + self.recog.img[name_p[i][0][1]:name_p[i][1][1], name_p[i][0][0]:name_p[i][1][0]], + self.recog.h * 1.1) + error_count += 1 + if error_count > 4: raise Exception("超过出错上限") - data['mood'] = self.read_screen(self.recog.img, cord=mood_p[i], change_color=True) - if data['agent'] not in self.operators.keys(): - self.operators[data['agent']] = {"type": "low", "name": data['agent'], "group": '', 'current_room': '', - 'resting_priority': 'low', "index": -1, 'mood': data['mood'], - "upper_limit": 24} + _mood = 24 + # 如果房间不为空 + if _name != '': + if _name not in self.op_data.operators.keys(): + self.op_data.add(Operator(_name, "")) + if self.op_data.operators[_name].need_to_refresh(): + _mood = self.read_screen(self.recog.img, cord=mood_p[i], change_color=True) + else: + _mood = self.op_data.operators[_name].mood + high_no_time = self.op_data.update_detail(_name, _mood, room, i) + if high_no_time is not None: + logger.debug(f"检测到高效组休息时间数据不存在:{room},{high_no_time}") + read_time_index.append(high_no_time) else: - self.operators[data['agent']]['mood'] = data['mood'] - self.operators[data['agent']]['current_index'] = i - self.operators[data['agent']]['current_room'] = room + _mood = -1 + data['agent'] = _name + data['mood'] = _mood if i in read_time_index: - if data['mood'] in [24] or (data['mood'] == 0 and not room.startswith('dorm')): + if _mood in [24] or (_mood == 0 and not room.startswith('dorm')): data['time'] = datetime.now() else: upperLimit = 21600 - if data['agent']in ['菲亚梅塔'] or data['agent'] in self.exaust_agent: + if _name in ['菲亚梅塔'] or _name in self.op_data.exhaust_agent: upperLimit = 43200 - data['time'] = self.double_read_time(time_p[i],upperLimit=upperLimit) + logger.debug(f"开始记录时间:{room},{i}") + data['time'] = self.double_read_time(time_p[i], upperLimit=upperLimit) + self.op_data.refresh_dorm_time(room, i, data) + logger.debug(f"停止记录时间:{str(data)}") result.append(data) - self.scan_time[room] = datetime.now() - # update current_room - for item in result: - operator = item['agent'] - if operator in self.operators.keys(): - self.operators[operator]['current_room'] = room - for _operator in self.operators.keys(): - if 'current_room' in self.operators[_operator].keys() and self.operators[_operator][ - 'current_room'] == room and _operator not in [res['agent'] for res in result] : - self.operators[_operator]['current_room'] = '' + for _operator in self.op_data.operators.keys(): + if self.op_data.operators[_operator].current_room == room and _operator not in [res['agent'] for res in + result]: + self.op_data.operators[_operator].current_room = '' logger.info(f'重设 {_operator} 至空闲') return result - def agent_arrange(self, plan: tp.BasePlan, read_time_room=[]): + def agent_arrange(self, plan: tp.BasePlan, metadata=None): logger.info('基建:排班') in_and_out = [] - fia_room = "" + fia_data = None rooms = list(plan.keys()) # 优先替换工作站再替换宿舍 rooms.sort(key=lambda x: x.startswith('dorm'), reverse=False) - time_result = {} for room in rooms: finished = False choose_error = 0 @@ -1425,18 +1364,8 @@ def agent_arrange(self, plan: tp.BasePlan, read_time_room=[]): in_and_out.append(room) update_base = True if '菲亚梅塔' in plan[room] and len(plan[room]) == 2: - fia_room = room + fia_data = (room, plan[room][0]) update_base = True - # 如果需要更新当前阵容 - self.scan_time[room] = None - # 是否该干员变动影响其他房间 - update_room = [] - for operator in (plan[room]): - if operator in self.operators.keys() and self.operators[operator]['current_room'] != '' and \ - self.operators[operator]['current_room'] != room: - update_room.append(self.operators[operator]['current_room']) - for __room in update_room: - self.scan_time[__room] = None if update_base or 'Current' in plan[room]: self.current_base[room] = self.get_agent_from_room(room) # 纠错 因为网络连接导致房间移位 @@ -1451,7 +1380,6 @@ def agent_arrange(self, plan: tp.BasePlan, read_time_room=[]): # 如果空房间或者名字错误,则使用default干员 plan[room][current_idx] = \ self.currentPlan[room][current_idx]["agent"] - self.scan_time[room] = None while self.find('arrange_order_options') is None: if error_count > 3: raise Exception('未成功进入干员选择界面') @@ -1465,22 +1393,21 @@ def agent_arrange(self, plan: tp.BasePlan, read_time_room=[]): x0 = self.recog.w // 3 * 2 # double confirm y0 = self.recog.h - 10 self.tap((x0, y0), rebuild=True) - time_index = [] - # 如果需要读取时间 - if room in read_time_room: - time_index = [[data["agent"] for data in self.currentPlan[room]].index('Free')] - current = self.get_agent_from_room(room, time_index) + read_time_index = [] + if metadata is not None: + read_time_index = self.op_data.get_refresh_index(room, plan[room]) + if fia_data is not None: + # 移除时间以刷新心情 + self.op_data.operators['菲亚梅塔'].time_stamp = None + self.op_data.operators[fia_data[1]].time_stamp = None + current = self.get_agent_from_room(room, read_time_index) for idx, name in enumerate(plan[room]): if current[idx]['agent'] != name: logger.error(f'检测到的干员{current[idx]["agent"]},需要安排的干员{name}') raise Exception('检测到安排干员未成功') - # 如果不匹配,则退出主界面再重新进房间一次 - if room in read_time_room: - __name = plan[room][[data["agent"] for data in self.currentPlan[room]].index('Free')] - time_result[room] = current[time_index[0]]['time'] - if not self.operators[__name]['exhaust_require']: - time_result[room] = time_result[room] - timedelta(seconds=600) finished = True + # 如果完成则移除该任务 + del plan[room] # back to 基地主界面 while self.scene() == Scene.CONNECTING: self.sleep(3) @@ -1492,8 +1419,8 @@ def agent_arrange(self, plan: tp.BasePlan, read_time_room=[]): while self.get_infra_scene() != Scene.INFRA_MAIN: self.back() self.recog.update() - back_count+=1 - if back_count>3: + back_count += 1 + if back_count > 3: raise e if choose_error > 3: raise e @@ -1516,20 +1443,19 @@ def agent_arrange(self, plan: tp.BasePlan, read_time_room=[]): # 急速换班 self.todo_task = True self.planned = True - if fia_room != '': - replace_agent = plan[fia_room][0] - fia_change_room = self.operators[replace_agent]["room"] - fia_room_plan = [data["agent"] for data in self.current_base[fia_room]] - fia_change_room_plan = ['Current']*len(self.currentPlan[fia_change_room]) - fia_change_room_plan[self.operators[replace_agent]["index"]] = replace_agent + if fia_data is not None: + replace_agent = fia_data[1] + fia_change_room = self.op_data.operators[replace_agent].room + fia_room_plan = [data["agent"] for data in self.current_base[fia_data[0]]] + fia_change_room_plan = ['Current'] * len(self.currentPlan[fia_change_room]) + fia_change_room_plan[self.op_data.operators[replace_agent].index] = replace_agent self.tasks.append( - {'time': self.tasks[0]['time'], 'plan': {fia_room: fia_room_plan, fia_change_room: fia_change_room_plan}}) + {'time': self.tasks[0]['time'], + 'plan': {fia_data[0]: fia_room_plan, fia_change_room: fia_change_room_plan}}) # 急速换班 self.todo_task = True self.planned = True logger.info('返回基建主界面') - if len(read_time_room) > 0: - return time_result @Asst.CallBackType def log_maa(msg, details, arg): @@ -1554,7 +1480,8 @@ def inialize_maa(self): def maa_plan_solver(self): try: - if self.maa_config['last_execution'] is not None and datetime.now() - timedelta(seconds=self.maa_config['maa_execution_gap']*3600)< self.maa_config['last_execution']: + if self.maa_config['last_execution'] is not None and datetime.now() - timedelta( + seconds=self.maa_config['maa_execution_gap'] * 3600) < self.maa_config['last_execution']: logger.info("间隔未超过设定时间,不启动maa") else: self.send_email('休息时长超过9分钟,启动MAA') @@ -1562,7 +1489,7 @@ def maa_plan_solver(self): # 任务及参数请参考 docs/集成文档.md self.inialize_maa() self.MAA.append_task('StartUp') - _plan= self.maa_config['weekly_plan'][get_server_weekday()] + _plan = self.maa_config['weekly_plan'][get_server_weekday()] logger.info(f"现在服务器是{_plan['weekday']}") fights = [] for stage in _plan["stage"]: @@ -1596,7 +1523,7 @@ def maa_plan_solver(self): 'shopping': True, 'buy_first': ['龙门币', '赤金'], 'blacklist': ['家具', '碳', '加急'], - 'credit_fight':fights[len(fights)-1]!='' + 'credit_fight': fights[len(fights) - 1] != '' }) self.MAA.append_task('Award') # asst.append_task('Copilot', { @@ -1660,7 +1587,7 @@ def maa_plan_solver(self): self.device.exit('com.hypergryph.arknights') # 生息演算逻辑 结束 remaining_time = (self.tasks[0]["time"] - datetime.now()).total_seconds() - logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") + logger.info(f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") self.send_email("脚本停止") time.sleep(remaining_time) self.MAA = None @@ -1669,7 +1596,7 @@ def maa_plan_solver(self): self.MAA = None remaining_time = (self.tasks[0]["time"] - datetime.now()).total_seconds() if remaining_time > 0: - logger.info(f"开始休息 {'%.2f' % (remaining_time/60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") + logger.info(f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{self.tasks[0]['time'].strftime('%H:%M:%S')}") time.sleep(remaining_time) self.device.exit('com.hypergryph.arknights') @@ -1680,7 +1607,7 @@ def send_email(self, tasks): msg.attach(MIMEText(conntent, 'plain', 'utf-8')) msg['Subject'] = self.email_config['subject'] msg['From'] = self.email_config['account'] - s = smtplib.SMTP_SSL("smtp.qq.com", 465) + s = smtplib.SMTP_SSL("smtp.qq.com", 465, timeout=10.0) # 登录邮箱 s.login(self.email_config['account'], self.email_config['pass_code']) # 开始发送 diff --git a/arknights_mower/utils/operators.py b/arknights_mower/utils/operators.py new file mode 100644 index 000000000..5cf611789 --- /dev/null +++ b/arknights_mower/utils/operators.py @@ -0,0 +1,201 @@ +from datetime import datetime, timedelta +from ..data import agent_list + + +class Operators(object): + config = None + operators = None + exhaust_agent = [] + exhaust_group = [] + groups = None + dorm = [] + max_resting_count = 4 + + def __init__(self, config,max_resting_count): + self.config = config + self.operators = {} + self.groups = {} + self.exhaust_agent = [] + self.exhaust_group = [] + self.dorm = [] + self.max_resting_count = max_resting_count + + def update_detail(self,name, mood, current_room, current_index): + agent = self.operators[name] + agent.current_room = current_room + agent.current_index = current_index + agent.mood = mood + agent.time_stamp = datetime.now() + # 如果移出宿舍,则清除对应宿舍数据 且重新记录高效组心情 + if agent.current_room.startswith('dorm') and not current_room.startswith('dorm') and agent.is_high(): + self.refresh_dorm_time(agent.current_room,agent.current_index,{'agent':''}) + agent.time_stamp = None + # 如果是高效组且没有记录时间,则返还index + if agent.current_room.startswith('dorm') and agent.is_high(): + for dorm in self.dorm: + if dorm.position[0] == current_room and dorm.position[1] == current_index and dorm.time is None: + return current_index + + def refresh_dorm_time(self,room,index, agent): + for idx, dorm in enumerate(self.dorm): + # Filter out resting priority low + # if idx >= self.max_resting_count: + # break + if dorm.position[0] == room and dorm.position[1] == index: + # 如果人为高效组,则记录时间 + _name = agent['agent'] + if _name in self.operators.keys() and self.operators[_name].is_high(): + dorm.name = _name + dorm.time = agent['time'] + else: + dorm.name = '' + dorm.time = None + break + + def get_refresh_index(self,room,plan): + ret = [] + for idx, dorm in enumerate(self.dorm): + # Filter out resting priority low + if idx >= self.max_resting_count: + break + if dorm.position[0] == room: + for i,_name in enumerate(plan): + if _name in self.operators.keys() and self.operators[_name].is_high() and self.operators[_name].resting_priority=='high' and not self.operators[_name].room.startswith('dorm'): + ret.append(i) + break + return ret + + def get_dorm_by_name(self,name): + for idx, dorm in enumerate(self.dorm): + if dorm.name == name: + return idx, dorm + return None,None + + def add(self, operator): + if operator.name not in agent_list: + return + if operator.name in self.config.keys() and 'RestingPriority' in self.config[operator.name].keys(): + operator.resting_priority = self.config[operator.name]['RestingPriority'] + if operator.name in self.config.keys() and 'ExhaustRequire' in self.config[operator.name].keys(): + operator.exhaust_require = self.config[operator.name]['ExhaustRequire'] + if operator.name in self.config.keys() and 'UpperLimit' in self.config[operator.name].keys(): + operator.upper_limit = self.config[operator.name]['UpperLimit'] + if operator.name in self.config.keys() and 'RestInFull' in self.config[operator.name].keys(): + operator.rest_in_full = self.config[operator.name]['RestInFull'] + self.operators[operator.name] = operator + # 需要用尽心情干员逻辑 + if (operator.exhaust_require or operator.group in self.exhaust_group) \ + and operator.name not in self.exhaust_agent: + self.exhaust_agent.append(operator.name) + if operator.group != '': + self.exhaust_group.append(operator.group) + # 干员分组逻辑 + if operator.group != "": + if operator.group not in self.groups.keys(): + self.groups[operator.group] = [operator.name] + else: + self.groups[operator.group].append(operator.name) + + def available_free(self, free_type='high', count=4): + ret = 0 + if free_type == 'high': + idx = 0 + for dorm in self.dorm: + if dorm.name =='' or (dorm.name in self.operators.keys() and not self.operators[dorm.name].is_high()): + ret += 1 + if idx == count - 1: + break + else: + idx += 1 + else: + idx = -1 + while idx < 0: + dorm = self.dorm[idx] + if dorm.name =='' or (dorm.name in self.operators.keys() and not self.operators[dorm.name].is_high()): + ret += 1 + if idx == count - len(self.dorm): + break + else: + idx -= 1 + return ret + + def assign_dorm(self,name): + is_high = self.operators[name].resting_priority=='high' + if is_high: + _room = next(obj for obj in self.dorm if obj.name not in self.operators.keys() or not self.operators[obj.name].is_high()) + else: + _room = None + idx = -1 + while idx < 0: + if self.dorm[idx].name=='': + _room = self.dorm[idx] + break + else: + idx -= 1 + _room.name = name + return _room + + def print(self): + ret = "{" + op = [] + dorm = [] + for k, v in self.operators.items(): + op.append("'"+k+"': "+ str(vars(v))) + ret += "'operators': {" + ','.join(op)+"}," + for v in self.dorm: + dorm.append(str(vars(v))) + ret += "'dorms': [" + ','.join(dorm) + "]}" + return ret + + +class Dormitory(object): + + def __init__(self, position, name='', time=None): + self.position = position + self.name = name + self.time = time + + +class Operator(object): + time_stamp = None + + def __init__(self, name, room, index=-1, group='', replacement=[], resting_priority='low', current_room='', + exhaust_require=False, + mood=24, upper_limit=24, rest_in_full=False, current_index=-1, lower_limit=0, operator_type="low"): + self.name = name + self.room = room + self.operator_type = operator_type + # if room.startswith('dormitory'): + # self.operator_type = "low" + self.index = index + self.group = group + self.replacement = replacement + self.resting_priority = resting_priority + self.current_room = current_room + self.exhaust_require = exhaust_require + self.upper_limit = upper_limit + self.rest_in_full = rest_in_full + self.mood = mood + self.current_index = current_index + self.lower_limit = lower_limit + + def is_high(self): + return self.operator_type =='high' + + def need_to_refresh(self, h=2): + # 是否需要读取心情 + if self.operator_type == 'high': + if self.time_stamp is None or ( + self.time_stamp is not None and self.time_stamp + timedelta(hours=h) < datetime.now()): + return True + return False + + def not_valid(self): + if self.operator_type == 'high': + if not self.room.startswith("dorm") and self.current_room.startswith("dorm"): + if self.mood == -1 or self.mood == 24: + return True + else: + return False + return self.need_to_refresh() or self.current_room != self.room or self.index != self.current_index + return False diff --git a/diy.py b/diy.py index 29047dcbd..4e446c835 100644 --- a/diy.py +++ b/diy.py @@ -202,6 +202,7 @@ def savelog(): config.SCREENSHOT_MAXNUM = 1000 config.ADB_DEVICE = maa_config['maa_adb'] config.ADB_CONNECT = maa_config['maa_adb'] + config.MAX_RETRYTIME = 10 config.PASSWORD = '你的密码' init_fhlr() @@ -214,7 +215,7 @@ def inialize(tasks,scheduler=None): base_scheduler.global_plan = plan base_scheduler.current_base = {} base_scheduler.resting=[] - base_scheduler.dorm_count=4 + base_scheduler.max_resting_count=4 base_scheduler.tasks = tasks # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true base_scheduler.read_mood = True From 281ef93af8c57518fea36e59ab29d287368ce01b Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Tue, 28 Mar 2023 23:11:48 -0700 Subject: [PATCH 21/39] update __main__.py --- arknights_mower/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arknights_mower/__main__.py b/arknights_mower/__main__.py index c1123c908..974fbe486 100644 --- a/arknights_mower/__main__.py +++ b/arknights_mower/__main__.py @@ -58,7 +58,7 @@ def inialize(tasks, scheduler=None): base_scheduler.global_plan = {'default': "plan_1", "plan_1": plan1} base_scheduler.current_base = {} base_scheduler.resting = [] - base_scheduler.dorm_count = 4 + base_scheduler.max_resting_count = 4 base_scheduler.tasks = tasks # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true base_scheduler.read_mood = conf['run_mode'] == 1 From aa18a41211e97d48962a790457dbb8a58f4ca74f Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Tue, 28 Mar 2023 23:51:57 -0700 Subject: [PATCH 22/39] fix: bug --- arknights_mower/utils/operators.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arknights_mower/utils/operators.py b/arknights_mower/utils/operators.py index 5cf611789..2e9ad1ba6 100644 --- a/arknights_mower/utils/operators.py +++ b/arknights_mower/utils/operators.py @@ -22,14 +22,15 @@ def __init__(self, config,max_resting_count): def update_detail(self,name, mood, current_room, current_index): agent = self.operators[name] - agent.current_room = current_room - agent.current_index = current_index - agent.mood = mood + agent.time_stamp = datetime.now() # 如果移出宿舍,则清除对应宿舍数据 且重新记录高效组心情 if agent.current_room.startswith('dorm') and not current_room.startswith('dorm') and agent.is_high(): self.refresh_dorm_time(agent.current_room,agent.current_index,{'agent':''}) agent.time_stamp = None + agent.current_room = current_room + agent.current_index = current_index + agent.mood = mood # 如果是高效组且没有记录时间,则返还index if agent.current_room.startswith('dorm') and agent.is_high(): for dorm in self.dorm: From c28bf795a6a04b2a17d61fa81b9a5a17625ef5a4 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Wed, 29 Mar 2023 00:51:38 -0700 Subject: [PATCH 23/39] =?UTF-8?q?fix:=20=E6=97=B6=E9=97=B4=E7=94=9F?= =?UTF-8?q?=E6=88=90+=E6=8E=92=E7=8F=AD=E7=94=9F=E6=88=90=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index e0eb0e9ce..90b1c89eb 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -278,6 +278,8 @@ def plan_metadata(self): if __dorm is not None: _type.append('dorm' + str(_dorm_idx)) planned_index.append(_dorm_idx) + if __dorm.time is not None and __dorm.time < _time: + _time = __dorm.time if x not in low_priority: low_priority.append(x) # 生成单个任务 @@ -551,8 +553,8 @@ def get_resting_plan(self, agents, exist_replacement, plan, high_free, low_free) agents.sort(key=lambda x: self.op_data.operators[x].mood) # 进行位置数量的初步判定 # 对于252可能需要进行额外判定,由于 low_free 性质等同于 high_free + success = True if high_free - _high >= 0 and low_free - _low >= 0: - success = True for agent in agents: if not success: break @@ -583,8 +585,10 @@ def get_resting_plan(self, agents, exist_replacement, plan, high_free, low_free) for idx, name in enumerate(__plan[k]): if plan[k][idx] == 'Current' and name != 'Current': plan[k][idx] = name - else: - _low, _high = 0, 0 + else: + success = False + if not success: + _high, _low = 0, 0 return exist_replacement, plan, high_free - _high, low_free - _low def initialize_operators(self): From 601eb8e1572cde5edbd5d6ddf7a0a1f053b9be04 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Fri, 31 Mar 2023 21:41:07 -0700 Subject: [PATCH 24/39] =?UTF-8?q?fix:=20=E5=8D=A1=E5=BF=83=E6=83=85?= =?UTF-8?q?=E6=BC=8F=E6=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 17 ++++++++--------- arknights_mower/utils/operators.py | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 90b1c89eb..57202da8d 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -8,7 +8,6 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart - from ..data import agent_list from ..utils import character_recognize, detector, segment from ..utils.operators import Operators, Operator, Dormitory @@ -248,7 +247,7 @@ def plan_metadata(self): _idx, __dorm = self.op_data.get_dorm_by_name(x) if x in self.op_data.operators.keys() and self.op_data.operators[x].rest_in_full: if __dorm is not None and __dorm.time is not None: - if __dorm.time > _time: + if __dorm.time > _time and self.op_data.operators[x].resting_priority == 'high': _time = __dorm.time __type.append('dorm' + str(_idx)) planned_index.append(_idx) @@ -278,7 +277,8 @@ def plan_metadata(self): if __dorm is not None: _type.append('dorm' + str(_dorm_idx)) planned_index.append(_dorm_idx) - if __dorm.time is not None and __dorm.time < _time: + if __dorm.time is not None and __dorm.time < _time and self.op_data.operators[ + x].resting_priority == 'high': _time = __dorm.time if x not in low_priority: low_priority.append(x) @@ -608,14 +608,14 @@ def initialize_operators(self): # 竖向遍历出效率高到低 for dorm in dorm_names: for _idx, _dorm in enumerate(plan[dorm]): - if _dorm['agent'] == 'Free' and (dorm + str(_idx)) not in added and len(added) Date: Sat, 1 Apr 2023 11:02:05 -0700 Subject: [PATCH 25/39] =?UTF-8?q?fix:=20=E5=8D=A1=E5=BF=83=E6=83=85bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/data/agent.json | 7 ++++++- arknights_mower/solvers/base_schedule.py | 17 ++++++++--------- arknights_mower/utils/operators.py | 2 ++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/arknights_mower/data/agent.json b/arknights_mower/data/agent.json index 975e53411..d322f2e6e 100644 --- a/arknights_mower/data/agent.json +++ b/arknights_mower/data/agent.json @@ -269,5 +269,10 @@ "截云", "麒麟X夜刀", "火龙S黑角", - "泰拉大陆调查团" + "泰拉大陆调查团", + "伊内丝", + "洋灰", + "休谟斯", + "摩根", + "U-Official" ] diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 90b1c89eb..57202da8d 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -8,7 +8,6 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart - from ..data import agent_list from ..utils import character_recognize, detector, segment from ..utils.operators import Operators, Operator, Dormitory @@ -248,7 +247,7 @@ def plan_metadata(self): _idx, __dorm = self.op_data.get_dorm_by_name(x) if x in self.op_data.operators.keys() and self.op_data.operators[x].rest_in_full: if __dorm is not None and __dorm.time is not None: - if __dorm.time > _time: + if __dorm.time > _time and self.op_data.operators[x].resting_priority == 'high': _time = __dorm.time __type.append('dorm' + str(_idx)) planned_index.append(_idx) @@ -278,7 +277,8 @@ def plan_metadata(self): if __dorm is not None: _type.append('dorm' + str(_dorm_idx)) planned_index.append(_dorm_idx) - if __dorm.time is not None and __dorm.time < _time: + if __dorm.time is not None and __dorm.time < _time and self.op_data.operators[ + x].resting_priority == 'high': _time = __dorm.time if x not in low_priority: low_priority.append(x) @@ -608,14 +608,14 @@ def initialize_operators(self): # 竖向遍历出效率高到低 for dorm in dorm_names: for _idx, _dorm in enumerate(plan[dorm]): - if _dorm['agent'] == 'Free' and (dorm + str(_idx)) not in added and len(added) Date: Sun, 2 Apr 2023 17:05:43 -0700 Subject: [PATCH 26/39] #159 --- arknights_mower/solvers/base_schedule.py | 52 +++++++++++--------- arknights_mower/utils/character_recognize.py | 4 -- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 57202da8d..6f262030d 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -52,6 +52,7 @@ def __init__(self, device: Device = None, recog: Recognizer = None) -> None: super().__init__(device, recog) self.op_data = None self.max_resting_count = 4 + self.party_time = None def run(self) -> None: """ @@ -65,6 +66,8 @@ def run(self) -> None: self.task = self.tasks[0] else: self.task = None + if self.party_time is not None and self.party_time None: # 线索交流开启 if clue_unlock is not None and get_all_clue: self.tap(clue_unlock) + self.party_time = datetime.now() + timedelta(days=1) + logger.info("为期一天的趴体开始") else: # 记录趴体时间 - - self.back(interval=2, rebuild=False) + self.back(interval=2) + self.party_time = self.double_read_time((1765, 422, 1920, 515)) + logger.info(f"趴体结束时间为: {self.party_time}") logger.info('返回基建主界面') self.back(interval=2) @@ -1391,7 +1397,6 @@ def agent_arrange(self, plan: tp.BasePlan, metadata=None): raise Exception('未成功进入干员选择界面') self.tap((self.recog.w * 0.82, self.recog.h * 0.2), interval=1) error_count += 1 - error_count = 0 self.choose_agent(plan[room], room) self.recog.update() self.tap_element('confirm_blue', detected=True, judge=False, interval=3) @@ -1446,9 +1451,7 @@ def agent_arrange(self, plan: tp.BasePlan, metadata=None): self.back(interval=0.5) self.back(interval=0.5) self.tasks.append({'time': self.tasks[0]['time'], 'plan': replace_plan}) - # 急速换班 - self.todo_task = True - self.planned = True + self.skip() if fia_data is not None: replace_agent = fia_data[1] fia_change_room = self.op_data.operators[replace_agent].room @@ -1459,10 +1462,13 @@ def agent_arrange(self, plan: tp.BasePlan, metadata=None): {'time': self.tasks[0]['time'], 'plan': {fia_data[0]: fia_room_plan, fia_change_room: fia_change_room_plan}}) # 急速换班 - self.todo_task = True - self.planned = True + self.skip() logger.info('返回基建主界面') + def skip(self): + self.todo_task = True + self.planned = True + @Asst.CallBackType def log_maa(msg, details, arg): m = Message(msg) diff --git a/arknights_mower/utils/character_recognize.py b/arknights_mower/utils/character_recognize.py index 7b11d7ab1..af7d48f32 100644 --- a/arknights_mower/utils/character_recognize.py +++ b/arknights_mower/utils/character_recognize.py @@ -188,10 +188,6 @@ def agent(img, draw=False): raise Exception("启动 Plan B") except Exception as e: # 大哥不行了,二哥上! - name = segment.read_screen(__img, - langurage="chi_sim", - type="text") - logger.warning(f'备选方案识别结果: {name}') ret_fail.append(poly) if name in ocr_error.keys(): name = ocr_error[name] From ebb9b6b3d7d94ffacf5811281b4c201c967b3361 Mon Sep 17 00:00:00 2001 From: Ksnow <1048879349@qq.com> Date: Wed, 5 Apr 2023 20:47:33 +0800 Subject: [PATCH 27/39] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9C=80=E5=A4=A7?= =?UTF-8?q?=E5=88=86=E7=BB=84=E6=95=B0=E5=92=8C=E6=97=A0=E4=BA=BA=E6=9C=BA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E9=98=88=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/__main__.py | 12 +++++++-- arknights_mower/solvers/base_schedule.py | 11 +++++--- .../utils/device/adb_client/core.py | 2 +- diy.py | 1 + menu.py | 25 ++++++++++++++++--- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/arknights_mower/__main__.py b/arknights_mower/__main__.py index 974fbe486..fd9dded4a 100644 --- a/arknights_mower/__main__.py +++ b/arknights_mower/__main__.py @@ -58,10 +58,13 @@ def inialize(tasks, scheduler=None): base_scheduler.global_plan = {'default': "plan_1", "plan_1": plan1} base_scheduler.current_base = {} base_scheduler.resting = [] - base_scheduler.max_resting_count = 4 + base_scheduler.max_resting_count = conf['max_resting_count'] + base_scheduler.drone_count_limit = conf['drone_count_limit'] base_scheduler.tasks = tasks # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true base_scheduler.read_mood = conf['run_mode'] == 1 + # 干员宿舍回复阈值 + # 高效组心情低于 UpperLimit * 阈值 (向下取整)的时候才会会安排休息 base_scheduler.scan_time = {} base_scheduler.last_room = '' @@ -108,10 +111,15 @@ def simulate(): (base_scheduler.tasks.sort(key=lambda x: x["time"], reverse=False)) sleep_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() logger.debug(base_scheduler.tasks) + remaining_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() if sleep_time > 540 and conf['maa_enable'] == 1: + subject = f"下次任务在{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}" + context = f"下一次任务:{base_scheduler.tasks[0]['plan']}" + logger.info(context) + logger.info(subject) + base_scheduler.send_email(context, subject) base_scheduler.maa_plan_solver() elif sleep_time > 0: - remaining_time = (base_scheduler.tasks[0]["time"] - datetime.now()).total_seconds() subject = f"开始休息 {'%.2f' % (remaining_time / 60)} 分钟,到{base_scheduler.tasks[0]['time'].strftime('%H:%M:%S')}" context = f"下一次任务:{base_scheduler.tasks[0]['plan']}" logger.info(context) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 6f262030d..bb6a1c768 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -256,6 +256,7 @@ def plan_metadata(self): if __room not in __plan.keys(): __plan[__room] = ['Current'] * len(self.currentPlan[__room]) __plan[__room][self.op_data.operators[x].index] = x + if __time < datetime.now(): __time = datetime.now() self.tasks.append({"type": ','.join(__type), 'plan': __plan, 'time': __time}) # 如果非 rest in full, 则同组取时间最小值 else: @@ -286,9 +287,11 @@ def plan_metadata(self): # 生成单个任务 if len(_plan.items()) > 0: _time -= timedelta(minutes=8) + if _time < datetime.now(): _time = datetime.now() self.tasks.append({"type": ','.join(_type), 'plan': _plan, 'time': _time if not short_rest else (datetime.now() + timedelta(hours=0.5))}) + def infra_main(self): """ 位于基建首页 """ if self.find('control_central') is None: @@ -537,6 +540,7 @@ def plan_solver(self): except Exception as e: logger.exception(e) # 如果下个 普通任务 >5 分钟则补全宿舍 + logger.debug('tasks:'+str(self.tasks)) if (next((e for e in self.tasks if e['time'] < datetime.now() + timedelta(seconds=300)), None)) is None: self.agent_get_mood() @@ -1043,8 +1047,8 @@ def drone(self, room: str, one_time=False, not_return=False): self.recog.update() self.recog.save_screencap('run_order') # 200 为识别错误 - if drone_count < 100 or drone_count == 200: - logger.info(f"无人机数量小于92->停止") + if drone_count < self.drone_count_limit or drone_count == 200: + logger.info(f"无人机数量小于{self.drone_count_limit}->停止") break st = accelerate[1] # 起点 ed = accelerate[0] # 终点 @@ -1491,12 +1495,13 @@ def inialize_maa(self): raise Exception("MAA 连接失败") def maa_plan_solver(self): + try: if self.maa_config['last_execution'] is not None and datetime.now() - timedelta( seconds=self.maa_config['maa_execution_gap'] * 3600) < self.maa_config['last_execution']: logger.info("间隔未超过设定时间,不启动maa") else: - self.send_email('休息时长超过9分钟,启动MAA') + self.send_email('启动MAA') self.back_to_index() # 任务及参数请参考 docs/集成文档.md self.inialize_maa() diff --git a/arknights_mower/utils/device/adb_client/core.py b/arknights_mower/utils/device/adb_client/core.py index 05393d481..99d05f37e 100644 --- a/arknights_mower/utils/device/adb_client/core.py +++ b/arknights_mower/utils/device/adb_client/core.py @@ -60,7 +60,7 @@ def __choose_devices(self) -> Optional[str]: for device in config.ADB_DEVICE: if device in devices: return device - if len(devices) > 0: + if len(devices) > 0 and len(config.ADB_DEVICE) <= 0: logger.debug(devices[0]) return devices[0] diff --git a/diy.py b/diy.py index d933b1c51..407cabf63 100644 --- a/diy.py +++ b/diy.py @@ -231,6 +231,7 @@ def inialize(tasks,scheduler=None): base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] base_scheduler.maa_config = maa_config base_scheduler.error = False + base_scheduler.drone_count_limit = 92 # 无人机高于于该值时才使用 base_scheduler.agent_base_config = agent_base_config return base_scheduler else : diff --git a/menu.py b/menu.py index a21c73cf1..cfc7fc4eb 100644 --- a/menu.py +++ b/menu.py @@ -41,7 +41,7 @@ def load_conf(): conf['mail_enable'] = conf['mail_enable'] if 'mail_enable' in conf.keys() else 0 conf['account'] = conf['account'] if 'account' in conf.keys() else '' conf['pass_code'] = conf['pass_code'] if 'pass_code' in conf.keys() else '' - conf['maa_enable'] = conf['maa_enable'] if 'maa_enable' in conf.keys() else '' + conf['maa_enable'] = conf['maa_enable'] if 'maa_enable' in conf.keys() else 0 conf['maa_path'] = conf['maa_path'] if 'maa_path' in conf.keys() else '' conf['maa_adb_path'] = conf['maa_adb_path'] if 'maa_adb_path' in conf.keys() else '' conf['maa_weekly_plan'] = conf['maa_weekly_plan'] if 'maa_weekly_plan' in conf.keys() else [ @@ -53,6 +53,8 @@ def load_conf(): {"weekday": "周六", "stage": ['AP-5'], "medicine": 0}, {"weekday": "周日", "stage": ['AP-5'], "medicine": 0} ] + conf['max_resting_count'] = conf['max_resting_count'] if 'max_resting_count' in conf.keys() else 4 + conf['drone_count_limit'] = conf['drone_count_limit'] if 'drone_count_limit' in conf.keys() else 92 def write_conf(): @@ -182,6 +184,13 @@ def menu(): key='radio_ling_xi_2', enable_events=True) ling_xi_3 = sg.Radio('均衡模式', 'ling_xi', default=conf['ling_xi'] == 3, key='radio_ling_xi_3', enable_events=True) + + max_resting_count_title = sg.Text('最大分组数:', size=25,key='max_resting_count_title') + max_resting_count = sg.InputText(conf['max_resting_count'], size=5, + key='int_max_resting_count', enable_events=True) + drone_count_limit_title = sg.Text('无人机使用阈值:', size=25,key='drone_count_limit_title') + drone_count_limit = sg.InputText(conf['drone_count_limit'], size=5, + key='int_drone_count_limit', enable_events=True) rest_in_full_title = sg.Text('需要回满心情的干员:', size=25) rest_in_full = sg.InputText(conf['rest_in_full'], size=60, key='conf_rest_in_full', enable_events=True) @@ -231,13 +240,15 @@ def menu(): plan_tab = sg.Tab(' 排班表 ', [[left_area, central_area, right_area], [setting_area]], element_justification="center") setting_tab = sg.Tab(' 高级设置 ', - [[run_mode_title,run_mode_1,run_mode_2],[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], [rest_in_full_title, rest_in_full], - [mail_frame], [maa_frame]]) + [[run_mode_title,run_mode_1,run_mode_2],[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], + [max_resting_count_title,max_resting_count,sg.Text('', size=16),drone_count_limit_title,drone_count_limit], + [rest_in_full_title, rest_in_full], + [mail_frame], [maa_frame]],pad= ((10,10),(10,10)) ) window = sg.Window('Mower', [[sg.TabGroup([[main_tab, plan_tab, setting_tab]], border_width=0, tab_border_width=0, focus_color='#bcc8e5', selected_background_color='#d4dae8', background_color='#aab6d3', tab_background_color='#aab6d3')]], font='微软雅黑', finalize=True, - resizable=True) + resizable=False) load_plan(conf['planFile']) btn = None @@ -249,6 +260,12 @@ def menu(): elif event.startswith('conf_'): key = event[5:] conf[key] = window[event].get() + elif event.startswith('int_'): + key = event[4:] + try: + conf[key] = int(window[event].get()) + except ValueError: + println(f'[{window[key+"_title"].get()}]需为数字') elif event.startswith('radio_'): v_index = event.rindex('_') conf[event[6:v_index]] = int(event[v_index + 1:]) From d0d533b18e74bd7825cbced241d1c49ae4c5298a Mon Sep 17 00:00:00 2001 From: Snowdash <62183802+Snow-dash@users.noreply.github.com> Date: Sat, 8 Apr 2023 00:42:09 +0800 Subject: [PATCH 28/39] Update diy.py (#162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加部分注释,格式化部分内容 --- diy.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/diy.py b/diy.py index 407cabf63..02c124428 100644 --- a/diy.py +++ b/diy.py @@ -9,10 +9,15 @@ email_config= { + # 发信账户 'account':"xxx@qq.com", - 'pass_code':'从QQ邮箱帐户设置—>生成授权码', + # 在QQ邮箱“帐户设置-账户-开启SMTP服务”中,按照指示开启服务获得授权码 + 'pass_code':'xxx', + # 收件人邮箱 'receipts':['任何邮箱'], + # 是否提醒,暂时没用 'notify':False, + # 邮件主题 'subject': '任务数据' } maa_config = { @@ -137,6 +142,12 @@ } } +# UpperLimit、LowerLimit:心情上下限 +# ExhaustRequire:是否强制工作到红脸再休息 +# ArrangeOrder:指定在宿舍外寻找干员的方式 +# RestInFull:是否强制休息到24心情再工作,与ExhaustRequire一起帮助暖机类技能工作更长时间 +# RestingPriority:休息优先级,低优先级不会使用单回技能。 + agent_base_config = { "Default":{"UpperLimit": 24,"LowerLimit": 0,"ExhaustRequire": False,"ArrangeOrder":[2,"false"],"RestInFull": False}, # 卡贸易站 @@ -208,35 +219,37 @@ def savelog(): # com.hypergryph.arknights.bilibili # Bilibili 服 init_fhlr() -def inialize(tasks,scheduler=None): + +def inialize(tasks, scheduler=None): device = Device() cli = Solver(device) if scheduler is None: base_scheduler = BaseSchedulerSolver(cli.device, cli.recog) - base_scheduler.package_name= config.APPNAME + base_scheduler.package_name = config.APPNAME base_scheduler.operators = {} base_scheduler.global_plan = plan base_scheduler.current_base = {} - base_scheduler.resting=[] - base_scheduler.max_resting_count=4 + base_scheduler.resting = [] + # 同时休息最大人数 + base_scheduler.max_resting_count = 4 base_scheduler.tasks = tasks # 读取心情开关,有菲亚梅塔或者希望全自动换班得设置为 true base_scheduler.read_mood = True base_scheduler.scan_time = {} base_scheduler.last_room = '' base_scheduler.free_blacklist = free_blacklist - base_scheduler.resting_treshhold=resting_treshhold + base_scheduler.resting_treshhold = resting_treshhold base_scheduler.MAA = None base_scheduler.email_config = email_config base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] base_scheduler.maa_config = maa_config base_scheduler.error = False - base_scheduler.drone_count_limit = 92 # 无人机高于于该值时才使用 + base_scheduler.drone_count_limit = 92 # 无人机高于于该值时才使用 base_scheduler.agent_base_config = agent_base_config return base_scheduler - else : - scheduler.device=cli.device - scheduler.recog=cli.recog + else: + scheduler.device = cli.device + scheduler.recog = cli.recog scheduler.handle_error(True) return scheduler From c19484614ab05adc8e945e58827e5111e7f06809 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Fri, 7 Apr 2023 10:42:11 -0700 Subject: [PATCH 29/39] #163,#164 --- arknights_mower/data/ocr.json | 3 +- arknights_mower/solvers/base_schedule.py | 53 ++++++++++++++++-------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/arknights_mower/data/ocr.json b/arknights_mower/data/ocr.json index df08edb30..7d68cb6ca 100644 --- a/arknights_mower/data/ocr.json +++ b/arknights_mower/data/ocr.json @@ -27,5 +27,6 @@ "CastIe3": "Castle-3", "Castle3": "Castle-3", "Lancet2": "Lancet-2", - "THRMEX": "THRM-EX" + "THRMEX": "THRM-EX", + "U-Officia": "U-Official" } \ No newline at end of file diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index bb6a1c768..58e6468b8 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -222,6 +222,11 @@ def plan_metadata(self): _type = [] # 第一个心情低的且小于3 则只休息半小时 short_rest = False + self.total_agent = list( + v for k, v in self.op_data.operators.items() if v.is_high() and not v.room.startswith('dorm') and not v.current_room.startswith('dorm')) + self.total_agent.sort(key=lambda x: x.mood - x.lower_limit, reverse=False) + if next((a for a in self.total_agent if (a.name not in self.op_data.exhaust_agent) and a.mood<=3),None) is not None: + short_rest= True low_priority = [] for idx, dorm in enumerate(self.op_data.dorm): # Filter out resting priority low @@ -242,7 +247,10 @@ def plan_metadata(self): __rest_agent.append(dorm.name) else: __rest_agent.extend(self.op_data.groups[self.op_data.operators[dorm.name].group]) - __time = dorm.time + if dorm.time is not None: + __time = dorm.time + else: + __time = datetime.max for x in __rest_agent: # 如果同小组也是rest_in_full则取最大休息时间 否则忽略 _idx, __dorm = self.op_data.get_dorm_by_name(x) @@ -250,7 +258,8 @@ def plan_metadata(self): if __dorm is not None and __dorm.time is not None: if __dorm.time > _time and self.op_data.operators[x].resting_priority == 'high': _time = __dorm.time - __type.append('dorm' + str(_idx)) + if _idx is not None: + __type.append('dorm' + str(_idx)) planned_index.append(_idx) __room = self.op_data.operators[x].room if __room not in __plan.keys(): @@ -347,6 +356,7 @@ def agent_get_mood(self, skip_dorm=False, force=False): if not force and next((k for k in self.tasks if k['time'] < datetime.now() + timedelta(seconds=300)), None) is not None: logger.info('有未完成的任务,跳过纠错') + self.skip() return logger.info('基建:记录心情') need_read = set(v.room for k, v in self.op_data.operators.items() if v.need_to_refresh()) @@ -409,15 +419,23 @@ def agent_get_mood(self, skip_dorm=False, force=False): # 替换到他应该的位置 logger.debug(f"高效组心情没有记录 或者高效组在宿舍{str(miss_list)}") for key in miss_list: - if miss_list[key].group != '' and miss_list[key].current_room.startswith("dorm"): + _agent = miss_list[key] + if _agent.group != '' and _agent.current_room.startswith("dorm"): # 如果还有其他小组成员在休息且没满心情则忽略 if next((k for k, v in self.op_data.operators.items() if - v.group == miss_list[key].group and not v.not_valid() and v.current_room.startswith( + v.group == _agent.group and not v.not_valid() and v.current_room.startswith( "dorm")), None) is not None: continue - if miss_list[key].room not in fix_plan.keys(): - fix_plan[miss_list[key].room] = [x['agent'] for x in current_base[miss_list[key].room]] - fix_plan[miss_list[key].room][miss_list[key].index] = key + if _agent.room not in fix_plan.keys(): + fix_plan[_agent.room] = ['Current'] * len(current_base[_agent.room]) + fix_plan[_agent.room][_agent.index] = key + # 如果是错位: + if (_agent.current_index != -1 and _agent.current_index != _agent.index) or (_agent.current_room !=""and _agent.room != _agent.current_room): + moved_room = _agent.current_room + moved_index = _agent.current_index + if moved_room not in fix_plan.keys(): + fix_plan[moved_room] = ['Current'] * len(self.currentPlan[moved_room]) + fix_plan[moved_room][moved_index] = self.currentPlan[moved_room][moved_index]["agent"] if len(fix_plan.keys()) > 0: # 不能在房间里安排同一个人 如果有重复则换成Free # 还要修复确保同一组在同时上班 @@ -431,12 +449,6 @@ def agent_get_mood(self, skip_dorm=False, force=False): None) is None and skip_dorm: remove_keys.append(key) continue - for idx, fix_agent in enumerate(fix_plan[key]): - if fix_agent not in fix_agents: - fix_agents.append(fix_agent) - else: - fix_plan[key][idx] = "Free" if fix_plan[key][idx] not in ['Free', 'Current'] else \ - fix_plan[key][idx] if len(remove_keys) > 0: for item in remove_keys: del fix_plan[item] @@ -514,14 +526,18 @@ def plan_solver(self): None) is None: self.enter_room(op.current_room) result = self.get_agent_from_room(op.current_room, [op.current_index]) + _time = datetime.now() + if result[op.current_index]['time'] is not None: + _time = result[op.current_index]['time'] self.back() # plan 是空的是因为得动态生成 exhaust_type = op.name if op.group != '': exhaust_type = ','.join(self.op_data.groups[op.group]) - self.tasks.append({"time": result[op.current_index]['time'] if result[op.current_index][ - 'time'] is not None else datetime.now(), - "plan": {}, "type": exhaust_type}) + if _time0: + continue + elif _time >= datetime.now() or (_time None: self.tap(clue_unlock) self.party_time = datetime.now() + timedelta(days=1) logger.info("为期一天的趴体开始") - else: + elif clue_unlock is None: # 记录趴体时间 self.back(interval=2) self.party_time = self.double_read_time((1765, 422, 1920, 515)) logger.info(f"趴体结束时间为: {self.party_time}") - + else: + self.back(interval=2) logger.info('返回基建主界面') self.back(interval=2) From 8108b003180fb16313c10042c2159ff01e7d3b9e Mon Sep 17 00:00:00 2001 From: Snowdash Date: Sun, 9 Apr 2023 12:13:17 +0800 Subject: [PATCH 30/39] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 怎么会有人不知道自己写的啥键名( --- diy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diy.py b/diy.py index 02c124428..455410a45 100644 --- a/diy.py +++ b/diy.py @@ -15,8 +15,8 @@ 'pass_code':'xxx', # 收件人邮箱 'receipts':['任何邮箱'], - # 是否提醒,暂时没用 - 'notify':False, + # 是否启用邮件提醒 + 'mail_enable':False, # 邮件主题 'subject': '任务数据' } From 3c271be317c97f7a36dd3808886822508f4c48c4 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Sat, 8 Apr 2023 21:40:18 -0700 Subject: [PATCH 31/39] #166 --- arknights_mower/solvers/base_schedule.py | 34 +++++++++++++++++++++--- arknights_mower/utils/operators.py | 2 ++ diy.py | 8 ++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 58e6468b8..bf4f5c395 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -53,6 +53,7 @@ def __init__(self, device: Device = None, recog: Recognizer = None) -> None: self.op_data = None self.max_resting_count = 4 self.party_time = None + self.drone_time = None def run(self) -> None: """ @@ -339,6 +340,11 @@ def infra_main(self): self.planned = True elif not self.todo_task: notification = detector.infra_notification(self.recog.img) + if self.drone_room is not None: + if self.drone_time is None or self.drone_time < datetime.now()- timedelta(hours=self.drone_execution_gap): + self.drone(self.drone_room) + logger.info(f"记录本次无人机使用时间为:{datetime.now()}") + self.drone_time = datetime.now() if self.party_time is None: self.clue() if notification is None: @@ -538,6 +544,9 @@ def plan_solver(self): continue elif _time >= datetime.now() or (_time tp.Rectangle: while self.find('control_central') is not None: self.tap(_room[0], interval=3) - def drone(self, room: str, one_time=False, not_return=False): + def drone(self, room: str, not_customize=False, not_return=False): logger.info('基建:无人机加速') - + all_in = 0 + if not not_customize: + all_in = len(self.check_in_and_out()) # 点击进入该房间 self.enter_room(room) # 进入房间详情 @@ -1042,11 +1053,23 @@ def drone(self, room: str, one_time=False, not_return=False): accelerate = self.find('factory_accelerate') if accelerate: + drone_count = self.read_screen(self.recog.img, type='drone_mood', cord=( + int(self.recog.w * 1150 / 1920), int(self.recog.h * 35 / 1080), int(self.recog.w * 1295 / 1920), + int(self.recog.h * 72 / 1080)), limit=200) + if drone_count< self.drone_count_limit or drone_count == 200: + logger.info(f"无人机数量不够 {drone_count}") + return logger.info('制造站加速') self.tap(accelerate) self.tap_element('all_in') + # 如果不是全部all in + if all_in>0: + tap_times = all_in * 10 + _count = 0 + while _count Date: Mon, 10 Apr 2023 23:21:37 -0700 Subject: [PATCH 32/39] =?UTF-8?q?fix=EF=BC=9A=E5=8D=95=E4=BA=BA=E7=BB=84?= =?UTF-8?q?=E4=B8=8A=E7=8F=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index bf4f5c395..0ef68bc02 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -423,17 +423,19 @@ def agent_get_mood(self, skip_dorm=False, force=False): miss_list = {k: v for (k, v) in self.op_data.operators.items() if v.not_valid()} if len(miss_list.keys()) > 0: # 替换到他应该的位置 - logger.debug(f"高效组心情没有记录 或者高效组在宿舍{str(miss_list)}") + logger.debug(f"高效组心情没有记录{str(miss_list)}") for key in miss_list: _agent = miss_list[key] - if _agent.group != '' and _agent.current_room.startswith("dorm"): - # 如果还有其他小组成员在休息且没满心情则忽略 - if next((k for k, v in self.op_data.operators.items() if - v.group == _agent.group and not v.not_valid() and v.current_room.startswith( - "dorm")), None) is not None: - continue + if _agent.group != '': + # 把所有小组成员都移到工作站 + agents = self.op_data.groups[_agent.group] + for a in agents: + __agent = self.op_data.operators[a] + if __agent.room not in fix_plan.keys(): + fix_plan[__agent.room] = ['Current'] * len(self.currentPlan[__agent.room]) + fix_plan[__agent.room][__agent.index] = a if _agent.room not in fix_plan.keys(): - fix_plan[_agent.room] = ['Current'] * len(current_base[_agent.room]) + fix_plan[_agent.room] = ['Current'] * len(self.currentPlan[_agent.room]) fix_plan[_agent.room][_agent.index] = key # 如果是错位: if (_agent.current_index != -1 and _agent.current_index != _agent.index) or (_agent.current_room !=""and _agent.room != _agent.current_room): From 8c90249932b49e50bb4ec8e050e33e2471e49e6c Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Tue, 11 Apr 2023 22:17:01 -0700 Subject: [PATCH 33/39] =?UTF-8?q?revert:=20=E5=A6=82=E6=9E=9C=E6=9C=89?= =?UTF-8?q?=E5=BF=83=E6=83=85=E6=9C=AA=E6=BB=A1=E5=88=99=E8=B7=B3=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 0ef68bc02..a338a6a56 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -426,7 +426,13 @@ def agent_get_mood(self, skip_dorm=False, force=False): logger.debug(f"高效组心情没有记录{str(miss_list)}") for key in miss_list: _agent = miss_list[key] - if _agent.group != '': + if _agent.group != '' and _agent.current_room.startswith("dorm"): + # 如果还有其他小组成员在休息且没满心情则忽略 + if next((k for k, v in self.op_data.operators.items() if + v.group == _agent.group and not v.not_valid() and v.current_room.startswith( + "dorm")), None) is not None: + continue + elif _agent.group != '': # 把所有小组成员都移到工作站 agents = self.op_data.groups[_agent.group] for a in agents: From 8bbd309faed4f0f88fd6db53bce6c95221ec9252 Mon Sep 17 00:00:00 2001 From: Ksnow <1048879349@qq.com> Date: Fri, 14 Apr 2023 01:06:29 +0800 Subject: [PATCH 34/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8E=92=E7=8F=AD?= =?UTF-8?q?=E8=A1=A8=E5=85=83=E7=B4=A0=E6=8B=96=E6=8B=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/__main__.py | 6 + arknights_mower/solvers/base_schedule.py | 28 ++-- .../utils/device/adb_client/core.py | 22 ++- diy.py | 1 + menu.py | 127 +++++++++++++++--- 5 files changed, 145 insertions(+), 39 deletions(-) diff --git a/arknights_mower/__main__.py b/arknights_mower/__main__.py index fd9dded4a..7fc5d3b4c 100644 --- a/arknights_mower/__main__.py +++ b/arknights_mower/__main__.py @@ -87,6 +87,9 @@ def inialize(tasks, scheduler=None): base_scheduler.maa_config = maa_config base_scheduler.ADB_CONNECT = config.ADB_CONNECT[0] base_scheduler.error = False + base_scheduler.drone_room = None if conf['drone_room'] == '' else conf['drone_room'] + base_scheduler.drone_execution_gap = 4 + base_scheduler.run_order_delay = conf['run_order_delay'] base_scheduler.agent_base_config = agent_base_config return base_scheduler else: @@ -147,8 +150,11 @@ def simulate(): except Exception as E: logger.exception(f"程序出错--->{E}") + agent_base_config = {} maa_config = {} + + def __init_params__(): global agent_base_config global maa_config diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index bf4f5c395..5cc18e036 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -204,7 +204,7 @@ def handle_error(self, force=False): # 如果没有任何时间小于当前时间的任务才生成空任务 if (next((e for e in self.tasks if e['time'] < datetime.now()), None)) is None: room = next(iter(self.currentPlan.keys())) - logger.info("由于出现错误情况,生成一次空任务来执行纠错") + logger.debug("由于出现错误情况,生成一次空任务来执行纠错") self.tasks.append({'time': datetime.now(), 'plan': {}}) # 如果没有任何时间小于当前时间的任务-10分钟 则清空任务 if (next((e for e in self.tasks if e['time'] < datetime.now() - timedelta(seconds=600)), None)) is not None: @@ -679,7 +679,7 @@ def get_run_roder_time(self, room): error_count += 1 execute_time = self.double_read_time((int(self.recog.w * 650 / 2496), int(self.recog.h * 660 / 1404), int(self.recog.w * 815 / 2496), int(self.recog.h * 710 / 1404))) - execute_time = execute_time - timedelta(seconds=(600)) + execute_time = execute_time - timedelta(seconds=(60*self.run_order_delay)) logger.info('下一次进行插拔的时间为:' + execute_time.strftime("%H:%M:%S")) logger.info('返回基建主界面') self.back(interval=2, rebuild=False) @@ -1019,9 +1019,9 @@ def enter_room(self, room: str) -> tp.Rectangle: # 获取基建各个房间的位置 base_room = segment.base(self.recog.img, self.find('control_central', strict=True)) - # 将画面外的部分删去 _room = base_room[room] + for i in range(4): _room[i, 0] = max(_room[i, 0], 0) _room[i, 0] = min(_room[i, 0], self.recog.w) @@ -1056,19 +1056,22 @@ def drone(self, room: str, not_customize=False, not_return=False): drone_count = self.read_screen(self.recog.img, type='drone_mood', cord=( int(self.recog.w * 1150 / 1920), int(self.recog.h * 35 / 1080), int(self.recog.w * 1295 / 1920), int(self.recog.h * 72 / 1080)), limit=200) - if drone_count< self.drone_count_limit or drone_count == 200: - logger.info(f"无人机数量不够 {drone_count}") + logger.info(f'当前无人机数量为:{drone_count}') + if drone_count< self.drone_count_limit : + logger.info(f"无人机数量小于{self.drone_count_limit}->停止") return logger.info('制造站加速') self.tap(accelerate) - self.tap_element('all_in') + # self.tap_element('all_in') # 如果不是全部all in - if all_in>0: - tap_times = all_in * 10 + if all_in > 0: + tap_times = drone_count - self.drone_count_limit # 修改为无人机阈值 _count = 0 - while _count停止") break st = accelerate[1] # 起点 @@ -1531,7 +1534,8 @@ def inialize_maa(self): self.MAA = Asst(callback=self.log_maa) # self.MAA.set_instance_option(2, 'maatouch') # 请自行配置 adb 环境变量,或修改为 adb 可执行程序的路径 - if self.MAA.connect(self.maa_config['maa_adb_path'], self.ADB_CONNECT): + # logger.info(self.device.client.device_id) + if self.MAA.connect(self.maa_config['maa_adb_path'], self.device.client.device_id): logger.info("MAA 连接成功") else: logger.info("MAA 连接失败") diff --git a/arknights_mower/utils/device/adb_client/core.py b/arknights_mower/utils/device/adb_client/core.py index 99d05f37e..b41af2b22 100644 --- a/arknights_mower/utils/device/adb_client/core.py +++ b/arknights_mower/utils/device/adb_client/core.py @@ -43,13 +43,25 @@ def __init_device(self) -> None: time.sleep(1) if self.device_id is None or self.device_id not in config.ADB_DEVICE: self.device_id = self.__choose_devices() - if self.device_id is None or self.device_id not in config.ADB_DEVICE: - if self.connect is None or self.device_id not in config.ADB_CONNECT: - for connect in config.ADB_CONNECT: - Session().connect(connect) + if self.device_id is None : + if self.connect is None: + if config.ADB_DEVICE[0] != '': + for connect in config.ADB_CONNECT: + Session().connect(connect) else: Session().connect(self.connect) self.device_id = self.__choose_devices() + elif self.connect is None: + Session().connect(self.device_id) + + # if self.device_id is None or self.device_id not in config.ADB_DEVICE: + # if self.connect is None or self.device_id not in config.ADB_CONNECT: + # for connect in config.ADB_CONNECT: + # Session().connect(connect) + # else: + # Session().connect(self.connect) + # self.device_id = self.__choose_devices() + logger.info(self.__available_devices()) if self.device_id not in self.__available_devices(): logger.error('未检测到相应设备。请运行 `adb devices` 确认列表中列出了目标模拟器或设备。') raise RuntimeError('Device connection failure') @@ -60,7 +72,7 @@ def __choose_devices(self) -> Optional[str]: for device in config.ADB_DEVICE: if device in devices: return device - if len(devices) > 0 and len(config.ADB_DEVICE) <= 0: + if len(devices) > 0 and config.ADB_DEVICE[0] == '': logger.debug(devices[0]) return devices[0] diff --git a/diy.py b/diy.py index 090b6fd6e..1892b2b4c 100644 --- a/diy.py +++ b/diy.py @@ -254,6 +254,7 @@ def inialize(tasks, scheduler=None): base_scheduler.drone_room = drone_room base_scheduler.drone_execution_gap = drone_execution_gap base_scheduler.agent_base_config = agent_base_config + base_scheduler.run_order_delay = 10 # 跑单提前10分钟运行 return base_scheduler else: scheduler.device = cli.device diff --git a/menu.py b/menu.py index cfc7fc4eb..6b01a20de 100644 --- a/menu.py +++ b/menu.py @@ -53,8 +53,10 @@ def load_conf(): {"weekday": "周六", "stage": ['AP-5'], "medicine": 0}, {"weekday": "周日", "stage": ['AP-5'], "medicine": 0} ] - conf['max_resting_count'] = conf['max_resting_count'] if 'max_resting_count' in conf.keys() else 4 - conf['drone_count_limit'] = conf['drone_count_limit'] if 'drone_count_limit' in conf.keys() else 92 + conf['max_resting_count'] = conf['max_resting_count'] if 'max_resting_count' in conf.keys() else 4 # 最大组人数 + conf['drone_count_limit'] = conf['drone_count_limit'] if 'drone_count_limit' in conf.keys() else 92 # 无人机阈值 + conf['run_order_delay'] = conf['run_order_delay'] if 'run_order_delay' in conf.keys() else 10 # 跑单提前10分钟运行 + conf['drone_room'] = conf['drone_room'] if 'drone_room' in conf.keys() else '' # 无人机使用房间 def write_conf(): @@ -166,7 +168,7 @@ def menu(): sg.InputText('', size=30, key='replacement' + str(i)) ]], key='setArea' + str(i), visible=False) setting_layout.append([set_area]) - setting_layout.append([sg.Button('保存', key='savePlan', visible=False)]) + setting_layout.append([sg.Button('保存', key='savePlan', visible=False),sg.Button('清空', key='clearPlan', visible=False)]) setting_area = sg.Column(setting_layout, element_justification="center", vertical_alignment="bottom", expand_x=True) @@ -174,9 +176,9 @@ def menu(): # --------高级设置页面 run_mode_title = sg.Text('运行模式:', size=25) run_mode_1 = sg.Radio('换班模式', 'run_mode', default=conf['run_mode'] == 1, - key='radio_run_mode_1', enable_events=True) + key='radio_run_mode_1', enable_events=True) run_mode_2 = sg.Radio('仅跑单模式', 'run_mode', default=conf['run_mode'] == 2, - key='radio_run_mode_2', enable_events=True) + key='radio_run_mode_2', enable_events=True) ling_xi_title = sg.Text('令夕模式(令夕上班时起作用):', size=25) ling_xi_1 = sg.Radio('感知信息', 'ling_xi', default=conf['ling_xi'] == 1, key='radio_ling_xi_1', enable_events=True) @@ -185,15 +187,23 @@ def menu(): ling_xi_3 = sg.Radio('均衡模式', 'ling_xi', default=conf['ling_xi'] == 3, key='radio_ling_xi_3', enable_events=True) - max_resting_count_title = sg.Text('最大分组数:', size=25,key='max_resting_count_title') + max_resting_count_title = sg.Text('最大组人数:', size=25, key='max_resting_count_title') max_resting_count = sg.InputText(conf['max_resting_count'], size=5, - key='int_max_resting_count', enable_events=True) - drone_count_limit_title = sg.Text('无人机使用阈值:', size=25,key='drone_count_limit_title') + key='int_max_resting_count', enable_events=True) + drone_count_limit_title = sg.Text('无人机使用阈值:', size=25, key='drone_count_limit_title') drone_count_limit = sg.InputText(conf['drone_count_limit'], size=5, - key='int_drone_count_limit', enable_events=True) + key='int_drone_count_limit', enable_events=True) + run_order_delay_title = sg.Text('跑单前置延时(分钟):', size=25, key='run_order_delay_title') + run_order_delay = sg.InputText(conf['run_order_delay'], size=5, + key='int_run_order_delay', enable_events=True) + drone_room_title = sg.Text('无人机使用房间(room_X_X):', size=25, key='drone_room_title') + drone_room = sg.InputText(conf['drone_room'], size=15, + key='conf_drone_room', enable_events=True) rest_in_full_title = sg.Text('需要回满心情的干员:', size=25) rest_in_full = sg.InputText(conf['rest_in_full'], size=60, key='conf_rest_in_full', enable_events=True) + + # --------外部调用设置页面 # mail mail_enable_1 = sg.Radio('启用', 'mail_enable', default=conf['mail_enable'] == 1, key='radio_mail_enable_1', enable_events=True) @@ -238,13 +248,16 @@ def menu(): [on_btn, off_btn]]) plan_tab = sg.Tab(' 排班表 ', [[left_area, central_area, right_area], [setting_area]], element_justification="center") - setting_tab = sg.Tab(' 高级设置 ', - [[run_mode_title,run_mode_1,run_mode_2],[ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], - [max_resting_count_title,max_resting_count,sg.Text('', size=16),drone_count_limit_title,drone_count_limit], - [rest_in_full_title, rest_in_full], - [mail_frame], [maa_frame]],pad= ((10,10),(10,10)) ) - window = sg.Window('Mower', [[sg.TabGroup([[main_tab, plan_tab, setting_tab]], border_width=0, + [[run_mode_title, run_mode_1, run_mode_2], [ling_xi_title, ling_xi_1, ling_xi_2, ling_xi_3], + [max_resting_count_title, max_resting_count, sg.Text('', size=16), run_order_delay_title, + run_order_delay], + [drone_room_title, drone_room, sg.Text('', size=7), drone_count_limit_title, + drone_count_limit], + [rest_in_full_title, rest_in_full]], pad=((10, 10), (10, 10))) + other_tab = sg.Tab(' 外部调用 ', + [[mail_frame], [maa_frame]], pad=((10, 10), (10, 10))) + window = sg.Window('Mower', [[sg.TabGroup([[main_tab, plan_tab, setting_tab, other_tab]], border_width=0, tab_border_width=0, focus_color='#bcc8e5', selected_background_color='#d4dae8', background_color='#aab6d3', tab_background_color='#aab6d3')]], font='微软雅黑', finalize=True, @@ -252,12 +265,17 @@ def menu(): load_plan(conf['planFile']) btn = None + bind_scirpt() # 为基建布局左边的站点排序绑定事件 + drag_task = DragTask() while True: event, value = window.Read() - if event == sg.WIN_CLOSED: break - elif event.startswith('conf_'): + if event.endswith('-script'): # 触发事件,进行处理 + run_script(event[:event.rindex('-')], drag_task) + continue + drag_task.clear() # 拖拽事件连续不间断,若未触发事件,则初始化 + if event.startswith('conf_'): key = event[5:] conf[key] = window[event].get() elif event.startswith('int_'): @@ -265,7 +283,7 @@ def menu(): try: conf[key] = int(window[event].get()) except ValueError: - println(f'[{window[key+"_title"].get()}]需为数字') + println(f'[{window[key + "_title"].get()}]需为数字') elif event.startswith('radio_'): v_index = event.rindex('_') conf[event[6:v_index]] = int(event[v_index + 1:]) @@ -284,11 +302,12 @@ def menu(): init_btn(event) elif event == 'savePlan': # 保存设施信息 save_btn(btn) - + elif event == 'clearPlan': # 清空当前设施信息 + clear_btn(btn) elif event == 'on': - if adb.get() == '': - println('adb未设置!') - continue + # if adb.get() == '': + # println('adb未设置!') + # continue on_btn.update(visible=False) off_btn.update(visible=True) @@ -309,6 +328,46 @@ def menu(): write_plan() +def bind_scirpt(): + for i in range(3): + for j in range(3): + event = f'btn_room_{str(i + 1)}_{str(j + 1)}' + window[event].bind("", "-motion-script") + window[event].bind("", "-ButtonRelease-script") + window[event].bind("", "-Enter-script") + + +def run_script(event, drag_task): + # logger.info(f"{event}:{drag_task}") + if event.endswith('-motion'): # 拖拽事件,标志拖拽开始 + if drag_task.step == 0 or drag_task.step == 2: # 若为2说明拖拽结束未进入其他元素,则初始化 + drag_task.btn = event[:event.rindex('-')] # 记录初始按钮 + drag_task.step = 1 # 初始化键位,并推进任务步骤 + elif event.endswith('-ButtonRelease'): # 松开按钮事件,标志着拖拽结束 + if drag_task.step == 1 : + drag_task.step = 2 # 推进任务步骤 + elif event.endswith('-Enter'): # 进入元素事件,拖拽结束鼠标若在其他元素,会进入此事件 + if drag_task.step == 2: + drag_task.new_btn = event[:event.rindex('-')] # 记录需交换的按钮 + swtich_plan(drag_task) + drag_task.clear() + else: + drag_task.clear() + + +def swtich_plan(drag_task): + key1 = drag_task.btn[4:] + key2 = drag_task.new_btn[4:] + value1 = plan[key1] if key1 in plan else None; + value2 = plan[key2] if key2 in plan else None; + if value1 is not None: + plan[key2] = value1 + if value2 is not None: + plan[key1] = value2 + write_plan() + load_plan(conf['planFile']) + + def init_btn(event): room_key = event[4:] station_name = plan[room_key]['name'] if room_key in plan.keys() else '' @@ -327,6 +386,7 @@ def init_btn(event): window['station_type_col'].update(visible=False) window['station_type'].update('') window['savePlan'].update(visible=True) + window['clearPlan'].update(visible=True) for i in range(1, 6): if i > visible_cnt: window['setArea' + str(i)].update(visible=False) @@ -355,6 +415,14 @@ def save_btn(btn): load_plan(conf['planFile']) +def clear_btn(btn): + if btn[4:] in plan: + plan.pop(btn[4:]) + init_btn(btn) + write_plan() + load_plan(conf['planFile']) + + # 输出日志 def log(pipe): try: @@ -390,6 +458,21 @@ def clear(): line = 0 +class DragTask: + def __init__(self): + self.btn = None + self.new_btn = None + self.step = 0 + + def clear(self): + self.btn = None + self.new_btn = None + self.step = 0 + + def __repr__(self): + return f"btn:{self.btn},new_btn:{self.new_btn},step:{self.step}" + + if __name__ == '__main__': freeze_support() menu() From f26a9d2d8855fde06a020472126f79a2f7afa938 Mon Sep 17 00:00:00 2001 From: Ksnow <1048879349@qq.com> Date: Fri, 14 Apr 2023 01:16:22 +0800 Subject: [PATCH 35/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8E=92=E7=8F=AD?= =?UTF-8?q?=E8=A1=A8=E5=85=83=E7=B4=A0=E6=8B=96=E6=8B=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 5cc18e036..4d3f522cc 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -1055,7 +1055,7 @@ def drone(self, room: str, not_customize=False, not_return=False): if accelerate: drone_count = self.read_screen(self.recog.img, type='drone_mood', cord=( int(self.recog.w * 1150 / 1920), int(self.recog.h * 35 / 1080), int(self.recog.w * 1295 / 1920), - int(self.recog.h * 72 / 1080)), limit=200) + int(self.recog.h * 72 / 1080)), limit=201) logger.info(f'当前无人机数量为:{drone_count}') if drone_count< self.drone_count_limit : logger.info(f"无人机数量小于{self.drone_count_limit}->停止") From d103d765645369a96205280e97ec98d8819c3750 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Thu, 13 Apr 2023 17:52:30 -0700 Subject: [PATCH 36/39] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 6 ++++-- arknights_mower/utils/operators.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 28770fced..1345155bb 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -1374,13 +1374,15 @@ def get_agent_from_room(self, room, read_time_index=None): _mood = 24 # 如果房间不为空 if _name != '': - if _name not in self.op_data.operators.keys(): + if _name not in self.op_data.operators.keys() and _name in agent_list: self.op_data.add(Operator(_name, "")) + update_time=False if self.op_data.operators[_name].need_to_refresh(): _mood = self.read_screen(self.recog.img, cord=mood_p[i], change_color=True) + update_time = True else: _mood = self.op_data.operators[_name].mood - high_no_time = self.op_data.update_detail(_name, _mood, room, i) + high_no_time = self.op_data.update_detail(_name, _mood, room, i,update_time) if high_no_time is not None: logger.debug(f"检测到高效组休息时间数据不存在:{room},{high_no_time}") read_time_index.append(high_no_time) diff --git a/arknights_mower/utils/operators.py b/arknights_mower/utils/operators.py index 96741e621..0be832d24 100644 --- a/arknights_mower/utils/operators.py +++ b/arknights_mower/utils/operators.py @@ -20,10 +20,10 @@ def __init__(self, config,max_resting_count): self.dorm = [] self.max_resting_count = max_resting_count - def update_detail(self,name, mood, current_room, current_index): + def update_detail(self,name, mood, current_room, current_index,update_time = False): agent = self.operators[name] - - agent.time_stamp = datetime.now() + if update_time: + agent.time_stamp = datetime.now() # 如果移出宿舍,则清除对应宿舍数据 且重新记录高效组心情 if agent.current_room.startswith('dorm') and not current_room.startswith('dorm') and agent.is_high(): self.refresh_dorm_time(agent.current_room,agent.current_index,{'agent':''}) From c9c7ebc6e279e8529b947b2e04e11525478a4c72 Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Sat, 15 Apr 2023 01:38:29 -0700 Subject: [PATCH 37/39] =?UTF-8?q?fix:=20U-Offical=20=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 1 + arknights_mower/utils/character_recognize.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index 1345155bb..d14bd55f7 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -54,6 +54,7 @@ def __init__(self, device: Device = None, recog: Recognizer = None) -> None: self.max_resting_count = 4 self.party_time = None self.drone_time = None + self.run_order_delay=10 def run(self) -> None: """ diff --git a/arknights_mower/utils/character_recognize.py b/arknights_mower/utils/character_recognize.py index af7d48f32..b84d700e9 100644 --- a/arknights_mower/utils/character_recognize.py +++ b/arknights_mower/utils/character_recognize.py @@ -9,7 +9,7 @@ from PIL import Image, ImageDraw, ImageFont from .. import __rootdir__ -from ..data import agent_list +from ..data import agent_list,ocr_error from . import segment from .image import saveimg from .log import logger @@ -172,7 +172,7 @@ def agent(img, draw=False): f'干员名称识别异常:{x[1]} 为不存在的数据,请报告至 https://github.com/Konano/arknights-mower/issues' ) saveimg(__img, 'failure_agent') - raise Exception("启动 Plan B") + raise Exception(x[1]) else: if 80 <= np.min(__img): continue @@ -189,9 +189,13 @@ def agent(img, draw=False): except Exception as e: # 大哥不行了,二哥上! ret_fail.append(poly) - if name in ocr_error.keys(): - name = ocr_error[name] - else: + if "Plan B" not in e: + if e in ocr_error.keys(): + name = ocr_error[e] + elif "Off" in e: + name = 'U-Official' + else: + continue ret_agent.append(name) ret_succ.append(poly) continue From b960fdb475f65da2d4b3ffc644799ebd6c698aee Mon Sep 17 00:00:00 2001 From: Shawnsdaddy Date: Sat, 15 Apr 2023 02:00:58 -0700 Subject: [PATCH 38/39] =?UTF-8?q?fix:=20U-Offical=20=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/utils/character_recognize.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/arknights_mower/utils/character_recognize.py b/arknights_mower/utils/character_recognize.py index b84d700e9..dd2adab26 100644 --- a/arknights_mower/utils/character_recognize.py +++ b/arknights_mower/utils/character_recognize.py @@ -188,11 +188,12 @@ def agent(img, draw=False): raise Exception("启动 Plan B") except Exception as e: # 大哥不行了,二哥上! + _msg = str(e) ret_fail.append(poly) - if "Plan B" not in e: - if e in ocr_error.keys(): - name = ocr_error[e] - elif "Off" in e: + if "Plan B" not in _msg: + if _msg in ocr_error.keys(): + name = ocr_error[_msg] + elif "Off" in _msg: name = 'U-Official' else: continue From 66fc96fa9212f0c28e828743c6774a1fee858ec9 Mon Sep 17 00:00:00 2001 From: Ksnow <1048879349@qq.com> Date: Sun, 16 Apr 2023 00:03:10 +0800 Subject: [PATCH 39/39] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E6=8B=96=E6=8B=BD?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=9C=A8=E6=9C=89=E7=A9=BA=E4=BD=8D=E6=97=B6?= =?UTF-8?q?=E5=87=BA=E7=8E=B0bug=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arknights_mower/solvers/base_schedule.py | 6 +++--- menu.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/arknights_mower/solvers/base_schedule.py b/arknights_mower/solvers/base_schedule.py index d14bd55f7..31621d01f 100644 --- a/arknights_mower/solvers/base_schedule.py +++ b/arknights_mower/solvers/base_schedule.py @@ -1066,7 +1066,7 @@ def drone(self, room: str, not_customize=False, not_return=False): int(self.recog.w * 1150 / 1920), int(self.recog.h * 35 / 1080), int(self.recog.w * 1295 / 1920), int(self.recog.h * 72 / 1080)), limit=201) logger.info(f'当前无人机数量为:{drone_count}') - if drone_count< self.drone_count_limit : + if drone_count< self.drone_count_limit or drone_count == 201: logger.info(f"无人机数量小于{self.drone_count_limit}->停止") return logger.info('制造站加速') @@ -1096,12 +1096,12 @@ def drone(self, room: str, not_customize=False, not_return=False): if not_customize: drone_count = self.read_screen(self.recog.img, type='drone_mood', cord=( int(self.recog.w * 1150 / 1920), int(self.recog.h * 35 / 1080), int(self.recog.w * 1295 / 1920), - int(self.recog.h * 72 / 1080)), limit=200) + int(self.recog.h * 72 / 1080)), limit=201) logger.info(f'当前无人机数量为:{drone_count}') self.recog.update() self.recog.save_screencap('run_order') # 200 为识别错误 - if drone_count < self.drone_count_limit: + if drone_count < self.drone_count_limit or drone_count == 201: logger.info(f"无人机数量小于{self.drone_count_limit}->停止") break st = accelerate[1] # 起点 diff --git a/menu.py b/menu.py index 6b01a20de..7cf49559b 100644 --- a/menu.py +++ b/menu.py @@ -362,8 +362,12 @@ def swtich_plan(drag_task): value2 = plan[key2] if key2 in plan else None; if value1 is not None: plan[key2] = value1 + elif key2 in plan: + plan.pop(key2) if value2 is not None: plan[key1] = value2 + elif key1 in plan: + plan.pop(key1) write_plan() load_plan(conf['planFile'])