Ein ambitioniertes Python-Projekt.
# improvement todos# + use faster OCR# + find ground line# - and all tanks on it# + simulate underground path# + simulate bounding (game edges# - and pink obstacles)# - auto-scale based on game window size# - calculate high and low hit paths automatically# - simulate bouncing weps (only on flat ground yet)# OR# - based on current shot settings, adapt to hit nearest enemy tankBENCH_OCR = False#TrueOCRS = ["paddleocr", "winrt", "tesseract"]NO_PP = True # paddle is very robust and doesn't need it, preprocessing can fail, tooimport mathimport sysimport reimport getoptimport osimport timefrom timeit import default_timer as timerimport threadingfrom tkinter import *# ocr# keras won't run and conflicts with paddleif "doctr" in OCRS: # very slow: >4sfrom doctr.models import ocr_predictor # pip install python-doctrmodel = ocr_predictor(pretrained=True)from doctr.io import DocumentFileif "tesseract" in OCRS: # slow: >200msimport pytesseract # pip install pytesseractpytesseract.pytesseract.tesseract_cmd = r'C:\Storage\Tools\Tesseract-OCR\tesseract.exe' # xxx editif "winrt" in OCRS: # very fast: 12msfrom screen_ocr import Reader # pip install screen-ocr[winrt]ocr_reader = Reader.create_quality_reader()if "easyocr" in OCRS: # slow: >300msimport easyocr # pip install easyocreasy_reader = easyocr.Reader(['en'])if "paddleocr" in OCRS: # fast and accurate: 60msfrom paddleocr import PaddleOCR # pip install paddlepaddle, paddleocrfrom numpy import asarraypaddle_ocr = PaddleOCR(lang='en', show_log=False)import loggingfrom paddleocr.ppocr.utils.logging import get_logger as ppocr_get_loggerppocr_get_logger().setLevel(logging.ERROR)from PIL import ImageTkimport PIL.ImageOps# screenshotimport pyautogui# windowsimport ctypesfrom ctypes.wintypes import HWND, DWORD, RECTdef GetWindowRectFromName(name:str)-> tuple:hwnd = ctypes.windll.user32.FindWindowW(0, name)rect = ctypes.wintypes.RECT()try:ctypes.windll.user32.GetWindowRect(hwnd, ctypes.pointer(rect))return (rect.left+9, rect.top, rect.right-8, rect.bottom-11)except:return (0,0,1882,1421)gcoord = GetWindowRectFromName('ShellShock Live')# Optionsxoffs,yoffs,x2,y2 = gcoordwidth = x2-xoffsheight = y2-yoffsverbose = Falseauto = True # auto now default ondef printhelp():print(os.path.basename(__file__)+' [-x OFFSET] [-y OFFSET] [-v|--verbose] [-a|--noauto]')# print("MODES:\n - 0|1: default trajectory\n - 2: slow projectile\n - 3: hover ball\n - 4: straight\n - 5: 3d bomb")try:opts, args = getopt.getopt(sys.argv[1:], "hax:y:v", ["verbose","auto"])except getopt.GetoptError:printhelp()sys.exit()# print(opts)for opt, arg in opts:if opt == '-h':printhelp()sys.exit()elif opt in ("-x"):xoffs = int(arg)width = 1882 # fixed to yoga window sizeelif opt in ("-y"):yoffs = int(arg)height = 1421 # fixedelif opt in ("-v", "--verbose"):verbose = Trueelif opt in ("-a", "--noauto"):auto = False# ConfigGUI_SIZE = "950x800" # init value, size changes are stored to cfg fileWINDOW_OFFS = (xoffs, yoffs) # for moved windowIMG_SCALE = 2.0 # scale output downSHOW_CROPS = verbose # show cropped img for wind and paGAME_CRD = (WINDOW_OFFS[0], WINDOW_OFFS[1], width+WINDOW_OFFS[0], height+WINDOW_OFFS[1]) # game window# size-dependent# todo find on scaled windowPLAY_CRD = (5, 180, width-5, height-(1421-1270)) # playing fieldWEP_CRD = (700-5, 1375, 999, 1405) # weapon nameFIRE_CRD = (1070, 1300, 1070+1, 1300+1) # a pixel in fire buttonWIND_CRD = (924, 143, 958, 167) # wind speed - this is super sensitive to window x displacements!!!WINDR_X = 979 # wind dir indicatorsWINDL_X = 902WIND_Y = 152TANKY_OFFS = 2 # center of shot offs to found tank centerBARREL_LENGTH = 21.0BARREL_LENGTH_Y = 21.0 # todo get rid of different yBOUNDER_HEIGHT = 218 # side-boundersA_GND_RANGE = 20 # pixel to scan left&right to find gnd angle# unscaled valuesBOMB_T = 194HOVER_T = 155 # ca. 0.8sMAX_T = 2000 # max. flight simulation time ca 10sBOOMERANG_F = 486.0 # pseudo wind factor for boomerang wepsRAM_F = 4.9 # grav increase on apex for battering wepsBOUNCE_FACTOR = (0.31,0.51,0.71,0.81) # reduction of bouncing wep speed, (normal, med, high, ultra-bounce); bouncing is experimental - stone-decoration makes ground-detection imprecise, bouncing is only correct on flat ground# ballistic parametersSF = 0.04885#0.04222 # speed factorWF = 0.0000128#sf/8.12*power/100.0 # wind acc factorGACC = 9.803/1000.0 # earth accelerationSCAN_WEP = True # OCR wep nameCOLOR = [(255, 255, 255), (200, 200, 255)] # trajectory color right;leftAUTO = autoPRINT = not auto# read weapon listweps = []try:for l in open("weps.txt").readlines():weps.append(l.strip())except:print("Weapon list wep.txt not found")WEAPONS = weps#Create an instance of tkinter framewin = Tk()win.title("Shellshock Simulator")#Set the geometry of tkinter frameCONFIG_FILE = os.path.basename(__file__)+".conf"if os.path.isfile(CONFIG_FILE):#Here I read the X and Y positon of the window from when I last closed it.with open(CONFIG_FILE, "r") as conf:win.geometry(conf.read())else:#Default window position.win.geometry(GUI_SIZE)wind = StringVar()power = StringVar()angle = StringVar()bfactor = DoubleVar()bfactor.set(0.3)subwep = IntVar()bounce = IntVar()bound = IntVar()autowep = IntVar()override = IntVar()md = IntVar() # modemd.set(0)autowep.set(1)sfv = DoubleVar()sfv.set(SF)wfv = DoubleVar()wfv.set(WF)gaccv = DoubleVar()gaccv.set(GACC)def ocr_winrt(im):return ocr_reader.read_image(im).as_string()def ocr_tesser(img, cfg='--psm 7 -c tessedit_char_whitelist=0123456789-,'):return pytesseract.image_to_string(img, config=cfg)def ocr_easy(im):im.save('temp.png')result = easy_reader.readtext('temp.png')r = ""for (bbox, text, prob) in result:r += textreturn rdef ocr_paddle(im):numpydata = asarray(im)result = paddle_ocr.ocr(numpydata)r = ""for idx in range(len(result)):res = result[idx]try:for line in res:r += line[1][0]except:pass#print(res) # reader failed; returned Nonereturn rdef ocr_doctr(im):im.save('temp.png')doc = DocumentFile.from_images("temp.png")result = model(doc)print(result)return ""def ocr(im, method=OCRS[0], cfg=''):if method == "paddleocr":return ocr_paddle(im)elif method == "winrt":return ocr_winrt(im)elif method == "tesseract":return ocr_tesser(im, cfg)elif method == "easyocr":return ocr_easy(im)elif method == "doctr":return ocr_doctr(im)return ""def distance(p1, p2):return math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)def preprocess(img):if NO_PP:return imgsize_x, size_y = img.sizeimg = img.resize((size_x*2, size_y*2), 3)# img = PIL.ImageOps.invert(img)# if SHOW_CROPS:# img.show()# img = img.resize(((pow_rect[2]-pow_rect[0])*2, (pow_rect[3]-pow_rect[1])*2), Image.ANTIALIAS)pixdata = img.load()d = 16for y in range(0, img.size[1]):for x in range(0, img.size[0]):r, g, b = pixdata[x, y]#splitp = 120#repl = 200#if (r in range(0, splitp)) or (g in range(0, splitp)) or (b in range(0, splitp)):# if (r in range(splitp,255)) and (g in range(splitp,255)) and (b in range(splitp,255)):if r in range(b-d, b+d) and g in range(b-d, b+d):# and g>150:# (r in range(0, splitp)) or (g in range(0, splitp)) or (b in range(0, splitp)):#b > g*1.5 and r > g*1.5:#g > 160:#pixdata[x, y] = (b//2, b//2, b//2)else:pixdata[x, y] = (0, 0, 0)#~ if (r in range(splitp, 256)) and (g in range(splitp, 256)) and (b in range(splitp, 256)):#~ pixdata[x, y] = (255, 255, 255)return PIL.ImageOps.invert(img)def postprocess(pa):try:p, a = re.split('[^0123456789-]+', pa.strip())if re.search(r"\d+", p.strip()) and re.search(r"\d+", a.strip()): # check if output is 2 numbersreturn (p, a)except:passreturn ""img_pa = ""def get_pow_ang(im, pow_rect):global img_paimg = im.crop(pow_rect)#.convert('L')#.resize()#.invert()img = preprocess(img)if SHOW_CROPS:img.show()img_pa = ImageTk.PhotoImage(img)pa = ocr(img)return patry:p, a = re.split('[^0123456789-]+', pa.strip())if re.search(r"\d+", p.strip()) and re.search(r"\d+", a.strip()): # check if output is 2 numbersreturn paexcept:passreturn ""def get_pow_ang_t(im, pow_rect, ocr_method = "tesseract"):global img_paimg = im.crop(pow_rect)img = preprocess(img)if SHOW_CROPS:img.show()img_pa = ImageTk.PhotoImage(img)pow_ang = ocr(img, ocr_method)#pytesseract.image_to_string(img, config='--psm 7 -c tessedit_char_whitelist=0123456789-,')return pow_angdef find_pa(im, tx, ty):im_ = im.load()limit = 210px = []for x in range(tx+30, tx+130):try:r,g,b = im_[x, ty+5]except:r,g,b = 0,0,0if r>limit and g>limit and b>limit:px.append(x)if len(px) > 2: # found on right sidereturn (min(px)-10, ty-10, max(px)+10, ty+30)for x in range(tx-130, tx-30):try:r,g,b = im_[x, ty+5]except:r,g,b = 0,0,0if r>limit and g>limit and b>limit:px.append(x)if len(px) > 2: # found on left sidereturn (min(px)-10, ty-10, max(px)+10, ty+30)return (tx-64, ty+25, tx+64, ty+70) # extended x bc on edges the display jumps a bit to the middleimg_wind = ""def get_wind(im, wind_rect):global img_windimg = im.crop(wind_rect)if SHOW_CROPS:img.show()img_wind = ImageTk.PhotoImage(img)dircheck = im.load()splitp = 90cnt = 0sign = 0# print('sign:')# print(dircheck[WINDL_X, WIND_Y])r, g, b = dircheck[WINDL_X, WIND_Y]if (r in range(0, splitp)) or (g in range(0, splitp)) or (b in range(0, splitp)): # black -> no arrowsign = 1cnt += 1# print(dircheck[WINDR_X, WIND_Y])r, g, b = dircheck[WINDR_X, WIND_Y]if (r in range(0, splitp)) or (g in range(0, splitp)) or (b in range(0, splitp)):sign = -1cnt += 1if cnt == 2: # no arrow seenif PRINT:print("no wind")return 0for o in OCRS:w = ocr(img, o)try:if w != "":w = int(w)return sign*int(w)except:passif PRINT:print("failed to read wind")return 0def calc_dir(gnd, x, wx):dir = 0try:if gnd[wx] < gnd[x]: # smaller means updir = 1if gnd[wx] > gnd[x]:dir = -1except:passreturn dirdef calc_gnd_angle(gnd, x):wx = xif x > 1: # walk backwards for N pixels as long as they are monotonouswx = x-1dir = calc_dir(gnd, x, wx)while wx>1 and x-wx < A_GND_RANGE and calc_dir(gnd, wx, wx-1) in [0,dir]:wx -= 1leftx = wxwx = xif x < len(gnd)-1: # walk forwards for N pixels as long as they are monotonouswx = x+1dir = calc_dir(gnd, x, wx)while wx<len(gnd)-1 and wx-x < A_GND_RANGE and calc_dir(gnd, wx, wx+1) in [0,dir]:wx += 1rightx = wxdx = rightx - leftxdy = gnd[rightx] - gnd[leftx]a = math.atan2(-dy, dx)if a >= math.pi/2.0:a -= math.pireturn (a, dx, dy)def calc_refl_angle(a, v0x, v0y):v1x = v0x*math.cos(2*a) + v0y*math.sin(2*a)v1y = v0x*math.sin(2*a) - v0y*math.cos(2*a)return (v1x, v1y)img = "" # keep img reference alivedef sim_shot(im, x, y, power, angle, wind, weapon, gnd):global imgslow_weps = ('Baby Seagull', 'Mama Seagull', 'Seagull', 'BFG-1000', 'BFG-9000', 'Chopper', 'Apache')straight_weps = ('AK-47', 'M4', 'M16', 'Glock', 'M9', 'Desert Eagle', 'UZI', 'MP5', 'P90', 'Laser Beam', 'Great Beam', 'Ultra Beam', 'Master Beam', 'Flintlock', 'Blunderbuss', 'Fat Stacks', 'Dolla Bills', 'Make-it-Rain')submersive_weps = ('Sub-Sniper', 'Tunneler', 'Mega-Tunneler', 'Torpedoes', '3D-Bomb', 'Rainbow', 'MegaRainbow', 'Dead Riser', 'Yin Yang', 'Yin Yang Yong', 'Penetrator', 'Penetrator v2', 'Sausage', 'Bratwurst', 'Kielbasa')noprojectile_weps = ('Earthquake', 'Mega-Quake', 'Shank', 'Dagger', 'Sword', 'Attractoids', 'Shockwave', 'Sonic Pulse', 'Rampage', 'Riot', 'Imploder', 'Ultimate Imploder', 'ShockShell', 'ShockShell Trio')hover_wep = ('Hover-Ball', 'Heavy Hover-Ball')drei_d_wep = ('3D-Bomb', '2x3D', '3x3D') # wind affects those, too!boomerang_wep = ('Boomerang', 'BigBoomerang')taser_wep = ('Taser', 'Heavy Taser')ram_wep = ('Battering Ram', 'Double Ram', 'Ram-Squad', 'Double Ram-Squad')ultra_bounce_wep = ('Snowball','Bounsplode','Double-Bounsplode','Triple-Bounsplode')highbounce_wep = ('One-Bounce', 'Three-Bounce', 'Five-Bounce', 'Seven-Bounce','Stone','Boulder','Fireball')medium_bounce_wep = ('Grenade', 'Tri-Nade', 'Multi-Nade','Sillyballs','Wackyballs','Crazyballs','Beacon','Beaconator','Jack-o-Lantern','Jack-o-Vomit')bounce_wep = ('Grenade Storm', 'Cactus Strike', 'Tunnel Strike', 'Air Strike','Helicopter Strike','AC-130','Artillery', 'Bolt','Lightning','2012','Disco Ball','Groovy Ball','Water Balloon','Water Trio','Water Fight','Pinata','Fiesta','Mine Layer','Sticky Rain','Rain','Hail','FireStorm','Shrapnel','Shredders','Carpet Bomb','Carpet Fire','Incendiary Bombs','Snowstorm','Bounstream','Bounstrike','Shooting Star','Kamikaze','Suicide Bomber','Acid Rain','Acid Hail','Recruiter','Enroller','Enlister','Cats and Dogs','Satellite','UFO','Kernels','Popcorn','Burnt Popcorn','Pinpoint','Needles','Pins and Needles','God Rays','Deity')if PRINT:print("SIM Power & Angle: ", power, angle)print("SIM Wind: ", wind)if weapon in noprojectile_weps:returnsize_x, size_y = im.sizets = BARREL_LENGTH # tank gun offsettsy = BARREL_LENGTH_Ysf = sfv.get()#SFwf = wfv.get()#WFapogee_cnt_init = 0max_t = MAX_Tgacc = gaccv.get()#GACCgacc_sub = gacc*10.0/9.803#GACC_SUBtasermax = 1taserangle = [angle, angle]bouncing = Falsebf = 0.0 # boomerangram = Falseram_active = Falseif weapon in slow_weps or md.get() == 2:#md.set(2)if PRINT:print("SLOW")sf = sf/2.0wf = wf/2.0#WF_SLOWgacc = gacc/4.0gacc_sub = gacc_sub/4.0elif weapon in hover_wep or md.get() == 3:#md.set(3)if PRINT:print("HOVER")apogee_cnt_init = int(HOVER_T)if weapon == "Heavy Hover-Ball":apogee_cnt_init = apogee_cnt_init*10//8# Heavy flies 1.0s, normal 0.8selif weapon in straight_weps or md.get() == 4:#md.set(4)if PRINT:print("STRAIGHT")gacc = 0.0gacc_sub = 0.0wind = 0.0elif weapon in drei_d_wep or md.get() == 5: # 3d bomb is different with wind, not quite right yet (shot up/dn is deviating more in real game)#md.set(5)if PRINT:print("3D")gacc = 0.0gacc_sub = 0.0# wf = 0.00005max_t = BOMB_T # limit flight timeelif weapon in taser_wep or md.get() == 6:#md.set(6)if PRINT:print("TASER")tasermax = 2taserangle = [angle-5.7, angle+5.7]elif weapon in bounce_wep or md.get() == 7:#md.set(7)if PRINT:print("BOUNCING")if override.get() == 0:bfactor.set(BOUNCE_FACTOR[0])bouncing = Truemax_t = 2*max_telif weapon in medium_bounce_wep or md.get() == 11:#md.set(11)if PRINT:print("MEDIUM-BOUNCING")if override.get() == 0:bfactor.set(BOUNCE_FACTOR[1])bouncing = Truemax_t = int(1.5*max_t)elif weapon in highbounce_wep or md.get() == 8:#md.set(8)if PRINT:print("HIGH-BOUNCING")if override.get() == 0:bfactor.set(BOUNCE_FACTOR[2])bouncing = Truemax_t = 2*max_telif weapon in ultra_bounce_wep or md.get() == 12:#md.set(12)if PRINT:print("ULTRAHIGH-BOUNCING")if override.get() == 0:bfactor.set(BOUNCE_FACTOR[3])bouncing = Truemax_t = 2*max_telif weapon in boomerang_wep or md.get() == 9:#md.set(9)sx = power*math.cos(angle*math.pi/180.0)bf = wf*BOOMERANG_F*sx/(100.0*math.cos(45.0*math.pi/180.0))if PRINT:print("BOOMERANG",sx,bf)elif weapon in ram_wep or md.get() == 10:#md.set(10)ram = True # Trigger grav increase on apexgacc_ram = gacc*RAM_Fif PRINT:print("RAM")else:pass#md.set(0)sw = subwep.get() != 0if weapon in submersive_weps:sw = Truepixdata = im.load()pixdata[x, y] = (255, 0, 0)pixdata[x+1, y+1] = (255, 0, 0)pixdata[x+1, y] = (255, 0, 0)pixdata[x, y+1] = (255, 0, 0)#bcnt=0 # dbgfor taserp in range(0, tasermax):if tasermax == 2:angle = taserangle[taserp]for q in range(0, 2): # ugly workaround for aiming dir (not unique)sx = sf*power*math.cos(angle*math.pi/180.0)sy = sf*power*math.sin(angle*math.pi/180.0)lx = x+ts*math.cos(angle*math.pi/180.0)if q==1:lx = x-ts*math.cos(angle*math.pi/180.0)ly = y-tsy*math.sin(angle*math.pi/180.0)p = [[lx, ly], [lx, ly]]renderbuf = [(0, 0)]*max_trbcnt = 0windacc = 0.0rfcooldown = 0bouncing_cnt = 0hover = Falseapogee_cnt = apogee_cnt_initfor t in range(0, max_t):oldsy = syif q==0:p[q][0] += sxelse:p[q][0] -= sxp[q][0] += windaccif q==0:bff=-bfwindacc += wind*wf + bffif not hover:p[q][1] -= syix = int(p[q][0]+0.5)iy = int(p[q][1]+0.5)# if q==0:# print(ix, iy, gnd[ix], sy)if bouncing: # hitting the ground bounces the weaponif ix<0 or ix>=size_x-1 or iy<=gnd[ix] or bouncing_cnt>0 or sy>0: # shot in field and in air, upwards, or bouncing cooldownif bouncing_cnt>0:bouncing_cnt-=1else:# print("bouncing")bouncing_cnt=5p[q][1] = gnd[ix]+1 # ugly hack to prevent endless bouncinggnd_angle,dx,dy = calc_gnd_angle(gnd, ix)# calc entry/exit angles and modify sx,sysxn, syn = calc_refl_angle(gnd_angle, sx, sy)#if q==0 and bcnt==0:# bcnt+=1# print("angle:", gnd_angle*180/math.pi,dx,dy)# print("in:",sx,sy)# print("out:",sxn,syn)sy = syn * bfactor.get()#BOUNCE_FACTOR[btype]sx = sxn * bfactor.get()#BOUNCE_FACTOR[btype]sy -= gacc # normal flight curveelif sw:if ix<0 or ix>=size_x-1 or iy<=gnd[ix]: # shot in field and in airif not hover:sy -= gaccelse:sy += gacc_sub # underground# print("sub")else:if not hover:sy -= gaccif oldsy>0 and sy<0 and apogee_cnt>0:hover = True# print("Apogee detected", t)if ram and oldsy>0 and sy<0:ram_active = Trueif ram_active:gacc = gacc_ramif hover:apogee_cnt -= 1if apogee_cnt == 0:hover = Falseif bound.get()!=0 and ix >= size_x and rfcooldown==0:# and iy in range(gnd[-1]-BOUNDER_HEIGHT,gnd[-1]+BOUNDER_HEIGHT): # right boundersx = -sxrfcooldown = 10windacc = -windaccif bound.get()!=0 and ix <= 0 and rfcooldown==0:# and iy in range(gnd[0]-BOUNDER_HEIGHT,gnd[0]+BOUNDER_HEIGHT): # left boundersx = -sxrfcooldown = 10windacc = -windaccif rfcooldown>0:rfcooldown -= 1if ix in range(0, size_x) and iy in range(0, size_y):renderbuf[rbcnt] = (ix, iy, COLOR[q])rbcnt += 1if ix not in range(-10, size_x+10): # end meaningless calculationbreakif iy > size_y+10 and sy < 0: # end meaningless calculationbreakfor t in range(0, rbcnt):ix, iy, c = renderbuf[t]pixdata[ix, iy] = c # draw curvetry:pixdata[ix, iy+1] = c # draw curvepixdata[ix+1, iy] = c # draw curvepixdata[ix+1, iy+1] = c # draw curveexcept:passif verbose:im.save("sss.jpg")im_r = im.resize((int(size_x/IMG_SCALE), int(size_y/IMG_SCALE)), 3)img = ImageTk.PhotoImage(im_r)canvas.create_image(0,0,anchor=NW,image=img)def find_tank(im):im_tank = im.load()found = Falsex = 0y = 0size_x, size_y = im.sizefor y in range(size_y-1, 0, -2):for x in range(0, size_x, 2):r, g, b = im_tank[x, y]if (r in range(100, 140)) and (g in range(200, 255)) and (b in range(100, 140) and (g-b > 90)): # color matching the green tanktank_at = (x, y)found = Truebreakif found:breakif found==False:return (0, 0)p = [[0]*2]*40*40cnt = 0for xx in range(x-40, x+40):for yy in range(y-40, y+40):if xx>=0 and yy>=0 and xx<size_x and yy<size_y:r, g, b = im_tank[xx, yy]if (r in range(0, 127)) and (g in range(160, 255)) and (b in range(0, 127) and (g-b > 100)):try:p[cnt] = [xx, yy]# im_tank[xx,yy] = (255,0,0) # highlight tank pixelscnt += 1except:passif cnt > 0:xsum = 0ysum = 0for pp in p:xsum += pp[0]ysum += pp[1]xsum /= cntysum /= cntrx, ry = int(xsum+0.5), int(ysum+0.5) + TANKY_OFFS# im_tank[rx,ry] = (0,0,0) # use this to precision-match the center# im_tank[rx+1,ry] = (0,0,0)# im_tank[rx,ry+1] = (0,0,0)# im_tank[rx+1,ry+1] = (0,0,0)# im.show()return (rx, ry)return (0, 0)def find_gnd_color(im, size_x, size_y): # find the general color of the ground, possible improvements: sample more, only take majority color# try left bottomcol = im[10, size_y-10]if col[0] in range(50,255) or col[1] in range(50,255) or col[2] in range(50,255): # with the risk that a tank or obstacle is therereturn colcol = im[size_x//2, size_y-10]if col[0] in range(50,255) or col[1] in range(50,255) or col[2] in range(50,255): # with the risk that a tank or obstacle is therereturn colcol = im[size_x-10, size_y-10]if col[0] in range(50,255) or col[1] in range(50,255) or col[2] in range(50,255): # with the risk that a tank or obstacle is therereturn coldef check_col(p, col, dn=-20, dp=50):if p[0] in range(col[0]-dn,col[0]+dp) and p[1] in range(col[1]-dn,col[1]+dp) and p[2] in range(col[2]-dn,col[2]+dp):return Truereturn Falsedef find_y_border(im, col, x, size_x, size_y):y = size_y-1 # scan from bottom -> upen = Falsecollimit = 120enlimit = 200if x >= size_x-25 or x <= 25: # darkened border reagoncollimit = 70enlimit = 100while y>0:p = im[x, y]if p[2] > enlimit:en = Trueif en and p[2] < collimit:# massive decrease in blue brightness -- check_col(p, col, -20, 50): # already gnd colorreturn y+1y -= 1#xxx find tanksreturn size_y-1def find_ground(im):im_gnd = im.load()size_x, size_y = im.sizeground = [-1]*size_xgnd_col = find_gnd_color(im_gnd, size_x, size_y)if PRINT:print("Ground color: ", gnd_col)for x in range(0,size_x): # todo multithreaded loop to speed upy = find_y_border(im_gnd, gnd_col, x, size_x, size_y)# print(x,y)# im_gnd[x, min(y,size_y-1)] = (255,0,0)# im_gnd[x, min(y+1,size_y-1)] = (255,0,0)# im_gnd[x, min(y+2,size_y-1)] = (255,0,0)ground[x] = y# im.show()# interpolate missing gnd?return groundim_play = ""running = Falsedef run_click():global running# run()# returnif running:btn_run["text"] = "Run"running = Falseelse:btn_run["text"] = "Stop"running = Truerun()img_wep = ""def run():global im_play, img_wep# global wind, weapon, power, angleim = pyautogui.screenshot()im = im.crop(GAME_CRD)if AUTO:f_rect = FIRE_CRDim_f = im.crop(f_rect)im_fire = im_f.load()c = im_fire[0, 0]r, g, b = c# print((r,g,b))if not (r>220 and g<40 and b<40):#r==g and g==bif running:# and not stop_event.is_set():win.after(10, run)returnif override.get()==0:#wind == "":wind_rect = WIND_CRD#(946, 102, 943+29, 102+18)wind.set(f"{get_wind(im, wind_rect)}")weapon = "not scanned"if autowep.get() != 0:wep_rect = WEP_CRDim_wep = im.crop(wep_rect)img_wep = ImageTk.PhotoImage(im_wep)for o in OCRS:weapon = ocr(im_wep, o, '--psm 7').strip()if weapon in WEAPONS:breakif SHOW_CROPS:im_wep.show()lw["text"] = weaponplay_rect = PLAY_CRD#(264, 34, 264+1393, 71+854)im_play = im.crop(play_rect)#start = timer()# find green tank's x and yx, y = find_tank(im_play)if y==0:if PRINT:print("Tank not found!")if PRINT:print("Tank at: ", x, y)gnd = find_ground(im_play)#print("find stuff:",timer()-start)canvas.delete("all")if y != 0:# pow_rect = ((max(0, x-42), y+22, min(x+42, size_x), min(y+54+17, size_y)), (max(0, x-105), y-10, max(1, x-20), min(y+20, size_y))) # match the 2 possible positions# for pr in pow_rect:pr = find_pa(im_play, x, y)# print(pr,x,y)if pr[1] < pr[3]:col = "#888"fnt = "Tahoma 14"ocrm = OCRS#["paddleocr", "winrt", "tesseract"]for m in ocrm: # sorted by accuracytry:pa = get_pow_ang_t(im_play, pr, m).strip()pap = postprocess(pa)except:pap = ""if pap != "" and not BENCH_OCR: # sim shotp, a = papif p.strip() != "" and a.strip() != "":# try:if override.get() == 0: #power == "":power.set(f"{int(p)}")if override.get() == 0: #angle == "":angle.set(f"{int(a)}")sim_shot(im_play, x, y, int(power.get()), int(angle.get()), int(wind.get()), weapon, gnd)if img_pa != "":canvas.create_image(5, 5, anchor=NW, image=img_pa)canvas.create_text(180, 20, text=f"{pa}", fill=col, font=fnt)break# except:if PRINT:print("invalid pa")else:if PRINT:print("invalid pa")if BENCH_OCR: # show ocr activityif img_wind != "":canvas.create_image(10, 5, anchor=NW, image=img_wind)canvas.create_text(150, 14, text=f"{wind.get()}", fill=col, font=fnt)ox = 350for o in OCRS:start = timer()pa = get_pow_ang_t(im_play, pr, o).strip()t = "{:1.1f}".format((timer()-start)*1000.0)canvas.create_text(ox, 80, text=o+f":\n{pa}\n{t}", fill=col, font=fnt)ox += 145if img_pa != "":canvas.create_image(10, 80-40, anchor=NW, image=img_pa)if img_wep != "":canvas.create_image(250, 0, anchor=NW, image=img_wep)if AUTO:if running:# and not stop_event.is_set():win.after(10, run)x=""stop_event = threading.Event()def run_auto():while(True):run()time.sleep(0.05)if stop_event.is_set():breakdef save():im_play.save("sss.jpg")#Create a canvassize_x = PLAY_CRD[2]-PLAY_CRD[0]size_y = PLAY_CRD[3]-PLAY_CRD[1]canvas = Canvas(win, width=int(size_x/IMG_SCALE), height=int(size_y/IMG_SCALE))canvas.pack()btn_run = Button(win, text = "Run", width = 200, command = run_click)btn_run.pack()f1 = Frame(win)Checkbutton(f1, text="Enable Underground", variable=subwep).pack(side='left')Checkbutton(f1, text="Enable Bounders", variable=bound).pack(side='left')Checkbutton(f1, text="Detect Weapon", variable=autowep).pack(side='left')Label(f1, text="SF:").pack(side=LEFT)Entry(f1, textvariable=sfv, width=10).pack(side=LEFT)Label(f1, text="WF:").pack(side=LEFT)Entry(f1, textvariable=wfv, width=10).pack(side=LEFT)Label(f1, text="G:").pack(side=LEFT)Entry(f1, textvariable=gaccv, width=10).pack(side=LEFT)f1.pack(fill='x', expand=True)f2 = Frame(win)lw = Label(f2, text="Weapon:")lw.pack(side=LEFT)Label(f2, text="Wind:").pack(side=LEFT)Entry(f2, textvariable=wind, width=4).pack(side=LEFT)Label(f2, text="Power:").pack(side=LEFT)Entry(f2, textvariable=power, width=4).pack(side=LEFT)Label(f2, text="Angle:").pack(side=LEFT)Entry(f2, textvariable=angle, width=4).pack(side=LEFT)Label(f2, text="Bounce:").pack(side=LEFT)Entry(f2, textvariable=bfactor, width=4).pack(side=LEFT)Checkbutton(f2, text="Override", variable=override).pack(side=LEFT)Button(f2, text = "Save", command = save).pack(side=LEFT)f2.pack(fill='x', expand=True)f3 = Frame(win)Radiobutton(f3, text="Normal", variable=md, value=0).pack(side=LEFT)Radiobutton(f3, text="Slow", variable=md, value=2).pack(side=LEFT)Radiobutton(f3, text="Hover", variable=md, value=3).pack(side=LEFT)Radiobutton(f3, text="3D", variable=md, value=5).pack(side=LEFT)Radiobutton(f3, text="Taser", variable=md, value=6).pack(side=LEFT)Radiobutton(f3, text="Bounce", variable=md, value=7).pack(side=LEFT)Radiobutton(f3, text="Med-", variable=md, value=11).pack(side=LEFT)Radiobutton(f3, text="High-", variable=md, value=8).pack(side=LEFT)Radiobutton(f3, text="Ultra-Bounce", variable=md, value=12).pack(side=LEFT)Radiobutton(f3, text="Boomerang", variable=md, value=9).pack(side=LEFT)Radiobutton(f3, text="Ram", variable=md, value=10).pack(side=LEFT)f3.pack(fill='x', expand=True)def on_close():global x, stop_eventif AUTO:stop_event.set()#x.join()with open(CONFIG_FILE, "w") as conf:conf.write(win.geometry())win.destroy()win.protocol("WM_DELETE_WINDOW", on_close)def key_handler(event):if event.keycode == 27:sys.exit()if event.char == "r" or event.keysym == "space":run_click()if event.char == "s":save()if event.char == "o":override.set(1-override.get())if event.char == "b":bound.set(1-bound.get())if event.char == "u":subwep.set(1-subwep.get())# print(event.char, event.keysym, event.keycode)win.bind("<Key>", key_handler)#if AUTO:# x = threading.Thread(target=run_auto)# x.start()# loopwin.mainloop()stop_event.set()#~ # the math solution#~ # calc shellshock#~ C = 573.0/(0.73 * 2.58 * math.cos(math.pi/4.0))#~ G = (0.73 * math.sqrt(2.0) * C)/2.58#~ D = 446.0 # Durchmesser Zielkreis#~ B = 446.0/D/1.53 # screen correction#S = 2.82/1.51#~ def cr(x, y, angle):#~ x = B*x#~ y = B*y#~ ang = angle*math.pi/180.0#~ num = G*x*x#~ den = C*C*(x*math.sin(2.0*ang) - 2.0*y*(math.cos(ang) ** 2.0))#~ k = math.sqrt(num/den)#~ return k * 100.0#~ print(cr(385, -226, 30))