Давным давно, еще в те времена когда на персональных компьютерах жил MsDOS довелось играть в игру Settlers II. Игра меня тронула, и я с удовольствием провел наедине с ней несколько дней. Много позже прошел ее повторно, а затем и еще раз, и каждый раз несмотря на древность этой игры с удовольствием проводил время играя в нее. Не так давно увидел рекламу онлайн игры The Settlers Online и поддавшись ностальгии зарегистрировался в ней. Первым впечатлением был восторг, настолько все было похоже на полюбившееся мне Settlers II. Но радужная эйфория быстро прошла. Я не буду рассказывать в этой статье о всех плюсах и минусах, расскажу только об одном минусе — торговле. О самой игре более подробно вы можете прочитать в статье The Settlers: теперь Online.
Причина
Торговля в игре реализована так, что требует постоянного присутствия. Любой торговый лот выставляется всего на 10 минут, а затем становится недоступен для других игроков. Как следствие, для того что-бы торговля принесла хоть сколько нибудь ощутимую прибыль необходимо провести в торговом интерфейсе не один час.
Следствие
Так и родилась идея написать бота который будет выставлять лот автоматически.
Пишем бота
Так как на моем десктопе установлен Linux, в качестве инструментов разработки был выбран язык программирования Perl, и утилита управления курсором мыши xdotool.
И так приступим.
бот будет состоять из 2-х файлов:
- simple.trade.bot.pl — непосредственно сам бот.
- trade.guns.pl — файл с координатами кнопок и дополнительными данными для торговли выбранным товаром (в моем случае это были пушки продаваемые за футбольные мячи).
Начнем с файла trade.guns.pl. В этом файле содержится список HASH (именованных) массивов содержащих координаты кнопок которые необходимо нажать чтобы выставить лот, и другую дополнительную информацию.
(
{ x=>'288', y=>'852'}, # Кнопка [добавить]. Инициирует интерфейс выставления торгового лота
{ x=>'790', y=>'667'}, # Кнопка [выбрать]. Интерфейс выбора продаваемого товара.
{ x=>'1126', y=>'658'}, # Кнопка [искусные]. Выбор категории продаваемого товара.
{ x=>'871', y=>'594'}, # Кнопка [пушки]. Непосредственно выбор самого товара.
{ # Действия с бегунком выставляющим количество продаваемого товара.
# Приведенные ниже данные соответствуют 100 единицам товара.
# По умолчанию выставляется 400 единиц.
x=>'983', y=>'742', # Координаты нажатия ЛКМ.
dx=>'874', dy=>'750', # Координаты отжатия ЛКМ.
# Получаем 99 единиц.
inc=>{ # Данный элемент содержит:
x=>'1010', y=>'745', # Координаты кнопки увеличивающей значение бегунка на 1.
inc=>'1', # На сколько увеличить значение бегунка.
rnd=>'0', # Если не 0 то рандомное значение от 0 до rnd добавляется к inc.
timer => [ # Содержит поправку цены в зависимости от времени суток.
{ sh=>0, eh=>24, inc=>0 } # В данном случае поправка равна 0 на весь день.
]
}
},
{ x=>'1064', y=>'733', rx=>'20', ry=>'10'}, # Кнопка [ОК]. Завершает выбор товара для продажи.
{ x=>'1131', y=>'668', rx=>'10', ry=>'2'}, # Кнопка [выбрать]. Интерфейс выбора покупаемого товара.
# Так как необходимый товар находится в категории по умолчанию
# выбор категории опущен.
{ x=>'1016', y=>'548', rx=>'20', ry=>'10'}, # Кнопка [мячики]. Непосредственно выбор покупаемого товара.
{ # Действия с бегунком выставляющим количество покупаемого товара.
# Приведенные ниже данные соответствуют 100 единицам товара.
# По умолчанию выставляется 400 единиц.
x=>'988', y=>'743', # Координаты нажатия ЛКМ.
dx=>'804', dy=>'746', # Координаты отжатия ЛКМ.
# Получаем 1 единицу.
inc=>{ # Данный элемент содержит:
x=>'1010', y=>'745', # Координаты кнопки увеличивающей значение бегунка на 1.
inc=>'19', # На сколько увеличить значение бегунка.
rnd=>'15', # Если не 0 то рандомное значение от 0 до rnd добавляется к inc.
timer => [ # Содержит поправку цены в зависимости от времени суток.
# Если поправок несколько - то каждая последующая обладает более высоким приоритетом.
{ sh=>0, eh=>24, inc=>0 },# 0 на весь день.
{ sh=>0, eh=>8, inc=>8 },# +8 c 00.00 до 08.00.
{ sh=>8, eh=>9, inc=>4 },# +4 c 08.00 до 09.00.
{ sh=>9, eh=>10, inc=>2 } # +2 c 09.00 до 10.00.
]
}
},
{ x=>'1083', y=>'736', rx=>'20', ry=>'10' }, # Кнопка [ОК]. Завершает выбор товара для покупки.
{ x=>'961', y=>'596', rx=>'14', ry=>'12' } # Кнопка [применить]. Отправляет лот на торг.
);
В более детальном описании помимо того что дано в комментариях данный файл не нуждается. Поэтому приступим к рассмотрению simple.trade.bot.pl. Это и есть сам бот.
#!/usr/bin/perl
# После запуска скрипт дает пользователю 20 секунд для того чтобы он открыл окно с игрой и зашел в торговый интерфейс
sleep 20;
# Получаем список из файла с координатами в массив
my <hh user=g_trade> = do "trade.guns.pl";
# Далее главный цикл программы. Он бесконечен.
# Для прерывания работы программы перейдите в консоль и нажмите [Ctrl]+c.
while(){
# Пишем в консоль о постановке нового лота на торги.
print "NEXT TRADEn";
# Данный цикл последовательно проходит все элементы из массива с координатами.
for my $l_cur ( <hh user=g_trade> ){
sleep(1);
if( defined $l_cur->{dx} ){
# Если в текущем элементе определен ключ dx значит данный элемент - оперирует бегунком
# выставляющим колличество товара.
# Расчитываем количество выставляемого товара ($l_count).
# Добавляем статическую поправку к количеству товара.
my $l_count=$l_cur->{inc}{inc};
# Получаем текущее время. В ячейке №2 - часы.
my <hh user=l_curtime>=localtime(time);
# Перемещаем курсор в координаты x, y текущего элемента.
move($l_cur);
# Получаем поправку к цене зависящую от времени суток.
my $l_inc=0;
# Последовательно перебираем массив с поправками к цене.
for my $l_timer ( @{$l_cur->{inc}{timer}} ){
# Если текущее время попадает в заданный интервал, запоминаеми поправку.
$l_inc=$l_timer->{inc} if $l_curtime[2] >= $l_timer->{sh} && $l_curtime[2] <= $l_timer->{eh};
}
# Расчитываем рандомную поправку к количеству товара.
my $l_rnd=int(rand($l_cur->{inc}{rnd}));
# Добавляем вычисленые поправки к общему количеству товара.
$l_count+=$l_inc;
$l_count+=$l_rnd;
# Выводим в консоль данные о количестве товара и состовляющие из которых это количество складывается.
print "tSET: [$l_count] [$l_cur->{inc}{inc} + $l_up + $l_rnd($l_cur->{inc}{rnd})]n";
# Увеличеваем значение бегунка на величину поправки.
while ($l_count){
$l_count--;
clicktoxy($l_cur->{inc});
usleep(80);
}
}else{
# Если ключ dx не определен - значит это просто клик мыши по заданным в текущем елементе координатам.
clicktoxy($l_cur);
}
}
# Лот выставлен. Ожидаем 11 минут + рандомно от 0 до 4 минут (человек ведь никогда не ставит лот секунда в секунду).
my $l_time=11+int(rand(4));
my $count=0;
# По прошествии каждой минуты пошевеливаем курсором мыши чтобы система не уснула.
for($count=0;$count<$l_time;$count++){
sleep 60;
mousemove(400,500);
sleep 1;
mousemove(1400,500);
sleep 1;
}
# Ждем еще рандомное количество секунд от 0 до 60
sleep(int(rand(60)));
# Цикл возвращается в начало и выставляется новый лот
}
sub usleep{
# Функция дает задержку в млилисекундах (Не работает в Ms Windows).
my $l_ptr=shift;
$l_ptr*=1000;
`usleep $l_ptr`;
}
sub move{
# Функция принимает текущий элемент и перемещает курсор мыши по заданным в элементе координатам.
my $l_coord=shift;
mousemove($l_coord->{x},$l_coord->{y});
mousedown(1);
usleep(600);
mousemove($l_coord->{dx},$l_coord->{dy});
mouseup(1);
}
sub click{
# Функция производит клик ЛКМ в текущей позиции курсора.
mousedown(1);
mouseup(1);
}
sub clicktoxy{
# Функция принимает текущий элемент, перемещает курсор мыши по заданным в элементе координатам
# и производит клик ЛКМ.
my $l_coord=shift;
mousemove($l_coord->{x},$l_coord->{y});
mousedown(1);
mouseup(1);
usleep(300);
}
sub mousedown{
# Функция отдает команду утилите xdotool имитировать нажатие кнопки мыши.
# Принимает значения:
# 1 - левая кнопка мыши (ЛКМ)
# 2 - правая кнопка мыши (ПКМ)
my $l_key = shift;
if( $l_key ){
`xdotool mousedown $l_key`;
}
}
sub mouseup{
# Функция отдает команду утилите xdotool имитировать отжатие кнопки мыши.
# Принимает значения:
# 1 - левая кнопка мыши (ЛКМ)
# 2 - правая кнопка мыши (ПКМ)
my $l_key = shift;
if( $l_key ){
`xdotool mouseup $l_key`;
}
}
sub mousemove{
# Функция отдает команду утилите xdotool переместить курсор мыши по координатам x, y.
my $l_x = shift;
my $l_y = shift;
my $l_com='xdotool mousemove';
$l_com.=" $l_x $l_y";
`$l_com`;
}
Бот готов.
В завершение
Данный бот очень простой. Он пока еще не имеет множество необходимых возможностей. Вот краткий список идей как сделать бота более совершенным:
- Научить бота видеть с экрана. Для этого можно использовать ImageMagick.
- Научить бота настраиваться под разные разрешения экрана и разные браузеры. Поиск границ и кнопок с помощью утилиты compare из состава ImageMagick.
- Научить бота читать с экрана. Для этого можно использовать ImageMagick + gocr.
- Научить бота принимать сделки. Но так как в игре практикуется передача обманных сделок — бот прежде чем принять сделку должен ее проверить. Для этого можно использовать ImageMagick + gocr.
- Научить бота знать заранее все лоты выставленные в торговом интерфейсе. Лоты передаются браузеру в открытом виде как XML. Для перехвата можно использовать perl модуль Net::Pcap. Для поиска в торговом интерфейсе выбранного на этапе перехвата пакетов товара можно использовать ImageMagick + gocr.
Следует учесть
- Данный бот написан под Linux. Для того чтобы заставить его работать под Ms Windows необходимо использовать вместо xdotool аналог для Ms Windows. Возможно подойдет утилита autoit. Также в Ms Windows необходимо будет установить Perl.
- Файл с координатами trade.guns.pl рассчитан на конкретный товар (100 пушек меняем на 20 — 34 мяча), на мое разрешение экрана и мой браузер. Для использования бота вам нужно будет вычислить свои координаты кнопок.
- По установленным в игре правилам за использование даже такого простого бота наказание бан без права восстановления. пункт 6.4.
Автор: lastuniverse