[Из песочницы] Гиковский подход к несложной браузерной игре

3e83dc6964114788aa23c0b3bda9c830.jpgНесколько лет назад с удовольствием играл в небольшую любительскую браузерную игру, созданную по мотивам классической «Majesty: The Fantasy Kingdom Sim». Очень скоро в ней обнаружилось несколько уязвимостей и багов, включая весьма пригодные для «эксплуатации». История того, как я писал скрипты-эксплоиты, репортил баги и позже немного участвовал в разработке игры — ниже. Думаю, она неплохо проиллюстрирует несколько нестандартный, гиковский подход пользователя к игре, о котором может быть полезно знать разработчикам.Сначала о самой игре. Ссылку не привожу, но при желании ее можно найти внутри исходников ниже. Задумка игры довольно проста: игрок создает одного или нескольких героев на выбор из 16 классов (Воин, Варвар, Монах и прочие, в точности как в Majesty) и затем исследует окружающий мир, побеждая монстров, постепенно набирая уровни и подбирая хорошую экипировку. Интерфейс гипертекстовый, графики минимум. Вся графика взята из Majesty, причем, насколько я помню, с разрешения правообладателей. Взаимодействие между игроками отсутствует, за исключением совершенно независимой от основного игрового процесса соревновательной арены и одного весьма косвенного пути, о котором я расскажу ниже.

Главная же особенность игры состоит в том, что герой начинает с запасом в 200 «ходов», которые довольно быстро тратятся на исследование стартовой локаций и первые путешествия. Новые ходы накапливаются со скоростью 1 в час реального времени, независимо от действий игрока. В сутки набирается 24 хода, что очень мало, хватает на 5–10 минут игры. Игра бесплатна, в ней нет доната, купить за «реал» нельзя ни ходы, ни что-либо другое. Единственный способ играть больше — создать много героев, и каждым играть 5–10 минут в день или порядка часа в неделю. Однако создавать более 16 героев — одного на каждый класс — не представляет особого интереса в силу отсутствия внутриигрового взаимодействия. Именно так я и играл — по одному герою каждого типа. Такого количества хватает примерно на час спокойной игры в день.

Генерация героевПри создании героя открывается первая возможность для гиковской оптимизации: генерация героя с наилучшими характеристиками. Статов в игре пять: Strength, Vitality, Intelligence, Willpower, Artifice. После небольшого тестирования я предположил, что значение каждого выбирается случайно из диапазона вида 20–25, то есть по 6 возможных значений на характеристику. Всего возможных комбинаций 6^5 = 7776, из которых идеальна лишь одна. Очевидно, вручную пересоздавать героя замучаешься, нужно писать скрипт! Однако сначала все же нужно точно определить диапазоны статов для каждого героя. Это можно сделать вручную (в среднем 5–6 генераций на каждый класс героя), но это долго и неинтересно. Для меня интереснее и быстрее всего было взять уже немного освоенный на других играх скриптовый язык Autohotkey, и написать код для управления браузером Opera. Капчи при создании героя тогда не было — ее добавили позже с моей подачи, — поэтому схема работы весьма проста: — выбрать класс, принять отправную гипотезу о диапазонах параметров (мин 50, макс 1); — залогиниться, отправить форму создания нового героя; — посмотреть, какие статы получились, поправить гипотезу (уменьшить мин, увеличить макс); — если все пять диапазонов стали по 6 единиц длиной, то записать диапазоны в файл и перейти к следующему классу.

Старый грязный код для любопытствующих Login = Password = Name = testing heronClass = 1;1=adept 2=barbarian 3=cultist 4=dwarf 5=elf 6=gnome 7=healer 8=monk;9=paladin 10=priest 11=ranger 12=rogue 13=solarus 14=warrior 15=WoD 16=WizardnGender = 1;1=male 2=femaleEmail = omican@yandex.ruMinStr = 50MinVit = 50MinInt = 50MinWil = 50MinArt = 50MaxStr = 1MaxVit = 1MaxInt = 1MaxWil = 1MaxArt = 1Delete = heroesofardania.net/options/DeleteHero.asp?_sn=831067546&State=1Register = heroesofardania.net/register.asp

