Pular para o conteúdo

Introdução ao scripting

Os scripts do MTAX são escritos em Lua 5.4. Cada resource roda em uma VM própria: variáveis globais de um resource não vazam para outro, e reiniciar um resource limpa tudo o que ele criou.

A API segue uma convenção previsível: verbo primeiro, domínio em seguida — createVehicle, setElementPosition, getPlayerName. Quem lê o nome de uma função sabe o que ela faz e onde procurar as vizinhas.

Cada script é declarado no mtaxmanifest.lua em uma das três listas:

ListaOnde executa
server_filesSó no servidor. O arquivo nunca sai da máquina.
client_filesBaixado e executado na máquina do jogador.
shared_filesExecutado nos dois lados.

O servidor é a autoridade. Tudo que precisa ser confiável — dinheiro, inventário, permissões — vive no servidor.

Nem toda função existe nos dois lados. A referência de funções é dividida em três grupos — compartilhadas (485), do servidor (130) e do cliente (569) — e é esse grupo que determina em qual lista do manifesto você pode usar cada função.

O ambiente Lua é restrito. Estas bibliotecas padrão estão abertas no servidor:

base · math · string · table · coroutine · utf8 · os · debug

Os scripts do lado do cliente abrem as mesmas bibliotecas com as mesmas restrições, mais uma: no cliente string.dump foi removido, enquanto o servidor mantém a biblioteca string inteira.

A biblioteca os está disponível de forma parcial: os.time, os.date, os.clock e os.difftime funcionam, mas os.execute, os.remove, os.rename, os.exit, os.getenv, os.tmpname e os.setlocale foram removidos.

A biblioteca debug está reduzida a duas funções: debug.traceback e debug.getinfo. Elas existem para que o relato de erros consiga nomear de onde algo veio; todo o resto que a biblioteca normalmente oferece — os hooks, os leitores de upvalues e locais, o registry — foi retirado, porque passaria por cima do sandbox.

Além da API do servidor, todo script recebe um conjunto de helpers prontos:

local pos = Vector3(10, 20, 30)
local outra = Vector3(0, 0, 5)
print(pos + outra) -- vector3: { x = 10.000, y = 20.000, z = 35.000 }
print(pos:getLength())
print(pos:getNormalized())
RecursoO que oferece
Vector2, Vector3, Vector4Vetores com operadores (+, -, *, /), getLength, getNormalized, dot, cross.
MatrixMatriz de transformação com position, rotation, forward, transformPosition, inverse.
bitAnd, bitOr, bitXor, bitNot, bitTestOperações bit a bit em 32 bits sem sinal. O bitTest responde se o AND de todos os argumentos é diferente de zero.
bitLShift, bitRShift, bitArShift, bitLRotate, bitRRotateDeslocamentos e rotações. O bitArShift preserva o bit de sinal; as rotações limitam o deslocamento a 0–31.
bitExtract(var, campo [, largura]) / bitReplace(var, valor, campo [, largura])Lê e escreve um campo de bits. A largura padrão é 1.
split(texto, separador)Divide um texto em uma tabela.
getDistanceBetweenPoints2D / getDistanceBetweenPoints3DDistância euclidiana entre dois pontos. Ambas aceitam números soltos ou Vector2/Vector3.
getColorFromString("#RRGGBBAA")Converte hexadecimal em r, g, b, a.
tocolor(r, g, b, a)Empacota uma cor em um único inteiro. Os canais são limitados a 0–255 e o padrão é 255; um canal que não seja número faz a função devolver false e avisar.
interpolateBetween / getEasingValueInterpolação com curvas de easing (Linear, InOutQuad, OutBounce, …).
utfLen, utfSub, utfChar, utfCode, utfSeekManipulação de texto UTF-8.
inspect(valor) / iprint(valor)Imprime tabelas de forma legível, seguro contra ciclos.
ref / derefGuarda um valor em um registro e recupera depois por número.

Um conjunto específico de funções aceita um Vector3 no lugar das coordenadas soltas:

createVehicle(411, 0, 0, 5)
createVehicle(411, Vector3(0, 0, 5)) -- equivalente

São elas: attachElements, createBlip, createBuilding, createExplosion, createMarker, createObject, createPed, createPickup, createVehicle, getElementsWithinRange, moveObject, removeWorldModel, restoreWorldModel, setCameraMatrix, setCameraTarget, setElementAngularVelocity, setElementAttachedOffsets, setElementInterior, setElementPosition, setElementRotation, setElementVelocity e setMarkerTarget.

