sábado, 22 de novembro de 2008

Progamação da AI do homunculo

Indice:
[0.00] Introdução
[0.10] Log de Alterações
[0.30] Comandos in-game Úteis
--------
Quando ouvi que os novos pets de alquimistas, os homunculos, teriam uma AI programavel, eu fiquei animada com a idéia de usar minhas habilidades na area de programação para fazer minha experencia de jogo mais divertida[0.10]
(Não pretendo incluir o change log, sorry)
[0.20]
Muitas pessoas que começarem a ler esse guia mas não tiverem experiencia prévia em programação poderão ter dificuldades em entender o que estou dizendo.
[0.30]
ALT+Clique Direito no chão: Seu homunculo se moverá para a celula que voce clicou.
ALT+Clique Direito em um monstro: Seu homunculo marcará aquele monstro como alvo. É nescessário um segundo clique para seu homunculo começar a atacar.
Digite "/hoai": Esse é o coração da AI customizavel - ele faz o homunculo trocara terrivel AI padrão pela sua AI editada.

Índice:
[1.00] Desvendando Const.lua

[1.10] Funções Client-Side

[1.11] Trace AI
[1.12] MoveToOwner
[1.13] Move
[1.14] Attack
[1.15] GetV
[1.16] GetActors
[1.17] GetTick
[1.18] GetMsg
[1.19] GetResMsg
[1.1A] SkillObject
[1.1B] SkillGround
[1.1C] IsMonster

[1.20] Skill ID para os skills ativos de homunculus

[1.21] Lif
[1.22] Amistir
[1.23] Filir
[1.24] Vanilmirth

[1.30] Constantes usadas em GetV

[1.31] V_OWNER
[1.32] V_POSITION
[1.33] V_TYPE
[1.34] V_MOTION
[1.35] V_ATTACKRANGE
[1.36] V_TARGET
[1.37] V_SKILLATTACKRANGE
[1.38] V_HOMUNTYPE
[1.39] V_HP, V_SP
[1.3A] V_MAXHP, V_MAXSP

[1.40] Constantes usadas para representar tipos de Homunculos.
[1.50] Constantes usadas para representar tipos de Movimento.
[1.60] Constantes usadas para representar tipos de Comandos.

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

[1.00]
Vou começar essa festa pelo mais basicos dos scripts, Const.lua, no qual veremos a definição de muitas constantes e funções que permitirão o resto dos scripts funcionarem, e também permitirá que nós façamos modificações nos scripts quando chegar a hora.

[1.10]
O primeiro texto no arquivo Const.lua dá o nome e parametros de diversas funções client-side pré-definidas que serão usadas no script. Aqui está a lista e em seguida a descrição de para que serve cada função.

CODE
function TraceAI (string) end
function MoveToOwner (id) end
function Move (id,x,y) end
function Attack (id,id) end
function GetV (V_,id) end
function GetActors () end
function GetTick () end
function GetMsg (id) end
function GetResMsg (id) end
function SkillObject (id,level,skill,target) end
function SkillGround (id,level,skill,x,y) end
function IsMonster (id) end


[1.11]
TraceAI (string): essa função mostra uma string (variavel formada por letras, numeros e/ou simbolos) e mostra-a no chat. Você pode ler essas strings in-game digitando "/traceai". Sua principal função é para debuggar o script, acompanhando mudanças de estado e variaveis.
[1.12]
MoveToOwner (id): essa função pega o ID do homunculo e o faz mover-se até seu dono. Auto-explicativa o bastante.
[1.13]
MoveTo (id,x,y): essa função também pega o ID do homunculo, mas também pega dois valores numericos para as coordenadas x e y. O homunculo então se move para as coordenadas x e y do mapa.
[1.14]
Attack (id, id): essa função pega dois valores de ID, o primeiro é o do homunculo, o segundo é o da criatura que o homunculo deve atacar. Naturalmente, essa função faz o homunculo atacar o alvo especificado.
[1.15]
GetV (V_, id): essa função pega um parametro e um ID, e retorna o parametro daquele ID. Na proxima sessão eu irei a fundo nos diferentes valores que GetV pode retornar. Visto que essa é uma das funções mais uteis para criar seus scripts, é importante saber usa-la bem.
[1.16]
GetActors (): essa função retorna uma lista de objetos mostrando todos objetos que aparecem na tela. Essa lista é usada quando procuramos por alvos em potencial.
[1.17]
GetTick (): essa função retorna o numero de milesimos de segundo que se passou desde um tempo, provavelmente meia-noite PST. Calcular a diferença entre dois ticks é um bom jeito de se saber quanto tempo se passou in-game, mas lembre-se que esse valor se dá em milesimos de segundo, então você precisa dividi-lo por 1000 para compara-lo com segundos ou mutiplicar os segundos por 1000.
[1.18]
GetMsg (id): essa função pega o ID do homunculo e retorna uma mensagem representando um comando que foi passado para o homunculo por seu dono. Já que os scripts de AI estarão processando comandos, eles precisam dessa função para pega-los.
[1.19]
GetResMsg (id): pega o ID do homunculo e retorna uma mensagem represantando um comando passado pelo client ou um comando em espera passado pelo dono. A mensagem reservada é outro tipo de mensagem que será processada pelos scripts de AI, então essa função é nescessária para pega-las.
[1.1A]
SkillObject (id,level,skill,target): Pega o ID do homunculo, o level do skill a ser usado, um número que represanta qual skill ser usado, o ID do alvo e usa o skill no alvo.

[1.20]
Valores conhecidos para skills de Homunculos são os seguintes (agradecimentos à Hahseckh por vários desses valores):
[1.21]
Lif:
Healing Touch = "8001"
Urgent Escape = "8002"
[1.22]
Amistr:
Castling = "8005"
Amistr Bulwark = "8006"
[1.23]
Filir:
Moonlight = "8009"
Flitting = "8010"
Accelerated Flight = "8011"
[1.24]
Vanilmirth:
Caprice = "8013"
Chaotic Blessing = "8014"

[1.1B]
SkillGround (id,level,skill,x,y): Mesmo processo da função anterior, mas ao invés de usar um ID para um objeto-alvo são usadas coordenadas x e y para se usar a skill no chão. Entretanto, até onde eu saiba não há nenhuma skill de efeito de area entre os homunculos.
[1.1C]
IsMonster (id): Pega o ID de um objeto e retorna 1 se o objeto for um monstro, 0 se não for. Isso pode ser usado para diferenciar monstros de jogadores, apesar de não ser muito util em situações de PvP (a não se que plantas invocadas e outros homunculos contem como monstros, ao menos).

[1.30]
A proxima parte do código pré-define os possiveis retornos da função GetV. O parametro V_ pedido pela função GetV é somente um número comum, mas aqui há a lista do que esse número pode representar:

CODE
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


[1.31]
V_OWNER, que é representado na função GetV pelo valor 0, irá fazer com que a função GetV retorne o número de ID do dono do ID especificado na função GetV. Esse valor, obviamente, só se aplica a homunculos.
[1.32]
V_POSITION fará com que a função GetV retorne as coordenadas x,y do ID em que lhe foi especificado. Esse valor pode ser usado em qualquer objeto.
[1.33]
V_TYPE fará com que a função GetV retorne o tipo do ID especificado. Não estou certo de como usar isso, visto que não é usado no script e os tipos de homunculo estão especificados em V_HOMUNTYPE.
[1.34]
V_MOTION fará com que a função GetV retorne o tipo de movimento do ID que lhe foi especificado. Os valores para isso estão definidos após os valores de tipos de homunculos, em breve chegaremos a isso.
[1.35]
V_ATTACKRANGE fará com que a função GetV retorne a distancia máxima que os ataques do ID especificado podem atingir. Útil para determinar se seu homunculo está perto o bastante para atingir o inimigo.
[1.36]
V_TARGET fará com que a função GetV retorne o alvo atual do ID especificado. Útil para determinar quando o homcunulo ou seu dono está sendo atacado por um monstro.
[1.37]
V_SKILLATTACKRANGE fará com que a função GetV retorne um número representando a distancia máxima que o skill selecionado do ID especificado pode alcançar. Útil para determinar se o homunculo está na distancia apropriada de um inimigo para usar algum skill.
[1.38]
V_HOMUNTYPE fará com que GetV retorne um valor representando o tipo de homunculo que teve seu ID especificado na função. Obviamente, só funciona em homunculos. Os valores retornados por essa função serão especificados na proxima sessão.
[1.39]
V_HP e V_SP fazem que GetV retorne o HP e o SP atual do ID especificado. A documentação da Gravity diz que esse valor só funciona para o homunculo e seu dono, ou seja, você não pode descobrir quanto de HP seu inimigo tem.
V_MAXHP e V_MAXSP fazem com que GetV retorne o valor maximo de HP e SP do ID especificado. A observação para os valores anteriores também se aplica aqui.

