Jump to content
Alloder.pro: about Allods with love
Search In
  • More options...
Find results that contain...
Find results in...

New program for writers

We turn from quantity to quality and tell you how we will supplement the Allods Team program with rewards in rubles.

More

The new Updater

Let us to introduce the new addon updater software and to share the details

Read more

Alloder 2.0

We have started the process of project evolve, and this relates not only, and not even primarily of the site's view

Read more

Как сделать свой аддон


SLA
 Share

Recommended Posts

Я уже довольно давно обещаю "в скором времени" написать учебник по созданию аддонов smile.png Но до него вечно не доходят руки... Пока его нет, будет эта тема. Потому, что она просто нужна.

Что можно сделать средствами аддонов.

Хотите сделать свой первый аддон? Прекрасно! smile.png Но сначала, нужно разобраться, что можно сделать средствами аддонов, и чего нельзя. В борьбе с (воображаемым?) ботоводством, разработчики игры довольно сильно ограничили спектр доступных в API функций. Поэтому, сразу предупреждаю, что ничего не выйдет, если вы хотите сделать:

  • Автоатаку.
  • Автоматизировать (почти любые) действия персонажа.
  • Программно отправлять сообщения в чат.

Всё остальное, что можно сделать средствами данного нам API, это:

  • Любые информативные аддоны (сбор, обработка, и показ любой доступной инфотмации)
  • Кое-какая автоматизация действий (при работе с аукционом, почтой, и др.)
  • Возможно, ещё что-то, до чего мы пока не додумались.

Ну что, не остыл ещё пыл? smile.png Поверьте, ещё миллион отличных аддонов можно сделать на том API, что у нас есть. Простор огромен, т.к. мы ещё только начинаем его осваивать smile.png

Что для этого нужно?

Во-первых, чтобы создавать аддоны, нужно хоть немного владеть языком программирования Lua. Этот язык нигде не преподаётся, и почти нигде профессионально не используется. Поэтому, программисты изучают его сами, на дому. Вот ссылки на учебники:

Кстати, в АО используется Lua 5.1 (LuaJIT 2.0.3)

Ну а, собственно, для АО, официальная документация по созданию аддонов находится в папке с игрой:
Allods Online\data\Mods\Docs\ModdingDocuments.zip\Index.html

онлайн-версия здесь

Несколько элементарных примеров аддонов находятся в папке:
Allods Online\data\Mods\SampleAddons

А теперь внимание! Лирчик Али-Бабы! вот он:
Interface.1.0.03.26.2.zip

Желательно, чтобы ваш аддон был бы совместим не только с русской, но и с иностранными версиями АО. Подробнее об этом написано здесь: "Совместимость аддонов с EU/US версиями". Кроме того, имеет смысл сделать его доступным и для иностранных игроков, ведь AO существует ещё и на английском, немецком, французском языках. О способе определения локализации клиента игры, написано здесь: "Многоязычный аддон / Multilangual addon".

Графический интерфейс.

Графику для аддона можно вытащить прямо из ресурсов игры, с помощью AoTextureViewer

Далее, в официальной документации (ModdingDocuments.zip) есть вводный учебник, и полная справка по Lua API (функции, события, переменные). Но там практически ничего не сказано о создании графических интерфейсов, то есть об XDB-файлах. Правда, все шаблоны XDB-файлов можно найти в папке ResourceSystem (которая там же, в ModdingDocuments.zip), но к ним никакой документации нет, не считая того, что есть в нескольких примитивных примерах аддонов в SampleAddons. Приходится с XDB-файлами разбираться самим. Поэтому, загляни ещё и сюда:

Создание GUI

Это, конечно, далеко не всё. Но этого вам точно хватит, чтобы начать создавать аддоны.

Тестирование аддона в игре.

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

Если что-то в вашем аддоне не работает или поломалось, то загляните в лог ошибок, наверняка там найдёте подробное сообщение об ошибке:

Allods Online\Personal\Logs\Mods.txt

