├── LICENSE.md ├── README.md ├── char.jpg ├── headers.txt ├── main.py ├── metin.jpg ├── stones.txt ├── uriel.jpg └── windowcapture.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 FatihAvdan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Metin2 Opencv Metin Kesme Botu 2 | 3 | Opencv matchTemplate, Win32api kullanılarak yapılmıştır. Görsel tanıma ile çalışıp ekrandaki metinleri, karakterin konumunu bulur ve karaktere en yakın metnin üzerine tıklar. Uriel hile korumasını algılayıp otomatik geçer.
4 | ![image](https://user-images.githubusercontent.com/72533615/191083335-53546aa4-e576-4632-a2bb-0f9b36bc1321.png) 5 | 6 | 7 | 8 | # Gerekli Düzenlemeler 9 | 10 | 1-Karakterinizin ismini char.jpg gibi ekran görüntüsü alıp kaydediniz.
11 | 2-Kesmek istediğiniz metnin ismini metin.jpg deki gibi ekran görüntüsü alıp kaydediniz.
12 | 3-headers.txt dosyasına main.py deki ListWindowNames fonksiyonu kullanarak yakalanacak ekranın ismini ekleyiniz. Birden fazla ekran ismi varsa alt alta ekleyiniz.
13 | 4-stone.txt dosyasına kesmek istediğiniz metinlerin ekran götüntülerinin isimlerini satır satır ekleyiniz. ( a.jpg , b.jpg gibi)
14 | 5-Başlatmak için "Start" , Test için "Test" , ekran isimleri için "listWindowNames" fonksiyonunu yorumdan çıkarınız.
15 | 16 | 17 | Kaynak: 18 | https://www.youtube.com/watch?v=KecMlLUuiE4&list=PL1m2M8LQlzfKtkKq2lK5xko4X-8EZzFPI 19 | -------------------------------------------------------------------------------- /char.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FatihAvdan/metin2-opencv-bot/e784f84efd0fbb21d27eca00ac3f3372735a97ba/char.jpg -------------------------------------------------------------------------------- /headers.txt: -------------------------------------------------------------------------------- 1 | Find window name with listallwindows function and paste like this 2 | Find window name with listallwindows function and paste like this 3 | Find window name with listallwindows function and paste like this 4 | Find window name with listallwindows function and paste like this -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from math import pi 2 | from operator import truediv 3 | from string import printable 4 | from windowcapture import WindowCapture 5 | import cv2 as cv 6 | import numpy as np 7 | import os 8 | import os.path 9 | import win32api, win32con 10 | from numpy.lib.shape_base import tile 11 | from PIL import ImageGrab 12 | from windowcapture import WindowCapture 13 | import keyboard 14 | import time 15 | from math import sqrt 16 | import random 17 | import win32gui, win32ui, win32con 18 | 19 | 20 | def euqli_dist(p, q, squared=False): 21 | # Calculates the euclidean distance, the "ordinary" distance between two 22 | # points 23 | # 24 | # The standard Euclidean distance can be squared in order to place 25 | # progressively greater weight on objects that are farther apart. This 26 | # frequently used in optimization problems in which distances only have 27 | # to be compared. 28 | if squared: 29 | return ((p[0] - q[0]) ** 2) + ((p[1] - q[1]) ** 2) 30 | else: 31 | return sqrt(((p[0] - q[0]) ** 2) + ((p[1] - q[1]) ** 2)) 32 | 33 | def closest(cur_pos, positions): 34 | low_dist = float('inf') 35 | closest_pos = None 36 | for pos in positions: 37 | dist = euqli_dist(cur_pos,pos) 38 | if dist < low_dist: 39 | low_dist = dist 40 | closest_pos = pos 41 | return closest_pos 42 | 43 | 44 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 45 | 46 | # List all windows headers for headers.txt 47 | def ListWindowNames(): 48 | def winEnumHandler(hwnd, ctx): 49 | if win32gui.IsWindowVisible(hwnd): 50 | print(win32gui.GetWindowText(hwnd)) 51 | win32gui.EnumWindows(winEnumHandler, None) 52 | 53 | 54 | def click(x,y): 55 | win32api.SetCursorPos((x,y)) 56 | win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0) 57 | time.sleep(0.02) #This pauses the script for 0.02 seconds 58 | win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0) 59 | 60 | 61 | def findClickPositions(needle_img_path, haystack_img, threshold, debug_mode=None): 62 | 63 | # https://docs.opencv.org/4.2.0/d4/da8/group__imgcodecs.html 64 | 65 | needle_img = cv.imread(needle_img_path, cv.IMREAD_UNCHANGED) 66 | locations = [] 67 | # Save the dimensions of the needle image 68 | 69 | needle_w = needle_img.shape[1] 70 | needle_h = needle_img.shape[0] 71 | 72 | 73 | 74 | 75 | # There are 6 methods to choose from: 76 | # TM_CCOEFF, TM_CCOEFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_SQDIFF, TM_SQDIFF_NORMED 77 | method = cv.TM_CCOEFF_NORMED 78 | result = cv.matchTemplate(haystack_img, needle_img, method) 79 | 80 | # Get the all the positions from the match result that exceed our threshold 81 | locations = np.where(result >= threshold) 82 | locations = list(zip(*locations[::-1])) 83 | # print(locations) 84 | 85 | # You'll notice a lot of overlapping rectangles get drawn. We can eliminate those redundant 86 | # locations by using groupRectangles(). 87 | # First we need to create the list of [x, y, w, h] rectangles 88 | rectangles = [] 89 | for loc in locations: 90 | rect = [int(loc[0]), int(loc[1]), needle_w, needle_h] 91 | # Add every box to the list twice in order to retain single (non-overlapping) boxes 92 | rectangles.append(rect) 93 | rectangles.append(rect) 94 | # Apply group rectangles. 95 | # The groupThreshold parameter should usually be 1. If you put it at 0 then no grouping is 96 | # done. If you put it at 2 then an object needs at least 3 overlapping rectangles to appear 97 | # in the result. I've set eps to 0.5, which is: 98 | # "Relative difference between sides of the rectangles to merge them into a group." 99 | rectangles, weights = cv.groupRectangles(rectangles, groupThreshold=1, eps=0.5) 100 | #print(rectangles) 101 | 102 | points = [] 103 | if len(rectangles): 104 | #print('Found needle.') 105 | 106 | line_color = (0, 255, 0) 107 | line_type = cv.LINE_4 108 | marker_color = (255, 0, 255) 109 | marker_type = cv.MARKER_CROSS 110 | 111 | # Loop over all the rectangles 112 | for (x, y, w, h) in rectangles: 113 | 114 | # Determine the center position 115 | center_x = x + int(w/2) 116 | center_y = y + int(h/2) 117 | # Save the points 118 | points.append((center_x, center_y)) 119 | 120 | if debug_mode == 'rectangles': 121 | # Determine the box position 122 | top_left = (x, y) 123 | bottom_right = (x + w, y + h) 124 | # Draw the box 125 | cv.rectangle(haystack_img, top_left, bottom_right, color=line_color, 126 | lineType=line_type, thickness=2) 127 | elif debug_mode == 'points': 128 | # Draw the center point 129 | cv.drawMarker(haystack_img, (center_x, center_y), 130 | color=marker_color, markerType=marker_type, 131 | markerSize=20, thickness=2) 132 | 133 | #if debug_mode: 134 | # cv.imshow('Matches', haystack_img) 135 | # cv.waitKey() 136 | # cv.imwrite('result_click_point.jpg', haystack_img) 137 | 138 | return points 139 | 140 | 141 | def tryAllHeaders(): 142 | file = open("headers.txt","r") 143 | headers = [] 144 | for header in file: 145 | clearheader = header.replace('\n','') 146 | headers.append(clearheader) 147 | # print(headers) 148 | for header in headers: 149 | try: 150 | wincap = WindowCapture(header) 151 | return header 152 | break 153 | except: 154 | continue 155 | 156 | def findCorrectStone(wincap,header,stoneTreshold): 157 | file = open("stones.txt","r") 158 | stones = [] 159 | for stone in file: 160 | clearstone = stone.replace('\n','') 161 | stones.append(clearstone) 162 | for stone in stones: 163 | screen = wincap.get_screenshot() 164 | points = findClickPositions(stone, screen,stoneTreshold, debug_mode='points' ) 165 | if (len(points)>0): 166 | return stone 167 | break 168 | 169 | def urielPass(Uriel,missingWindowHeight): 170 | urielX = Uriel[0][0] 171 | urielY = Uriel[0][1]+missingWindowHeight 172 | urielPossibleCords=[(urielX,urielY+100),(urielX+100,urielY+100),(urielX+250,urielY+100), 173 | (urielX,urielY+200),(urielX+100,urielY+200),(urielX+250,urielY+200)] 174 | randomSelect = random.choice(urielPossibleCords) 175 | click(randomSelect[0],randomSelect[1]) 176 | time.sleep(1) 177 | 178 | def Start(): 179 | char = "char.jpg" 180 | uriel = 'uriel.jpg' 181 | i = 0 182 | missingWindowHeight = 110 183 | stoneTreshold = 0.6 184 | playerTreshold = 0.6 185 | urielTreshold = 0.6 186 | pause = True 187 | while keyboard.is_pressed('end') == False: 188 | if (keyboard.is_pressed('f12') == True): 189 | pause = True 190 | print("Bot Started") 191 | time.sleep(1) 192 | 193 | if pause !=True: 194 | time.sleep(1) 195 | 196 | if pause: 197 | # Try All Headers 198 | header = tryAllHeaders() 199 | wincap = WindowCapture(header) 200 | 201 | # Find Correct Stone 202 | correctStone = findCorrectStone(wincap,header,stoneTreshold) 203 | 204 | while pause: 205 | 206 | if (keyboard.is_pressed('f12') == True): 207 | pause = False 208 | print("Bot Stopped") 209 | time.sleep(1) 210 | break 211 | 212 | if (keyboard.is_pressed('end') == True): 213 | break 214 | 215 | try: 216 | 217 | screen = wincap.get_screenshot() 218 | 219 | StonePoints = findClickPositions(correctStone, screen,stoneTreshold) 220 | if(i%10 ==0): 221 | playerLocation = findClickPositions(char, screen,playerTreshold) 222 | if(i%100 ==0): 223 | Uriel = findClickPositions(uriel, screen,urielTreshold) 224 | if(len(Uriel)>0): 225 | isActive = True 226 | while isActive: 227 | screen = wincap.get_screenshot() 228 | Uriel = findClickPositions(uriel, screen,urielTreshold) 229 | if (len(Uriel)>0): 230 | urielPass(Uriel,missingWindowHeight) 231 | else: 232 | isActive=False 233 | 234 | stoneX = StonePoints[0][0] 235 | stoneY = StonePoints[0][1]+missingWindowHeight 236 | 237 | playerX = playerLocation[0][0] 238 | playerY = playerLocation[0][1]+missingWindowHeight 239 | 240 | if(i%20 ==0): 241 | print("I'm Going To The Stone Nearest To You") 242 | 243 | 244 | closestStone = [] 245 | closestStone.append(closest((playerX, playerY), StonePoints)) 246 | 247 | closestStoneX = closestStone[0][0] 248 | closestStoneY = closestStone[0][1]+missingWindowHeight 249 | 250 | click(closestStoneX,closestStoneY) 251 | 252 | i = i+1 253 | 254 | except: 255 | i = i+1 256 | Uriel =[] 257 | if(i%100 ==0): 258 | Uriel = findClickPositions(uriel, screen,urielTreshold, debug_mode='rectangles') 259 | if(len(Uriel)>0): 260 | isActive = True 261 | while isActive: 262 | screen = wincap.get_screenshot() 263 | Uriel = findClickPositions(uriel, screen,urielTreshold, debug_mode='rectangles') 264 | if (len(Uriel)>0): 265 | urielPass(Uriel,missingWindowHeight) 266 | else: 267 | isActive=False 268 | 269 | StonePoints = findClickPositions(correctStone, screen,stoneTreshold, debug_mode='rectangles' ) 270 | playerLocation = findClickPositions(char, screen,playerTreshold, debug_mode='points') 271 | if(len(StonePoints)>0): 272 | stoneX = StonePoints[0][0] 273 | stoneY = StonePoints[0][1]+missingWindowHeight 274 | click(stoneX,stoneY) 275 | else: 276 | click(500,500) 277 | 278 | if(len(playerLocation)==0): 279 | print('Cant find Character') 280 | if(len(StonePoints)==0): 281 | print('Cant Find Stone') 282 | 283 | 284 | def Test(): 285 | char = "char.jpg" 286 | i = 0 287 | missingWindowHeight = 110 288 | stoneTreshold = 0.6 289 | playerTreshold = 0.6 290 | 291 | #Try All headers 292 | header = tryAllHeaders() 293 | wincap = WindowCapture(header) 294 | # Find Correct Stone 295 | correctStone = findCorrectStone(wincap,header,stoneTreshold) 296 | loop_time = time.time() 297 | while keyboard.is_pressed('end') == False: 298 | try: 299 | # FPS printer 300 | fps = format(1 / (time.time() - loop_time)) 301 | loop_time = time.time() 302 | print(fps) 303 | 304 | screen = wincap.get_screenshot() 305 | 306 | StonePoints = findClickPositions(correctStone, screen, stoneTreshold, debug_mode='rectangles') 307 | playerLocation = findClickPositions(char, screen, playerTreshold, debug_mode='points') 308 | cv.imshow("Points",screen) 309 | cv.waitKey(5) 310 | i+=1 311 | 312 | except: 313 | print("Cant find stone or character") 314 | 315 | 316 | # ListWindowNames() 317 | # Start() 318 | Test() 319 | 320 | 321 | 322 | 323 | 324 | 325 | -------------------------------------------------------------------------------- /metin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FatihAvdan/metin2-opencv-bot/e784f84efd0fbb21d27eca00ac3f3372735a97ba/metin.jpg -------------------------------------------------------------------------------- /stones.txt: -------------------------------------------------------------------------------- 1 | metin.jpg -------------------------------------------------------------------------------- /uriel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FatihAvdan/metin2-opencv-bot/e784f84efd0fbb21d27eca00ac3f3372735a97ba/uriel.jpg -------------------------------------------------------------------------------- /windowcapture.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import win32gui, win32ui, win32con 3 | 4 | 5 | 6 | class WindowCapture: 7 | 8 | # properties 9 | w = 0 10 | h = 0 11 | hwnd = None 12 | cropped_x = 0 13 | cropped_y = 0 14 | offset_x = 0 15 | offset_y = 0 16 | 17 | # constructor 18 | def __init__(self, window_name): 19 | # find the handle for the window we want to capture 20 | self.hwnd = win32gui.FindWindow(None, window_name) 21 | if not self.hwnd: 22 | raise Exception('Window not found: {}'.format(window_name)) 23 | 24 | # get the window size 25 | window_rect = win32gui.GetWindowRect(self.hwnd) 26 | self.w = window_rect[2] - window_rect[0]-150 27 | self.h = window_rect[3] - window_rect[1]-55 28 | 29 | # account for the window border and titlebar and cut them off 30 | border_pixels = 1 31 | titlebar_pixels = 100 32 | self.w = self.w - (border_pixels * 2) 33 | self.h = self.h - titlebar_pixels - border_pixels 34 | self.cropped_x = border_pixels 35 | self.cropped_y = titlebar_pixels 36 | 37 | # set the cropped coordinates offset so we can translate screenshot 38 | # images into actual screen positions 39 | self.offset_x = window_rect[0] + self.cropped_x 40 | self.offset_y = window_rect[1] + self.cropped_y 41 | 42 | def get_screenshot(self): 43 | 44 | # get the window image data 45 | wDC = win32gui.GetWindowDC(self.hwnd) 46 | dcObj = win32ui.CreateDCFromHandle(wDC) 47 | cDC = dcObj.CreateCompatibleDC() 48 | dataBitMap = win32ui.CreateBitmap() 49 | dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h) 50 | cDC.SelectObject(dataBitMap) 51 | cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY) 52 | 53 | # convert the raw data into a format opencv can read 54 | #dataBitMap.SaveBitmapFile(cDC, 'debug.bmp') 55 | signedIntsArray = dataBitMap.GetBitmapBits(True) 56 | img = np.fromstring(signedIntsArray, dtype='uint8') 57 | img.shape = (self.h, self.w, 4) 58 | 59 | # free resources 60 | dcObj.DeleteDC() 61 | cDC.DeleteDC() 62 | win32gui.ReleaseDC(self.hwnd, wDC) 63 | win32gui.DeleteObject(dataBitMap.GetHandle()) 64 | 65 | # drop the alpha channel, or cv.matchTemplate() will throw an error like: 66 | # error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() 67 | # && _img.dims() <= 2 in function 'cv::matchTemplate' 68 | img = img[...,:3] 69 | 70 | # make image C_CONTIGUOUS to avoid errors that look like: 71 | # File ... in draw_rectangles 72 | # TypeError: an integer is required (got type tuple) 73 | # see the discussion here: 74 | # https://github.com/opencv/opencv/issues/14866#issuecomment-580207109 75 | img = np.ascontiguousarray(img) 76 | 77 | return img 78 | 79 | # find the name of the window you're interested in. 80 | # once you have it, update window_capture() 81 | # https://stackoverflow.com/questions/55547940/how-to-get-a-list-of-the-name-of-every-open-window 82 | def list_window_names(self): 83 | def winEnumHandler(hwnd, ctx): 84 | if win32gui.IsWindowVisible(hwnd): 85 | print(hex(hwnd), win32gui.GetWindowText(hwnd)) 86 | win32gui.EnumWindows(winEnumHandler, None) 87 | 88 | # translate a pixel position on a screenshot image to a pixel position on the screen. 89 | # pos = (x, y) 90 | # WARNING: if you move the window being captured after execution is started, this will 91 | # return incorrect coordinates, because the window position is only calculated in 92 | # the __init__ constructor. 93 | def get_screen_position(self, pos): 94 | return (pos[0] + self.offset_x, pos[1] + self.offset_y) 95 | --------------------------------------------------------------------------------