[1.40]
A proxima parte do código define os valores para os tipos de homunculos.

CODE
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

[1.50]
A seguir se encontra uma lista dos tipos de movimento, retornados pelo V_MOTION. Novamente, não são nescessários comentários, visto que só é preciso comparar os valores de GetV (V_MOTION, id).

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



[1.60]
Finalmente, aqui estão os números que representam os comandos que o dono pode passar para o homunculo. Esses valores sao pré-definidos aqui de forma a que os numeros retornados pelas mensagens possam ser lidados também com seu nome de variavel.

CODE
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

Indice:
[2.00] Desvendando Util.lua
[2.10] A utilidade de listas e pilhas
[2.20] Funções úteis
[2.21] GetDistance
[2.22] GetDistance2
[2.23] GetOwnerPosition
[2.24] GetDistanceFromOwner
[2.25] IsOutOfSight
[2.26] IsInAttackSight
---------------------
[2.00]
Agora que já vimos o basico das funções de scripts e como algumas variaveis fazem valores númericos mais faceis de se lidar, prosseguiremos para o próximo arquivo importante: o Util.lua. Nesse arquivo nós temos a maioria das funções de utilidades, que fazem tarefas matematicas semi-complexas e tarefas uteis que serão utilizadas no arquivo final de script.

CODE
require "./AI/Const.lua"


Essa linha do código mostra que o Util.lua depende do arquivo Const.lua para funcionar. Isso é devido ao fato de que aqui são usadas muitas das variaveis pré-definidas e funções client-side definidas no Const.lua.
[2.10]

CODE
List = {}

function List.new ()
return { first = 0, last = -1}
end

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

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

function List.size (list)
local size = list.last - list.first + 1
return size
end


A função acima são todas as utilidades de listas que são geralmente usadas em estruturas de dados conhecidas como Stacks (pilhas). Em uma pilha,, voce adiciona valores ao seu topo, e quando você chega a um ponto em que um dos valores precisa ser processado, você retira ele do topo da pilha, o que irá remove-lo da lista e retornar o valor a ser processado. Isso é exatamente o que essas funções fazem; List.new() cria uma nova lista de tamanho 0. As funções List.pushleft e List.pushright adicionam valores à esquerda ou à direita da pilha, respectivamente. List.popleft e List.popright retorna os valores destes mesmos lados e os removem desta posição na lista. List.Clear() remove todos valores na lista, e List.Size() retorna o tamanho da lista. Ao todo, essas funções não são muito importantes para alterar sua AI, mas elas são importantes no que se diz respeito a como o sistema lida com comandos enfileirados.

[2.20]
Seguindo em frente, aqui encontraremos funções que nos darão valores uteis para o script final.
[2.21]

CODE
function GetDistance (x1,y1,x2,y2)
return math.floor(math.sqrt((x1-x2)^2+(y1-y2)^2))
end


Essa função retorna a distancia entre x1,y1 e x2,y2 usando a formula matematica de calculo de distancias. Útil para calcular a distancia entre o homunculo e um inimigo ou para comparar a distancia maxima do ataque do homunculo com sua distancia do inimigo.
[2.22]

CODE
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


Essa função faz o mesmo que a anterior, entretanto nela você especifica dois IDs ao invés de dois pares de coordenadas. Note que, após ela pegar os pares de coordenadas de cada objeto, ela utiliza a função anterior para calcular a distancia.
[2.23]

CODE
function GetOwnerPosition (id)
return GetV (V_POSITION,GetV(V_OWNER,id))
end


Essa função retorna a posição atual do dono do homunculo. Útil se o homunculo quiser voltar para perto do dono.
[2.24]

CODE
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


Outra função de distancias, essa retorna a distancia entre o dono do homunculo e um ID. Util para saber qual o inimigo mais proximo do dono ou para verificar se o homunculo não se afastou demais.
[2.25]

CODE
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

Essa função retorna "true" se o objeto represando por ID2 estiver a mais de 20 celulas de distancia do objeto represantado por ID1, e "false" se estiverem menos de 20 celulas de distancia. O nome sugere que deveria ser uma checagem para quando um objeto está fora do alcance visual do outro, mas o uso de 20 celulas é estranho, visto que o campo visual no jogo é algo em torno de 10 a 15 celulas. Isso pode resultar em homunculos agressivos saindo do campo visual de seu dono se este não for cuidadoso. Pode ser util modificar essa função para corrigir isso, discutiremos sobre isso depois.
[2.26]

CODE
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

Essa função retorna "true" se o objeto represantado por id2 estiver dentro da distancia maxima de ataque do objeto representado por id1. Normalmente, id1 representa o homunculo. Se o objeto estiver tentando usar um skill, a distancia maxima do skill será levada em conta.

Índice: (lembrando que dividirei em 3 o indice 3 pois o forum num suporta)
[3.00] Desvendando AI.lua

[3.10] Constantes usadas para representar estados

[3.20] Variaveis Globais
[3.21] MyState
[3.22] MyEnemy
[3.23] MyDestX, MyDestY
[3.24] MyPatrolX, MyPatrolY
[3.25] ResCmdList
[3.26] MyID
[3.27] MySkill, MySkillLevel

[3.30] Processamento de Comandos
[3.31] OnMOVE_CMD
[3.32] OnSTOP_CMD
[3.33] OnATTACK_OBJECT_CMD
[3.34] OnATTACK_AREA_CMD
[3.35] OnPATROL_CMD
[3.36] OnHOLD_CMD
[3.37] OnSKILL_OBJECT_CMD
[3.38] OnSKILL_AREA_CMD
[3.39] OnFOLLOW_CMD
[3.3A] ProcessCommand

[3.40] Processamento de Estados
[3.41] OnIDLE_ST
[3.42] OnFOLLOW_ST
[3.43] OnCHASE_ST
[3.44] OnATTACK_ST
[3.45] OnMOVE_CMD_ST
[3.46] OnSTOP_CMD_ST, OnATTACK_OBJECT_CMD_ST
[3.47] OnATTACK_AREA_CMD_ST
[3.48] OnPATROL_CMD_ST
[3.49] OnHOLD_CMD_ST
[3.4A] OnSKILL_OBJECT_CMD_ST
[3.4B] OnSKILL_AREA_CMD_ST
[3.4C] OnFOLLOW_CMD_ST

[3.50] Funções de Seleção de Inimigos
[3.51] GetOwnerEnemy
[3.52] GetMyEnemy
[3.52] GetMyEnemyA
[3.53] GetMyEnemyB

[3.60] A Função AI
------------
[3.00]
Agora que já vimos todos nossos valores de constantes, nossas funções client-side e nossas funções de utilidades, só nos resta uma coisa a fazer: usar todo esse conhecimento adquirido para debulhar o arquivo AI.lua

CODE
require "./AI/Const.lua"
require "./AI/Util.lua"


Novamente, o arquivo de AI nescessita de outros arquivos na mesma pasta para rodar, pois usa funções de utilidades do Util.lua e valores definidos no Const.lua.
[3.10]

