Death Smile

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Death Smile » Флудильня » Перевод гайда Драко по АИ гомункулусов с рагнаинфо


Перевод гайда Драко по АИ гомункулусов с рагнаинфо

Сообщений 1 страница 23 из 23

1

Перевод не дословный, кое-что я могу добавить свое, но большая часть - это оно.

Итак. Во-первых, для того, чтобы написать более-менее стоящий скрипт, необходимо знать программирование как таковое. Во-вторых, Драко рекомендует знать синтаксис и структуру LUA (скриптовый язык, на котором собственно пишутся скрипты для покемонов), однако в случае выполнения первого пункта разобраться с этим проблем не составит - язык очень прост. Полное описание можно взть на родном сайте http://lua.org.

Урок первый: AI() функция (AI.lua)
Код:
function AI(myid)

   MyID = myid
   local msg   = GetMsg (myid)
   local rmsg   = GetResMsg (myid)

   if msg[1] == NONE_CMD then
      if rmsg[1] ~= NONE_CMD then
         if List.size(ResCmdList) < 10 then
            List.pushright (ResCmdList,rmsg)
         end
      end
   else
      List.clear (ResCmdList) 
      ProcessCommand (msg)
   end

    if (MyState == IDLE_ST) then
      OnIDLE_ST ()
   elseif (MyState == CHASE_ST) then               
      OnCHASE_ST ()
   elseif (MyState == ATTACK_ST) then
      OnATTACK_ST ()
   elseif (MyState == FOLLOW_ST) then
      OnFOLLOW_ST ()
   elseif (MyState == MOVE_CMD_ST) then
      OnMOVE_CMD_ST ()
   elseif (MyState == STOP_CMD_ST) then
      OnSTOP_CMD_ST ()
   elseif (MyState == ATTACK_OBJECT_CMD_ST) then
      OnATTACK_OBJECT_CMD_ST ()
   elseif (MyState == ATTACK_AREA_CMD_ST) then
      OnATTACK_AREA_CMD_ST ()
   elseif (MyState == PATROL_CMD_ST) then
      OnPATROL_CMD_ST ()
   elseif (MyState == HOLD_CMD_ST) then
      OnHOLD_CMD_ST ()
   elseif (MyState == SKILL_OBJECT_CMD_ST) then
      OnSKILL_OBJECT_CMD_ST ()
   elseif (MyState == SKILL_AREA_CMD_ST) then
      OnSKILL_AREA_CMD_ST ()
   elseif (MyState == FOLLOW_CMD_ST) then
      OnFOLLOW_CMD_ST ()
   end

end

Чтобы можно было как-то начать, необходимо понимать, что делает эта функция. Как видно из названия, это основная функция AI, единственная необходимая для работы скрипта. Без нее клиент РО будет выдавать ошибку.
Она вызывается клиентом каждый раз, когда покемон "думает", что ему делать, - точно так же мобы каждые N милисекунд определяют свое поведение (атаковать, сменить цель, применить скил и т.п.).
Основное назначение этой функции - обрабатывать отданные покемончику команды (в основном через Alt+клик) и на их основе задавать определнное поведение. Всю функцию можно разделить на 3 части.

Самая первая:
Код:

function AI(myid)

   MyID = myid
   local msg   = GetMsg (myid)
   local rmsg   = GetResMsg (myid)

Как мы видим, клиент вызывает эту функцию, используя в качестве аргумента некий ID - в данном случае это ID покемона.
В РО у каждого объекта (игрок, моб, ловушка, непись и так далее) существует свой ID (для справки: ID игроков есть ID их аккаунта. Это означает, что для всех чаров на одном аккаунте используется один ID. Зачем это нужно, я переведу позже  ).
Этот ID, который передается в функцию в виде переменной myid, сразу же заносится в другую переменную MyID (зачем - сложно сказать... может быть, myid может быть перезаписан в любой момент, или у Гравити возникли какие-то проблемы в ходе отладки, которые они не смогли убрать... кто знает). Затем клиент запрашивает сообщение, которое игрок передает покемону (хранится в переменной msg), и зарезервированные сообщения (довольно странная штука, похоже, некие команды, отдаваемые самим клиентом без участия игрока. Заносится в переменную rmsg).

Далее следующая часть кода:
Код:

   if msg[1] == NONE_CMD then
      if rmsg[1] ~= NONE_CMD then
         if List.size(ResCmdList) < 10 then
            List.pushright (ResCmdList,rmsg)
         end
      end
   else
      List.clear (ResCmdList)
      ProcessCommand (msg)
   end

Она начинается с проверки, была ли игроком отдана команда покемону. Если не была, то обрабатывается зарезервированная команда (если она была) путем добавления ее в список зарезервированных команд ResCmdList (видно, что в него можно пололожить не больше 10 элементов), но это все не то, что нас интересует. Главное здесь начинается тогда, когда игрок ОТДАВАЛ команду покемону: ResCmdList очищается (а нас это по-прежнему не волнует) и - самое главное - вызывается функция ProcessCommand(). Собственно с этого момента и начинается создание своего скрипта (ранее были общие вещи, которые не имели никакого отношения к собственно поведению покемона). Эту функцию мы разберем (точнее, я переведу ^_^) позже подробнее, а сейчас нужно понять суть: на основе того, какая команда была отдана игроком, вызывается определенная функция, которая и определяет, что будет делать покемончик.

Наконец, последняя, третья часть:
Код:

    if (MyState == IDLE_ST) then
      OnIDLE_ST ()
   elseif (MyState == CHASE_ST) then               
      OnCHASE_ST ()
   elseif (MyState == ATTACK_ST) then
      OnATTACK_ST ()
   elseif (MyState == FOLLOW_ST) then
      OnFOLLOW_ST ()
   elseif (MyState == MOVE_CMD_ST) then
      OnMOVE_CMD_ST ()
   elseif (MyState == STOP_CMD_ST) then
      OnSTOP_CMD_ST ()
   elseif (MyState == ATTACK_OBJECT_CMD_ST) then
      OnATTACK_OBJECT_CMD_ST ()
   elseif (MyState == ATTACK_AREA_CMD_ST) then
      OnATTACK_AREA_CMD_ST ()
   elseif (MyState == PATROL_CMD_ST) then
      OnPATROL_CMD_ST ()
   elseif (MyState == HOLD_CMD_ST) then
      OnHOLD_CMD_ST ()
   elseif (MyState == SKILL_OBJECT_CMD_ST) then
      OnSKILL_OBJECT_CMD_ST ()
   elseif (MyState == SKILL_AREA_CMD_ST) then
      OnSKILL_AREA_CMD_ST ()
   elseif (MyState == FOLLOW_CMD_ST) then
      OnFOLLOW_CMD_ST ()
   end

end

Тут все довольно просто: на основе того, какое состояние сейчас у покемончика (оно заносится в переменную MyState в других частях кода), вызывается функция, которая определяет, что собственно ему (покемону) следует делать, будучи в таком состоянии.

Следующий урок: Прежде чем копать глубже, рассмотрим функции и константы (что внутри файлов Const.lua и Utils.lua)

Второй урок: Прежде чем копать глубже, рассмотрим функции и константы (что внутри файлов Const.lua и Utils.lua)