Как видите, с LUA-файлами всё просто smile.png Нажал кнопочку, аддон перезагрузился - удобно. Однако, с тестированием графики (XDB-файлы, BIN-файлы) и вообще, всех ресурсов (ещё и TXT-файлы), всё гораздо сложнее. Во-первых, если вы что-то меняете в XDB/BIN/TXT-файлах, то для того, чтобы изменения отразились в игре, перезагрузки аддона недостаточно. Придётся ПОЛНОСТЬЮ ПЕРЕЗАГРУЖАТЬ ИГРУ... Это усугубляется ещё и тем, что никаких ошибок XDB/BIN/TXT в логе не сообщается. Если у вас что-то из графики не работает, то вы это увидите в своём аддоне, да. Но никто вам не сообщит, в чём проблема - придётся разбираться самому, где в XDB-файлах вы допустили ошибку.

Публикация готовых аддонов.

Как правильно выложить и оформить аддон, читайте здесь:

Публикация новых аддонов (Инструкция)

Ну вот! smile.png Буду править и дополнять это сообщение по мере необходимости.

Edited by Fye D. Flowright
Link to comment
Share on other sites

  • 4 weeks later...
  • 3 months later...

мои замечания.

Файлы с расширением .xdb и .lua - обычные текстовые. Желательно их редактировать в обычном NotePad(Блокнот) - так чтобы кодировка осталась та же. Я например долго мучилась, редактируя .xdb в XML NotePad - он переворачивал текст в кодировку виндовскую что ли, и эти файлы переставали работать.

Файлы .xdb содержат описание графического интерфейса, .lua - содержат программы обработки.

Для начала можно скопировать похожий на ваш будущий аддон уже существующий в новую паку (с названием вашего аддона) и переправить там некоторые названия в .xdb - просматривая все что вложено по тегу <Children>

Code:

<Children>

<Item href="GUI/MainPanel.(WidgetPanel).xdb#xpointer(/WidgetPanel)" />

</Children>

начиная с MainForm.(WidgetForm).xdb

так например в файле MainPanel.(WidgetPanel).xdb написать в <Children>:

<Item href="InputMyName.(WidgetEditLine).xdb#xpointer(/WidgetEditLine)" />

Заметьте, что типВиджета в строке: (ТипВиджета).xdb#xpointer(ТипВиджета) - должны совпадать

затем переименовать файл Input1.(WidgetEditLine).xdb в InputMyName.(WidgetEditLine).xdb

Затем в этом файле поменять тег имени:

<Name>Input1</Name>

на

<Name>InputMyName</Name>

И тоже самое сделать в программе - редактируя .lua - вставить новые имена в инициализации:

Code:

function Init()

wtButton1 = wtMainPanel:GetChildChecked( "InputMyName" , false )

...

затем загрузите игру и убедитесь что графический интерфейс виден - значит файлы .xdb без ошибок. Если некоторые из них не видны проверьте теги

<Visible>true</Visible>

<Enabled>true</Enabled>

а так же в скриптах (файлы .lua) чтобы эти виджеты не гасились, например так:

Code:
wtMainPanel:Show(PanelShow) 

все ненужные строки в скрипте закомментируйте тремя минусами:

Code:
--- wtMainPanel:Show(PanelShow) 

дальше все просто - программа-клиент (игра ОА) будет посылать вашему скрипту события - надо их отловить и обработать, и возможно послать события другие обратно. Для этого надо объявить какие события будет обрабатывать какая ваша функция:

Code:

function OnProba()

--- напишем это в логФайл

LogInfo("Hello World!")

end

function Init()

common.RegisterEventHandler( OnProba, "EVENT_INVENTORY_ITEM_CHANGED" ) 

end

--------------------------------------------------------------------------------

Init()

--------------------------------------------------------------------------------

Link to comment
Share on other sites

Вроде, нет. Но есть какие-то события, по которым можно косвенно определить, что пользователь работает с аукционом.

Link to comment
Share on other sites

если вам хочется знать какие функции и поля имеет тот или иной объект, можете использовать такую функцию:

Code:

---выдает все поля и значения а так же функции объекта

function researchObj(tab,obj)

tab = tab .. "    "

---ограничим рекурсию

if string.len (tab) > 50 then 

LogInfo (" рекурсия ограничена!")

return 

end 

local metaTable = getmetatable (obj)

if metaTable then

---- покажем функции объекта

for k,v in pairs( metaTable ) do

LogInfo ( tab, k,":=",v)

end

end

if type(obj) == "table" then

---- покажем поля (переменные) таблицы

if GetTableSize( obj ) == 0 

then LogInfo ( tab, "{}")

return

end

for k,v in pairs(obj) do 

LogInfo ( tab, k,":=",v,  "{", type (v), "}")

if type (v) == "table" and k ~= "__index"  and k ~= "_G" then