CODE
IDLE_ST = 0
FOLLOW_ST = 1
CHASE_ST = 2
ATTACK_ST = 3
MOVE_CMD_ST = 4
STOP_CMD_ST = 5
ATTACK_OBJECT_CMD_ST = 6
ATTACK_AREA_CMD_ST = 7
PATROL_CMD_ST = 8
HOLD_CMD_ST = 9
SKILL_OBJECT_CMD_ST = 10
SKILL_AREA_CMD_ST = 11
FOLLOW_CMD_ST = 12


Assim como os valores definidos em Const.lua, esses valores estão aqui para que lidar com os estados seja mais facil. Esses valores serão usados para alterar o valor da variavel MyState, que será discutido brevemente, assim como para comparar o estado atual do homunculo com outros estados.
[3.20]

CODE
MyState = IDLE_ST
MyEnemy = 0
MyDestX = 0
MyDestY = 0
MyPatrolX = 0
MyPatrolY = 0
ResCmdList = List.new()
MyID = 0
MySkill = 0
MySkillLevel = 0


[3.21]
MyState representa o estado atual do Homunculo. Ele guarda um valor númerico, mas esses valores serão lidados no arquivo de acordo com o que foi definido acima. O valor padrão de MyState é 0, portanto o homunculo inicializa em IDLE_ST.
[3.22]
MyEnemy guarda o ID do objeto que o homunculo tem atualmente como alvo. Esse valor é usado para saber o que o homunculo está visando, tanto para propositos de ataques como perseguições.
[3.23]
MyDestX e MyDestY são usadas para guardar a destinação atual do homunculo. Usadas para move ro homunculo ou para perseguir um inimigo.
[3.24]
MyPatrolX e MyPatrolY são usadas para armazenas as coordenadas da patrulha do homunculo. Na função de patrulha, o homunculo alterna entre (MyDestX,MyDestY) e (MyPatrolX,MyPatrolY).
[3.25]
ResCmdList é a lista em que os comandos do client e os comandos enfileirados são armazenados. Note que ele usa a função List.new() do Util.lua para criar uma nova lista de comandos.
MyID é usada para guardar a ID do homunculo para uso facil. O unico momento em que a ID do homunculo é passada para o programa é na função AI (id), que é a ultima função nesse arquivo, e esse número de ID é usado para obter todas outras informações nescessárias (como o ID do dono, que é nescessário para a posição do dono, inimigo do dono, etc.), portanto é uma variavel extremamente importante.
MySkill e MySkillLevel são usados para armazenar o ID do skill selecionado do homunculo e seu level, para uso na função SkillObject do Const.lua.
[3.30]
Agora passemos para o processamento de comandos, que define as respostas do homunculo aos comandos que seu dono pode lhe passar. Esses são todos chamados pela função Process (msg), que por sua vez é chamada pela função AI. Ou seja, essas são as funções que defininem cada comando que pode ser passado para o homunculo.
[3.31]

CODE
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


Essa função tenta mover o homunculo até o ponto (x,y). Se o ponto for muito longe, o homunculo irá se mover até metade do caminho e então marcar a destinação final como seu destino atual, fazendo na pratica dois movimentos para seguir a rota inteira. Comandar o homunculo para se mover faz com que ele desselecione quaisquer skills e alvos que havia selecionado e mude seu estado para MOVE_CMD_ST, o estado em que ele é comandado para se mover. Simples, não?
[3.32]

CODE
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


Essa função faz o homunculo parar de se mover, entrar em estado IDLE_ST (estado Idle, "standby") e desselecionar quaisquer skills e alvos. Se o homunculo estiver fazendo algo além de estar parado, essa função o fará mover para sua posição atual (ou seja, parar) e então mudar seu estado para Idle.
[3.33]

CODE
function OnATTACK_OBJECT_CMD (id)

TraceAI ("OnATTACK_OBJECT_CMD")

MySkill = 0
MyEnemy = id
MyState = CHASE_ST

end


Essa função pega o ID de um inimigo e marca-o como alvo do homunculo. Então o homunculo muda para o estado de perseguição, no qual ele irá perseguir o inimigo. Também faz com que o homunculo desselecione qualquer skill, ou seja, ele irá atacar o alvo normalmente.
[3.34]

CODE
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


Essa função pega uma posição (x,y) e manda o homunculo "atacar" aquela celula. Basicamente, o homunculo se moverá até o local e então procurará por inimigos proximos para começar a atacar (o que é parte do ATTACK_AREA_CMD_ST que será observado mais adiante).
[3.35]

CODE
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


Essa função pega uma posição e faz o homunculo patrulhar entre sua posição atual e a posição escolhida.
[3.36]

CODE
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


Esse é o ALT+T. Faz seu homunculo parar de fazer o que estiver fazendo e mudar para o estado de Hold.

[3.37]

CODE
function OnSKILL_OBJECT_CMD (level,skill,id)

TraceAI ("OnSKILL_OBJECT_CMD")

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

end


Essa função pega um level de skill, uma ID da skill, um ID de inimigo e faz seu homunculo selecionar a skill no level escolhido e marcar o inimigo como seu alvo, mudando então para o estado de perseguição para se aproximar o bastante do inimigo e usar a skill escolhida.
[3.38]

CODE
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


Essa função é basicamente igual à anterior, entretanto o homunculo escolhe uma posição ao invés de um inimigo. O Homunculo então se move para essa posição e seleciona o skill escolhido antes de mudar de estado para SKILL_AREA_CMD_ST.
[3.39]

CODE
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


Essa função faz o homunculo mover até seu dono e mudar para o estado de de seguir, se já não estiver nele. Caso já esteja no estado de seguir, faz o homunculo mudar para o estado Idle.
[3.3A]

CODE
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


Essa é a função principal de todo o processo de comandos, faz seu homunculo realizar a função de acordo com o comando que é passado para ele. Note que msg[1], msg[2], msg[3], etc., todas apontam para diferentes partes da mesma mensagem. Portanto, em uma mensagem especificando um comando de movimento, você teria algo como msg = {MOVE_CMD, x,y}. Ou seja, onde a função chama por OnMOVE_CMD(msg[2],msg[3]), está na realidade chamando por OnMOVE_COMMAND(x,y), onde x e y são quaisquers numeros que foram usados nessa mensagem. A função usa o valor da primeira parte da mensagem para determinar quantas outras partes a mensagem contém. Estranhamente, o OnSKILL_OBJECT_COMMAND usa quatro partes de mensagem, ao invés de três que é o que a função realmente nescessita. Entretanto, como tudo parece funcionar mesmo assim, deixarei como está.
[3.40]
Agora veremos os processos de estados, que definem qual o comportamento do homunculo em determinado estado. Após o comando Process(msg) chamar uma função, o estado do homunculo normalmente muda, mostrando que o comando foi processado. A função AI então chama essas funções de estado para fazer o homunculo se comportar como ele deveria.
[3.41]

CODE
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


Essa função define o comportamento do homunculo no estado Idle. A primeira coisa que ela faz é processar qualquer comando que estiver enfileirado, como movimentos finais quando o movimento inicial era longo demais, ou qualquer comando passado enquanto o homunculo mudava de estado. Então ele procura por inimigos do dono, se encontrar algum ele o atacará. Se não encontrar nenhum, ele procurará por inimigos do homunculo e os atacará. Se não encontrar nenhum, ele voltará para perto do seu dono caso esteja a mais de 3 celulas de distancia. Se nenhuma dessas condições for verdadeira, o homunculo não fará nada.

[3.42]

CODE
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


Essa função define o comportamento do homunculo no estado de seguir. Primeiro ele checa se está a menos de três celulas do dono, se sim, ele muda para o estado idle. Caso contrário, se estiver parado, ele se moverá para perto do dono. Note o erro de ditação em "Idle" no TraceAI. Foi a Gravity, não eu.
[3.43]