Итак, мы немного разобрались в приниципах работы скрипта. Теперь займемся тем, что Гравити предоставила нам для создания AI. В большинстве случаев это функции (часть написана на LUA, часть выполняется клиентом - то есть их код недоступен в открытых файлах, и вообще они написаны на С).
Некоторые функции используют числа для определения свойств какого-либо объекта (например, состояние покемона или же тип объекта). Поэтому вместо того, чтобы использовать наборы чисел, созданы константы (пишутся БОЛЬШИМИ БУКВАМИ - согласно какому-то там соглашению. Меня никогда этому не учили, но пусть будет х_х).
Все это (функции и константы) могут быть найдены соответственно в файлах Utils.lua и Const.lua.

Итак, константы. Открываем Const.lua и видим следующее...
Код:
-------------------------------------------------
-- constants
-------------------------------------------------

--------------------------------
V_OWNER            =   0       
V_POSITION         =   1
V_TYPE            =   2
V_MOTION         =   3
V_ATTACKRANGE      =   4
V_TARGET         =   5
V_SKILLATTACKRANGE   =   6
V_HOMUNTYPE         =   7
V_HP            =   8
V_SP            =   9
V_MAXHP            =   10
V_MAXSP            =   11
---------------------------------   

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

LIF            = 1
AMISTR         = 2
FILIR         = 3
VANILMIRTH      = 4
LIF2         = 5
AMISTR2         = 6
FILIR2         = 7
VANILMIRTH2      = 8
LIF_H         = 9
AMISTR_H      = 10
FILIR_H         = 11
VANILMIRTH_H   = 12
LIF_H2         = 13
AMISTR_H2      = 14
FILIR_H2      = 15
VANILMIRTH_H2   = 16
--------------------------------------------

--------------------------
MOTION_STAND   = 0
MOTION_MOVE      = 1
MOTION_ATTACK   = 2
MOTION_DEAD     = 3
MOTION_ATTACK2   = 9
--------------------------

--------------------------
-- command 
--------------------------
NONE_CMD         = 0
MOVE_CMD         = 1
STOP_CMD         = 2
ATTACK_OBJECT_CMD   = 3
ATTACK_AREA_CMD      = 4
PATROL_CMD         = 5
HOLD_CMD         = 6
SKILL_OBJECT_CMD   = 7
SKILL_AREA_CMD      = 8
FOLLOW_CMD         = 9
--------------------------

Как видно, кода тут не так уж и много. Первая группа консант (начинаются с V_) используются для определения свойств объектов. Из названий понятно, какая константа что обозначает (если сложно сообразить, вот пример: V_HP символизирует текущие HP объекта). Подробнее мы их разберем, рассматривая функцию getV().
Затем идет список типов покемончиков (эволюционные формы и альтернативные спрайты здесь тоже есть). Эта группа в основном используется для определения типа нашего (вашего? О.о) покемона (хотя можно определить тип и чужого покемона - почему бы и нет?).
Затем идет список констант состояния объекта (MOTION_) - они определяют, что сейчас делает объект (стоит, атакует, идет, кастует и т.д.).
ВНИМАНИЕ! В скрипт по умолчанию не входит константа MOTION_SIT, она соответствует номеру 6. Кому надо - дописываем ручками, ага 
Наконец, _CMD-константы определяют команды, которые мы (вы?) отдаем нашей зверюшке через клиент. Да-да, те самые, которые храним в msg (вспоминаем первый урок).

Теперь посмотрим, что нам подарила Гравити, а именно функции. Начнем с С-функций, выполняемых клиентом. Повторюсь: их код не содержится в файлах .lua, но их список (закомментированный, естественно) можно найти в начале Const.lua.
Код:
function   TraceAI (string) end

Эта функция выводит указанный в качестве параметра текст в файл traceAI.txt, который находится в корневой папке игры (НЕ в папке AI). Используется в основном для отладки.
Замечу, что эта функция не очень удобна - помимо нужного текста она пишет какую-то фигню на корейском, которую не любит Блокнот. Поэтому не удивляйтесь неизвестным символам 
Код:
function   MoveToOwner (id) end

Эта функция использует ID покемончика в качестве аргмента и отдает ему приказ немедленно двигаться в ту же точку, где стоит его хозяин (так слишком долго, поэтому вдальнейшем я буду просто говорить "туда, где стоит Мастер" и т.д. ^_^)
Код:
function    Move (id,x,y) end

Аналогична предыдущей, но точка назначения определяется параметрами x и y. Одна из основных функций 
Код:
function   Attack (id,id) end

Принцип все тот же. Эта функция отдает приказ покемону (первый id в списке параметров) атаковать объект (второй id). Очевидно, что если цель является неправильным противником (например, игрок вне арены/дуэли/гв), то скорее всего ничего не произойдет.
Код:
function    GetV (V_,id) end

Эта одна из двух функций, которые являются всеми органами чувств нашего покемончика. Проще говоря, эта функция позволяет получить тонны информации, если ее использовать правильно. Первый параметр определяет, что собственно мы хотим получить (вспоминаем список V_-констант), второй - цель, с которой будет получена информация. Пара примеров для наглядности:
GetV(V_OWNER, MyID) возвращает ID Мастера, если в MyID хранится ID покемона.
MyX, MyY = GetV(V_POSITION,MyID) занесет в MyX и MyY текущие координаты покемона (требование к MyID аналогично предыдущему примеру - там должен лежать ID покемона).
GetV(V_TYPE, ID) тут Драко честно сознается, что не знает, как можно использовать эту функцию. Я тоже не знаю.
GetV(V_MOTION, MyID) вернет текущее состояние покемона (список состояний был выше). Все довольно очевидно, исключая различие между MOTION_ATTACK и MOTION_ATTACK2. Предполагаю, что это просто различные анимации обычной физической атаки (у ванильки, например, их две).
GetV(V_ATTACKRANGE, MyID) вернет "дальность" покемона (в пределах которого он будет выслеживать цели).
GetV(V_SKILLATTACKRANGE, MyID, MySkill) вернет дальность действия указанного скилла. Список ID скиллов, равно как и ОЧЕНЬ ВАЖНОЕ ЗАМЕЧАНИЕ я приведу чуть попозже.
GetV(V_HOMUNTYPE, MyID) возвращает тип гомункулуса (список, опять же, был выше). Используется для того, чтобы не заставлять ванилек спамить мунлайтами 
GetV(V_HP, MyID) вернет текущие HP покемона. Удивительное дело - если использовать V_SP, то мы получим... текущие SP покемона. "Фантастика!" (с)
GetV(V_MAXHP, MyID) угадайте, чего возвращает... угу, правильно, максимальные HP. А V_MAXSP... ну понятно, думаю 
Код:
function   GetActors () end

Это вторая функция, которая позволяет получать информацию о внешнем мире. Возвращает список, состоящий из ID каждого моба, игрока или гомункулуса на экране. Без нее не обойтись в процедурах определения целей.
Код:
function   GetTick () end

Нигде не используется в скрипте по умолчанию, однако очень и очень функциональна. Возвращает текущий тик (количество милисекунд, прошедших с определенного момента. По умолчанию используется для синхронизации между клиентом и сервером). С помощью нее можно считать, сколько прошло времени с какого-то момента (например, после применения скилла - отсчитываем задержку).
Для этого после того, как был применен скилл, кладем в переменную значение GetTick(), затем в нужном месте снова вызываем GetTick() и из него вычитаем старое значение. Получаем количество милисекунд, прошедших с того момента. Чего делать дальше - решать вам :Р
Код:
function   GetMsg (id) end

Уже должны знать сами, что делает эта функция. Используя в качестве параметра ID покемона, возвращает список с командой в виде ID (смотрим список _CMD-констант) на первом месте и прочей информацией далее (ID цели, координаты и т.д.)
Код:
function   GetResMsg (id) end