Nas demais, passe os componentes separadamente (pos.x, pos.y, pos.z).

No cliente, setCameraMatrix também aceita um Matrix direto. Como no MTA, só a posição sai de fato dele: o ponto de mira repassado é a rotação da matriz convertida para radianos, o que na prática é um ponto perto da origem do mundo. Para olhar na direção da matriz, passe você mesmo a posição e um ponto à frente dela:

setCameraMatrix(minhaMatriz) -- câmera na posição da matriz, olhando para a origem
local p, f = minhaMatriz.position, minhaMatriz.forward
setCameraMatrix(p.x, p.y, p.z, p.x + f.x, p.y + f.y, p.z + f.z) -- olhando na direção da matriz

Quase tudo no mundo é um elemento: jogadores, veículos, peds, objetos, marcadores, blips. Elementos formam uma árvore.

Dois globais já vêm prontos em todo script:

GlobalConteúdo
rootA raiz da árvore — o ancestral de todos os elementos do servidor.
resourceRootA raiz deste resource.

Eles equivalem a getRootElement() e getResourceRootElement(), mas dispensam a chamada.

Elementos criados por um resource são filhos da raiz dele e são destruídos automaticamente quando o resource para.

local veiculo = createVehicle(411, 0, 0, 5)
setElementPosition(veiculo, 10, 20, 5)
setElementData(veiculo, "dono", "Gabriel")
local x, y, z = getElementPosition(veiculo)

As mesmas funções nativas também podem ser chamadas como métodos e propriedades no próprio elemento. As duas formas estão sempre disponíveis, nos dois lados, sem precisar habilitar nada:

local veiculo = Vehicle(411, 0, 0, 5)
veiculo.health = 750
veiculo:setData("dono", "Gabriel")
print(veiculo.position) -- Vector3

São 32 classes de elemento e 10 namespaces estáticos cobrindo a API inteira. Veja o guia de OOP para as regras e a referência de classes para cada membro.

Eventos são o principal meio de reagir ao que acontece no mundo.

addEventHandler("onPlayerJoin", root, function()
outputDebugString(getPlayerName(source) .. " entrou")
end)

Dentro de um manipulador, cinco globais estão disponíveis:

GlobalConteúdo
sourceO elemento que originou o evento.
clientO jogador que enviou o evento, quando ele veio do cliente.
eventNameO nome do evento em execução.
sourceResourceO resource cujo script disparou o evento; nil quando quem disparou foi a engine.
sourceResourceRootO elemento raiz desse resource, pela mesma regra.

Elas só existem durante a execução do manipulador — depois voltam a ser nil.

A página de eventos traz a lista completa e os parâmetros de cada um.

addCommandHandler("curar", function(player, cmd, quantidade)
if player == false then return end -- veio do console do servidor
if not getElementData(player, "podeCurar") then return end
setElementHealth(player, tonumber(quantidade) or 100)
end)

O manipulador recebe (source, nomeDoComando, ...). Quando a linha vem do console do servidor, source é false; quando um jogador a digita no F8, source é esse jogador.

Uma linha digitada no F8 executa primeiro os manipuladores do cliente e o onClientConsole, depois é encaminhada automaticamente: onPlayerCommand → manipuladores do servidor → onConsole. O executeCommandHandler do cliente, em contraste, só executa manipuladores locais e não atravessa a rede. A página de comandos detalha os quatro caminhos.

Por padrão os comandos diferenciam maiúsculas de minúsculas. Para desligar isso:

addCommandHandler("curar", handler, false, false)
-- ^ ^
-- restricted caseSensitive

Declare no manifesto o que o resource oferece:

-- mtaxmanifest.lua
exports = { "getDinheiro" }

E chame de outro resource:

local dinheiro = exports.banco:getDinheiro(player)

Chamar um resource que não está rodando não gera erro: você recebe false e um aviso no debug.

outputDebugString("valor: " .. tostring(x))
outputDebugString("algo deu errado", 1) -- 1 = erro, 2 = aviso, 3 = informação
iprint(minhaTabela) -- imprime tabelas de forma legível

Erros dentro de um manipulador de evento não derrubam o servidor: eles são registrados no console com o nome do resource e do evento.