Перейти к содержанию

Дайджесты за январь-февраль

Обновления гайдов и аддонов

Январь Февраль

Мониторинг серверов и редактор аддонов

Представляем вам две легенды. То, о чем можно было только мечтать, стало реальностью.

Мониторинг серверов Редактор аддонов

Подсказки из игры на вашем сайте

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

Подробнее

Апдейтер аддонов

Представляем вам программу для автообновления аддонов и делимся подробностями.

Подробнее Скачать

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


SLA

Рекомендуемые сообщения

Я уже довольно давно обещаю "в скором времени" написать учебник по созданию аддонов 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 Буду править и дополнять это сообщение по мере необходимости.

Изменено пользователем Fye D. Flowright
Ссылка на комментарий
Поделиться на другие сайты

  • 4 недели спустя...
  • 3 месяца спустя...

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

Файлы с расширением .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()

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

Ссылка на комментарий
Поделиться на другие сайты

а есть события какие чтобы понять - открылось ли окно аукциона?

Ссылка на комментарий
Поделиться на другие сайты

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

Ссылка на комментарий
Поделиться на другие сайты

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

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)

Ссылка на комментарий
Поделиться на другие сайты

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

а в AddonDesc.(UIAddon).xdb

писать:

Code:
  <Item href="../ScriptLIB.lua" />
Ссылка на комментарий
Поделиться на другие сайты

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

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

Ссылка на комментарий
Поделиться на другие сайты

  • 2 недели спустя...

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

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 )

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

Ссылка на комментарий
Поделиться на другие сайты

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

if TAB == {} then

будет true?

Code:
local u = {}

u.failedConditions = {}

exObj("u", u)

if u.failedConditions == {}  then

LogInfo (" true ")

end

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

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

Ссылка на комментарий
Поделиться на другие сайты

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

Ссылка на комментарий
Поделиться на другие сайты

Code:
if GetTableSize( <переменная таблицы> ) == 0 then <если таблица пустая> end

я так проверяю пустая или нет
Ссылка на комментарий
Поделиться на другие сайты

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

Code:
Globals( "MyTab", {} )

local tt = {}

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

Code:
if ttt == {} then

...

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

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

IsEmpty (table)

Ссылка на комментарий
Поделиться на другие сайты

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 :)
Ссылка на комментарий
Поделиться на другие сайты

А подробней?

Свою функцию - какую?

Может девушка ченить гениальное придумала в очередной раз :)

Ссылка на комментарий
Поделиться на другие сайты

да нет - точно так же, как выше в сообщении - если есть первый элемент значит ЛОЖЬ

Ссылка на комментарий
Поделиться на другие сайты

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

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

в папке с аддоном создаем файл 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" />

...

Ссылка на комментарий
Поделиться на другие сайты

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

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

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

Ссылка на комментарий
Поделиться на другие сайты

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

создаем 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

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

Ссылка на комментарий
Поделиться на другие сайты

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

Ссылка на комментарий
Поделиться на другие сайты

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

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 - это типа как методы объекта?

Ссылка на комментарий
Поделиться на другие сайты

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

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>

Ссылка на комментарий
Поделиться на другие сайты

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

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

Ссылка на комментарий
Поделиться на другие сайты

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

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

Attempt to read from undeclared global variable: unitId

Ссылка на комментарий
Поделиться на другие сайты

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Восстановить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...

Важная информация

Пользуясь сайтом, вы принимаете Условия использования