На хабре как-то писали о создании ботов к WoW, а вот статей о написании аддонов я не нашел и решил описать этот процесс на примере одной поделки, которую я писал когда-то давно, когда я был еще студентом и моего времени хватало на игру в World of Warcraft и создание аддонов к нему. С деньгами тогда было туго и играл я не на официальном сервере, а на одном из бесплатных, имеющем кучу недоделок, багов, фич и аномалий. 
После того, как за случайное использование одной из них я был забанен, появилась идея создать аддон, указывающий в игре на все отличия данного шарда от официального сервера, с предупреждениями о наказаниях за их использование.
Я выложил аддон на Google Code и форум серевера и он стал достаточно популярным (как для пиратского сервера). Кому интересно, как делаются аддоны — велкам под кат.
Введение
Итак, начнём с определения. Аддон в WoW — это набор некоторых файлов, которые позволяют пользователю изменить интерфейс клиентской части игры, но никак не влияют на серверную игровую механику. Аддоны были в WoW изначально, что выгодно отличает эту ММОРПГ от некоторых других, кричащих о своём превосходстве, но не имеющих возможности создания официальных аддонов и по сей день. Аддонов есть тысячи, если не десятки тысяч. Почему так много? А потому, что стилей игры и игровых задач ровно столько же. И конечно же, разработчики молодцы, что с самого начала дали способ каждому создать что-то своё, а не навязывать единый интерфейс, требуя считать его удобным.
Главным источником документации при создании аддона у нас будет портал WoWWiki. Пару ссылок по теме:
Полезной информации там еще очень мног��, но до неё можно добраться по ссылкам с этих четырёх страниц в пару кликов.
Инструментарий
Никаких специальных инструментов для создания аддонов не нужно. Все они представляют собой текстовые файлы, так что берите свой любимый текстовый редактор — и вперёд! Если он имеет подсветку синтаксиса языка Lua (именно на нём создаются аддоны) — вообще прекрасно. Этим требованиям отвечает, например, Notepad++.
А еще, энтузиасты создали на базе движка Free Visual Studio Shell специальную open-source IDE, которая называется AddOn Studio.

Штука прикольная и берет на себя ряд функций, таких как:
- Дизайнер интерфейса
- Просмотре интерфейса в виде XML-дерева
- Браузер ресурсов WoW
- Автодополнение по WoW API
- Автогенерацию некоторых файлов
+ пару других прикольных вещей. В общем, можно пользоваться. Ну и еще можно просмотреть эту страницу, может быть найдете плагин к своей любимой IDE или просто что-то знакомое.
Пару концептуальных моментов
- Есть такая штука, как политика Blizzard, требующая от аддона бесплатности, соответствия EULA, отсутствия вреда для других пользователей и пару других скучных, но идейно верных вещей. Так что о идеях написания ботов, хаков, читов и прочего мусора лучше забыть.
- Хотите Вы того или нет, Ваш аддон будет opensource, потому что пишется на LUA+XML и распространяется в исходных кодах.
Из чего состоит аддон
1. TOC-файл (table of conteset). Это оглавление нашего аддона. Объясняет оболочке WoW кто мы такие, как называемся, кто автор, из чего состоим и пару других вещей. Вот, например, toc-файл моего аддона:
## Interface: 30000 ## Title: Wnet Featurer 1.3 ## Notes: Wnet Server Features List ## RequiredDeps: ## OptionalDeps: ## LoadWith: Blizzard_TalentUI ## SavedVariables: Data.lua WnetWarnings.lua WnetFeaturer.xml
2. XML-файлы, описывающие изменения в интерфейсе, которые мы хотим внести (фреймы, кнопки, привязку событий в интерфейсе к определенным функциям в коде). Вот xml-файл моего аддона:
<Ui xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.blizzard.com/wow/ui/" xmlns="http://www.blizzard.com/wow/ui/"> <Script file="WnetFeaturer.lua" /> <Frame name="WnetFeaturerFrame"> <Scripts> <OnLoad> WnetFeaturer_OnLoad(self); </OnLoad> <OnEvent> WnetFeaturer_OnEvent(self, event, ...); </OnEvent> </Scripts> </Frame> <Frame name="GameTooltip" parent="GameTooltip"> <Scripts> <OnUpdate> WnetChecker_GameTooltip_OnUpdate(); </OnUpdate> </Scripts> </Frame> </Ui>
Что тут написано? Мы объявляем привязку этого файла интерфейса к файлу кода WnetFeaturer.lua, далее декларируем два фрейма — один чисто для возможности повесить на события загрузки плагина своё событие, второй будет использоваться в игре для показа собственных тултипов (подсказок) к определенным объектам игрового мира.
3. Lua-файлы. Это код на языке Lua. Надо признать, что Lua в WoW несколько урезанный (детали можно почитать в WoWWiki), но для большинства задач его хватает. Вот один из файло�� кода аддона (самый короткий).
--сохраняем процедуру отрисовки текста квестов (мы ее заменим на свою) local before_wnet_featurer_old_QuestFrameDetailPanel_OnShow = QuestFrameDetailPanel_OnShow; function WnetFeaturer_Log( text ) SELECTED_CHAT_FRAME:AddMessage( text ); end function WnetFeaturer_OnLoad(self) if ( not IsAddOnLoaded( "Blizzard_TalentUI" ) ) then _, reason = LoadAddOn( "Blizzard_TalentUI" ); end if ( reason ) then return; end -- Hook the Talent Frame's Update function hooksecurefunc( "TalentFrame_Update", Planner_TalentFrame_AfterUpdate ); -- Register for Loading Variables self:RegisterEvent( "PLAYER_ENTERING_WORLD" ); self:RegisterEvent( "PLAYER_LEAVING_WORLD" ); -- Locale/Gender-neutral class names FTW wnet_featurer_classes = { [1] = "DEATHKNIGHT", [2] = "DRUID", [3] = "HUNTER", [4] = "MAGE", [5] = "PALADIN", [6] = "PRIEST", [7] = "ROGUE", [8] = "SHAMAN", [9] = "WARLOCK", [10] = "WARRIOR", }; end function WnetFeaturer_OnEvent( self, event, ... ) if ( event == "PLAYER_ENTERING_WORLD" ) then tp_name = UnitName( "player" ).." of "..GetRealmName(); _, class = UnitClass( "player" ); for k, v in ipairs( wnet_featurer_classes ) do if ( v == class ) then tp_class = k; end end elseif ( tp_name ) then if ( event == "PLAYER_LEAVING_WORLD" ) then tp_name = nil; tp_class = nil; end end end function Planner_TalentFrame_AfterUpdate( frame ) local page = PanelTemplates_GetSelectedTab( PlayerTalentFrame ); local numTalents = GetNumTalents( page ); local inspect = PlayerTalentFrame.inspect; local pet = PlayerTalentFrame.pet; local _, class = UnitClass( "player" ); if ( pet or tp_standby or not tp_name or not tp_class or frame ~= PlayerTalentFrame or inspect ) then return; end for id = 1, numTalents, 1 do local name, texture, row, col, rank, maxRank = GetTalentInfo( page, id ); local color = nil; if( wnet_talents[tp_class][page][name] ) then color = wnet_talents[tp_class][page][name]; else color = NORMAL_COLOR; end if ( color ) then getglobal( "PlayerTalentFrameTalent"..id.."Slot" ):SetVertexColor( color.r, color.g, color.b ); getglobal( "PlayerTalentFrameTalent"..id.."RankBorder" ):Show(); getglobal( "PlayerTalentFrameTalent"..id.."Rank" ):SetText( rank.."/"..maxRank.." " ); getglobal( "PlayerTalentFrameTalent"..id.."Rank" ):Show(); else getglobal( "PlayerTalentFrameTalent"..id.."RankBorder" ):Hide(); getglobal( "PlayerTalentFrameTalent"..id.."Rank" ):Hide(); end end end function checkIsItemValid( text ) if ( wnet_items[text] ) then return 1; else return nil; end end function checkIsNPCValid( text ) if ( wnet_npc[text] ) then return 1; else return nil; end end WnetChecker_GameTooltip_OnUpdate=function() local errorString = nil; for i=1, GameTooltip:NumLines(), 1 do local currentTooltipStr = getglobal("GameTooltipTextLeft"..i):GetText(); --проверка на баговых NPC и вещи if ( i == 1 ) then --имя NPC или вещи находится всегда в первой строке тултипа. Искать во всех незачем. if( checkIsNPCValid( currentTooltipStr ) ) then errorString = "Wnet: этот NPC может работать неверно!"; elseif( checkIsItemValid( currentTooltipStr ) ) then errorString = "Wnet: эта вещь может работать неверно!"; end end if strfind( currentTooltipStr, "Unique(.)Equipped" ) then errorString = "Wnet: Одевать 2 таких вещи нельзя!"; elseif strfind( currentTooltipStr, "Target Dummy" ) then errorString = "Wnet: Использовать 2 таких вещи нельзя!"; elseif( strfind( currentTooltipStr, "Chance on hit(.) Stuns target for" ) or strfind( currentTooltipStr, "Chance on hit(.) Оглушает цель на" ) ) then errorString = "Wnet: Использовать эту вещь нельзя!"; elseif( strfind( currentTooltipStr, "Chance on hit(.) Knocks target silly for" ) ) then errorString = "Wnet: Использовать эту вещь нельзя!"; elseif( strfind( currentTooltipStr, "Blackblade of Shahram" ) ) then errorString = "Wnet: Использовать эту вещь нельзя!"; elseif( strfind( currentTooltipStr, "Seduction" ) ) then errorString = "Wnet: этот спелл запрещен!"; elseif( strfind( currentTooltipStr, "Ursius" ) or strfind( currentTooltipStr, "Avian Darkhawk" ) or strfind( currentTooltipStr, "Avian Ripper" ) or strfind( currentTooltipStr, "Windroc Matriarch" ) ) then errorString = "Wnet: петом брать нельзя!"; elseif( strfind( currentTooltipStr, "Wnet:") ) then errorString = nil; end end if errorString then GameTooltip:AddLine( errorString, 1, 0, 0 ); local i; local s=10; for i=1, GameTooltip:NumLines(), 1 do s=s+getglobal("GameTooltipTextLeft"..i):GetHeight()+2; end GameTooltip:SetHeight(s+10); GameTooltip:SetWidth( max( GameTooltip:GetWidth(), 300 ) ); end end function checkIsQuestNotValid( questTitle ) if ( wnet_quests[questTitle] ) then return 1; else return nil; end end QuestFrameDetailPanel_OnShow=function() before_wnet_featurer_old_QuestFrameDetailPanel_OnShow(); if( checkIsQuestNotValid( GetTitleText() ) ) then QuestDescription:SetText("|c00ff0000 Wnet: Этот квест может не работать!|r|n"..GetQuestText()); end end
Этот код выполняет следующий задачи:
- WnetFeaturer_Log — функция вывода в чат игроку всяких логов
- WnetFeaturer_OnLoad — вызывается при загрузке аддона (помните, в хмл-файле выше мы привязывали вызов этой функции к событию загрузки аддона). Проверяет необходимые зависимости, регистрирует пару хуков, подписывается на пару событий (например, событие входа игрока в игровой мир).
- WnetFeaturer_OnEvent — вызывается при возникновении некоторых событий, на которые мы подписались ранее. Внутри — обработчики каждого события.
- Planner_TalentFrame_AfterUpdate — самая сложная функция в этом файле. Нужна для отрисовки в дереве талантов персонажа зеленых, желтых и красных квадратиков поверх работающих, частично работающих и полностью сломанных талантов.

- checkIsItemValid, checkIsNPCValid, checkIsQuestNotValid — проверка того, является ли вещь или NPC валидно работающими на данном сервере. Эти функции вызываются из других мест кода, которые потом возле невалидных вещей рисуют вот такие картинки:

- WnetChecker_GameTooltip_OnUpdate — это хук на функцию показа тултипа. Здесь мы анализируем текст тултипа и, если в первой строке находим упоминание некоторого объекта, который работает неверно, мы изменяем текст тултипа.
В проекте есть еще пару файлов кода, но они, в большей своей части, являются просто базой невалидных объектов и подписей к ним. Интереса не вызывают.
Полные исходники можно посмотреть тут: http://code.google.com/p/wnet-featurer/downloads/list
А вот тема на форуме, где я более детально описывал фичи аддона.
Буду рад, если кому-нибудь это поможет. Спрашивайте, если что-то непонятно.