CODE
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


Essa função define o comportamento do homunculo no estado de perseguição. Se o inimigo estiver fora do campo visual, o homunculo irá parar desseleciona-lo e mudar para o estado Idle. Se o inimigo estiver dentro da distancia de ataque do homunculo, este irá mudar para o estado de ataque. Caso contrário, o homunculo continuará a se mover em direção ao inimigo.
[3.44]

CODE
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


Essa função define o comportamento do homunculo enquanto ataca um inimigo. Se o inimigo estiver fora do campo visual ou morto, o homunculo irá voltar para o estado Idle.
[3.45]

CODE
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


Essa função define o comportamento do homunculo enquanto se move. Se a sua posição atual for igual ao destino que lhe havia sido dado, ele muda para o estado Idle.
[3.46]

CODE
function OnSTOP_CMD_ST ()


end

function OnATTACK_OBJECT_CMD_ST ()

end


Essas funções estão vazias, fazendo com que o homunculo não faça nda enquanto estiver nelas. Faz você se perguntar por que elas foram definidas, não faz?
[3.47]

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


Essa função define como o homunculo se comporta quando é ordenado a atacar uma localidade. Após se mover para essa localidade, ele irá procurar por inimigos assim como faz no estado Idle.
[3.48]

CODE
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


Essa função define como o homunculo se comporta enquanto patrulha uma localidade. Ele primeiro procura por inimigos. Se encontrar algum, ele muda para o estado de Perseguição e o persegui-rá. Se nenhum inimigo for encontrado, ele ficará andando entre as posições que foram demarcadas até encontrar um.
[3.49]

CODE
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


Essa função define como o homunculo se comporta no estado de Hold. Se ele tiver um inimigo que estiver dentro de distancia de ataque, ele atacará o inimigo. Caso contrário, ele desselecionará o inimigo.
[3.4A]

CODE
function OnSKILL_OBJECT_CMD_ST ()

end


Outra função vazia.

CODE
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


Essa função define como o homunculo se comporta ao tentar usar uma skill no chãol. Se ele estiver dentro da distancia minima para uso da skill, ele a usará no local indicado e mudará para o estado Idle.
[3.4C]

CODE
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


Essa função define como o homunculo se comporta no estado de Seguir. Ele se moverá para uma distancia maxima de 3 celulas do dono e tentará manter essa distancia.

[3.50]
A seguir estão as funções de seleção de inimigos, que são usadas em algumas das funções acima para fazer o homunculo escolher um inimigo como alvo. Há alguns critérios diferentes para se definir inimigos, estes estão separados nas funções abaixo:
[3.51]

CODE
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


Essa função é bastante longa, mas apenas retorna o inimigo atacante do dono mais proximo para que o homunculo tenha ele como inimigo. Ela faz uma lista de todos objetos na tela e procura em todos eles aqueles que estão atacando o dono, seja um monstro ou não-monstro (jogadores em situações de PvP), então procura qual deles está mais perto do dono e marca este como o alvo de maior prioridade para o homunculo.
[3.52]

CODE
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


Essa função é usada em geral quando o dono do homunculo não tem inimigos e irá procurar por inimigos do homunculo. Para Amistr e Lif, essa função irá chamar GetMyEnemyA, a função passiva de procura por inimigos. Se for Filir ou Vanilmirth, irá chamar por GetMyEnemyB, a função agressiva de procura por inimigos.

[3.53]

CODE
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


Função de procura de inimigo padrão para Amistr e Lif, é muito parecida com GetOwnerEnemy: ela procura por inimigos do dono, mas também por inimigos que estejam atacando o homunculo. A prioridade é para aquele que estiver mais perto do homunculo.
[3.54]

CODE
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


Essa função é parecida com a anterior, entretanto ela aceita todos inimigos na tela como alvos potenciais. É o sistema padrão de localização de inimigos para Filir e Valnirmith. Há um problema de lógica com esse script: ele não checa para ver se o monstro já está lutando com outro jogador, resultando em Kill-Steal (KS) por parte do homunculo. Isso pode ser facilmente corrigido e será discutido na sessão de custom AI.
[3.60]

CODE
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


Esse é o coração de todo o script de AI. Ele pega o ID do homunculo e imediatamente o armazena na variavel MyID, o que permite que muitas informações sejam adquiridas a partir da função GetV. Após isso, a função pega uma mensagem e uma mensagem reserve. Se não houver nenhuma mensagem, ele adicionará a rmsg para a sua lista e tentará efetuar os processos de estado (o padrão sendo Idle). Se houver uma mensagem, ela será processada pela função ProcessCommand(msg) que chamará o comando apropriadoÍndice:
[3.00] Desvendando AI.lua

[3.10] Constantes usadas para representar estados

[3.20] Variaveis Globais
[3.21] MyState
[3.22] MyEnemy
[3.23] MyDestX, MyDestY
[3.24] MyPatrolX, MyPatrolY
[3.25] ResCmdList
[3.26] MyID
[3.27] MySkill, MySkillLevel

[3.30] Processamento de Comandos
[3.31] OnMOVE_CMD
[3.32] OnSTOP_CMD
[3.33] OnATTACK_OBJECT_CMD
[3.34] OnATTACK_AREA_CMD
[3.35] OnPATROL_CMD
[3.36] OnHOLD_CMD
[3.37] OnSKILL_OBJECT_CMD
[3.38] OnSKILL_AREA_CMD
[3.39] OnFOLLOW_CMD
[3.3A] ProcessCommand

[3.40] Processamento de Estados
[3.41] OnIDLE_ST
[3.42] OnFOLLOW_ST
[3.43] OnCHASE_ST
[3.44] OnATTACK_ST
[3.45] OnMOVE_CMD_ST
[3.46] OnSTOP_CMD_ST, OnATTACK_OBJECT_CMD_ST
[3.47] OnATTACK_AREA_CMD_ST
[3.48] OnPATROL_CMD_ST
[3.49] OnHOLD_CMD_ST
[3.4A] OnSKILL_OBJECT_CMD_ST
[3.4B] OnSKILL_AREA_CMD_ST
[3.4C] OnFOLLOW_CMD_ST

[3.50] Funções de Seleção de Inimigos
[3.51] GetOwnerEnemy
[3.52] GetMyEnemy
[3.52] GetMyEnemyA
[3.53] GetMyEnemyB

[3.60] A Função AI
------------
[3.00]
Agora que já vimos todos nossos valores de constantes, nossas funções client-side e nossas funções de utilidades, só nos resta uma coisa a fazer: usar todo esse conhecimento adquirido para debulhar o arquivo AI.lua

CODE
require "./AI/Const.lua"
require "./AI/Util.lua"


Novamente, o arquivo de AI nescessita de outros arquivos na mesma pasta para rodar, pois usa funções de utilidades do Util.lua e valores definidos no Const.lua.
[3.10]

CODE
IDLE_ST = 0
FOLLOW_ST = 1
CHASE_ST = 2
ATTACK_ST = 3
MOVE_CMD_ST = 4
STOP_CMD_ST = 5
ATTACK_OBJECT_CMD_ST = 6
ATTACK_AREA_CMD_ST = 7
PATROL_CMD_ST = 8
HOLD_CMD_ST = 9
SKILL_OBJECT_CMD_ST = 10
SKILL_AREA_CMD_ST = 11
FOLLOW_CMD_ST = 12


Assim como os valores definidos em Const.lua, esses valores estão aqui para que lidar com os estados seja mais facil. Esses valores serão usados para alterar o valor da variavel MyState, que será discutido brevemente, assim como para comparar o estado atual do homunculo com outros estados.
[3.20]

CODE
MyState = IDLE_ST
MyEnemy = 0
MyDestX = 0
MyDestY = 0
MyPatrolX = 0
MyPatrolY = 0
ResCmdList = List.new()
MyID = 0
MySkill = 0
MySkillLevel = 0