run e:\program files\opera\opera.exeWinWait, Blank page — Opera, IfWinNotActive, Blank page — Opera, WinActivate, Blank page — Opera, WinWaitActive, Blank page — Opera,

send ^tsendinput heroesofardania.net{enter}send 2; ожидание полной загрузки страницы — изменения цвета пикселя в Опереloop, 300{sleep, 100PixelGetColor, temp, 287, 62if (temp=0xFF0000)breakif (a_index=299){clipboard=-1exitapp}}sleep, 100send {tab}send 2sleep, 100

; залогиниваниеsendinput {tab}%Login%{tab}%Password%{tab}{enter}send 2loop, 300{sleep, 100PixelGetColor, temp, 287, 62if (temp=0xFF0000)breakif (a_index=299){clipboard=-1exitapp}}sleep, 100send 2sleep, 100

; удаление старого героя перед началом циклаsendinput h%Delete%{enter}send 2loop, 300{sleep, 100PixelGetColor, temp, 287, 62if (temp=0xFF0000)breakif (a_index=299){clipboard=-1exitapp}}sleep, 100send ^+! w

;---------------- основной циклloop{;----------------send ^tsendinput %register%{enter}send 2

loop, 300{sleep, 100PixelGetColor, temp, 287, 62if (temp=0xFF0000)breakif (a_index=299){clipboard=-1exitapp}}sleep, 100send 2sleep, 100

; создание герояsleep, 200send {tab}sendinput %Login%{tab}%Password%{tab}%Password%{tab}loop, %nClass%send {down}send {tab}loop, %nGender%send {down}sendinput {tab}%Name%{tab}%Email%{tab}{tab}{enter}sleep, 100send 2sleep, 100

sleep, 200loop, 300{sleep, 100PixelGetColor, temp, 287, 62if (temp=0xFF0000)breakif (a_index=299){clipboard=-1exitapp}}send 2

;--------------------------------------------------------------------герой созданsleep, 100; определение выпавшей статы и поправка гипотезыif (MaxStr-MinStr!=5){mouseclickdrag, l, 113, 415, 130, 420; выделение статы мышкойsend ^cif (clipboardMaxStr)MaxStr=%clipboard%sleep, 100}if (MaxVit-MinVit!=5){mouseclickdrag, l, 113, 430, 130, 435send ^cif (clipboardMaxVit)MaxVit=%clipboard%sleep, 100}if (MaxInt-MinInt!=5){mouseclickdrag, l, 113, 445, 130, 450send ^cif (clipboardMaxInt)MaxInt=%clipboard%sleep, 100}if (MaxWil-MinWil!=5){mouseclickdrag, l, 113, 460, 130, 465send ^cif (clipboardMaxWil)MaxWil=%clipboard%sleep, 100}if (MaxArt-MinArt!=5){mouseclickdrag, l, 113, 475, 130, 480send ^cif (clipboardMaxArt)MaxArt=%clipboard%sleep, 100}

; запись в файл и переход к следующему классуif (MaxStr-MinStr=5&&MaxVit-MinVit=5&&MaxInt-MinInt=5&&MaxWil-MinWil=5&&MaxArt-MinArt=5){FileAppend, Class: %nClass%`n, E:\games\HoA Scripts\Stats.txtFileappend, Str: %MinStr%-%MaxStr%`n, E:\games\HoA Scripts\Stats.txtFileappend, Vit: %MinVit%-%MaxVit%`n, E:\games\HoA Scripts\Stats.txtFileappend, Int: %MinInt%-%MaxInt%`n, E:\games\HoA Scripts\Stats.txtFileappend, Wil: %MinWil%-%MaxWil%`n, E:\games\HoA Scripts\Stats.txtFileappend, Art: %MinArt%-%MaxArt%`n`n, E:\games\HoA Scripts\Stats.txtnClass++MinStr = 50MinVit = 50MinInt = 50MinWil = 50MinArt = 50MaxStr = 1MaxVit = 1MaxInt = 1MaxWil = 1MaxArt = 1}

sendinput h%delete%{enter}send 2loop, 300{sleep, 100PixelGetColor, temp, 287, 62if (temp=0xFF0000)breakif (a_index=299){clipboard=-1exitapp}}send ^+! w

; готово! if (nClass>16){MsgBox Done! exitapp}

;-----------------};-----------------

В результате получаем искомые диапазоны для всех классов и (нестрогое) подтверждение гипотезы — все характеристики имеют ровно 6 возможных значений. Далее, для каждого класса важны лишь некоторые из характеристик, причем не всегда нужен самый верх диапазона, а достаточно промежуточного порогового значения. Например, параметр Strength дает прибавку +1 к урону за каждые 6 единиц, поэтому из диапазона 21–26 нам подойдут 24, 25 и 26. Это позволяет поднять вероятность получения подходящей комбинации с [1 из 7776] до разумного занчения в [1 из нескольких десятков или сотен]. Это важно, потому что пересоздание героя занимает 10–15 секунд, и идеальные статы генерились бы в среднем более суток, а нам помимо них нужно еще кое-что.А именно стартовые деньги — вторая возможность для гиковской оптимизации. Их мало, всего 200 монет, чего недостаточно для покупки даже слабенькой экипировки. Хватает лишь на самую простую дубинку и слабую броню, которая не спасает от укусов самых слабых монстров в первой же боевой локации. Поэтому обычно свежесозданным героям приходится тратить много ходов на лечение от ран или даже воскрешение после смерти (в игре смерть это потеря части денег и нескольких бесценных ходов). Однако в стартовом городе есть казино! Размеры ставок 50, 100, 500 и 1000 монет, типы ставок такие: — «на черное»: шанс ½ удвоить ставку; — «на красное»: шанс 1/5 упятерить ставку; — «на золотое»: шанс 1/20 «удвадцатерить» ставку.

Очевидно, что в отличие от реальных казино, матожидание выигрыша равно нулю. Однако каждая ставка тратит один ход, что делает игру в казино в конечном счете невыгодной для всех кроме героев класса Gnome (у них повышенная удача и матожидание положительно). Для генерации же героев казино очень полезно: можно поставить стартовые деньги на золотое, и в случае неудачи просто создать героя заново. Ставки в 200 монет нет, поэтому приходится делать две ставки по 100 и рассчитывать на выигрыш 2000 монет (суммарная вероятность примерно 1/10). Столько монет все равно маловато для хорошего старта, поэтому нужно ставить опять: две ставки по 1000 с надеждой получить 20,000 монет (вероятность снова ~1/10).

8bf8b59fb52b43d39fec3ac0d4979576.png

Соответствующий скрипт устроен просто: — пересоздавать героя до тех пор, пока статы не окажутся подходящими; — сходить в казино, сделать 2 ставки по 100, если выиграл, то еще 2 по 1000; — если выиграл, то перейти к следующему классу героя; — в случае неуспеха на любом этапе вернуться к пересозданию.

Код реролла героев #SingleInstance force; подходящие статы классовhero_class2=1hero_name2=Mano Rigorhero_gender2=1min_str2=22min_vit2=21min_int2=24min_wil2=22min_art2=0

hero_class3=2hero_name3=Tiger Grinhero_gender3=1min_str3=31min_vit3=26min_int3=11min_wil3=0;9–14min_art3=15

hero_class4=3hero_name4=Tigrahero_gender4=2min_str4=17min_vit4=14min_int4=27min_wil4=0;5–10min_art4=21

hero_class5=4hero_name5=Dorn Bumpkinhero_gender5=1min_str5=25min_vit5=28min_int5=15min_wil5=0;17–22min_art5=29

hero_class6=6hero_name6=Mister Pronkahero_gender6=1min_str6=7min_vit6=9min_int6=18min_wil6=24min_art6=0

hero_class7=7hero_name7=Sister of Silencehero_gender7=2min_str7=7min_vit7=13min_int7=28min_wil7=31min_art7=0

hero_class8=8hero_name8=Sphinxhero_gender8=2min_str8=23min_vit8=25min_int8=26min_wil8=33min_art8=0

hero_class9=9hero_name9=Tessa Virtuehero_gender9=2min_str9=27min_vit9=26min_int9=20min_wil9=29min_art9=0

hero_class10=10hero_name10=Fesshero_gender10=1min_str10=8min_vit10=12min_int10=30min_wil10=24min_art10=0

hero_class11=11hero_name11=Nausikahero_gender11=2min_str11=18min_vit11=19min_int11=22min_wil11=0;19–24min_art11=24

hero_class12=12hero_name12=Aleyak Sumakaihero_gender12=1min_str12=15min_vit12=17min_int12=21min_wil12=0;4–9min_art12=30

hero_class13=13hero_name13=Morpheushero_gender13=1min_str13=25min_vit13=24min_int13=22min_wil13=21min_art13=0

hero_class14=14hero_name14=Saudade Jimhero_gender14=1min_str14=24min_vit14=24min_int14=14min_wil14=20min_art14=0

hero_class15=15hero_name15=Tor Dur Barhero_gender15=1min_str15=41min_vit15=41min_int15=8min_wil15=18min_art15=0

hero_class16=16hero_name16=Wahookahero_gender16=1min_str16=7min_vit16=10min_int16=31min_wil16=23min_art16=0

log_file=e:\games\scripts\log.txt

gold1=0str1=0vit1=0int1=0wil1=0art1=0

; основная функцияreroll (i){globalloop{send +{enter}wait_image («crier», 190, 150, 370, 370); ожидание загрузки страницы по картинкеget_data ()if (str1

; функция логирования строкиlog (str){globalformattime, time, H: mm: ssfileappend, %time% %str%`n, %log_file%}

; функция ставки в казиноgamble (bet){send {end}sleep, 20send {F8}sleep, 50clipboard=http://www.heroesofardania.net/Content.asp? State=1&Bet=%bet%send^vsleep, 30send {enter}wait_image («hall», 190, 150, 370, 370)loop, 2{send {end}sleep, 20send {F8}sleep, 50clipboard=http://www.heroesofardania.net/Content.asp? State=2&Colour=Gold&Process=Ysend^vsend {enter}wait_image («hall», 190, 150, 370, 370)}return}

; функция удаления текущего герояdelete_hero (){send {F8}sleep, 50clipboard=http://www.heroesofardania.net/options/DeleteHero.asp?_sn=490&State=1send^vsleep, 30send {enter}wait_image («paladin», 50, 235, 200, 400)send ^{F4}PixelGetColor, color, 33, 147while color!=0xFFFFFF{sleep, 30PixelGetColor, color, 33, 147}sleep, 50send {tab}sleep, 20return}

; получение состояния герояget_data (){globalsend ^asleep, 30send ^csleep, 30StringReplace, clipboard, clipboard, `r`n, AllRegExMatch (clipboard, «i)Gold[^0–9]{0,10}([0–9]+)», gold)RegExMatch (clipboard, «i)Strength[^0–9]{0,10}([0–9]+)», str)RegExMatch (clipboard, «i)Vitality[^0–9]{0,10}([0–9]+)», vit)RegExMatch (clipboard, «i)Intelligence[^0–9]{0,10}([0–9]+)», int)RegExMatch (clipboard, «i)Willpower[^0–9]{0,10}([0–9]+)», wil)RegExMatch (clipboard, «i)Artifice[^0–9]{0,10}([0–9]+)», art)return}