--- "__index" - он такую же точно таблицу вложенную имеет что приводит к зацикливанию

researchObj(tab,v)

end

end

else

LogInfo ( tab, "_:=",obj, "{", type (obj), "}")

end

end

*исправлено: if type (v) == "table" and k ~= "__index" and k ~= "_G" then

так например если хотите исследовать виджет EditLine то после его инициализации пишем например так:

Code:

function Init()

wtMainPanel = mainForm:GetChildChecked( "MainPanel" , false )

wtInput1 = wtMainPanel:GetChildChecked( "Input1" , false )

        researchObj("wtEdit:",wtInput1)

end

все! всю инфо об объекте смотрим в файле C:\Program Files\Games\Allods Online\Personal\Logs\mods.txt

Info: addon AucEDSman: wtEdit: GetCursorPos:=function: 207769C8

Info: addon AucEDSman: wtEdit: GetBackgroundColor:=function: 25BC0278

Info: addon AucEDSman: wtEdit: Enable:=function: 2EEE5148

Info: addon AucEDSman: wtEdit: SetText:=function: 196A7B80

Info: addon AucEDSman: wtEdit: SetFocus:=function: 1CD944D8

Info: addon AucEDSman: wtEdit: SetBackgroundTexture:=function: 24D080A0

Info: addon AucEDSman: wtEdit: IsFocused:=function: 1771E2B8

...

далее можно исследовать каждую функцию - сколько у нее параметров и какие по сообщениям об ошибке в том же ЛОГфайле

Code:
wtInput1:GetCursorPos()

а

Code:
researchObj("_G",_G)

выдаст список всех функций, которые можно вызывать (API, LUA)

Link to comment
Share on other sites

предлагаю файлы с часто используемыми функциями объединить в одном ScriptLIB.lua и положить его в папку \Addons

а в AddonDesc.(UIAddon).xdb

писать:

Code:
  <Item href="../ScriptLIB.lua" />
Link to comment
Share on other sites

Система UI9 НЕ поддерживает заливку библиотек. Когда-то, хотели сделать, чтобы на сайт можно было заливать отдельные библиотеки, но практика показала, что это никому из разработчиков не было нужно (например, довольно хорошая библиотека AOClassLibrary, из DarkDPSMeter, осталась невостребованной). Ни одна из библиотек не стала настолько популярной, или крупной, чтобы появился смысл её заливать отдельно. К тому же, отдельные библиотеки - это было бы ОЧЕНЬ сложно для пользователей. Решили так - все нужные аддону библиотеки всегда кладутся внутрь самого аддона.

Кстати, именно из-за того, что когда-то задумывалась поддержка библиотек, все скачиваемые аддоны сейчас содержат "лишнюю" папку "Addons". Это была ошибка, которую нужно будет когда-нибудь исправить - избавиться от этой папки "Addons" в скачиваемых аддонах... Даже одна эта "лишняя" папка, как показала практика, создаёт проблемы для пользователей, некоторые не понимают, как правильно распаковать аддон (до сих пор, регулярно, задают такие вопросы), и у разработчиков аддонов, которые не понимают, как упаковать аддон для заливки. А что уж говорить, если бы мы ввели отдельные библиотеки, и пришлось бы объяснять пользователям, что для работы такого-то аддона, нужно дополнительно скачать и установить такую-то библиотеку. Причём, установить не в папку аддонов, а на один уровень выше (как "SampleCommon" - так предлагает инструкция по созданию аддонов от разработчиков игры). Нас бы, тогда, ЗАСЫПАЛИ глупыми вопросами "почему аддон не работает?", "как установить аддон?", "как установить библиотеку?" - спасибо, но НЕТ, это был бы настоящий Ад :)

Link to comment
Share on other sites

  • 2 weeks later...

выполнение на лету

Code:

------- choice.timeCoeff  is Global

local stat

local mess

stat, mess = assert(loadstring("choice.timeCoeff = 123", "DB load+"))()

LogInfo (stat,":",mess,":",self.timeCoeff )

--- error: stat, mess = assert(loadstring("self.timeCoeff = 13", "DB load-"))(self)

LogInfo (stat,":",mess,":",self.timeCoeff )

stat, mess = pcall( loadstring("choice.timeCoeff = 100", "DB load2"))

LogInfo (stat,":",mess,":",self.timeCoeff )

а можно ли как туда передать значение?

Link to comment
Share on other sites

еще вопрос - если таблица без полей {} то при сравнении

if TAB == {} then

будет true?

Code:
local u = {}

u.failedConditions = {}

exObj("u", u)

if u.failedConditions == {}  then

LogInfo (" true ")

end

LogInfo (" == {} ? ",  u.failedConditions == {} )

выдает ложь ((

Link to comment
Share on other sites

u.failedConditions == Адрес таблицы вроде (что-то вроде FF2B621A) и оно не равно "{}". т.к. что такое "{}" я на данный момент не знаю, не интересовался.

Link to comment
Share on other sites

что такое {} ??? - пустая таблица! - вы же не спрашиваете что это такое когда пишите

Code:
Globals( "MyTab", {} )

local tt = {}

так почему нельзя и в сравненни написать:

Code:
if ttt == {} then

...

GetTableSize() - она перебирает все поля! если у вас их 100000 то пока она не переберет их всех не ответит вам - пусто или нет... это не рационально я считаю

в общем проще свою функцию создать:

IsEmpty (table)

Link to comment
Share on other sites

Quote:
  1. что такое {} ???

  1. так почему нельзя и в сравненни написать

Programming in Lua. 2.5 - Tables


Quote:
в общем проще свою функцию создать:
IsEmpty (table)

Code:
function IsEmptyTable( tab )
for id, value in tab do
return false
end
return true
end


Как сделать свой аддон\Лaрчик Али-Бабы\Common\Script\ScriptCommonUtility.lua :)
Link to comment
Share on other sites

вот у меня идея релизовалась:

делать конфиг файлы у аддона.

в папке с аддоном создаем файл config.txt и пишем туда начальную инициализацию, например:

Code:
--- initial config

--- smart params

smart.on = true --- on|off

smart.wcc = GREED --- WrongCharacterClass

smart.ll = GREED --- LowLevel

smart.lln = 3 --- LowLevel different number

timeOffCoeff = 0.8 --- коэффициент авто выбора на Можно если вы АФК

затем в подпапке /Scripts (там где у нас все скрипты лежат) создаем Globals.lua и объявляем там все переменные:

Code:

Global( "PASS", CHOICE_GREED_NEED_PASS )

Global( "GREED", CHOICE_GREED_NEED_GREED )

Global( "NEED", CHOICE_GREED_NEED_NEED )

-----

Global( "smart", {} )

Global( "timeOffCoeff", 0.8 )

затем в основном файле аддона AddonDesc.(UIAddon).xdb вставляем:

Code:

  <ScriptFileRefs>

...

    <Item href="Scripts/Globals.lua" />

    <Item href="config.txt" />

...

Link to comment
Share on other sites

Guest Erendir

Хочу немного покурочить чат в сторону уменьшения спама (не от игроков, а всякие "сокровище найдено" и проч.).

Насколько я понял, в апи нет возможности остановить обработку события, значит, нужно править сам "стандартный чат"-аддон. Проблема в том, что актуальная версия зашифрована, а 1.0.03.26.2, боюсь, слегка устарела. Да и не хочу я делать целый аддон, мне надо будет подменить буквально один файл. Соответственно вопрос: как это сделать?

edit: возможно, я ошибался на счёт невозможности изменения стандартных обработчиков. Если в одном скрипте стоит Global( "onEvent", {} ), можно ли получить к глобальной переменной onEvent доступ из другого скрипта ?

Link to comment
Share on other sites

для отладки и просто для других целей - как сделать вывод в окно чата.

создаем 2 функции

Code:

------------------------------------------------------------------

---- output in Chat. created by icreator(EDS) 2011/01/23

--- initial ref

--- Chat...Chat

local wtChat = nil

local chatRows = 0 --- for clear buffer after show messages

function LogToChat(message, color, fontSize)

if not wtChat and not errMess.Chat then

---- если окно еще не искали и ошибки при этом не было. а если уже ошибка была то не искать

--- найдем окно чата

--- Chat..Chat

local w

w = stateMainForm:GetChildUnchecked("Chat", false)

if not w then

--- главня форма не найдена - найден по ребенку

w = stateMainForm:GetChildUnchecked("Chat", true)

else

w = w:GetChildUnchecked("Chat", true)

end

wtChat = w

end

if not wtChat or not wtChat.PushFrontValuedText then

if not errMess.Chat then

errMess.Chat = "can't find Chat window"

LogError(errMess.Chat)

end

return

end

local valuedText = common.CreateValuedText()

--- fontname=\"AllodsWest\"

local format = "<body alignx='left' fontname='AllodsSystem' fontsize='"..(fontSize or 14)

format = format.."' shadow='1' ><rs class='color'><r name='text'/></rs></body>"

valuedText:SetFormat(userMods.ToWString(format))

if color then

valuedText:SetClassVal( "color", color )

else

valuedText:SetClassVal( "color", "LogColorYellow" )

end

if not common.IsWString( message ) then message = userMods.ToWString(message) end

---message = ToWS(chatRows.. ":".. FromWS(message))

valuedText:SetVal( "text", message )

---wtChat:PopBack() --- ++

chatRows =  chatRows + 1

wtChat:PushFrontValuedText( valuedText )

---wtChat:PushFrontRawText( message ) --- текст не видно ((

end

--- call by "EVENT_SECOND_TIMER" - for clear messages from chat

function ClearChat( size )

for i=1, size or math.ceil( chatRows / 30 ) + 1 do

if chatRows < 1 then break end

chatRows = chatRows - 1

wtChat:PopBack()

end

end

------------------------------------------------------------------------------------

вызов для вывода 1 строчки делаем так:

Code:
LogToChat({ text = "Привки ВСЕМ!", color = "LogColorGreen" })

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

Code:
local delay = 20

function OnEventSecondTimer( params )

 --- очистить то что было в чат послано, чтобы не раздувать размер массива строк в окне чата - иначе он тормозит жутко потом

if delay< 0 then ClearChat() else delay = delay - 1 end

end

тогда чат не будет тормозить из-за того что массив его строчек раздувается до бесконечности

Link to comment
Share on other sites

может есть событие при открывании/закрывании окон? надо бы разрабам дать задание чтобы сделали - чтобы любые окна интерфейса ловить - аукцион и пр

Link to comment
Share on other sites

а в чем отличие в описаниях функций:

1

Code:
function SetConfig( name, value )

2

Code:
function DnD.GetWidgetID( wtWidget )

3

Code:
function DnD:IsDragging()

4

Code:
choice.makeChoice = function( self, rollId, NGP)

3 и 4 - это типа как методы объекта?

Link to comment
Share on other sites

кстати если вам влом свои интерфейс делать - окошки там иконки, кнопки - можно взять готовый прямо из системных аддонов:

Code:

Global( "sysRGNaddomName", "RollGreedNeed" ) --- имя системного аддона и его главной формы

addon.GetWidgets = function( self )

local wtSysName = sysRGNaddomName

--- скопирум виджеты с ситемного аддона

local wSys = stateMainForm:GetChildChecked(wtSysName, false):GetChildChecked( "MainPanel", false )

local wDesc = wSys:GetWidgetDesc()

--- и создадим такие же в нашей главной форме

mainForm:CreateWidgetByDesc( wDesc ):SetName( "MainPanel" )

local myWiidget = mainForm:GetChildChecked( "MainPanel", false )

 

все - теперь вся структура скопирована и можно выгрузить системный аддон и пользовать готовые виджеты

в описании аддона надо всеже главную форму создать:

AddonDesc.(UIAddon).xdb

Code:
  <Forms>

    <Item>

      <Form href="MainForm.(WidgetForm).xdb#xpointer(/WidgetForm)" />

      <Id>MainForm</Id>

    </Item>

  </Forms>

  <MainFormId>MainForm</MainFormId>

  • Upvote 1
Link to comment
Share on other sites

Подскажите, если не сложно, на наглядном примере, как использовать avatar.GetTarget(), у меня всё время пустое поле получается :(

И ещё проблема с EVENT_AVATAR_TARGET_CHANGED, почему то, когда меняю цели - ничего не происходит, если подставляю вместо этого EVENT_AVATAR_MONEY_CHANGED и продаю\покупаю что то - всё работает как надо.

Link to comment
Share on other sites

В общем с EVENT_AVATAR_TARGET_CHANGED я поспешил, для смены цели EVENT_AVATAR_PRIMARY_TARGET_CHANGED используется.

А с avatar.GetTarget не выходит пока ничего, пытаюсь сделать как в примере: local unitId = avatar.GetTarget() в логах:

Attempt to read from undeclared global variable: unitId

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

By using our site you agree to the Terms of Use