Возвращает зарезервированную команду клиента. Уже говорилось, что неизвестно, как это использовать %) Требует ID покемона в качестве параметра.
Код:
function   SkillObject (id,level,skill,target) end

Отдает покемону (определяется через ID) приказ использовать скилл (skill - сюда кладется ID скилла) заданного уровня (level) на указанную цель (target - определяется через ID).
Обещанное ОЧЕНЬ ВАЖНОЕ ЗАМЕЧАНИЕ.
Как стало известно в ходе экспериментов, через эту команду можно использовать не только скиллы гомункулуса, но и самого Мастера. На этом построены многие очень сомнительные функции - автолечение покемона Potion Pitcher`ом, автоатака с Fireblend`ов, использование Cart Revolution`а и так далее.
ПОЗИЦИЯ НАШЕЙ АДМИНИСТРАЦИИ К ЭТИМ ДЕЙСТВИЯМ НА ДАННЫЙ МОМЕНТ НЕИЗВЕСТНА!!! ПОЭТОМУ ВПОЛНЕ ВОЗМОЖНО, ЧТО ИСПОЛЬЗОВАНИЕ ПОДОБНЫХ ФУНКЦИЙ БУДЕТ КАРАТЬСЯ БАНОМ
Пользуйте на свой страх и риск, я вас предупредила х_х
Теперь список ID скиллов (взято из исходников сервера за версией 2219):
Код:

скиллы торговцы:
36 MC_INCCARRY
37 MC_DISCOUNT
38 MC_OVERCHARGE
39 MC_PUSHCART
40 MC_IDENTIFY
41 MC_VENDING
42 MC_MAMMONITE
153 MC_CARTREVOLUTION
154 MC_CHANGECART
155 MC_LOUD

скиллы хима:
226 AM_AXEMASTERY
227 AM_LEARNINGPOTION
228 AM_PHARMACY
229 AM_DEMONSTRATION
230 AM_ACIDTERROR
231 AM_POTIONPITCHER
232 AM_CANNIBALIZE
233 AM_SPHEREMINE
234 AM_CP_WEAPON
235 AM_CP_SHIELD
236 AM_CP_ARMOR
237 AM_CP_HELM
238 AM_BIOETHICS
243 AM_CALLHOMUN
244 AM_REST
247 AM_RESURRECTHOMUN

скилл Twilight Pharmacy - доступен под обкастом соул линкера
496 AM_TWILIGHT1
497 AM_TWILIGHT2
498 AM_TWILIGHT3

скиллы биохима:
478 CR_SLIMPITCHER
479 CR_FULLPROTECTION
490 CR_ACIDDEMONSTRATION
491 CR_CULTIVATION

скиллы гомункулусов:
Лиф:
8001 HLIF_HEAL
8002 HLIF_AVOID
8003 HLIF_BRAIN
8004 HLIF_CHANGE

Амистр:
8005 HAMI_CASTLE
8006 HAMI_DEFENCE
8007 HAMI_SKIN
8008 HAMI_BLOODLUST

Филир:
8009 HFLI_MOON
8010 HFLI_FLEET
8011 HFLI_SPEED
8012 HFLI_SBR44

Ванильки:
8013 HVAN_CAPRICE
8014 HVAN_CHAOTIC
8015 HVAN_INSTRUCT
8016 HVAN_EXPLOSION

Замечу, что четвертые скиллы у гомункулусов доступны только эволюционным формам.
Код:
function   SkillGround (id,level,skill,x,y) end

Аналогично предыдущей функции, но применяется на местность. Поэтому вместо ID цели указываются координаты точки применения.
Код:
function   IsMonster (id) end

Эта функция возвращает 1, если цель является монстром, и 0, если не является.

Функции, написанные на LUA, их код находится в файле Utils.lua.
Код:
require "./AI/Const.lua"

Подключаем файл Const.lua. Хороший пример, как подключать дополнительные файлы к скрипту.
Код:
--------------------------------------------
-- List utility
--------------------------------------------
List = {}

Довольно большой кусок, названный List Utility, позволяет работать с двусторонними списками. В общем-то, совсем необязательно понимать, как тут что работает, т.к. нам нужны будут от этих функций только результаты. Но раз Драко разобрал, то и мы займемся...
Код:
function List.new ()
   return { first = 0, last = -1}
end

Эта функция создает и инициализирует список. Присваиваем значение этой функции какой-нибудь переменной, чтобы создать список. Например, так:
MyStack = List.new()
Код:
function List.pushleft (list, value)
   local first = list.first-1
   list.first  = first
   list[first] = value;
end

function List.pushright (list, value)
   local last = list.last + 1
   list.last = last
   list[last] = value
end

Эти функции заносят значения в список. Первая добавляет элемент слева от уже существующих, вторая - справа.
Код:
function List.popleft (list)
   local first = list.first
   if first > list.last then
      return nil
   end
   local value = list[first]
   list[first] = nil         -- to allow garbage collection
   list.first = first+1
   return value
end

function List.popright (list)
   local last = list.last
   if list.first > last then
      return nil
   end
   local value = list[last]
   list[last] = nil
   list.last = last-1
   return value
end

Эти функции противоположны предыдущим: они убират самый левый (и соответственно самый правый) элемент из списка и возвращают его. Запомните - они не только возвращают значение, но и УДАЛЯЮТ. Пример:
MyValue = List.popleft(MyList)
Код:
function List.clear (list)
   for i,v in ipairs(list) do
      list[i] = nil
   end
--[[
   if List.size(list) == 0 then
      return
   end
   local first = list.first
   local last  = list.last
   for i=first, last do
      list[i] = nil
   end
--]]
   list.first = 0
   list.last = -1
end

Эта функция очищает список и переинициализирует (ну и слово х_х) его заново. После этой функции список полностью аналогичен тому, что получится после использования List.new()
Код:
function List.size (list)
   local size = list.last - list.first + 1
   return size
end

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

Возвращает количество элементов в списке. Без комментариев.

Затем идут "маленькие, но гордые" функции - они используются очень часто.
Код:
function   GetDistance (x1,y1,x2,y2)
   return math.floor(math.sqrt((x1-x2)^2+(y1-y2)^2))
end

function   GetDistance2 (id1, id2)
   local x1, y1 = GetV (V_POSITION,id1)
   local x2, y2 = GetV (V_POSITION,id2)
   if (x1 == -1 or x2 == -1) then
      return -1
   end
   return GetDistance (x1,y1,x2,y2)
end

Getdistance() возвращает расстояние между двумя точками, заданными координатами, в то время как GetDistance2 возвращает расстояние между двумя объектами.
Код:
function   GetOwnerPosition (id)
   return GetV (V_POSITION,GetV(V_OWNER,id))
end

function   GetDistanceFromOwner (id)
   local x1, y1 = GetOwnerPosition (id)
   local x2, y2 = GetV (V_POSITION,id)
   if (x1 == -1 or x2 == -1) then
      return -1
   end
   return GetDistance (x1,y1,x2,y2)
end

GetOwnerPosition() возвращает координаты Мастера. В основном используется в функции GetDistanceFromOwner(), которая возвращает расстояние до Мастера. Полезно, чтобы покемончик не терялся и не шлялся где-то без нас 
Код:
function   IsOutOfSight (id1,id2)
   local x1,y1 = GetV (V_POSITION,id1)
   local x2,y2 = GetV (V_POSITION,id2)
   if (x1 == -1 or x2 == -1) then
      return true
   end
   local d = GetDistance (x1,y1,x2,y2)
   if d > 20 then
      return true
   else
      return false
   end
end

Возвращает истину, если объекты id1 и id2 НЕ видят друг друга (как видно, область видимости - 20 клеток. Хотя в окне клиента видно примерно 15, что оставляет небольшой запас), и возвращает ложь, если объекты ВИДЯТ друг друга. Однако это не значит, что если моб нас видит, то будет атаковать: их область агрессивности гораздо меньше 20 клеток).
Код:
function   IsInAttackSight (id1,id2)
   local x1,y1 = GetV (V_POSITION,id1)
   local x2,y2 = GetV (V_POSITION,id2)
   if (x1 == -1 or x2 == -1) then
      return false
   end
   local d      = GetDistance (x1,y1,x2,y2)
   local a     = 0
   if (MySkill == 0) then
      a     = GetV (V_ATTACKRANGE,id1)
   else
      a     = GetV (V_SKILLATTACKRANGE,id1,MySkill)
   end

   if a >= d then
      return true;
   else
      return false;
   end
end

Наконец, последняя функция. Вполне соответствует своему названию. Однако есть одно "но": в качестве id1 всегда нужно использовать ID покемона, так как проверяется область атаки по дальности действия скиллов покемона.

Урок третий: а теперь, собственно, настоящий AI!

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

Из первого урока мы помним, что функция ProcessCommand() вызывает всякие функции в зависимости от того, какие команды мы отдали нашему покемону. Вот ее код (еще раз):
Код:
function   ProcessCommand (msg)

   if      (msg[1] == MOVE_CMD) then
      OnMOVE_CMD (msg[2],msg[3])
      TraceAI ("MOVE_CMD")
   elseif   (msg[1] == STOP_CMD) then
      OnSTOP_CMD ()
      TraceAI ("STOP_CMD")
   elseif   (msg[1] == ATTACK_OBJECT_CMD) then
      OnATTACK_OBJECT_CMD (msg[2])
      TraceAI ("ATTACK_OBJECT_CMD")
   elseif   (msg[1] == ATTACK_AREA_CMD) then
      OnATTACK_AREA_CMD (msg[2],msg[3])
      TraceAI ("ATTACK_AREA_CMD")
   elseif   (msg[1] == PATROL_CMD) then
      OnPATROL_CMD (msg[2],msg[3])
      TraceAI ("PATROL_CMD")
   elseif   (msg[1] == HOLD_CMD) then
      OnHOLD_CMD ()
      TraceAI ("HOLD_CMD")
   elseif   (msg[1] == SKILL_OBJECT_CMD) then
      OnSKILL_OBJECT_CMD (msg[2],msg[3],msg[4],msg[5])
      TraceAI ("SKILL_OBJECT_CMD")
   elseif   (msg[1] == SKILL_AREA_CMD) then
      OnSKILL_AREA_CMD (msg[2],msg[3],msg[4],msg[5])
      TraceAI ("SKILL_AREA_CMD")
   elseif   (msg[1] == FOLLOW_CMD) then
      OnFOLLOW_CMD ()
      TraceAI ("FOLLOW_CMD")
   end
end

Все просто. Функция проверяет msg[1], который содержит ID отданной команды, печатает отладочное сообщение в TraceAI.txt о том, какая команда была получена, и вызывает соответствующую OnXXX_CMD() функцию, передавая дополнительные параметры, если это необходимо. Настоящий код по сути - это как раз эти функции, которые находятся выше ProcessCommand() в файле AI.lua.
Код:
function   OnMOVE_CMD (x,y)
   
   TraceAI ("OnMOVE_CMD")

   if ( x == MyDestX and y == MyDestY and MOTION_MOVE == GetV(V_MOTION,MyID)) then
      return
   end

   local curX, curY = GetV (V_POSITION,MyID)
   if (math.abs(x-curX)+math.abs(y-curY) > 15) then
      List.pushleft (ResCmdList,{MOVE_CMD,x,y})     
      x = math.floor((x+curX)/2)
      y = math.floor((y+curY)/2)
   end

   Move (MyID,x,y)   
   
   MyState = MOVE_CMD_ST
   MyDestX = x
   MyDestY = y
   MyEnemy = 0
   MySkill = 0

end

Первым делом печатается отладочное сообщение о том, что функция была вызвана. Затем функция немедленно завершается, если покемончик уже в точке назначения И находится в состоянии движения, дабы избежать лишних прогонов кода. Более заковыристая часть кода ниже есть всего лишь проверка, находится ли целевая точка в радиусе 15 клеток (дальность обзора клиента. Логично, что посылать покемона на большое расстояние будет проблематично). Если да, то целевая клетка меняется на клетку в том же направлении, но вдвое ближе, а команда двигаться в изначальную клетку кладется в список зарезервированных команд ResCmdList (очевидно, она будет выполнена позже, когда гомункуль будет в пределах 15 клеток от цели, если до этого момента не отдать другой команды). Наконец, просто отдается приказ передвинуться в целевую клетку, одновременно с этим меняются некоторые глобальные переменные, обозначающие состояние покемона. Их названия говорят сами за себя, поэтому особо разбирать их не будем. Более подробно их использование мы разберем, изучая третью часть AI().
Код:
function   OnSTOP_CMD ()

   TraceAI ("OnSTOP_CMD")

   if (GetV(V_MOTION,MyID) ~= MOTION_STAND) then
      Move (MyID,GetV(V_POSITION,MyID))
   end
   MyState = IDLE_ST
   MyDestX = 0
   MyDestY = 0
   MyEnemy = 0
   MySkill = 0

end

Аналогично предыдущей, но еще проще. Единственное на первый взгляд непонятное - часть кода, которая передвигает покемона в ту точку, где он уже находится. Кажется странным, однако это позволяет "поймать" зверюшку там, где она есть, и прекратить ее движение.
Код:
function   OnATTACK_OBJECT_CMD (id)

   TraceAI ("OnATTACK_OBJECT_CMD")

   MySkill = 0
   MyEnemy = id
   MyState = CHASE_ST

end

Нечего объяснять х_х
Код:
function   OnATTACK_AREA_CMD (x,y)

   TraceAI ("OnATTACK_AREA_CMD")

   if (x ~= MyDestX or y ~= MyDestY or MOTION_MOVE ~= GetV(V_MOTION,MyID)) then
      Move (MyID,x,y)   
   end
   MyDestX = x
   MyDestY = y
   MyEnemy = 0
   MyState = ATTACK_AREA_CMD_ST
   
end

Вызывается, когда мы приказываем атаковать местность (это как, интересно? х_х). Двигает покемона в указанную клетку, если он уже не там или уже не в состоянии движения.
Код:
function   OnPATROL_CMD (x,y)

   TraceAI ("OnPATROL_CMD")

   MyPatrolX , MyPatrolY = GetV (V_POSITION,MyID)
   MyDestX = x
   MyDestY = y
   Move (MyID,x,y)
   MyState = PATROL_CMD_ST

end

Она вызывается, когда мы говорим покемону идти куда-то, а затем вернуться. Запоминается текущая позиция (чтобы было куда возвращаться) в переменных MyPatrolX, MyPatrolY, затем покемон передвигается в указанную клетку.
Код:
function   OnHOLD_CMD ()

   TraceAI ("OnHOLD_CMD")

   MyDestX = 0
   MyDestY = 0
   MyEnemy = 0
   MyState = HOLD_CMD_ST

end

Простейшая команда "оставаться на месте".
Код:
function   OnSKILL_OBJECT_CMD (level,skill,id)

   TraceAI ("OnSKILL_OBJECT_CMD")

   MySkillLevel = level
   MySkill = skill
   MyEnemy = id
   MyState = CHASE_ST

end

Аналогично атаке, только используется скилл. Заметим, что состояние становится аналогичным (CHASE_ST).
Код:
function   OnSKILL_AREA_CMD (level,skill,x,y)

   TraceAI ("OnSKILL_AREA_CMD")

   Move (MyID,x,y)
   MyDestX = x
   MyDestY = y
   MySkillLevel = level
   MySkill = skill
   MyState = SKILL_AREA_CMD_ST
   
end

Точно так же, только на местность.
Код:
function   OnFOLLOW_CMD ()

   if (MyState ~= FOLLOW_CMD_ST) then
      MoveToOwner (MyID)
      MyState = FOLLOW_CMD_ST
      MyDestX, MyDestY = GetV (V_POSITION,GetV(V_OWNER,MyID))
      MyEnemy = 0
      MySkill = 0
      TraceAI ("OnFOLLOW_CMD")
   else
      MyState = IDLE_ST
      MyEnemy = 0
      MySkill = 0
      TraceAI ("FOLLOW_CMD_ST - IDLE_ST")
   end

end

Если покемон УЖЕ НЕ ПРЕСЛЕДУЕТ кого-то, отдается команда вернуться к Мастеру. Если он все-таки за кем-то гоняется, мы это прекращаем, выставляя ему в состояние "стоять" (IDLE_ST)

Угу. Это все функции, которые вызываются при отдаче приказа. Теперь последняя часть - функции, которые собственно отвечают за поведение покемона в зависимости от его состояния и других переменных, которые мы определили.
Код:
function   OnIDLE_ST ()
   
   TraceAI ("OnIDLE_ST")

   local cmd = List.popleft(ResCmdList)
   if (cmd ~= nil) then       
      ProcessCommand (cmd)
      return
   end

   local   object = GetOwnerEnemy (MyID)
   if (object ~= 0) then
      MyState = CHASE_ST
      MyEnemy = object
      TraceAI ("IDLE_ST  CHASE_ST : MYOWNER_ATTACKED_IN")
      return
   end

   object = GetMyEnemy (MyID)
   if (object ~= 0) then
      MyState = CHASE_ST
      MyEnemy = object
      TraceAI ("IDLE_ST  CHASE_ST : ATTACKED_IN")
      return
   end

   local distance = GetDistanceFromOwner(MyID)
   if ( distance > 3 or distance == -1) then
      MyState = FOLLOW_ST
      TraceAI ("IDLE_ST  FOLLOW_ST")
      return;
   end

end

Становится сложно, да? 
Это - то, что делает наш покемон, когда ничего не делает. Первым делом выполняем первую команду из зарезервированного списка команд (типа движения к удаленной клетке и т.п.). Затем проверяется, бьет ли кого Мастер, функцией GetOwnerEnemy(); если нашли кого-то, то переключаем состояние на преследование, запоминаем цель как противника, после чего функция немедленно завершается (следующий пробег по функции AI() обнаружит, что покемончик уже в состонии преследования, и выполнит соответсвующую функцию). Если противников таким образом не найдено, то ищутся противники на экране, используя функцию GetMyEnemy(): она вернет или уже атакуемую цель, если она есть (поведение по умолчанию для Амистра и Лифа), или просто ближайшую цель (поведение по умолчанию для Филира и Ванильки). Код этой функции мы разберем позже. Наконец, если противников не обнаружено, покемончик переключается в режим следования за Мастером. Мы ведь не хотим уйти, оставив нашу зверюшку одну?
Код:
function   OnFOLLOW_ST ()

   TraceAI ("OnFOLLOW_ST")

   if (GetDistanceFromOwner(MyID) <= 3) then
      MyState = IDLE_ST
      TraceAI ("FOLLOW_ST  IDLW_ST")
      return;
   elseif (GetV(V_MOTION,MyID) == MOTION_STAND) then
      MoveToOwner (MyID)
      TraceAI ("FOLLOW_ST  FOLLOW_ST")
      return;
   end

end

Этот код попроще: если покемончик рядом с Мастером, переключает его состояние (покемона, не Мастера  ) на стояние (idle), иначе отдает приказ зверюшке бежать к вам.
Код:
function   OnCHASE_ST ()

   TraceAI ("OnCHASE_ST")

   if (true == IsOutOfSight(MyID,MyEnemy)) then
      MyState = IDLE_ST
      MyEnemy = 0
      MyDestX, MyDestY = 0,0
      TraceAI ("CHASE_ST  IDLE_ST : ENEMY_OUTSIGHT_IN")
      return
   end
   if (true == IsInAttackSight(MyID,MyEnemy)) then
      MyState = ATTACK_ST
      TraceAI ("CHASE_ST  ATTACK_ST : ENEMY_INATTACKSIGHT_IN")
      return
   end

   local x, y = GetV (V_POSITION,MyEnemy)
   if (MyDestX ~= x or MyDestY ~= y) then
      MyDestX, MyDestY = GetV (V_POSITION,MyEnemy);
      Move (MyID,MyDestX,MyDestY)
      TraceAI ("CHASE_ST  CHASE_ST : DESTCHANGED_IN")
      return
   end

end

Во-первых, возвращает покемона в состояние "ничегонеделанья" (как idle правильно перевести? х_х) и завершается, если противник вышел из поля зрения. Затем переключает в состояние атаки и завершается, если противник в пределах досягаемости. Наконец, уточняются координаты цели и отдается приказ двигаться к ней, если противник сдвинулся (а это часто происходит).
Код:
function   OnATTACK_ST ()

   TraceAI ("OnATTACK_ST")
   
   if (true == IsOutOfSight(MyID,MyEnemy)) then
      MyState = IDLE_ST
      TraceAI ("ATTACK_ST  IDLE_ST")
      return
   end

   if (MOTION_DEAD == GetV(V_MOTION,MyEnemy)) then
      MyState = IDLE_ST
      TraceAI ("ATTACK_ST  IDLE_ST")
      return
   end
       
   if (false == IsInAttackSight(MyID,MyEnemy)) then
      MyState = CHASE_ST
      MyDestX, MyDestY = GetV (V_POSITION,MyEnemy);
      Move (MyID,MyDestX,MyDestY)
      TraceAI ("ATTACK_ST  CHASE_ST  : ENEMY_OUTATTACKSIGHT_IN")
      return
   end
   
   if (MySkill == 0) then
      Attack (MyID,MyEnemy)
   else
      SkillObject (MyID,MySkillLevel,MySkill,MyEnemy)
      MySkill = 0
   end
   TraceAI ("ATTACK_ST  ATTACK_ST  : ENERGY_RECHARGED_IN")
   return

end

Если противник вне поля зрения, возвращает в состояние стояния и убивается (завершается, в смысле). Аналогично, если противник умер (довольно необычное движение - MOTION_DEAD...) Наконец, цель просто атакуется, если не нужно использовать скилл, или использует скилл, если это было приказано.
Код:
function   OnMOVE_CMD_ST ()

   TraceAI ("OnMOVE_CMD_ST")

   local x, y = GetV (V_POSITION,MyID)
   if (x == MyDestX and y == MyDestY) then
      MyState = IDLE_ST
   end
end

Если точка назначения достигнута, включается мое любимое состояние ничегонеделанья.
Код:
function OnSTOP_CMD_ST ()
end

function OnATTACK_OBJECT_CMD_ST ()
end
There's just... nothing there xD
Code:
function OnATTACK_AREA_CMD_ST ()

   TraceAI ("OnATTACK_AREA_CMD_ST")

   local   object = GetOwnerEnemy (MyID)
   if (object == 0) then                     
      object = GetMyEnemy (MyID)
   end

   if (object ~= 0) then
      MyState = CHASE_ST
      MyEnemy = object
      return
   end

   local x , y = GetV (V_POSITION,MyID)
   if (x == MyDestX and y == MyDestY) then
         MyState = IDLE_ST
   end

end

Классическое состяоние "атаковать местность": пытается найти сначала противника Мастера, затем своего противника (и начинает его преследовать, если нашла). В противном же случае, если достигнута точка назначения... да-да, "idle" состояние.
Код:
function OnPATROL_CMD_ST ()

   TraceAI ("OnPATROL_CMD_ST")

   local   object = GetOwnerEnemy (MyID)
   if (object == 0) then                     
      object = GetMyEnemy (MyID)
   end

   if (object ~= 0) then
      MyState = CHASE_ST
      MyEnemy = object
      TraceAI ("PATROL_CMD_ST  CHASE_ST : ATTACKED_IN")
      return
   end

   local x , y = GetV (V_POSITION,MyID)
   if (x == MyDestX and y == MyDestY) then
      MyDestX = MyPatrolX
      MyDestY = MyPatrolY
      MyPatrolX = x
      MyPatrolY = y
      Move (MyID,MyDestX,MyDestY)
   end

end

Простейшее состояние патрулирования: сначала пытается найти противника вышеуказанным способом, если не нашла, проверяет, достигнута ли точка назначения. Если да, отдает приказ идти в изначальную клетку (предварительно запомнив текущие координаты еще раз - для бесконечного патрулирования между двумя клетками, конечно же)
Код:
function OnHOLD_CMD_ST ()

   TraceAI ("OnHOLD_CMD_ST")
   
   if (MyEnemy ~= 0) then
      local d = GetDistance(MyEnemy,MyID)
      if (d ~= -1 and d <= GetV(V_ATTACKRANGE,MyID)) then
            Attack (MyID,MyEnemy)
      else
         MyEnemy = 0;
      end
      return
   end

   local   object = GetOwnerEnemy (MyID)
   if (object == 0) then                     
      object = GetMyEnemy (MyID)
      if (object == 0) then                   
         return
      end
   end

   MyEnemy = object

end

Режим удержания местности (Драко только сейчас сообразил, что это и предыдущее состояние сперто из стратегий в реальном времени х_х): атаковать противника, если он близко (они могли и должны были использовать функцию IsInAttackSight(), но в Гравити, похоже, сами не пользуются своими функциями х___х), иначе забить и искать другого через GetOwnerEnemy() и GetMyEnemy(), однако не преследовать их. Следующий проход этой функции определит, следует ли атаковать найденного противника или нет.
Код:

function OnSKILL_OBJECT_CMD_ST ()   
end

Угу, здесь ничего нет.
Код:
function OnSKILL_AREA_CMD_ST ()

   TraceAI ("OnSKILL_AREA_CMD_ST")

   local x , y = GetV (V_POSITION,MyID)
   if (GetDistance(x,y,MyDestX,MyDestY) <= GetV(V_SKILLATTACKRANGE,MyID,MySkill)) then
      SkillGround (MyID,MySkillLevel,MySkill,MyDestX,MyDestY)
      MyState = IDLE_ST
      MySkill = 0
   end

end

М... применяет скилл туда, куда указали (если цель в пределах досягаемости) и возвращает покемона в ленивое состояние (лингво меня спасет - там куча значений слова idle  ).
Код:
function OnFOLLOW_CMD_ST ()

   TraceAI ("OnFOLLOW_CMD_ST")

   local ownerX, ownerY, myX, myY
   ownerX, ownerY = GetV (V_POSITION,GetV(V_OWNER,MyID))
   myX, myY = GetV (V_POSITION,MyID)

   local d = GetDistance (ownerX,ownerY,myX,myY)

   if ( d <= 3) then
      return
   end

   local motion = GetV (V_MOTION,MyID)
   if (motion == MOTION_MOVE) then
      d = GetDistance (ownerX, ownerY, MyDestX, MyDestY)
      if ( d > 3) then
         MoveToOwner (MyID)
         MyDestX = ownerX
         MyDestY = ownerY
         return
      end
   else
      MoveToOwner (MyID)
      MyDestX = ownerX
      MyDestY = ownerY
      return
   end
   
end

Во-первых... ДА, ОНИ ДОЛЖНЫ БЫЛИ ИСПОЛЬЗОВАТЬ GetDistanceFromOwner()!!! Но это Гравити, чего еще ждать х_х
Вы уже должны сами понимать, что здесь происходит, однако ж: если недалеко от Мастера, завершается, иначе идти к Мастеру, если покемон не двигается или двигается в клетку, которая очень далеко от Мастера.

Уря, мы закончили все функции, обрабатывающие состояния покемонов! Теперь остались только функции выбора противника. Потерпите, остался последний рывок х_х
Код:
function   GetOwnerEnemy (myid)
   local result = 0
   local owner  = GetV (V_OWNER,myid)
   local actors = GetActors ()
   local enemys = {}
   local index = 1
   local target
   for i,v in ipairs(actors) do
      if (v ~= owner and v ~= myid) then
         target = GetV (V_TARGET,v)
         if (target == owner) then
            if (IsMonster(v) == 1) then
               enemys[index] = v
               index = index+1
            else
               local motion = GetV(V_MOTION,i)
               if (motion == MOTION_ATTACK or motion == MOTION_ATTACK2) then
                  enemys[index] = v
                  index = index+1
               end
            end
         end
      end
   end

   local min_dis = 100
   local dis
   for i,v in ipairs(enemys) do
      dis = GetDistance2 (myid,v)
      if (dis < min_dis) then
         result = v
         min_dis = dis
      end
   end
   
   return result
end

Ух. Не позволяйте себя испугать, тут все проще, чем кажется на первый взгляд.
"for i,v in ipairs(actors) do" просто перебирает вектор actors, где i - текущий индекс элемента, а v - значение этого элемента. По сути, эта функция работает так: получает список активных целей (actors - всё, что может обладать ID - мобы, игроки, трапы, etc). Если это не покемон и не его Мастер, проверяется его цель. Если оно выцеливает Мастера и атакует (да, это разные вещи), запоминаем его в списке enemys. Наконец, перебираем список отобранных целей (enemys), выбирая ближайшего.
Код:
function   GetMyEnemy (myid)
   local result = 0

   local type = GetV (V_HOMUNTYPE,myid)
   if (type == LIF or type == LIF_H or type == AMISTR or type == AMISTR_H or type == LIF2 or type == LIF_H2 or type == AMISTR2 or type == AMISTR_H2) then
      result = GetMyEnemyA (myid)
   elseif (type == FILIR or type == FILIR_H or type == VANILMIRTH or type == VANILMIRTH_H or type == FILIR2 or type == FILIR_H2 or type == VANILMIRTH2 or type == VANILMIRTH_H2) then
      result = GetMyEnemyB (myid)
   end
   return result
end

Таак, знаменитая функция GetMyEnemy()... Как мы видим, она всего лишь вызывает другие функции в зависимости от типа покемона
Код:
function   GetMyEnemyA (myid)
   local result = 0
   local owner  = GetV (V_OWNER,myid)
   local actors = GetActors ()
   local enemys = {}
   local index = 1
   local target
   for i,v in ipairs(actors) do
      if (v ~= owner and v ~= myid) then
         target = GetV (V_TARGET,v)
         if (target == myid) then
            enemys[index] = v
            index = index+1
         end
      end
   end

   local min_dis = 100
   local dis
   for i,v in ipairs(enemys) do
      dis = GetDistance2 (myid,v)
      if (dis < min_dis) then
         result = v
         min_dis = dis
      end
   end

   return result
end

Защитная версия: учитывает только противников, выцеливающих самого покемона, и выбирает ближайшего
Код:
function   GetMyEnemyB (myid)
   local result = 0
   local owner  = GetV (V_OWNER,myid)
   local actors = GetActors ()
   local enemys = {}
   local index = 1
   local type
   for i,v in ipairs(actors) do
      if (v ~= owner and v ~= myid) then
         if (1 == IsMonster(v))   then
            enemys[index] = v
            index = index+1
         end
      end
   end

   local min_dis = 100
   local dis
   for i,v in ipairs(enemys) do
      dis = GetDistance2 (myid,v)
      if (dis < min_dis) then
         result = v
         min_dis = dis
      end
   end

   return result
end

Агрессивная версия: учитывает каждого моба вокруг (тут Драко говорит о том, что в ПвП эта функция игнорирует игроков. Уж не знаю, на арене моя Ванилька с удовольствием гоняла сагов, будучи запущена с дефолтным скриптом) и выбирает ближайшего.

Уря, ВСЕ! Теперь вы знаете, как думают ваши покемоны. В следующем уроке мы разберем примеры модифицирования, начиная с простейших добавлений и заканчивая супер-пупер-мега-ультра-гипно-модификациями ^_^

Урок четвертый: первые модификации

Ну что, устали от теории? Если честно, мы (я и Драко  ) тоже. К счастью, она на этом заканчивается. Теперь будем разбирать настоящие примеры. Начнем с самых простеньких.
Прежде чем выполнять все, что здесь написано, скопируйте все три файла (AI.lua, Const.lua и Utils.lua в папку USER_AI) и вдальнейшем работайте с этими копиями.
Это нужно на случай, если что-то пойдет не так, а вы не сможете отловить и исправить ошибку. Всегда, потвторяюсь, ВСЕГДА храните где-нибудь копию оригинальных скриптов.
Теперь хватит пугать, передйем к делу 
PS Да, еще одно. Чтобы клиент использовал скрипты из папки \AI\USER_AI (а не из \AI, как он это делает по умолчанию), в игре нужно использовать команду /hoai (ее достаточно ввести один раз, клиент запоминает даже на время выхода, какие файлы нужно читать).

ВНИМАНИЕ!!! Все приведенные ниже примеры были написаны Драко (да-да, я здесь непричем  ) еще до того, как он разобрался с работой AI. Например, использование GetV() функции, чтобы получить уровень HP моба: на самом деле это работать не будет, так как клиент всегда будет выдавать -1. Однако в качестве примера (и просто хорошей идеи) использовать это можно.

Отображение HP противника.
Довольно простое, но очень полезное (если бы работало х_х) дополнение: попробуем использовать нашу гипно-функцию GetV() для получения той информации, которую просто так не узнаешь.
Первая проблема - это куда добавлять код. В данном случае логично, что это должно работать, когда покемон атакует моба, поэтому OnATTACK_ST() - это то, что нам нужно. Например, после блоков Attack() и SkillObject(). Теперь вторая проблема - что же туда добавлять? Вы уже должны сами ее решить, однако это все-таки гайд 
Код:
-- Кладем это все в конец функции OnATTACK_ST()
   do
      local HP = GetV(V_HP, MyEnemy)
      local MaxHP = GetV(V_MAXHP, MyEnemy)
      TraceAI("Enemy HP: "..HP.."/"..MaxHP.." ["..(100*HP/MaxHP).."%]")
   end

Заметим, что весь код лежит между операторов do ... end, так что эту часть можно легко перемещать, а локальные переменные действуют только внутри нее. Сам по себе код кристалльно чист (это дословный перевод х_х), за исключением, пожалуй, большого количества .. (оператор соединения строк).

Улучшаем наше дополнение.
Сам по себе указанный выше код должен работать, однако с ним возможны несколько проблем. Первое - мы не знаем, как часто клиент обращается к функции AI(). Весьма может быть, что в результате файл TraceAI.txt будет зафлужен одинаковыми сообщениями. Есть два выхода: или прописывать сообщение, если HP изменилось, или же просто каждые сколько-то секунд.
Первый способ довольно просто внедрить: просто объявим (и зададим значение по умолчанию 0) глобальную переменную MyEnemyHP и будем ее использовать для хранения и проверки нового значения.
Код:
-- Добавляем это в самый верх файла
MyEnemyHP = 0

Код:
-- А это - как и раньше
   do
      local HP = GetV(V_HP, MyEnemy)
      if MyEnemyHP ~= HP then
         local MaxHP = GetV(V_MAXHP, MyEnemy)
         MyEnemyHP = HP
         TraceAI("Enemy HP: "..HP.."/"..MaxHP.." ["..(100*HP/MaxHP).."%]")
      end
   end

Заметим, что MaxHP создается и получает значение только если необходимо (маленькая и по сути бесполезная оптимизация).

Второй метод - хороший пример использования функции GetTick(). Нам просто нужно хранить следующее время вывода вражеского HP в глобальной переменной (назовем ее EnemyHPDisplayTick), и проверять, нужно ли сейчас его отображать.
Код:
-- Желательно добавлять в самый верх файла
EnemyHPDisplayTick = 0

Код:
-- А это кладем все туда же х_х
   if GetTick() >= EnemyHPDisplayTick then
      local HP = GetV(V_HP, MyEnemy)
      local MaxHP = GetV(V_MAXHP, MyEnemy)
      TraceAI("Enemy HP: "..HP.."/"..MaxHP.." ["..(100*HP/MaxHP).."%]")
      EnemyHPDisplayTick = GetTick() + 3000 -- Определяем, как часто будет выводиться HP (в милисекундах)
   end

Тут все должно быть ясно.
А теперь что можно сделать, операясь на эту идею: покемон будет жаловаться, когда у него низкий уровень HP/SP; отображать HP цели Мастера. Оба дополнения очень просты, которые нужно добавить сразу в AI(), чтобы они выполнялись каждый раз, когда покемон получает команду или меняет состояние.

Как получить ID другого игрока (или немного об ID аккаунтов).
Помните, я говорила про это? Да-да. ID аккаунтов можно использовать для... создания френд-листа. А уж возможностей его применения - море. Да, ID - единственное, что мы можем получить через GetV() с других объектов (исключая нашу зверюшку и самого Мастера). GetActors() выдает нам ID игроков, но в общей куче с другими, поэтому нужно придумать какой-то способ обозначить нужного нам игрока (например, как-то подвигать покемона вокруг нужного игрока), а затем через скрипт получить не очень хорошую, но рабочую функцию, которая будет заносить ID игроков, которых мы обозначили, в файл.

Запуск функции только один раз (инициализация).
Очень просто, на самом деле. Можно сделать через глобальную логическую переменную, можно чуть сложнее. Например, мы хотим, чтобы в traceai.txt клалось некое приветствие при первой обработке AI() (перевызов покемона, левел-ап, переход на другую локацию).
Код:

-- Лучше всего положить к другим глобальным переменным
FirstLaunch = true

Код:

-- В самое начало AI()
if FirstLaunch then
   FirstLaunch = false
   TraceAI("Превед, Мастерчег!")
end

Проще некуда. Естественно, TraceAI() можно заменить чем угодно.
Второй способ чуть хитрее и основан на гибкости языка LUA, однако позволяет обходиться без глобальных переменных. Основан он на том, что в LUA всему, даже сами функциям, можно присваивать новые значения. Когда объявляем функцию
Код:

function FunctionName() TraceAI("Blank Function") end

по сути, аналогично следующему:
Код:

FunctionName = function() TraceAI("Blank Function") end

Можно опустить часть кода, проверяющего нашу логическую переменную и переназначить функцию.
Код:

function AI(id)
   --[[ Пока покемон на карте, его ID не изменяется, поэтому эту часть нужно вызывать только один раз--]]
   MyID = id

   -- Превед, мирчег!
   TraceAI("Превед, мирчег! Вызываем это только один раз!")

   -- Переназначаем функцию
   AI = AI_part2

   -- Сразу же и вызываем новую функцию, чтобы не пропускать первый проход.
   AI_part2()
end

function AI_part2()
   local cmd = GetCmd(MyID)

   -- и т.д.
end

Заметим, что при переназначении функции не надо использовать скобки.

Атаковать цель Мастера.
По умолчанию покемон атакует ближайшего моба, который бьет Мастера. Или же ищет просто ближайшего, если Мастера никто не бьет. Если же мы хотим, чтобы зверюшка помогала убивать нашу цель, надо немного модифицировать GetOwnerEnemy() (конечно, есть и другие пути, но пусть будет этот)
Код:
 
-- Добавляем после объявления локальных переменных но до основного кода
   target = GetV(V_TARGET,owner)
   if(target ~= 0) then
      return target
   end

Если GetV() может возвращать цель Мастера (а тут я не помню, надо проверить, кстати), то это заставит покемона атаковать такого моба в первую очередь. Если же Мастер никого не бьет, то он будет выбирать ближайшего атакующего Мастера и т.д.

За сим уроки Драко заканчиваются. От себя добавлю лишь ссылку и, возможно, еще кое-какой материал (работа с текстовыми файлами из скрипта - формирование френд-листа, пример функции проверки на килстил, может еще чего придумаю).
http://www.ragnainfo.net/forums/viewtop … mp;start=0 - это топик на рагнаинфо "поделись своим скриптом". Огромное количество AI, есть, чему поучиться для тех, кому это интересно, и есть, чего скачать - это тем, кому лень самим писать

2

слишкам многа букав, ни асилил :pardon: но наверно все очень интересно и познавательно, какие сложные эти гомункулы  :O  http://img.roinfo.ru/smilesro/hmm.gif

3

Надеюсь осилю в ближайшем будущемм но сегодня ни как

4

Этц ппц. Букв непросто много, это цук война и мир, ток непонятно к тому же)) Ниасилив данное наверняка очень умное повествование я понял-корней Чуковский мой писатель(с) http://img.roinfo.ru/smilesro/hmm.gif

5

А вы как хотели. Хотите гомункулусов с тру аи-програмируйте его сами=))

6

Напишу тру скрипт гомик... гомункулу. Очень дорого :) Блин, гравити жжёт, сначало ро съедает мозг, а потом заставляет остатками думать :)

7

Чего то мне кажется что гомонкулы станут неубиваемыми очередными ботами с этим програмированием http://img.roinfo.ru/smilesro/gg.gif

8

Да, вообще-то все тру. Настроить можно. Только хима кач это еще пол года убивать до 99  как минимум...

9

жестока... экспиременты, говорите... эммм... А можно своего чара поломать сразу на 99999999 опыта? нЭ??? :butc:

10

Сцук..это типа для умных??Да и ваще еси мне нада буит готовый АИ возьму,и не буду моск парить))) http://hackstudio.by.ru/images/smiles/RO/nya.gif

11

Aiko-san написал(а):

А можно своего чара поломать сразу на 99999999 опыта? нЭ???

http://img.roinfo.ru/smilesro/ok.gif за бабки у ГМов Гравити...

Про гомункулов: тут вообще хитро получается. Например, можно хима поставить на локе где гомункул будет в состоянии замочить ползающих там мобов (настройка его не очень сложная, судя по выше указанному, для того кто умеет хоть малость программить), а самому просто стоять по середине и нифига не делать. Всю работу делает за тебя гомункул. Очень актуально например ставить его на ночь, а днем самому кач...Ночью спишь, а экспа какая-никакая а идет...получается официальное (в какой-то степени) ботоводство http://img.roinfo.ru/smilesro/gg.gif

12

Токо не забываем, что его кормить надо http://img.roinfo.ru/smilesro/gg.gif Тогда нужно бота прописывать на корьмёжку, а это уже будет бот.

13

Его надо кормить и лечить=))) А то сдохнет http://img.roinfo.ru/smilesro/gg.gif (тамагочи ёпть :O )

14

С лечением там написано. А с кормежкой думаю люди тоже придумают феньки. Раз скрипт можно писать самому, то тут еще и не такое наворотить можно...

15

Гомункулусы превращают ро в соревнование программеров? Кто лучше написал тот и папка. Ппц..Лично у меня негативное отношение к этому http://img.roinfo.ru/smilesro/ok.gif

16

"Всё уже украли до нас" (с)

Скриптов на забугорных форумах тьма, ничего не надо придумывать.

17

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

18

Блин Кость найди кому дать лове!иу ва сбудет Сакра 99 лвл)))а потом паладин)))

19

Эниг написал(а):

Блин Кость найди кому дать лове!иу ва сбудет Сакра 99 лвл)))а потом паладин)))

Это тут причем? Невкурил вообще...

20

Костяныч написал(а):

Это тут причем? Невкурил вообще..

Зато Эниг покурил)) ^_^

21

Про АД:
Особенности:
- Урон зависит только от вашего INT и от VIT цели
- Урон не зависит от оружия в руках
- У урона у нет максимального и минимального рубежа, при броске в одну и ту же цель. Урон фиксированный.
- MATK не влияет на урон от АДа
- Карты на Атаку, Расу и Размер не влияют на урон, наносимым Адом. Влияют только вещи и карты на INT.
- Пенальти на 50% оригинального урона при атаке Игрока
- Элемент атаки всегда Нейтральный и не зависит от оружия и энчанта
- Игнорирует защиту от экипировки и VIT
- Игнорирует Flee цели
- На Урон от АД влияют карты Райдрика, Лягушки Тары, Гостринга, Девелинга, Шапка-кака, Маска Зеалотуса
- Туман сводит урон от АД в 0, при этом шанс поломки Брони и Оружия остается
- Игнорирует Стену Безопасности
- Пробивает Кирие Элейсон
- Кирие Элейсон блокирует шанс поломки, пока не будет пробито
- Карта Аллигатора, Длинный жезл уменьшают урон от АД
- Навык Крестоносца - Защитная Аура не влияет на урон от АД

22

Сначала листал колесиком мышки, потом задолбало стал листать Page Down'ом чтобы до коментов дойти, АД зло, непомню формулу, но там дробь, в знаменателе твой инт и вит цели складываются, а в числителе твой инт в квадрате, вражеский вит и ещё какая то поебистика вроде умножаются. http://img.roinfo.ru/smilesro/gg.gif

23

ОМГ !!! чит блин. Увидел хима, кидай девину.


Вы здесь » Death Smile » Флудильня » Перевод гайда Драко по АИ гомункулусов с рагнаинфо