[3.21]
MyState representa o estado atual do Homunculo. Ele guarda um valor númerico, mas esses valores serão lidados no arquivo de acordo com o que foi definido acima. O valor padrão de MyState é 0, portanto o homunculo inicializa em IDLE_ST.
[3.22]
MyEnemy guarda o ID do objeto que o homunculo tem atualmente como alvo. Esse valor é usado para saber o que o homunculo está visando, tanto para propositos de ataques como perseguições.
[3.23]
MyDestX e MyDestY são usadas para guardar a destinação atual do homunculo. Usadas para move ro homunculo ou para perseguir um inimigo.
[3.24]
MyPatrolX e MyPatrolY são usadas para armazenas as coordenadas da patrulha do homunculo. Na função de patrulha, o homunculo alterna entre (MyDestX,MyDestY) e (MyPatrolX,MyPatrolY).
[3.25]
ResCmdList é a lista em que os comandos do client e os comandos enfileirados são armazenados. Note que ele usa a função List.new() do Util.lua para criar uma nova lista de comandos.
MyID é usada para guardar a ID do homunculo para uso facil. O unico momento em que a ID do homunculo é passada para o programa é na função AI (id), que é a ultima função nesse arquivo, e esse número de ID é usado para obter todas outras informações nescessárias (como o ID do dono, que é nescessário para a posição do dono, inimigo do dono, etc.), portanto é uma variavel extremamente importante.
MySkill e MySkillLevel são usados para armazenar o ID do skill selecionado do homunculo e seu level, para uso na função SkillObject do Const.lua.
[3.30]
Agora passemos para o processamento de comandos, que define as respostas do homunculo aos comandos que seu dono pode lhe passar. Esses são todos chamados pela função Process (msg), que por sua vez é chamada pela função AI. Ou seja, essas são as funções que defininem cada comando que pode ser passado para o homunculo.
[3.31]

CODE
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


Essa função tenta mover o homunculo até o ponto (x,y). Se o ponto for muito longe, o homunculo irá se mover até metade do caminho e então marcar a destinação final como seu destino atual, fazendo na pratica dois movimentos para seguir a rota inteira. Comandar o homunculo para se mover faz com que ele desselecione quaisquer skills e alvos que havia selecionado e mude seu estado para MOVE_CMD_ST, o estado em que ele é comandado para se mover. Simples, não?
[3.32]

CODE
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


Essa função faz o homunculo parar de se mover, entrar em estado IDLE_ST (estado Idle, "standby") e desselecionar quaisquer skills e alvos. Se o homunculo estiver fazendo algo além de estar parado, essa função o fará mover para sua posição atual (ou seja, parar) e então mudar seu estado para Idle.
[3.33]

CODE
function OnATTACK_OBJECT_CMD (id)

TraceAI ("OnATTACK_OBJECT_CMD")

MySkill = 0
MyEnemy = id
MyState = CHASE_ST

end


Essa função pega o ID de um inimigo e marca-o como alvo do homunculo. Então o homunculo muda para o estado de perseguição, no qual ele irá perseguir o inimigo. Também faz com que o homunculo desselecione qualquer skill, ou seja, ele irá atacar o alvo normalmente.
[3.34]

CODE
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


Essa função pega uma posição (x,y) e manda o homunculo "atacar" aquela celula. Basicamente, o homunculo se moverá até o local e então procurará por inimigos proximos para começar a atacar (o que é parte do ATTACK_AREA_CMD_ST que será observado mais adiante).
[3.35]

CODE
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


Essa função pega uma posição e faz o homunculo patrulhar entre sua posição atual e a posição escolhida.
[3.36]

CODE
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


Esse é o ALT+T. Faz seu homunculo parar de fazer o que estiver fazendo e mudar para o estado de Hold.
[3.37]

CODE
function OnSKILL_OBJECT_CMD (level,skill,id)

TraceAI ("OnSKILL_OBJECT_CMD")

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

end


Essa função pega um level de skill, uma ID da skill, um ID de inimigo e faz seu homunculo selecionar a skill no level escolhido e marcar o inimigo como seu alvo, mudando então para o estado de perseguição para se aproximar o bastante do inimigo e usar a skill escolhida.
[3.38]

CODE
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


Essa função é basicamente igual à anterior, entretanto o homunculo escolhe uma posição ao invés de um inimigo. O Homunculo então se move para essa posição e seleciona o skill escolhido antes de mudar de estado para SKILL_AREA_CMD_ST.
[3.39]

CODE
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


Essa função faz o homunculo mover até seu dono e mudar para o estado de de seguir, se já não estiver nele. Caso já esteja no estado de seguir, faz o homunculo mudar para o estado Idle. 3.3A]

CODE
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


Essa é a função principal de todo o processo de comandos, faz seu homunculo realizar a função de acordo com o comando que é passado para ele. Note que msg[1], msg[2], msg[3], etc., todas apontam para diferentes partes da mesma mensagem. Portanto, em uma mensagem especificando um comando de movimento, você teria algo como msg = {MOVE_CMD, x,y}. Ou seja, onde a função chama por OnMOVE_CMD(msg[2],msg[3]), está na realidade chamando por OnMOVE_COMMAND(x,y), onde x e y são quaisquers numeros que foram usados nessa mensagem. A função usa o valor da primeira parte da mensagem para determinar quantas outras partes a mensagem contém. Estranhamente, o OnSKILL_OBJECT_COMMAND usa quatro partes de mensagem, ao invés de três que é o que a função realmente nescessita. Entretanto, como tudo parece funcionar mesmo assim, deixarei como está.
[3.40]
Agora veremos os processos de estados, que definem qual o comportamento do homunculo em determinado estado. Após o comando Process(msg) chamar uma função, o estado do homunculo normalmente muda, mostrando que o comando foi processado. A função AI então chama essas funções de estado para fazer o homunculo se comportar como ele deveria.
[3.41]

CODE
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


Essa função define o comportamento do homunculo no estado Idle. A primeira coisa que ela faz é processar qualquer comando que estiver enfileirado, como movimentos finais quando o movimento inicial era longo demais, ou qualquer comando passado enquanto o homunculo mudava de estado. Então ele procura por inimigos do dono, se encontrar algum ele o atacará. Se não encontrar nenhum, ele procurará por inimigos do homunculo e os atacará. Se não encontrar nenhum, ele voltará para perto do seu dono caso esteja a mais de 3 celulas de distancia. Se nenhuma dessas condições for verdadeira, o homunculo não fará nada.
[3.42]

CODE
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


Essa função define o comportamento do homunculo no estado de seguir. Primeiro ele checa se está a menos de três celulas do dono, se sim, ele muda para o estado idle. Caso contrário, se estiver parado, ele se moverá para perto do dono. Note o erro de ditação em "Idle" no TraceAI. Foi a Gravity, não eu.

[3.43]

CODE
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


Essa função define o comportamento do homunculo no estado de perseguição. Se o inimigo estiver fora do campo visual, o homunculo irá parar desseleciona-lo e mudar para o estado Idle. Se o inimigo estiver dentro da distancia de ataque do homunculo, este irá mudar para o estado de ataque. Caso contrário, o homunculo continuará a se mover em direção ao inimigo.
[3.44]

CODE
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


Essa função define o comportamento do homunculo enquanto ataca um inimigo. Se o inimigo estiver fora do campo visual ou morto, o homunculo irá voltar para o estado Idle. Se o inimigo se afastar e não estiver mais dentro da distancia de ataque, o homunculo voltará para a instancia de perseguição. Caso contrário, o homunculo irá atacar. Ele usará o skill selecionado no level selecionado, se algum foi. Ou apenas atacará normalmente, se nenhum skill foi selecionado.
[3.45]

CODE
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


Essa função define o comportamento do homunculo enquanto se move. Se a sua posição atual for igual ao destino que lhe havia sido dado, ele muda para o estado Idle.
[3.46]

CODE
function OnSTOP_CMD_ST ()


end



