################################################################################################################################################### # # # # # YYYYYYY YYYYYYY LLLLLLLLLLL SSSSSSSSSSSSSSS FFFFFFFFFFFFFFFFFFFFFF MMMMMMMM MMMMMMMM # # Y:::::Y Y:::::Y L:::::::::L SS:::::::::::::::S F::::::::::::::::::::F M:::::::M M:::::::M # # Y:::::Y Y:::::Y L:::::::::L S:::::SSSSSS::::::S F::::::::::::::::::::F M::::::::M M::::::::M # # Y::::::Y Y::::::Y LL:::::::LL S:::::S SSSSSSS FF::::::FFFFFFFFF::::F M:::::::::M M:::::::::M # # YYY:::::Y Y:::::YYY L:::::L S:::::S F:::::F FFFFFF M::::::::::M M::::::::::M # # Y:::::Y Y:::::Y L:::::L S:::::S F:::::F M:::::::::::M M:::::::::::M # # Y:::::Y:::::Y L:::::L S::::SSSS F::::::FFFFFFFFFF M:::::::M::::M M::::M:::::::M # # Y:::::::::Y L:::::L SS::::::SSSSS F:::::::::::::::F M::::::M M::::M M::::M M::::::M # # Y:::::::Y L:::::L SSS::::::::SS F:::::::::::::::F M::::::M M::::M::::M M::::::M # # Y:::::Y L:::::L SSSSSS::::S F::::::FFFFFFFFFF M::::::M M:::::::M M::::::M # # Y:::::Y L:::::L S:::::S F:::::F M::::::M M:::::M M::::::M # # Y:::::Y L:::::L LLLLLL S:::::S F:::::F M::::::M MMMMM M::::::M # # Y:::::Y LL:::::::LLLLLLLLL:::::L SSSSSSS S:::::S FF:::::::FF M::::::M M::::::M # # YYYY:::::YYYY L::::::::::::::::::::::L S::::::SSSSSS:::::S F::::::::FF M::::::M M::::::M # # Y:::::::::::Y L::::::::::::::::::::::L S:::::::::::::::SS F::::::::FF M::::::M M::::::M # # YYYYYYYYYYYYY LLLLLLLLLLLLLLLLLLLLLLLL SSSSSSSSSSSSSSS FFFFFFFFFFF MMMMMMMM MMMMMMMM # # # # # ################################################################################################################################################### 'Full-automatic FGO Script' __author__='hgjazhgj' import time,os,numpy,cv2,re,logging #from uiautomator2 import Device from airtest.core.android.android import Android from airtest.core.android.constant import CAP_METHOD,ORI_METHOD logging.getLogger('airtest').handlers[0].formatter.datefmt='%H:%M:%S' logger=(lambda logger:(logger.setLevel(logging.DEBUG),logger.addHandler((lambda handler:(handler.setFormatter(logging.Formatter('[%(asctime)s][%(levelname)s]<%(name)s> %(message)s','%H:%M:%S')),handler)[1])(logging.StreamHandler())),logger)[2])(logging.getLogger('fgoFunc')) IMG_APEMPTY=cv2.imread('image/apempty.png') IMG_ATTACK=cv2.imread('image/attack.png') IMG_BEGIN=cv2.imread('image/begin.png') IMG_BATTLEBEGIN=cv2.imread('image/battlebegin.png') IMG_BOUND=cv2.imread('image/bound.png') IMG_BOUNDUP=cv2.imread('image/boundup.png') IMG_CARDSEALED=cv2.imread('image/cardsealed.png') IMG_CHOOSEFRIEND=cv2.imread('image/choosefriend.png') IMG_END=cv2.imread('image/end.png') IMG_FAILED=cv2.imread('image/failed.png') IMG_FRIEND=[[file[:-4],cv2.imread('image/friend/'+file)]for file in os.listdir('image/friend')if file.endswith('.png')] IMG_GACHA=cv2.imread('image/gacha.png') IMG_HOUGUSEALED=cv2.imread('image/hougusealed.png') IMG_LISTEND=cv2.imread('image/listend.png') IMG_LISTNONE=cv2.imread('image/listnone.png') IMG_NOFRIEND=cv2.imread('image/nofriend.png') IMG_STAGE=[cv2.imread(f'image/stage{i}.png')for i in range(1,4)] IMG_STAGETOTAL=[cv2.imread(f'image/total{i}.png')for i in range(1,4)] IMG_STILL=cv2.imread('image/still.png') skillInfo=[[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0]]] houguInfo=[[1,1],[1,1],[1,1],[1,1],[1,1],[1,1]] dangerPos=[0,0,1] friendPos=4 masterSkill=[[0,0,0],[0,0,0],[0,0,0]] terminateFlag=False suspendFlag=False check=None def verifyFlag(): while suspendFlag and not terminateFlag:time.sleep(.1) if terminateFlag:exit(0) def sleep(x,part=.1): timer=time.time()+x-part while time.time()<timer: verifyFlag() time.sleep(part) time.sleep(max(0,timer+part-time.time())) def show(img):cv2.imshow('imshow',img),cv2.waitKey(),cv2.destroyAllWindows() class Fuse: def __init__(self,fv=300): self.__value=0 self.__max=fv @property def value(self):return self.__value @property def max(self):return self.__max def inc(self): if self.__value>self.__max: logger.warning('Fused') check.save().show() exit(0) self.__value+=1 return self def reset(self): logger.debug(f'Fuse {self.__value}') self.__value=0 return self fuse=Fuse() def acquireLock(func): def wrapper(self,*args,**kwargs): while self.lock:time.sleep(.05) verifyFlag() self.lock=True try:return func(self,*args,**kwargs) finally:self.lock=False return wrapper class Base(Android): def __init__(self,serialno=None): self.lock=False try: super().__init__(serialno,cap_method=CAP_METHOD.JAVACAP,ori_method=ORI_METHOD.ADB) self.serial=self.serialno except:self.serial=None else: self.render=[round(i)for i in self.get_render_resolution(True)] if self.render[2]*9>self.render[3]*16: self.scale=1080/self.render[3] self.border=(round(self.render[2]-self.render[3]*16/9)>>1,0) else: self.scale=1920/self.render[2] self.border=(0,round(self.render[3]-self.render[2]*9/16)>>1) self.key={c:[round(p[i]/self.scale+self.border[i]+self.render[i])for i in range(2)]for c,p in {' ':(1820,1030),'1':(277,640),'2':(648,640),'3':(974,640),'4':(1262,640),'5':(1651,640),'6':(646,304),'7':(976,304),'8':(1267,304), 'A':(109,860),'B':(1680,368),'C':(845,540),'D':(385,860),'E':(1493,470),'F':(582,860),'G':(724,860),'H':(861,860),'J':(1056,860),'K':(1201,860), 'L':(1336,860),'M':(1200,1000),'N':(248,1041),'P':(1854,69),'Q':(1800,475),'R':(1626,475),'S':(244,860),'V':(1105,540),'W':(1360,475),'X':(259,932), '\x64':(70,221),'\x65':(427,221),'\x66':(791,221),'\x67':(70,69),'\x68':(427,69),'\x69':(791,69),#NUM4 #NUM5 #NUM6 #NUM7 #NUM8 #NUM9 '\x09':(1800,304),'\x12':(960,943),'\xA0':(41,197),'\xA1':(41,197),'\xBA':(1247,197)}.items()}# VK_LSHIFT # VK_RSHIFT #; VK_OEM_1 #tab VK_TAB #alt VK_MENU @acquireLock def touch(self,p):super().touch([round(p[i]/self.scale+self.border[i]+self.render[i])for i in range(2)]) #@acquireLock #def swipe(self,rect,duration=.15,steps=2,fingers=1):super().swipe(*[[round(rect[i<<1|j]/self.scale)+self.border[j]+self.render[j]for j in range(2)]for i in range(2)],duration,steps,fingers) @acquireLock def swipe(self,rect):#v3.9.3 p1,p2=[numpy.array(self._touch_point_by_orientation([rect[i<<1|j]/self.scale+self.border[j]+self.render[j]for j in range(2)]))for i in range(2)] vd=p2-p1 lvd=numpy.linalg.norm(vd) vd*=5/lvd/self.scale vx=numpy.array([0.,0.]) getPos=lambda x:' '.join([str(int(i))for i in self.minitouch.transform_xy(*x)]) self.minitouch.safe_send('d 0 '+getPos(p1)+' 50\nc\n') time.sleep(.01) for _ in range(2): self.minitouch.safe_send('m 0 '+getPos(p1+vx)+' 50\nc\n') vx+=vd time.sleep(.02) vd*=5 while numpy.linalg.norm(vx)<lvd: self.minitouch.safe_send('m 0 '+getPos(p1+vx)+' 50\nc\n') vx+=vd time.sleep(.008) self.minitouch.safe_send('m 0 '+getPos(p2)+' 50\nc\n') time.sleep(.35) self.minitouch.safe_send('u 0\nc\n') time.sleep(.02) @acquireLock def press(self,c):super().touch(self.key[c]) @acquireLock def snapshot(self):return cv2.resize(cv2.resize(super().snapshot(),self.get_current_resolution(),interpolation=cv2.INTER_CUBIC)[self.render[1]+self.border[1]:self.render[1]+self.render[3]-self.border[1],self.render[0]+self.border[0]:self.render[0]+self.render[2]-self.border[0]],(1920,1080),interpolation=cv2.INTER_CUBIC) #class Base(Device): # def __init__(self,serialno=None): # self.lock=False # try:super().__init__(serialno) # except:self.serial=None # else: # self.render=[0,0]+sorted(self.window_size(),reversed=True) # if self.render[2]*9>self.render[3]*16: # self.scale=1080/self.render[3] # self.border=(round(self.render[2]-self.render[3]*16/9)>>1,0) # else: # self.scale=1920/self.render[2] # self.border=(0,round(self.render[3]-self.render[2]*9/16)>>1) # self.key={c:[round(p[i]/self.scale+self.border[i]+self.render[i])for i in range(2)]for c,p in # {' ':(1820,1030),'1':(277,640),'2':(648,640),'3':(974,640),'4':(1262,640),'5':(1651,640),'6':(646,304),'7':(976,304),'8':(1267,304), # 'A':(109,860),'B':(1680,368),'C':(845,540),'D':(385,860),'E':(1493,470),'F':(582,860),'G':(724,860),'H':(861,860),'J':(1056,860),'K':(1201,860), # 'L':(1336,860),'M':(1200,1000),'N':(248,1041),'P':(1854,69),'Q':(1800,475),'R':(1626,475),'S':(244,860),'V':(1105,540),'W':(1360,475),'X':(259,932), # '\x64':(70,221),'\x65':(427,221),'\x66':(791,221),'\x67':(70,69),'\x68':(427,69),'\x69':(791,69),#NUM4 #NUM5 #NUM6 #NUM7 #NUM8 #NUM9 # '\x09':(1800,304),'\x12':(960,943),'\xA0':(41,197),'\xA1':(41,197),'\xBA':(1247,197)}.items()}# VK_LSHIFT # VK_RSHIFT #; VK_OEM_1 #tab VK_TAB #alt VK_MENU # @acquireLock # def touch(self,p):super().click([round(p[i]/self.scale+self.border[i]+self.render[i])for i in range(2)]) # @acquireLock # def swipe(self,rect):super().swipe(*[round(rect[i<<1|j]/self.scale)+self.border[j]+self.render[j]for j in range(2)for i in range(2)]) # @acquireLock # def press(self,c):super().click(self.key[c]) # @acquireLock # def screenshot(self):return cv2.resize(super().screenshot(format="opencv")[self.render[1]+self.border[1]:self.render[1]+self.render[3]-self.border[1],self.render[0]+self.border[0]:self.render[0]+self.render[2]-self.border[0]],(1920,1080),interpolation=cv2.INTER_CUBIC) base=Base() def doit(pos,wait):[(base.press(i),sleep(j*.001))for i,j in zip(pos,wait)] class Check: def __init__(self,forwordLagency=.01,backwordLagency=0): sleep(forwordLagency) fuse.inc() self.im=base.snapshot() global check check=self sleep(backwordLagency) def compare(self,img,rect=(0,0,1920,1080),delta=.05):return cv2.minMaxLoc(cv2.matchTemplate(self.im[rect[1]:rect[3],rect[0]:rect[2]],img,cv2.TM_SQDIFF_NORMED))[0]<delta and fuse.reset() def select(self,img,rect=(0,0,1920,1080)):return(lambda x:x.index(min(x)))([cv2.minMaxLoc(cv2.matchTemplate(self.im[rect[1]:rect[3],rect[0]:rect[2]],i,cv2.TM_SQDIFF_NORMED))[0]for i in img]) def tapOnCmp(self,img,rect=(0,0,1920,1080),delta=.05):return(lambda loc:loc[0]<delta and(base.touch((rect[0]+loc[2][0]+(img.shape[1]>>1),rect[1]+loc[2][1]+(img.shape[0]>>1))),fuse.reset())[1])(cv2.minMaxLoc(cv2.matchTemplate(self.im[rect[1]:rect[3],rect[0]:rect[2]],img,cv2.TM_SQDIFF_NORMED))) def save(self,name=''):cv2.imwrite(time.strftime('%Y-%m-%d_%H.%M.%S',time.localtime())+'.jpg'if name==''else name,self.im);return self def show(self):show(cv2.resize(self.im,(0,0),None,.4,.4,cv2.INTER_NEAREST));return self def isTurnBegin(self):return self.compare(IMG_ATTACK,(1567,932,1835,1064)) def isBattleBegin(self):return self.compare(IMG_BATTLEBEGIN,(1673,959,1899,1069)) def isBattleFinished(self):return(self.compare(IMG_BOUND,(95,235,460,318))or self.compare(IMG_BOUNDUP,(978,517,1491,596))) def isBegin(self):return self.compare(IMG_BEGIN,(1630,950,1919,1079)) def isHouguReady(self):return(lambda im:[not any(self.compare(j,(470+346*i,258,768+346*i,387),.3)for j in(IMG_HOUGUSEALED,IMG_CARDSEALED))and(numpy.mean(self.im[1014:1021,217+480*i:235+480*i])>90or numpy.mean(im[1014:1021,217+480*i:235+480*i])>90)for i in range(3)])(Check(.7).im) def isSkillReady(self):return[[not self.compare(IMG_STILL,(65+480*i+141*j,895,107+480*i+141*j,927),.1)for j in range(3)]for i in range(3)] def isApEmpty(self):return self.compare(IMG_APEMPTY,(906,897,1017,967)) def isChooseFriend(self):return self.compare(IMG_CHOOSEFRIEND,(1628,314,1772,390)) def isNoFriend(self):return self.compare(IMG_NOFRIEND,(369,545,1552,797),.1) def isGacha(self):return self.compare(IMG_GACHA,(973,960,1312,1052)) def isListEnd(self,pos):return any(self.compare(i,(pos[0]-35,pos[1]-29,pos[0]+35,pos[1]+10),.15)for i in(IMG_LISTEND,IMG_LISTNONE)) def getABQ(self):return[-1if self.compare(IMG_CARDSEALED,(43+386*i,667,345+386*i,845),.3)else(lambda x:x.index(max(x)))([numpy.mean(self.im[771:919,108+386*i:318+386*i,j])for j in(2,1,0)])for i in range(5)] def getStage(self):return self.select(IMG_STAGE,(1296,20,1342,56))+1 def getStageTotal(self):return self.select(IMG_STAGETOTAL,(1325,20,1372,56))+1 def getPortrait(self):return[self.im[640:740,195+480*i:296+480*i]for i in range(3)] def tapFailed(self):return self.tapOnCmp(IMG_FAILED,(277,406,712,553)) def tapEnd(self):return self.tapOnCmp(IMG_END,(243,863,745,982)) def gacha(): while fuse.value<30: if Check(.1).isGacha():doit('MK',(200,2700)) base.press('P') def chooseFriend(): refresh=False while not Check(.2).isChooseFriend(): if check.isNoFriend(): if refresh:sleep(10) doit('\xBAJ',(500,1000)) refresh=True if len(IMG_FRIEND)==0: time.sleep(.2) return base.press('8') while True: timer=time.time() while not Check(.2,.1).isListEnd((1860,1064)): for i,_ in(i for i in IMG_FRIEND if check.tapOnCmp(i[1],delta=.015)): logger.info(f'Friend {i}') try:p=re.search('[0-9x]{11}$',i).group() except AttributeError:pass else: skillInfo[friendPos]=[[skillInfo[friendPos][i][j]if p[i*3+j]=='x'else int(p[i*3+j])for j in range(3)]for i in range(3)] houguInfo[friendPos]=[houguInfo[friendPos][i]if p[i]=='x'else int(p[i])for i in range(9,11)] return base.swipe((400,900,400,300)) if refresh:sleep(max(0,timer+10-time.time())) doit('\xBAJ',(500,1000)) refresh=True while not Check(.2).isChooseFriend(): if check.isNoFriend(): sleep(10) doit('\xBAJ',(500,1000)) def oneBattle(): turn,stage,stageTurn,servant=0,0,0,[0,1,2] while True: if Check(.1).isTurnBegin(): turn+=1 stage,stageTurn,skill,newPortrait=(lambda chk:(lambda x:[x,stageTurn+1if stage==x else 1])(chk.getStage())+[chk.isSkillReady(),chk.getPortrait()])(Check(.2)) if turn==1:stageTotal=check.getStageTotal() else:servant=(lambda m,p:[m+p.index(i)+1if i in p else servant[i]for i in range(3)])(max(servant),[i for i in range(3)if servant[i]<6and cv2.matchTemplate(newPortrait[i],portrait[i],cv2.TM_SQDIFF_NORMED)[0][0]>=.03]) if stageTurn==1:doit('\x69\x68\x67\x66\x65\x64'[dangerPos[stage-1]]+'P',(250,500)) portrait=newPortrait logger.info(f'{turn} {stage} {stageTurn} {servant}') for i,j in((i,j)for i in range(3)if servant[i]<6for j in range(3)if skill[i][j]and skillInfo[servant[i]][j][0]and stage<<4|stageTurn>=min(skillInfo[servant[i]][j][0],stageTotal)<<4|skillInfo[servant[i]][j][1]): doit(('ASD','FGH','JKL')[i][j],(300,)) if skillInfo[servant[i]][j][2]:doit(chr(skillInfo[servant[i]][j][2]+49),(300,)) sleep(1.7) while not Check(.1).isTurnBegin():pass sleep(.16) for i in(i for i in range(3)if stage==min(masterSkill[i][0],stageTotal)and stageTurn==masterSkill[i][1]): doit('Q'+'WER'[i],(300,300)) if masterSkill[i][2]:doit(chr(masterSkill[i][2]+49),(300,)) sleep(1.7) while not Check(.1).isTurnBegin():pass sleep(.16) doit(' ',(2250,)) doit((lambda chk:(lambda c,h:([chr(i+54)for i in sorted((i for i in range(3)if h[i]),key=lambda x:-houguInfo[servant[x]][1])]if any(h)else[chr(j+49)for i in range(3)if c.count(i)>=3for j in range(5)if c[j]==i])+[chr(i+49)for i in sorted(range(5),key=lambda x:(c[x]&2)>>1|(c[x]&1)<<1)])(chk.getABQ(),(lambda h:[servant[i]<6and h[i]and houguInfo[servant[i]][0]and stage>=min(houguInfo[servant[i]][0],stageTotal)for i in range(3)])(chk.isHouguReady())))(Check())[:3],(350,350,10000)) elif check.isBattleFinished(): logger.info('Battle Finished') return True elif check.tapFailed(): logger.warning('Battle Failed') return False def main(appleCount=0,appleKind=0,battleFunc=oneBattle): apple,battle=0,0 while True: while not Check(.2,.3).isBegin(): check.tapEnd() base.press(' ') battle+=1 base.press('8') if Check(.7,.3).isApEmpty(): if apple==appleCount: logger.info('Ap Empty') return base.press('\x12') else: apple+=1 logger.info(f'Apple {apple}') doit('W4K8'[appleKind]+'L',(400,1200)) logger.info(f'Battle {battle}') chooseFriend() while not Check(.1).isBattleBegin():pass doit(' ',(12000,)) if not battleFunc():doit('VJ',(500,500)) doit(' ',(200,200,200,200,200,200,200,200)) def userScript(): while not Check(0,.2).isTurnBegin():pass doit('AHJ3L3QE2 654',(3000,3000,350,3000,350,3000,300,350,3000,2400,350,350,10000)) while not Check(0,.2).isTurnBegin():pass assert Check().getStage()==2 doit('S 654',(3000,2400,350,350,10000)) while not Check(0,.2).isTurnBegin():pass assert Check().getStage()==3 doit(' 754',(2400,350,350,10000)) while not Check(0,.2).isBattleOver():pass return True #while not Check(.1).isTurnBegin():pass #doit('S2DF2GH2J2KL2QE2 654 ',(350,3000,3000,350,3000,3000,350,3000,350,3000,3000,350,3000,350,350,3000,2400,350,350,16000,300,300,300,300,300,300)) #while not Check(.1).isBattleFinished():assert not check.isTurnBegin() #return True