Os scripts .txt definem o que existe. O Lua define o que acontece: o loot, um menu novo no clique direito, uma ação com animação, uma luz que segue o jogador, um canhão de água num caminhão.
O Lua de Project Zomboid é Lua 5.1
O jogo usa o Kahlua, uma versão do Lua 5.1 que roda dentro do Java. Se você aprendeu Lua em outro lugar, fique de olho nestas diferenças:
- Não existe
gotonem rótulos::algo::. Para pular um passo dentro de umfor, use umif. - Não existe a divisão inteira
//. Usemath.floor(a / b). - Não existem
&,|,~,<<nem>>. - As palavras reservadas (
end,function,repeat...) não podem ser chaves soltas: escrevat["end"], nãot.end. - Quando você chama um método que não existe, o erro é um enigmático
tried to call nil, e ele aparece ao carregar a partida, não ao carregar o mod.
Cliente, servidor e shared
media/lua/
├── client/ <- só no PC do jogador: menus, interface, efeitos visuais
├── server/ <- só no servidor: loot, regras, o que "manda"
└── shared/ <- nos dois: dados, utilitários e ações com tempoNo single player o seu PC faz o papel de cliente e de servidor ao mesmo tempo, então tudo roda. A diferença importa no multijogador, e é melhor pensar assim desde o começo: o mod funciona nos dois modos sem mudanças.
Um namespace por mod
Todos os mods compartilham o mesmo Lua global. Para não atropelar os outros, coloque tudo o que é seu em uma única tabela:
MiMod = MiMod or {}
function MiMod.saludar(player)
player:Say("Weno pa'l leseo")
endNunca deixe variáveis globais soltas como contador = 0: cedo ou tarde elas batem de frente com as de outro mod.
Eventos: quando o seu código roda
O seu código se "pendura" em eventos do jogo:
Events.OnGameStart.Add(function()
print("[MiMod] la partida empezó")
end)A lista completa da B42 está na referência de eventos de Lua. Alguns que usamos em mods publicados:
| Evento | Para que usamos |
|---|---|
OnPostDistributionMerge | Adicionar loot (veja Loot) |
OnTick | Coisas contínuas, como uma luz que segue o jogador. Limite ele: roda a cada frame. |
OnClientCommand | O servidor recebe pedidos dos jogadores |
OnSave | Limpar coisas temporárias antes de salvar |
OnPlayerDeath | Limpar o que o jogador tinha |
OnWeaponSwing | Reagir ao golpe de uma arma (a pistola de gravidade) |
Para o OnTick não pesar, faça o trabalho de tempos em tempos:
local ultimo = 0
Events.OnTick.Add(function()
local ahora = getTimestampMs()
if ahora - ultimo < 1000 then return end -- uma vez por segundo
ultimo = ahora
-- trabalho pesado aqui
end)Multijogador: o cliente pede, o servidor decide
Se um jogador faz algo que muda o mundo, o cliente não aplica isso diretamente: ele pede ao servidor que faça. O servidor confere se faz sentido e aplica para todo mundo. É assim que funciona o canhão de água do Wanaco:
-- client/: o jogador aperta o botão
sendClientCommand(player, "Wanaco", "toggleCannon", { on = true })-- server/: o servidor confere e aplica
Events.OnClientCommand.Add(function(module, command, player, args)
if module ~= "Wanaco" or command ~= "toggleCannon" then return end
local vehicle = player:getVehicle()
if not vehicle then return end -- nunca confie no cliente
-- ... ligar o canhão e avisar todo mundo
end)- O primeiro texto (
"Wanaco") é o canal do seu mod. - O servidor valida tudo de novo. Um cliente modificado poderia mandar qualquer coisa.
- Isso também funciona no single player, então você não precisa de duas versões do código.
Ações com tempo (timed actions)
As ações com barra de progresso e animação (comer, construir, dançar) são timed actions. Elas são criadas derivando de ISBaseTimedAction:
require "TimedActions/ISBaseTimedAction"
MiMod.BailarAction = ISBaseTimedAction:derive("MiModBailarAction")
function MiMod.BailarAction:isValid()
return self.character:getVehicle() == nil
end
function MiMod.BailarAction:start()
self:setActionAnim("MiBaile")
endArmadilhas de API que encontramos na B42
getCell():getVehicles()agora é um Set, sem:get(i). Para percorrer objetos, usegetCell():getObjectListForLua().- As propriedades dos tiles são verificadas com
:has(IsoFlagType.water), em minúscula.:Is(...)não existe e dátried to call nil. HaloTextHelper.addText(player, texto, color)não existe. UseaddGoodText(verde),addBadText(vermelho) ouaddText(player, texto).