Познакомился с World of Warcraft очень давно и люблю его весь, но одна вещь больше всего не давала мне покоя — рыбная ловля. Это нудное повторяющееся действие, где ты просто нажимаешь на кнопку рыбной ловли и тыкаешь на поплавок раз в 5-15 секунд. Мой навык разработки рос, а ситуация с рыбной ловле так и не улучшалась с каждым годом что я играл, поэтому я решил убить двух зайцев сразу — начать осваивать python и всё же сделать бота для себя.

Я уже видел ботов, которые умеют ловить рыбу, работающие в свернутом режиме не перехватывая управления над компьютером. Также я знаю насколько беспощадны близард по вопросам банов читеров. Изменение данных в оперативной памяти легко определяется встроенным античитом. Ну и последнее — на мак я не нашёл ни одного бота.

Поэтому я решил закрыть все эти вопросы разом и сделать бота, который будет перехватывать управление мыши, кидать поплавок, и тыкать на него на экране когда нужно. Как я полагал python располагает широким выбором инструментов для автоматизации таких штук, и не ошибся.

Немножечко погуглив, я нашёл OpenCV, в котором есть поиск шаблону с не сложным гайдом. С помощью него мы и будем искать наш поплавок на экране.

Сперва мы должны получить саму картинку с поплавком. Ищем и находим библиотеку pyscreenshot с гайдом как делать скриншоты, немножечко редактируем:

import pyscreenshot as ImageGrab

screen_size = None
screen_start_point = None
screen_end_point = None

# Сперва мы проверяем размер экрана и берём начальную и конечную точку для будущих скриншотов
def check_screen_size():
	print "Checking screen size"
	img = ImageGrab.grab()
	# img.save('temp.png')
	global screen_size
	global screen_start_point
	global screen_end_point

	# я так и не смог найти упоминания о коэффициенте в методе grab с параметром bbox, но на моем макбуке коэффициент составляет 2. то есть при создании скриншота с координатами x1=100, y1=100, x2=200, y2=200), размер картинки будет 200х200 (sic!), поэтому делим на 2
	coefficient = 2
	screen_size = (img.size[0] / coefficient, img.size[1] / coefficient) 

	# берем примерно девятую часть экрана примерно посередине.
	screen_start_point = (screen_size[0] * 0.35, screen_size[1] * 0.35)
	screen_end_point = (screen_size[0] * 0.65, screen_size[1] * 0.65)
	print ("Screen size is " + str(screen_size))

def make_screenshot():
	print 'Capturing screen'
	screenshot = ImageGrab.grab(bbox=(screen_start_point[0], screen_start_point[1], screen_end_point[0], screen_end_point[1]))
	# сохраняем скриншот, чтобы потом скормить его в OpenCV
	screenshot_name = 'var/fishing_session_' + str(int(time.time())) + '.png'
	screenshot.save(screenshot_name)
	return screenshot_name

def main():
	check_screensize()
	make_screenshot()


Получаем примерно следующую картинку:



Далее — найти поплавок. Для этого у нас должен быть сам шаблон поплавка, который мы ищем. После сотни попыток я всё таки подобрал те, которые OpenCV определяет лучше всего. Вот они:



Берём код из ссылки выше, добавляем цикл и наши шаблоны:

import cv2
import numpy as np
from matplotlib import pyplot as plt