; ожидание появления нужной картинки в нужном местеwait_image (name, x1=1, y1=1, x2=1024, y2=768){ImageSearch, x, y, %x1%, %y1%, %x2%, %y2%, *80 E:\Games\Scripts\%name%.bmpwhile ErrorLevel{sleep, 150ImageSearch, x, y, %x1%, %y1%, %x2%, %y2%, *80 E:\Games\Scripts\%name%.bmp}sleep, 50}

; клавиша запуска скрипта~^! r::{IfWinNotActive, Majestyreturnsleep, 1000loop, 16{if A_index<2 ;starting numbercontinue; первичное заполнение формы создания героя — нужно лишь один раз на классsend ^asleep, 50send {del}sleep, 20send % hero_name%A_index%send {tab}sleep, 20send {home}loop, % hero_class%A_index%send, {down}send {tab}sleep, 20send {home}loop, % hero_gender%A_index%send, {down}loop, 3send {tab}reroll(A_index)sleep, 1000}return}^!q::exitapp

В сумме герою нужно подобрать характеристики (вероятность ~1/200 — 1/50) и выиграть два раза подряд в казино (вероятность ~1/100). Общая вероятность весьма невелика (~1/10,000), поэтому в среднем скрипт у меня работал по 20–30 часов на каждого героя. Делать несколько потоков я счел излишним, да и не хотелось загружать сервер игры и портить жизнь любителям-разработчикам. С этими приятными людьми, к тому же, я успел познакомиться, зарепортив до этого несколько багов. Самым фееричным багам и тому, как я стал Королевским Ловцом Багов, посвящена последняя часть статьи.Итак, в итоге я получил 16 героев, каждый с хорошими статами и 20к золота. С этими деньгами за стартовые 200 ходов уже можно закупить неплохую стартовую экипировку, сбегать с другой город за супер-полезными амулетами и с удобством набрать десяток уровней. Однако на этом преимущество «улучшенного старта» заканчивается, и дальше герой играется как любой другой. Уровню к 20–25-му разница нивелируется, и главным становится уже знание игры — в какую локацию идти на текущем уровне и с имеющимся снаряжением, чтобы эффективно тратить ходы на развитие героя. Как помочь своим героям на данном этапе? Оказывается, и тут есть возможность немного облегчить им жизнь.