function OnATTACK_OBJECT_CMD_ST ()


end


Essas funções estão vazias, fazendo com que o homunculo não faça nda enquanto estiver nelas. Faz você se perguntar por que elas foram definidas, não faz?
[3.47]

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


Essa função define como o homunculo se comporta quando é ordenado a atacar uma localidade. Após se mover para essa localidade, ele irá procurar por inimigos assim como faz no estado Idle.
[3.48]

CODE
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


Essa função define como o homunculo se comporta enquanto patrulha uma localidade. Ele primeiro procura por inimigos. Se encontrar algum, ele muda para o estado de Perseguição e o persegui-rá. Se nenhum inimigo for encontrado, ele ficará andando entre as posições que foram demarcadas até encontrar um.
[3.49]

CODE
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


Essa função define como o homunculo se comporta no estado de Hold. Se ele tiver um inimigo que estiver dentro de distancia de ataque, ele atacará o inimigo. Caso contrário, ele desselecionará o inimigo. Caso o homunculo não tenha nenhum alvo atual, ele irá procurar por novos.
[3.4A]

CODE
function OnSKILL_OBJECT_CMD_ST ()

end


Outra função vazia.

CODE
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


Essa função define como o homunculo se comporta ao tentar usar uma skill no chãol. Se ele estiver dentro da distancia minima para uso da skill, ele a usará no local indicado e mudará para o estado Idle.
[3.4C]

CODE
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


Essa função define como o homunculo se comporta no estado de Seguir. Ele se moverá para uma distancia maxima de 3 celulas do dono e tentará manter essa distancia.

[3.50]
A seguir estão as funções de seleção de inimigos, que são usadas em algumas das funções acima para fazer o homunculo escolher um inimigo como alvo. Há alguns critérios diferentes para se definir inimigos, estes estão separados nas funções abaixo:
[3.51]

CODE
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


Essa função é bastante longa, mas apenas retorna o inimigo atacante do dono mais proximo para que o homunculo tenha ele como inimigo. Ela faz uma lista de todos objetos na tela e procura em todos eles aqueles que estão atacando o dono, seja um monstro ou não-monstro (jogadores em situações de PvP), então procura qual deles está mais perto do dono e marca este como o alvo de maior prioridade para o homunculo.

[3.52]

CODE
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


Essa função é usada em geral quando o dono do homunculo não tem inimigos e irá procurar por inimigos do homunculo. Para Amistr e Lif, essa função irá chamar GetMyEnemyA, a função passiva de procura por inimigos. Se for Filir ou Vanilmirth, irá chamar por GetMyEnemyB, a função agressiva de procura por inimigos.
[3.53]

CODE
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


Função de procura de inimigo padrão para Amistr e Lif, é muito parecida com GetOwnerEnemy: ela procura por inimigos do dono, mas também por inimigos que estejam atacando o homunculo. A prioridade é para aquele que estiver mais perto do homunculo. Ou seja, Amistir e Lif, pelo padrão, só atacam se forem atacados (ou se seu dono for atacado).
[3.54]

CODE
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


Essa função é parecida com a anterior, entretanto ela aceita todos inimigos na tela como alvos potenciais. É o sistema padrão de localização de inimigos para Filir e Valnirmith. Há um problema de lógica com esse script: ele não checa para ver se o monstro já está lutando com outro jogador, resultando em Kill-Steal (KS) por parte do homunculo. Isso pode ser facilmente corrigido e será discutido na sessão de custom AI.
[3.60]

CODE
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


Esse é o coração de todo o script de AI. Ele pega o ID do homunculo e imediatamente o armazena na variavel MyID, o que permite que muitas informações sejam adquiridas a partir da função GetV. Após isso, a função pega uma mensagem e uma mensagem reserve. Se não houver nenhuma mensagem, ele adicionará a rmsg para a sua lista e tentará efetuar os processos de estado (o padrão sendo Idle). Se houver uma mensagem, ela será processada pela função ProcessCommand(msg) que chamará o comando apropriado. Após isso, o estado atual do homunculo é comparado com os varios estados definidos, e quando retornar verdadeiro ele chama a função para que o homunculo se comporte como deve naquele estado. É assim que o homunculo toma decisões

Índice:
[4.00] Personalizando sua AI
[4.10] Criando uma função GetMyEnemy melhor
[4.20] Sem mais homunculos perdidos
[4.30] Personalizando o estilo de batalha de seu homunculo
[4.31] Uso automaticos de skills de efeito direto
[4.32] Uso automatico de buffs
[4.33] Modos personalizados de batalha
[4.40] Custo em SP para habilidades de Homunculos
[4.41] Lif
[4.42] Amistir
[4.43] Filir
[4.44] Valnirmith
[4.50] Skill Delay para os buffs de Homunculos
[4.51] Lif
[4.52] Amistir
[4.53] Filir
[4.54] Valnirmith
[4.60] Truques legais para mascotes legais
[4.61] Script de conjuração automatica.
-------

[4.00]
Agora a parte que provavelmente todos estiveram esperando. Aqui eu entrarei em diversas técnicas que você pode usar para personalizar a AI de seu homunculo para que ele faça qualquer tipo de coisa, desde andar pelo mapa até usar skills. Você pode especificar qualquer tipo de coisa na AI personalizada, e o unico limite real é a sua imaginação e o que os códigos podem fazer. Mas antes de irmos para a parte criativa, vamos primeiro para as modifcações que acredito que todos devam fazer, coisas que a própria Gravity deveria ter feito.

[4.10]
O primeiro arquivo irá corrigir o Kill-Stealing da função agressiva de seleção de inimigos. Do modo padrão, o homunculo irá atacar qualquer inimigo que estiver na tela, mesmo que ele já esteja lutando com outro jogador. Nós não queremos isso, visto que é uma forma facil de conseguir um ban caso você jogue por uma semana sem conseguir pressionar Alt+T rapido o bastante. O que nós faremos para corrigir esse problema é adicionar uma nova função, GetMyEnemyC, com uma condição "if" modificada que faz com que o homunculo ataque apenas inimigos que não tenham alvos (exceto, claro, se o alvo for o próprio homunculo ou seu dono). Essa função também não seleciona plantas e guardiões de castelos.

CODE
function GetMyEnemyC (myid)
local result = 0
local owner = GetV (V_OWNER,myid)
local actors = GetActors ()
local enemys = {}
local index = 1
local type
local target
for i,v in ipairs(actors) do
if (v ~= owner and v ~= myid) then
target = GetV (V_TARGET, v)
if (1 == IsMonster(v)) then
type = GetV (V_HOMUNTYPE, v)
if (target ~= 0) then
if (target == myid or target == owner) then
enemys[index] = v
index = index+1
end
elseif (type~=1555 and type~=1575 and type~=1579 and type~=1589 and type~= 1590 and (type < 1285 or type > 1288) and (type < 1078 or type > 1085)) then
enemys[index] = v
index = index+1
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


Essa função adiciona um critério novo no meio, checando para ver se o inimigo possui algum alvo, e se possuir checa para ver se o alvo não é nem o dono e nem o homunculo. Também checa para ver se o monstro é o uma planta (ou cogumelo) ou um Guardião, se for ele os ignora. Há um caso em que o homunculo ainda atacará os monstros de outro jogador: caso o jogador entre em sua tela com monstros os seguindo mas sem o atacar. Nesse caso o homunculo não irá reconhecer que os monstros estão visando o jogador, e irá ataca-los. Entretanto esse é um caso mais isolado que o Kill Steal simples e direto que o homunculo faria normalmente. Para implementar essa função em sua AI, você deve coloca-la logo após GetMyEnemyB e então modificar a função GetMyEnemy para que os homunculus agressivos usem GetMyEnemyC ao invés de GetMyEnemyB.