def find_float(screenshot_name):
	print 'Looking for a float'
	for x in range(0, 7):
		# загружаем шаблон
		template = cv2.imread('var/fishing_float_' + str(x) + '.png', 0)
		# загружаем скриншот и изменяем его на чернобелый
		src_rgb = cv2.imread(screenshot_name)
		src_gray = cv2.cvtColor(src_rgb, cv2.COLOR_BGR2GRAY)
		# берем ширину и высоту шаблона
		w, h = template.shape[::-1]
		# магия OpenCV, которая и находит наш темплейт на картинке
		res = cv2.matchTemplate(src_gray, template, cv2.TM_CCOEFF_NORMED)
		# понижаем порог соответствия нашего шаблона с 0.8 до 0.6, ибо поплавок шатается и освещение в локациях иногда изменяет его цвета, но не советую ставить ниже, а то и рыба будет похожа на поплавок
		threshold = 0.6
		# numpy фильтрует наши результаты по порогу
		loc = np.where( res >= threshold)
		# выводим результаты на картинку
		for pt in zip(*loc[::-1]):
		    cv2.rectangle(src_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
		# и если результаты всё же есть, то возвращаем координаты и сохраняем картинку
		if loc[0].any():
			print 'Found float at ' + str(x) 
			cv2.imwrite('var/fishing_session_' + str(int(time.time())) + '_success.png', src_rgb)
			return (loc[1][0] + w / 2) / 2, (loc[0][0] + h / 2) / 2 # опять мы ведь помним, что макбук играется с разрешениями? поэтому снова приходится делить на 2

def main():
	check_screensize()
	img_name = make_screenshot()
	find_float(img_name)
	

Итак, у нас есть координаты поплавка, двигать курсор мыши умеет autopy буквально с помощью одной строки, подставляем свои координаты:

import autopy

def move_mouse(place):
	x,y = place[0], place[1]
	print("Moving cursor to " + str(place))
	autopy.mouse.smooth_move(int(screen_start_point[0]) + x , int(screen_start_point[1]) + y)

def main():
	check_screensize()
	img_name = make_screenshot()
	cords = find_float(img_name)
	move_mouse(cords)


Курсор на поплавке и теперь самое интересное — как же узнать когда нужно нажать? Ведь в самой игре поплавок подпрыгивает и издает звук будто что-то плюхается в воду. После тестов с поиском картинки я заметил, что OpenCV приходится подумать пол секунды прежде чем он возвращает результат, а поплавок прыгает даже быстрее и изменение картинки мы врядли сможем определить с помощью OpenCV, значит будем слушать звук. Для этой задачки мне пришлось поковырять разные решения и остановился вот над этим — пример использования гугл апи для распознавания голоса, оттуда мы возьмём код, который считывает звук.

import pyaudio
import wave
import audioop
from collections import deque
import time
import math

def listen():
	print 'Listening for loud sounds...'
	CHUNK = 1024
	FORMAT = pyaudio.paInt16
	CHANNELS = 2
	RATE = 18000 # битрейт звука, который мы хотим слушать
	THRESHOLD = 1200  # порог интенсивности звука, если интенсивность ниже, значит звук по нашим меркам слишком тихий
	SILENCE_LIMIT = 1  # длительность тишины, если мы не слышим ничего это время, то начинаем слушать заново

	# открываем стрим
	p = pyaudio.PyAudio()

	stream = p.open(format=FORMAT,
	                channels=CHANNELS,
	                rate=RATE,
	                # output=True, # на мак ос нет возможности слушать output, поэтому мне пришлось прибегнуть к использованию <a href="https://github.com/RogueAmoeba/Soundflower-Original">Soundflower</a>, который умеет перенаправлять канал output в input, таким образом мы перехватываем звук игры будто это микрофон 
	                input=True,
	                frames_per_buffer=CHUNK) 
	cur_data = ''
	rel = RATE/CHUNK
	slid_win = deque(maxlen=SILENCE_LIMIT * rel)
	
	# начинаем слушать и по истечении 20 секунд (столько максимум длится каждый заброс поплавка), отменяем нашу слушалку.
	success = False
	listening_start_time = time.time()
	while True:
		try:
			cur_data = stream.read(CHUNK)
			slid_win.append(math.sqrt(abs(audioop.avg(cur_data, 4))))
			if(sum([x > THRESHOLD for x in slid_win]) > 0):    
				print 'I heart something!'
				success = True
				break
			if time.time() - listening_start_time > 20:
				print 'I don\'t hear anything during 20 seconds!'
				break
		except IOError:
			break
	# обязательно закрываем стрим
	stream.close()
	p.terminate()
	return success

def main():
	check_screensize()
	img_name = make_screenshot()
	cords = find_float(img_name)
	move_mouse(cords)
	listen()

Последнее, что осталось — споймать саму рыбку, когда мы услышим звук, снова используем autopy:

def snatch():
	print('Snatching!')
	autopy.mouse.click(autopy.mouse.RIGHT_BUTTON)

def main():
	check_screensize()
	img_name = make_screenshot()
	cords = find_float(img_name)
	move_mouse(cords)
	if listen():
		snatch()

По моим тестам, что я оставлял бота рыбачить по ночам, за неделю такого абуза он сделал около 7000 бросков и словил поймал около 5000 рыб. Погрешность 30% вызвана тем, что иногда не получается отловить звук или найти поплавок из-за освещения или поворотов поплавка. Но результатом я доволен — впервые попробовал python, сделал бота и сэкономил себе кучу времени.

Полный код можно посмотреть тут, но очень советую не использовать для абуза фишинга, ибо автоматизация процессов игры нарушает пользовательское соглашение и можно получить бан.

Буду рад любым комментариям.

Комментарии (78)


  1. LoadRunner
    14.08.2017 12:53
    +2

    После прочтения вспоминается старый-добрый UOpilot. Интересно, он есть на Маке?


    1. kio_tk Автор
      14.08.2017 13:15

      Он умел распознавать картинку?


      1. LoadRunner
        14.08.2017 13:18

        Цвет пикселя под курсором. Или в заданных координатах тоже — уже не помню.


        1. kio_tk Автор
          14.08.2017 13:29

          Ну вот как-то сложновато выходит, ведь освещение меняется и поплавок качается, и свой алгоритм по вычислению поплавка — немножко другая задача.
          Я думал что проще вычислять изменения курсора, ибо когда наводишь на поплавок, он меняется иконку на крючок, но не нашел апи курсора и не курсор сам в скриншот не попадает.


          1. LoadRunner
            14.08.2017 13:38

            Ну для работы с цветом там довольно хорошие функции:


          1. Goodkat
            14.08.2017 14:19

            Можно было вебкамерой курсор снимать, а мышку механически двигать — только хардкор, заодно и от античитов должно помочь.

            Ещё периодически «ходить покурить», сворачивать игру, переключаться на фэйсбучег и т.п — когда-то давно, уже не помню, для какой игры, я так записывал макросы родной майкрософтской программулиной.


            1. kio_tk Автор
              14.08.2017 14:58
              +1

              Для этого нужно будет ставить зеркало перед маком, чтобы он смог видеть своей встроенной вебкамерой что происходит на экране. Да и в электронике я слаб, так что механическое изменение положения мышки для меня сложнее, хотя идея механически использовать трекпад несколько проще, вам не кажется?


              1. Rampages
                14.08.2017 19:15

                Все же проще купить USB Web-камеру с драйверами под мак, чем ставить зеркало. Насчет трекпада не уверен, с шариковой мышкой может быть тогда еще проще. Можно кстати взять ездящую платформу как у многих 3D принтеров, установить вместо сопла сенсор мыши (лазерный/оптический) и двигать платформу относительно сенсора.


                1. Moon_darker
                  14.08.2017 23:39
                  +1

                  Хардкор хардкором, но проще взять какой-нибудь микроконтроллер с поддержкой USB и симулировать обычную HID мышку


                  1. Rampages
                    15.08.2017 03:37

                    ?\_(?)_/? Ну мы не ищем легких путей и любим создавать себе проблемы с заделом на будущее


          1. edge790
            14.08.2017 18:26

            Сразу вспомнил была статья про то, как сделали бота для аукциона и почты в WoW используя AutoIT и внутриигровой аддон: https://habrahabr.ru/post/113258/


      1. equand
        14.08.2017 19:49

        AutoIT умеет распозновать разницу. Я использовал его в Линейке, находил статическую позицию и использовал как торгового бота с автоматизацией ответа. Вещи за ночь продавал :)


  1. kovalevsky
    14.08.2017 13:19
    +3

    Хорошая идея, но Вам прилетит бан в следующем «бан-вейве». Вы действительно думаете, что до того, как лезть в память, никто не искал поплавок на экране? :)
    Я уверен Вы попались сразу же по движению курсора, которое не характерно реальному человеку, а идёт ровно от Х1, Y1 до X2, Y2.


    1. kio_tk Автор
      14.08.2017 13:26

      Конечно искали, но статьи такой я не нашел и подумал было бы интересно написать — мало ли в каких других ситуациях или играх понадобится такая штука.
      Можно про «бан-вейв» подробнее? Уже прошло пару месяцев как я последний раз его запускал и пока ничего страшного не произошло.


      1. foldr
        14.08.2017 13:40
        +2

        Да множество случаев. Устраивают не часто, может и полгода полет будет успешный, как повезет


        1. kio_tk Автор
          14.08.2017 13:50
          +1

          Тогда это будет наконец достойный повод завязать. Но пока бана нет — это того стоило


          1. kovalevsky
            14.08.2017 14:26
            +1

            Сделайте второй аккаунт, создайте гильдию и прячьте деньги в гильд-банк. Аккаунт с ботом, скорее всего, забанят, а вот деньги из ГБ не забирают :)


            1. kio_tk Автор
              14.08.2017 14:50

              Если речь о деньгах, то заработал всего тысячу-полтора за эту неделю ночных абузов, так что не очень выгодно, но ведь маунта, ачивки и игрушки не скинешь… (Только не смейтесь)


              1. kovalevsky
                14.08.2017 15:54
                +1

                Второй аккаунт на это же имя и слёзно просить саппорт объеденить их


    1. Desprit
      14.08.2017 14:50

      Добавить немного шума в итоговые координаты + сделать шанс на «случайный» промах по поплавку. Чем это будет отличаться от реального человека? Разумеется, за 20 часов мимо может много раз кто-то пробежать, заговорить, не получиться ответ и кинуть репорт, но это уже вообще другой разговор)


      1. dimkss
        14.08.2017 17:31

        Еще можно двигать курсор с ускорение. И добавить редкие рандомные движения курсора по экрану в случайные отрезки времени. И время от времени открывать инвентори, или статы какие-то.


        1. kio_tk Автор
          14.08.2017 18:47
          +3

          И переписку с другим ботом, которого нужно будет сделать специально чтобы поддерживать «жизнь» нашего рыболова. Благо достаточно всяких апи с говорящими ботами.


      1. bigbrotherwatchingyou
        15.08.2017 08:49
        +2

        заказать внутриигровую майку:
        «1. Только пришел
        2. Не клюёт
        3. На червя
        4. Водку буду»


  1. bro-dev
    14.08.2017 13:44

    Недавно была статья про openai играет в доту, тоже на питоне. Но там задача значительно сложнее, как они решают эту проблему определения где что на экране? мб есть идеи?


    1. kio_tk Автор
      14.08.2017 14:02

      Я знаю, что Source 2 довольно не новый движок, и его уже перековыряли со всех сторон. Есть боты же и для CS:GO и для той же доты, которые взрывают бомбы минеров по нужному количеству хп у противников в любой части карты.
      Так что вполне возможно и внедрение. То, что было на The international, я думаю это обычное подключение бота через апи самого Source 2 — есть же обычные боты и в доте и в CS:GO.


      Кроме того — я же не претендую на статью об искусственном интеллекте. Возможно их бот работает на очень мощном железе и его писали ребята, которые делают очень большие проекты на openai. Тогда они вполне могли бы и картинку обрабатывать на уровне повыше обычного метчинга темплейтов. Как раз совсем недавно общался на эту тему со знакомым, который предлагал мне сделать на python искусственный интеллект, который смог бы играть в Gravity Defied используя только картинку, но я пока не решаюсь переписывать Gravity Defied на python.


  1. Myosotis
    14.08.2017 14:12

    Чтобы уменьшилась вероятность бана, я бы добавила рандомные перерывы в рыбалке. Не может обычный человек играть без перерыва на пару минут всю ночь.


    1. Demogor
      14.08.2017 14:31

      Тогда уже и рандомные перемещения курсора и паузы между кликами.
      Ну и по поводу «всей ночи» — Вы сильно недооцениваете школьников или корейских игроков)


      1. Goodkat
        14.08.2017 23:47

        Уже не школьники были и не корейцы, но так с братом и играли, с вечера я (мне утром нужно было на работу вставать), часа в два ночи будил брата, он потом играл до утра. Не в WoW, правда.


    1. arheops
      15.08.2017 03:31
      +1

      Да ну? Не может обычный человек в РЕАЛЕ ловить рыбу по 10 часов каждую ночь, в течении последних 10 лет, включая снег и дождь(у нас так пенсионеры делают).


  1. Demogor
    14.08.2017 14:29

    В свое время делал очень странного бота для Rift.
    У рыбалки там несколько стадий. На тот момент был готовый бот, который использовал лог-файл игры для определения этих стадий, но, насколько помню, самая важная стадия(поклевка) в логе не отображалась и бот использовал какое-то решение, которое по какой-то причине меня не устроило(а может, просто захотелось написать свой собственный, не помню).
    В итоге получилось забавно — я в силу своих скромных способностей в реверсе приложений умудрился найти только функцию, отвечающую за воспроизведение звуковых файлов. В итоге перехватывал эту функцию и, в случае воспроизведения соотв. поклевке файла, производил необходимые действия.
    В итоге получилось что-то средней монструозности, но, на удивление, более-менее эффективное: за относительно короткий срок вкачал рыбалку на максималку и еще какое-то время неплохо зарабатывал на рыбе.

    Но вообще лучший бот, который я когда-либо использовал — https://www.solarstrike.net/ для Runes of Magic. Логика на xml+lua шикарна, бот предоставляет базовый набор функций+куча функционала и готовых waypoint'ов от других пользователей + никто не мешает писать свою логику. Чего там только народ не ваял)


  1. Abyrvalgov
    14.08.2017 14:32
    +1

    Воспользуйтесь опытом поколений! Добавьте случайные (именно случайные, а не луповые) вариации в движении курсора, в скорости реакции, во всех возможных действиях. А то ведь отловят.


  1. LoadRunner
    14.08.2017 15:15
    -2

    Мне тут товарищ подкинул идею попроще: труд детей. Своих, разумеется.
    В армии с детьми проще (для командиров).


    1. questor
      15.08.2017 09:24

      Неспортивно предлагать такое на ресурсе программистов.


  1. igormich88
    14.08.2017 15:29

    Статья интересная, но думаю лучше добавить предупреждение в начале (или в конце) что эти действия нарушают пользовательское соглашение.
    PS интересно есть ли смысл делать полностью внешнего бота (с веб-камерой и аппаратным эмулятором мыши) для того что бы затруднить его обнаружение?


    1. kio_tk Автор
      14.08.2017 15:46
      +1

      Done.

      У нас есть соседняя компания, которая занимается электроникой. Они думают, что мы занимаемся фигнёй, программируя всякие штуки, ведь все это можно сделать с помощью микросхем, механики и физики. Мы думаем то же самое о них, ведь зачем брать в руки паяльник, если клавиатуры достаточно. Так что может и есть смысл, если так проще для конкретного разработчика

      На самом деле у меня не было задачи полностью скрыть использование бота, но избежать автоматического бана античита, у которого неопровержимые доказательства изменения памяти игры. Конечно запись координат курсора — тоже неопровержимое доказательство, но мне кажется это уже дикость следить за таким.


      1. igormich88
        14.08.2017 18:49

        Я просто думал на тему более хитрого бота — что бы ротацию крутить (последовательность способностей), распознавая иконки статуса персонажа, но опасения бана и лень оказались сильнее.


      1. DistortNeo
        15.08.2017 04:06

        В WoW настолько суровый античит? Я просто играл в Lineage 2 на офсервере — там использовался GameGuard, но исключительно как пассивное средство, мешающее работать ботам (на самом деле нет), но не стучащее о подозрительном софте и изменении памяти процесса на сервер.

        Ну а что касается от отслеживании координат и их рандомизации — мне кажется, это бесполезная затея. Игровые клиенты — толстые клиенты, они отправляют на сервер действия, а не движения мышки и клики. Возможно, античит логирует локально все движения мышки, но крайне маловероятно, что делает на их основе вердикт.

        Чисто теоретически, бота от человека можно отличить по паттернам действий, но простыми наборами правил здесь не отделаться из-за огромных объёмов данных и высокой сложности. Тут уже начинают работать методы BigData. Но вот использует ли Blizzard AI для обнаружения ботов — большой вопрос.


        1. kio_tk Автор
          15.08.2017 08:49

          В пользовательском соглашении есть пункт об наказании за автоматизации, а также пункт о разрешении сканирование компьютера и памяти на наличие всяких читерских штук. Насколько я знаю, делает это даже не сам клиент игры, а клиент батлнет, ибо он как раз и предлагает это соглашение, когда ставится. Хотя пруфов его деятельности у меня нет.


    1. Anarions
      14.08.2017 17:35

      Рыбачил с год, (ботом) каждую ночь, по 5-10 часов, без всяких предосторожностей (ибо на акк было пофиг), прошло три года — бан не прилетел.


  1. OlegPyatakov
    14.08.2017 18:14
    +1

    Делал ради интереса подобного бота на Sikuli (программа для автоматизации, с питоновским синтаксисом скриптов). На самом деле, все было очень похоже как в вашем примере, но с парой отличий:


    • Определение момента для подсекания реализовал через отслеживание изменения области кадра с найденным поплавком.
    • Строк кода вышло <100. Экономия, в основном, за счет уже готовых инструментов для поиска нужной части кадра и отказа от слежения за аудио.


    1. kio_tk Автор
      14.08.2017 18:54
      +1

      Но ведь поплавок и так качается — вы смотрели по сильному смещению поплавка? Значит у вас очень хорошо работала обработка кадра. Как я уже писал, на моем Intel Iris анализ каждого кадра в OpenCV с включенной игрой занимает 300-500мс (включая сам скриншот через ImageGrab), я посчитал это несколько неразумным в моем случае, поэтому взялся за звук.


      1. OlegPyatakov
        14.08.2017 19:29
        +1

        Да, отслеживал именно по длительному (>0,3 секунды) выходу поплавка из узкой зоны, где он обычно находится.


        Сейчас проверил быстродействие визуального поиска. На ноуте с Core i7 и мобильным Radeon HD 8530M на поиск элемента по всему экрану 1366x768 уходит около 0,03 секунды. Если сократить область поиска в 5-6 раз, т.е. до примерной области рыбалки в WoW, то время на поиск уменьшается вдвое.


  1. AlexBin
    14.08.2017 19:50

    Как альтернативный метод, еще можно по звуку поклева, он там один всего лишь и слышен четко.


    1. kio_tk Автор
      14.08.2017 19:56

      Вы точно дочитали статью?


      1. AlexBin
        14.08.2017 20:15

        Каюсь


  1. Dmitry_5
    14.08.2017 19:55
    +2

    Главное, не поймать рыбу-бота


  1. lxsmkv
    14.08.2017 22:11

    все логично, ботаем вовку и диаблу, а в освободившееся время лабаем ИскИн'а на StarCraft II API


    1. Goodkat
      14.08.2017 23:52

      А в диабле что ботать?


      1. lxsmkv
        15.08.2017 00:19

        фарм редких предметов я думаю… Хотя аукцион убрали, но и без него… неужели бегать по стотыщпятисотому разу по одним и тем же локациям раскидывая монстров направо и налево?


  1. b1ackbird
    14.08.2017 23:22
    -1

    Пробовал запустить скрипт Ваш, куча проблем с библиотеками питоновскими на Маке. Есть какой-то может магический девелопмент пакет для питона, о котором я не знаю? :)


  1. mrgloom
    15.08.2017 00:53

    Году эдак в 2007 с одногрупниками писали бота для ловли рыбы в Lineage и по-моему это был как раз UOpilot.


  1. vsb
    15.08.2017 09:05
    +2

    Делал бота, тоже для мака, правда на ObjC. Делал по-другому: после забрасывания водил мышкой по экрану и отслеживал изображение курсора. Как только курсор менялся: опять водил по экрану очерчивая границы курсора. Курсор на самом деле это ромб, как только чётко определял его координаты, ставил курсор на 1 пиксель выше верхней точки этого ромба и ждал. Когда курсор прыгает, этот ромб немного сдвигается вверх и курсор на небольшой момент меняется. В этот момент нажимал на поплавок. Работало на 100%. Самое сложное было быстро найти поплавок: если двигать мышку слишком быстро, то курсор мог не успеть измениться и можно было её «пролететь», если двигать слишком медленно, то не успевал найти прежде, чем начинает клевать. Распознавание изображений пытался сделать, но не осилил, к сожалению.

    Кстати ваш способ будет плохо работать, когда в одном месте рыбачат несколько человек. В идеале надо комбинировать ваш подход для определения вероятных точек нахождения поплавка и потом проверять курсором, где он находится на самом деле.


  1. White_Scorpion
    15.08.2017 09:48

    Если питон позволяет (не знаю ибо не знаком) картинку поплавка можно перевести в grayscale с последующим уменьшением разрядности цвета после чего будет проще отыскивать границы поплавка.