«Отмывание денег» через систему кланов Здесь всплывает тот самый единственный косвенный способ взаимодействия героев. Каждый герой с 5 уровня может вступить в созданный другим героем клан, а с 10 — и создать свой. В общую казну клана можно жертвовать деньги и на эти деньги потом возводить постройки типа тренажерного зала, сада для медитаций и т.д. Эти постройки позволяют несколько более эффективно тратить ходы — быстрее лечиться, получать временные бонусы, даже немного улучшить статы. Все эти бонусы невелики за одним исключением — заклинаний из клановых храмов. После постройки храмов, посвященных местным божествам, в них можно жертвовать деньги на временные полезные заклинания, распространяющиеся на всех героев клана. Бонусы от некоторых из заклинаний уже очень ощутимы — полное лечение после каждого боя (экономия ходов и денег), усиление брони, увеличение количества атак. Стоит это огромных денег, поэтому при нормальной игре даже очень крупные кланы высокоразвитых героев не могут себе позволить часто активировать эти заклинания. Кстати, в клане не может быть более 15 или 20 героев, и цена заклинаний в храме пропорциональна числу членов клана.Обойти эту проблему можно с помощью временных членов клана, каждый из которых вступает в клан, жертвует крупную сумму денег в казну, и покидает клан — так стоимость храмовых заклинаний не увеличивается, а казна растет. Вручную это делать не эффективно — для постоянного поддержания всех полезных бонусов на каждого героя-бенефициара нужно порядка дюжины героев-доноров, причем за каждого донора еще нужно не забывать играть, чтобы фармить деньги. По моим подсчетам, для непрерывного обеспечения всех 16 героев бонусами нужно было запустить в работу 200 гномов-доноров. Именно гномов, а не героев другого класса, потому что из-за повышенной удачи они намного легче выигрывают стартовые 20к (иногда даже 40к) золота, а после очень быстро могут получить нужный уровень, скинуть деньги в клан и начать продуктивно фармить.

Тут нам снова поможет скрипт, чуть более сложный: — сгененировать 200 гномов с уникальными именами и достаточными статами и 20–40к золота из казино; — каждым из них сбегать с другой город, закупить хорошую экипировку и амулеты; — почти все оставшиеся ходы потратить на прокачку и фарминг золота в подходящей локации; — оставшиеся несколько ходов потратить на путешествие в клановый город и пожертвование всех денег в нужный клан.

Код был создан только для первого пункта:

launcher ; инкремент счетчика в файлеincrement (filename){fileread, count, %filename%count+=1filedelete, %filename%fileappend, %count%, %filename%return %count%}; создание логинаgen_login (n){ret=omican_cash%n%return ret}

; простейшее создание уникального имени вида Lucky PQgen_name (n){let1:=floor (n/26)+65let1:=chr (let1)let2:=mod (n,26)+65let2:=chr (let2)ret=Lucky %let1%%let2%return ret}

; main loopmsgbox CLick OK to launch main scriptloop{fileread, count, e:\games\scripts\login_index.txtif (count>200){break}login:=gen_login (count)name:=gen_name (count)filedelete, e:\games\scripts\data.txtfileappend, %login%`n, e:\games\scripts\data.txtfileappend, %name%, e:\games\scripts\data.txtfiledelete, e:\games\scripts\output.txtfiledelete, e:\games\scripts\index.txtfileappend, 0001, e:\games\scripts\index.txtrun e:\games\Scripts\HoA main.ahkSetTitleMatchMode, 2Loop{; если все завислоif (clipboard=-1){sleep, 1000winkill, Operaclipboard=0sleep, 3000run e:\games\Scripts\HoA main.ahk}sleep, 5000; если текущий гном готовif (clipboard=1){sleep, 1000winkill, Operaclipboard=0increment («e:\games\scripts\login_index.txt»)sleep, 3000break}}}

main cycle filereadline, login, e:\games\scripts\data.txt, 1filereadline, Name, e:\games\scripts\data.txt, 2nClass = 6;1=adept 2=barb 3=cult 4=dwarf 5=elf 6=gnome 7=heal 8=monk 9=pal;10=priest 11=ranger 12=rogue 13=sol 14=warrior 15=WoD 16=WizardnGender = 1;1=male 2=femaleStr = 0Vit = 0Int = 0Wil = 0Attempts = 0

Password = <..pwd..>index_file=e:\games\scripts\index.txtEmail = <..email..>Delete = www.heroesofardania.net/options/DeleteHero.asp?_sn=490&State=1Register = heroesofardania.net/register.aspHall = heroesofardania.net/Content.asp?_sn=3808&_dt=1%2F29%2F2009+11%3A45%3A44+AM&ContentID=251G100 = heroesofardania.net/Content.asp?_sn=1649&_dt=1%2F29%2F2009+11%3A54%3A42+AM&State=1&Bet=100G1000 = heroesofardania.net/Content.asp?_sn=1649&_dt=1%2F29%2F2009+11%3A54%3A42+AM&State=1&Bet=1000Gold = heroesofardania.net/Content.asp?_sn=9942&_dt=1%2F29%2F2009+11%3A56%3A44+AM&State=2&Colour=Goldlogout = www.heroesofardania.net/logout.asp?_sn=163&_dt=2%2F10%2F2009+2%3A09%3A03+PM&

; ожидание полной загрузки страницы (определяем по цвету пикселя в опере)wait_load (){loop, 300{sleep, 100PixelGetColor, temp, 290, 62if (temp=0xFF0000)breakif (a_index=299){clipboard=-1exitapp}}}

; переход по адресу в новой вкладкеaddress_new (adr){send ^tsleep, 200clipboard = %adr%sendinput ^v{enter}send 2}

; переход по адресу во вкладке номер 2address_exist (adr){send 2sleep, 200sendinput hsleep, 200clipboard = %adr%sendinput ^v{enter}send 2}

; переход по адресу в текущей вкладкеaddress_here (adr){send hsleep, 200clipboard = %adr%sendinput ^v{enter}send 2}

; инкремент счетчика гномов в файлеincrement (filename){fileread, count, %filename%count+=1if (count<1000)count=0%count%if(count<100)count=0%count%if(count<10)count=0%count%filedelete, %filename%fileappend, %count%, %filename%return %count%}

flag = 0run e:\program files\opera\opera.exeWinWait, Blank page — Opera, IfWinNotActive, Blank page — Opera, WinActivate, Blank page — Opera, WinWaitActive, Blank page — Opera,

count=0001send {tab};---------------- основной циклloop{;----------------send ^{F4}sleep, 100send ^{F4}address_new («heroesofardania.net»)wait_load ()send 2sleep, 200sendinput {tab}%Login%{tab}%Password%{tab}{enter}sleep, 200send 2wait_load ()address_exist (Delete)wait_load ()address_exist (Register)wait_load ()send 2sleep, 200

; проверка на редкий баг «пустое окно создания героя«loop{PixelGetColor, temp, 234, 715if (temp=0×800080){send 2sleep, 200address_new (Register)wait_load ()sleep, 200send 2sleep, 200}elsebreak}

; заполнение формы создания герояsleep, 200send {tab}sendinput %Login%{tab}%Password%{tab}%Password%{tab}loop, %nClass%send {down}send {tab}loop, %nGender%send {down}sendinput {tab}%Name%{tab}%Email%{tab}{tab}{enter}sleep, 200send 2sleep, 200wait_load ()send 2

;-------------------------------------------------------------------- герой созданsleep, 200; проверка статов выделением мышкойif (Str){mouseclickdrag, l, 79, 407, 97, 407send ^cif (clipboard0)flag=1sleep, 200}if (Vit){mouseclickdrag, l, 79, 424, 97, 424send ^cif (clipboard0)flag=1sleep, 200}if (Int){mouseclickdrag, l, 79, 441, 97, 441send ^cif (clipboard0)flag=1sleep, 200}if (Wil){mouseclickdrag, l, 79, 458, 97, 458send ^cif (clipboard0)flag=1sleep, 200}

; если плохие статыif (flag){count:=increment (index_file)sleep, 200};------------------------------------------------------------- конец случая плохих статовif (! flag){; выигрывание 2000 в казиноaddress_here (Hall)wait_load ()address_exist (G100)wait_load ()address_exist (Gold)wait_load ()address_exist (Gold)wait_load ()

send 2sleep, 200mouseclickdrag, l, 80, 289, 120, 289; выделение денег мышкойsend ^cif (clipboard<1900 && clipboard>-1){FileAppend, %count%: %A_Hour%:%A_Min%:%A_sec% Lose 1`n, E:\games\Scripts\output.txtcount:=increment (index_file)sleep, 200}if (clipboard>1900 && clipboard<1000000){; выигрывание 20,000address_here(G1000)wait_load()address_exist(Gold)wait_load()address_exist(Gold)wait_load()

send 2sleep, 200mouseclickdrag, l, 80, 289, 120, 289send ^cif (clipboard>19000 && clipboard<1000000){if(Attempts<1){FileAppend, Done! %clipboard%`n, E:\games\Scripts\output.txtaddress_here(logout)clipboard=1exitapp}if(Attempts>0){; выигрывание еще 20,000address_here (G1000)loop, %Attempts%{if (a_index=1){wait_load ()address_exist (Gold)wait_load ()}else{address_here (Gold)wait_load ()}send 2sleep, 200mouseclickdrag, l, 80, 289, 120, 289send ^cif (clipboard>22000)break}if (clipboard>22000 && clipboard<1000000){FileAppend, Done! %clipboard%`n, E:\games\Scripts\output.txtMsgBox Done!exitapp}FileAppend, %count%: %A_Hour%:%A_Min%:%A_sec% Lose 3`n, E:\games\Scripts\output.txtcount:=increment(index_file)}}if(clipboard<19000 && clipboard>-1){FileAppend, %count%: %A_Hour%:%A_Min%:%A_sec% Lose 2`n, E:\games\Scripts\output.txtcount:=increment (index_file)sleep, 200}

}}flag=0;-----------------};-----------------

Остальные пункты так и не были реализованы, потому что одновременно произошло несколько событий: — играть и писать эксплоиты мне уже несколько надоело; — от разработчиков поступило предложение помочь с разработкой игры; — толпы подозрительных гномов были замечены игроками. Дело в том, что писать генератор реалистичных уникальных имен для гномов мне было лень, и гномы имели имена типа Lucky XY, Lucky PQ и т.д. Оказалось, что при последовательной игре несколькими героями за последние 10 минут они все отображались в списке онлайн-игроков, и моих слишком-похожих гномов там стабильно висело 5–6 штук. На форуме появился топик, в котором озадаченные игроки гадали, что это за гномы и кому они нужны. Тут уже я понял, что пора раскрыться, рассказал про эксплоиты и свернул свою темную деятельность.Немного о моральной стороне вопроса. В силу отсутствия любой возможности испортить жизнь другим игрокам, а также слабой соревновательной составляющей игры на тот момент, получение мною нечестных преимуществ совершенно не сказывалось на игровом социуме. Да и вообще проходило незамеченным. Мои герои были молоды, из-за системы »1 ход в час» не могли тягаться со старожилами и совершенно терялись в нижней части всех рейтингов. В процессе же моего копания в игре я нашел и зарепортил массу багов и уязвимостей, включая и те, которые поначалу эксплоитил.

Bug Catching Первый из обнаруженных мною крупных багов был связан с вещами, которые давали +Vitality. Согласно данным в игровой википедии, увеличение HPmax при повышении уровня подчиняется формуле HPmax += floor (Vitality/4) + rand (1, floor (Vitality/4)). Таким образом, каждые 4 очка Vitality давали в среднем +1.5HP на уровень. Если на герое в момент повышения уровня были одеты вещи с +Vitality, то и добавочные HP должны были быть выше. Однако из-за бага эти вещи не учитывались (как мне рассказал разработчик, зачем-то перед повышением уровня все вещи снимались с героя, а после надевались заново).После моего репорта баг пофиксили, что привело к совершенно неожиданным результатам. Герои с очень низким показателем Vitality, ранее еле-еле выживавшие в боях, теперь стали получать намного больше HP на уровень. В частности, гномы, у которых базовая Vitality лежит в диапазоне 4–9, за счет экипировки легко удвоили и даже утроили свое здоровье и, с учетом других своих способностей, превратились из самых задохликов в потенциально сильнейших героев. То же коснулось и в оригинале чрезвычайно хрупких Healer«ов. Крайне важным для таких героев стало на как можно низком уровне найти соответствующие вещи, чтобы пораньше начать получать бонусы к HP.

Небольшая недоработка была в казино: в него можно было проходить с вещами, повышающими удачу и чаще выигрывать. После моего репорта в казино добавили вышибал, которые вынюхивали такие вещи и просто не пускали внутрь. В казино же в другом городе путем несложных махинаций с GET-параметрами запроса можно было всегда выигрывать в одной из игр. Теперь там тоже вышибалы: «Cheaters are not welcomed here!»

Игра в целом делалась на коленке, и поэтому была чрезвычайно дырявой. В частности, для выхода из первого города нужно было купить карту за 600 или 800 монет, и только тогда в диалоге у ворот появлялась опция «покинуть город». Однако все опции в диалогах имели ссылки вида »…&State=n», и подбор нужного значения GET-параметра позволял воспользоваться скрытой опцией диалога.

Самая же феерическая уязвимость связана с числовыми полями ввода типа «сколько ходов отдохнуть» или «сколько ходов молиться божеству». В большинстве случаев проверка неотрицательности ввода делалась Javascript«ом на стороне клиента. В результате однажды мой герой для эксперимента отдохнул -1000 ходов и… умер! «You have rested for -1000 turns and restored -6000 health points. You are killed!». Но смерть это лишь потеря денег и нескольких ходов. Которых у меня теперь почти на 1000 больше… При молитве же -1000 ходов божеству, вместо кучи благословлений герой получает не меньшую кучу проклятий.

Впечатленные моими репортами, разработчики выдали мне для более эффективного поиска багов специального героя с непробиваемой броней и кучей ходов:

a1c2d27883f945dfac1fa22042b70c9f.png

Позднее разработчики дали мне доступ к тестовой ветке, где я с грехом пополам (оказалось, что вся игра написана на совершенно неизвестном мне VBScript) написал новую фичу — быстрое переключение наборов экипировки. Фича прошла «в продакшн», но на этом мой вклад закончился, ибо я все же не смог преодолеть свою неприязнь к VBScript«у и продолжить разработку.

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

© Habrahabr.ru