[4.20]
Essa é uma modificação à função IsOutOfSight do arquivo Util.lua. Há um problema com os homunculos agressivos em que eles costumam sair correndo pelo mapa, simplesmente porque o monstro está dentro do seu campo se visão. Então eles se perdem em algum lugar do mapa e seu dono tem que ir atras deles. O que devemos fazer é alterar um critério para determinar se o monstro está dentro ou fora do campo de visão do homunculo. Como a maioria dos usuarios de homunculos preferire manter seu homunculo nos limites de sua tela, usaremos a posição do dono do homunculo como centro para determinar o campo visual. Além disso, diminuiremos um pouco o valor maximo de distância. O valor padrão é de vinte celulas.

CODE
function IsOutOfSight (id1,id2)
local x1,y1 = GetV (V_POSITION,GetV(V_OWNER,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 > 16 then
return true
else
return false
end
end


Ai está. Ele usa a distancia do dono do homunculo para o monstro, ao invés da distancia do homunculo para o monstro e diminui a distancia maxima de vinte para dezesseis celulas.

[4.30]
Agora vamos ao que interessa: como fazer os homunculos tomarem todas as suas decisões no calor da batalha? Programando-os para isso, óbvio. Há muitas coisas que um homunculo pode fazer por ele mesmo enquanto luta, como usar skills automaticamente, buffar-se, e ainda conseguir controlar o level que utiliza seus skills e buffs de forma util.

[4.40]
Aqui está uma referencia ao custo de SP dos skills que serão usados adiante, e você poderá achar util nas suas próprias aventuras nos scripts.
Obrigado novamente à Hahseckh por vários desses valores:

[4.41]
Lif:
Healing Touch:
lvl 1 = ?
lvl 2 = ?
lvl 3 = ?
lvl 4 = ?
lvl 5 = ?
Urgent Escape:
lvl 1 = ?
lvl 2 = ?
lvl 3 = ?
lvl 4 = ?
lvl 5 = 40 sp

[4.42]
Amistr:
Castling:
lvl 1 = ?
lvl 2 = ?
lvl 3 = ?
lvl 4 = ?
lvl 5 = 10 sp
Amistr Bulwark:
lvl 1 = 20 SP
lvl 2 = 25 SP
lvl 3 = 30 SP
lvl 4 = 35 SP
lvl 5 = 40 SP

[4.43]
Filir:
Moonlight:
lvl 1 = 4 SP
lvl 2 = 8 SP
lvl 3 = 12 SP
lvl 4 = 16 SP
lvl 5 = 20 SP
Flitting:
lvl 1 = 30 SP
lvl 2 = 40 SP
lvl 3 = 50 SP
lvl 4 = 60 SP
lvl 5 = 70 SP
Accelerated Flight:
lvl 1 = 30 SP
lvl 2 = 40 SP
lvl 3 = 50 SP
lvl 4 = 60 SP
lvl 5 = 70 SP

[4.44]
Vanilmirth:
Caprice:
lvl 1 = ?
lvl 2 = ?
lvl 3 = ?
lvl 4 = 28 SP
lvl 5 = 30 SP
Chaotic Blessings:
lvl 1 = ?
lvl 2 = ?
lvl 3 = ?
lvl 4 = ?
lvl 5 = 40 SP

[4.31]
Começarem com algumas técnicas básicas de uso automatico de skills de efeito direto. "Skills de Efeito Direto" é a forma com a qual me refiro a skills cujo o efeito é sentido instanameante, como skills de cura ou de causar dano. A primeira coisa que precisaremos são algumas variaveis globais, então vamos definir elas primeiro. Aqui está o pseudo-código para a definição dessas variaveis.

CODE
SkillDelay = 0

Homunc_Auto_DESkill = 1
Homunc_DESkillID = "XXXX"
Homunc_DESkillLevel = "Y"
Homunc_DESkillUseSP = Z
Homunc_DESkillDelay = 0
Homunc_DESkillDelayTimes = A


SkillDelay é um único valor que represanta o intervalo geral entre usos de skills, para se ter certeza que nenhum skill tentará ser usado dentro do tempo de Delay de outro skill. Qualquer coisa em 5 e 10 é um valor adequado para isso, entretanto começaremos com zero no arquivo para que o homunculo possa começar a usar skills imediatamente.
Homunc_Auto_DESkill é uma variavel que deve ser posta em 1 ou 0 para representar LIGADO ou DESLIGADO respectivamente. Essa variavel será usada mais adiante para determinar se o homunculo deve usar skills automaticamente ou não.
Homunc_DESkillID guarda o ID do skill que o homunculo deve usar. Os ID de skills foram mostrados na sessão [1.20].
Homunc_DESkillLevel guarda o level da skill que o homunculo deve usar.
Homunc_DESkillUseSP guarda a quantidade de SP nescessária para se usar o skill. Será usado para se comparar com o SP atual do homunculo, para que ele nao tente usar a skill sem ter SP suficiente.
Homunc_DESkillDelay guarda o tempo de Skill Delay da skill escolhida.
Homunc_DESkillDelayTimes será usado para definir o número de ataques que o homunculo deve usar antes de usar um skill novamente. Qualquer coisa entre 5 e 10 é um bom valor para essa váriavel.
Agora voltemos ao código da função OnATTACK_ST, onde implementaremos o uso do AutoSkill:

CODE
if (SkillDelay <= 0 and Homunc_Auto_DESkill == 1 and GetV(V_SP, MyID) >= Homunc_DESkillUseSP) then
if (Homunc_DESkillDelay > 0) then
Homunc_DESkillDelay = Homunc_DESkillDelay - 1
else
SkillObject (MyID, Homunc_DESkill_Level, Homunc_DESkillID, MyEnemy)
SkillDelay = 5
Homunc_DESkillDelay = Homunc_DESkillDelayTimes
end
end
if (SkillDelay > 0) then
SkillDelay = SkillDelay - 1
end


Esse código deve ser posto no OnATTACK_ST, logo após a chamada de TraceAI. Se o skill estiver configurado para ser usado automaticamente, não houver skill delay e o homunculo tiver SP suficiente, ele usará o skill e então gravará os valores de skill delay especificos, subtraindo-os a medida que ataca, até poder usar o skill novamente.

[4.32]
Agora entraremos no uso automatico de buffs. A principal diferença com o que fizemos acima é que buffs possuem skill delay maior e um determinado tempo de duração.
[4.50]
Falando sobre tempo de duração, aqui está o intervalo de skills de acordo com a descrição das mesmas:

[4.51]
Lif:
Urgent Escape:
lvl 1 = 40 seconds
lvl 2 = 35 seconds
lvl 3 = 30 seconds
lvl 4 = 25 seconds
lvl 5 = 20 seconds

[4.52]
Amistr:
Amistr Bulwark:
lvl 1 = 40 seconds
lvl 2 = 35 seconds
lvl 3 = 30 seconds
lvl 4 = 25 seconds
lvl 5 = 20 seconds

[4.53]
Filir:
Flitting:
lvl 1 = 60 second delay
lvl 2 = 70 second delay
lvl 3 = 80 second delay
lvl 4 = 90 second delay
lvl 5 = 120 second delay
Accelerated Flight:
lvl 1 = 60 second delay
lvl 2 = 70 second delay
lvl 3 = 80 second delay
lvl 4 = 90 second delay
lvl 5 = 120 second delay

[4.54]
Vanilmirth:
No buff skills

Para usarmos buffs automaticamente, precisamos também de uma lista de variaveis globais. Algumas são iguais as da função anteirior, outras não. Vejamos novamente em forma de pseudo-código:

CODE
SkillDelay = 0

Homunc_Auto_Buff = 1
Homunc_BuffID = "XXXX"
Homunc_BuffLevel = "Y"
Homunc_BuffTime = A
Homunc_BuffUseSP = Z
Homunc_NowTick_Buff = 0
Homunc_StartTick_Buff = 0


SkillDelay tem o mesmo uso que tinha antes: uma pequena margem de intervalo de tempo entre usos de skills. Não muito importante aqui, visto que usaremos GetTick() para manter a contagem do tempo, mas ainda pode ser útil.
Homunc_Auto_Buff deve ser deixado como 1 ou 0 para LIGADO ou DESLIGADO. Determina se o homunculo usará auto-buffs ou não.
Homunc_BuffID guarda o ID do skill do buff, para ser usado na função de SkillObject.
Homunc_BuffLevel guarda o level do buff especifico.
Homunc_BuffTime guarda o intervalo de tempo que deve se decorrer entre os usos do buff.
Homunc_BuffUseSP guarda o custo de SP do skill, para que o homunculo nao tente usa-lo sem ter SP suficiente.
Homunc_NowTick_Buff guardará o tick atual, para ser comparado com a proxima variavel.
Homunc_StartTick_Buff guardará o tick em que o buff foi usado, a partir da função GetTick() do Const.lua. Comparando com o valor da variavel acima pode ser possivel determinar quanto tempo se passou desde o ultimo uso do buff.

Agora, assim como para os efeitos diretos, nós devemos implementar isso no arquivo, dentro da função onATTACK_ST. O código ficará mais ou menos assim:

CODE
if (SkillDelay <= 0 and Homunc_Auto_Buff == 1 and GetV(V_SP, MyID) >= Homunc_BuffUseSP) then
Homunc_NowTick_Buff = GetTick()
if (Homunc_NowTick_Buff - Homunc_StartTick_Buff >= Homunc_BuffTime * 1000) then
Homunc_StartTick_Buff = GetTick()
SkillObject (MyID, Homunc_Buff_Level, Homun_BuffID, MyID)
SkillDelay = 5
end
end
if (SkillDelay > 0) then
SkillDelay = SkillDelay - 1
end


A primeira checagem verifica se o buff está marcado como auto-cast. Se sim, e não houver skill delay, e o homunculo tiver SP suficiente, ele passaráa contar quanto tempo se passou desde o ultimo buff. Se for tempo o suficiente, o buff será usado e o StartTick será marcado para o tick atual. Note que você pode combinar os dois scripts aqui para que seu homunculo use buffs e skills diretos automaticamente.
Há muitos outros critérios que poderiam ser usados. Se você não quiser que o homunculo use algum skill sempre, preferir que ele o use apenas quando tiver menos de 50% hp, pode usar algo assim:

CODE
Homunc_HPPercent = GetV(V_HP,MyID)*100/GetV(V_MaxHP,MyID)
if (Homunc_HPPercent < 50) then
...
end


O código para o autocast viria dentro do espaço onde estão as "...".

[4.33]
Outra idéia que pode ser de grande uso é a de criar um Modo de Batalha, onde alguns critérios mudam o comportamento do Homunculo. Essas mudanças podem ser algo como ligar ou desligar o uso de autoskills ou aumentar o nivel da skill que está sendo usada. Essa função precisa de duas partes importantes, além de algumas variaveis globais. As partes importantes são uma clausula de entrada, onde todas as mudanças podem ser efetuadas, e uma clausula de saida, onde tudo volta aos valores padrões. Aqui estão as variaveis globais nescessárias para esse tipo de função:

CODE
Homunc_Craziness = 1
Homunc_GoneCrazy = 0
Homunc_CrazyTime = 60
Start_Tick_Crazy = 0
Now_Tick_Crazy = 0


Homunc_Craziness pode ser 1 ou 0 para LIGADO ou DESLIGADO. Determina se o homunculo poderá usar o novo modo (o qual chamei de "crazy") ou não.
Homunc_GoneCrazy guarda o valor 1 para quando o homunculo está no Crazy Mode ou 0 para quando está normal.
Homunc_CrazyTime define o tempo minimo que o modo vai durar.
Start_Tick_Crazy guarda o tick em que o Crazy Mode iniciou.
Now_TickCrazy guarda o tick atual para ser comparado com as duas variaveis acima.
Agora que temos as variaveis definidas, precisamos de definir a função GoCrazy() que fará o homunculo entrar nesse modo. Estou considerando o uso das funções de AutoSkill e AutoBuff aqui, pois elas serão editadas pelo Crazy Mode. O critério que usaremos será que, quando o HP do homunculo estiver abaixo de 50%, ele entrará em Crazy Mode por pelo menos um minuto. Após um minuto, se o HP do homunculo estiver acima de 50%, ele voltará ao normal. Caso contrário, ele continuará em Crazy Mode até ter HP superior a 50%.

CODE
function GoCrazy()
Homunc_HP = GetV(V_HP, MyID)*100/GetV(V_MAXHP, MyID)
if (Homunc_Craziness == 1) then
if (Homunc_GoneCrazy == 0) then
if (Homunc_HP <= 50) then
Start_Tick_Crazy = GetTick()
Homunc_GoneCrazy = 1
Homunc_DESkillLevel = "5"
Homunc_DESkillDelayTimes = 5
Homunc_BuffLevel = "5"
Homunc_BuffTime = 120
end
elseif (Homunc_GoneCrazy == 1) then
if (Homunc_HP > 50) then
Homunc_NowTick_Crazy = GetTick()
if (Homunc_NowTick_Crazy - Homunc_StartTick_Crazy >= Homunc_CrazyTime*1000) then
Homunc_GoneCrazy = 0
Homunc_DESkillLevel = "3"
Homunc_DESkillDelayTimes = 10
Homunc_BuffLevel = "3"
Homunc_BuffTime = 50
end
end
end
end
end


Tenha em mente que, em uma função real, outros valores deveriam ser levados em conta, como o custo de SP. Um bom local para posicionar essa função no arquivo AI.lua seria logo no começo do processo de Command, antes de OnMOVE_CMD. Após adiciona-la a seu arquivo, você deve ir até o OnATTACK_ST e adicionar o comando GoCrazy() logo após a chamada de TraceAI para que o homunculo possa entrar nesse modo antes de proceder com seus ataques, se for o caso.

[4.60]
Bem, aqui chega ao fim os basicos da criação de scripts de AI. Essa ultima sessão possui alguns scripts individuais que achei inteiressantes.

[4.61]
O primeiro script a ser adicionado aqui é um auto-cast para Amistr usar Castling quando seu dono estiver cercado por três ou mais monstros e estiver com menos de 25% de hp. Isso faz com que Amistir tanque o dano e é extremamente util para alquimistas baseados em AGI.

CODE
--New Global Variables
Amistr_CastlingID = "8005"
Amistr_CastlingLevel = "5"
Amistr_CastlingUseSP = 10
...
function OnATTACK_ST ()

TraceAI ("OnATTACK_ST")
------------------------------------------------
local owner = GetV (V_OWNER,MyID)
local actors = GetActors ()
local index = 0
local target
Owner_HPPercent = GetV(V_HP,owner)*100/GetV(V_MAXHP,owner)
for i,v in ipairs(actors) do
if (v ~= owner and v ~= MyID) then
target = GetV (V_TARGET,v)
if (target == owner) then
index = index+1
end
end
end
if (index >= 3 and GetV(V_SP, MyID) >= Amistr_CastlingUseSP and OwnerHP_Percent > 25) then
SkillObject (MyID, Amistr_CastlingLevel, Amistr_CastlingID, owner)
return
end
------------------------------------------------
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
...


E aqui está. Ele usa uma função parecida com a do GetOwnerEnemy para determinar quantos inimigos estão atacando o dono, se for maior do que 3, ele verifica o HP do dono. Se o HP estiver abaixo de 25%, Amistr usará castling e passará a atacar um dos inimigos.
Qualquer dúvida, comentário, sugestão, poe ae em baixo ^^
bem isso é tudo uma noite todinha de trabalho para a felicidade de todos ^^
sayonara

BEM ACHEI ESSE TUTOR FEITO POR ROZANINHA DO FORUM REVOLUTION, MAS CASO ALGUEM SAIBA O VERDADEIRO AUTOR ME INFORMEM EM POST!!!

Nenhum comentário: