Saltar para o conteúdo principal

Scripting Lua: Resolução de problemas

webapp_banner.jpg

Esta página aborda problemas comuns que pode encontrar ao escrever ou depurar poderes em Lua, além de conselhos de migração para autores que vêm do EliteScript. Se está a depurar scripts de NPC, veja Scripts de NPC. Se procura exemplos funcionais, veja Exemplos e Padrões. Se está apenas a começar, veja Começar.

Motor Lua Partilhado

O EliteMobs usa o motor de scripting Lua do MagmaCore, partilhado entre os plugins da Nightbreak. Para documentação sobre conceitos partilhados como a sandbox, o agendador (scheduler), as zonas, a API de mundo, as tabelas de entidades e os métodos de UI do jogador, veja a página Motor de Scripting Lua do MagmaCore.


Problemas comuns​

1. O poder não carrega de todo​

Verifique a consola do servidor à procura de erros quando o servidor arranca. A causa mais comum é um erro de sintaxe Lua (falta de end, parênteses por fechar, etc.). Verifique também que o ficheiro termina em .lua e está colocado no diretório powers correto.

Para nomes personalizados, verifique se um ficheiro de metadados .yml ou .yaml no mesmo diretório e com o mesmo nome base define isEnabled: false; isso impede a descoberta. Um ficheiro de metadados com o mesmo nome base ao lado de um poder incluído também aplica a esse poder os seus campos isEnabled, effect, powerType e de tempos de recarga. Se dois ficheiros de poder tiverem o mesmo nome em pastas diferentes, só um é carregado; a consola indica a cópia ignorada.

O ficheiro tem de devolver uma tabela com api_version = 1. Essa tabela só admite api_version, o campo opcional priority e nomes de hooks suportados; os valores dos hooks têm de ser funções. Mantenha as funções auxiliares locais em vez de adicionar chaves arbitrárias à tabela devolvida. O código de topo é executado na validação da definição e na inicialização do runtime, sem contexto de hook.

2. O hook nunca dispara​

Verifique que o nome do hook está escrito exatamente como listado na lista de hooks. Erros comuns: on_boss_hit (errado) vs. on_boss_damaged_by_player (correto), ou on_tick (errado) vs. on_game_tick (correto).

Uma chave não suportada na tabela devolvida impede o carregamento de toda a definição. Se carregar mas não executar, verifique também se o boss referencia o nome do ficheiro do poder, se o evento relevante ocorre e se o runtime não falhou, não está pausado nem tem os poderes suprimidos.

3. context.player é nil​

Só os hooks cujo evento subjacente contém um jogador preenchem context.player. É sempre nil em on_spawn, on_game_tick, on_boss_damaged, on_boss_damaged_by_elite, on_exit_combat, on_heal, on_death, on_phase_switch e on_mind_action.

É preenchido em on_boss_damaged_by_player, on_reinforcement_damaged_by_player, on_player_damaged_by_boss, on_enter_combat e on_boss_target_changed, e em on_zone_enter / on_zone_leave quando a entidade envolvida é um jogador. Verifique sempre se é nil antes de o usar. Veja Hooks e Ciclo de Vida para a tabela completa.

4. Timeout / orçamento de execução excedido​

Cada hook, callback agendado e avaliação de ficheiro partilha com as chamadas aninhadas um orçamento de 250 000 instruções Lua e 50 ms de tempo de CPU da thread. Se a JVM não conseguir medir esse tempo de CPU, aplica-se um limite alternativo de 250 ms de tempo decorrido. O erro identifica o limite excedido:

Lua instruction budget exceeded (250000 instruction limit)
Lua CPU-time budget exceeded (50ms current-thread CPU limit)
Lua elapsed-time fallback budget exceeded (250ms fallback; current-thread CPU time unavailable)

A VM interrompe ciclos Lua descontrolados durante a execução. Não consegue interromper uma chamada Java em curso, e não existe um limite separado de 50 ms de tempo real após o retorno. Causas comuns incluem percorrer demasiadas entidades, gerar partículas numa zona grande sem coverage e trabalho pesado em cada tick. Use cooldowns, menos pontos de amostragem ou context.scheduler:run_every(...) para distribuir o trabalho por vários ticks.

5. O callback do agendador usa dados desatualizados​

Provavelmente está a usar o context exterior em vez do parâmetro do callback. Mude function() ... context.boss ... end para function(tick_context) ... tick_context.boss ... end.

Os callbacks agendados recebem um contexto novo sem o evento que os originou, o alvo direto ou o jogador. Capture explicitamente um alvo necessário antes do agendamento e verifique se continua válido ao usá-lo. Volte a ler as localizações atuais e outras propriedades variáveis em vez de guardar uma tabela anterior.

6. A consulta de zona não devolve entidades​

Verifique novamente a definição da zona. Para zonas nativas, garanta que kind está em minúsculas ("sphere", não "SPHERE"). Para utilitários de script, garanta que shape está em maiúsculas ("CONE", não "cone"). Verifique também que origin ou Target realmente resolve para uma localização válida.

Verifique também o filtro de entidades e o mundo. As consultas nativas de zonas do boss usam living por predefinição, excluem o boss e procuram no seu mundo. As zonas do utilitário de scripts usam PLAYER por predefinição. Obtenha os alvos de zona Lua através de full_target() ou border_target() do objeto de zona; um simples context.script:target({targetType = "ZONE_FULL"}) não tem zona associada.

7. As partículas não aparecem​

Use um nome válido do enum Particle do Bukkit; "FLAME" e "flame" são normalizados para maiúsculas. Verifique a localização, a quantidade e os dados necessários. BLOCK exige dados de bloco que este auxiliar do mundo não fornece: use outra partícula suportada ou a API de partículas de script com a opção de material.

8. O cooldown parece não funcionar​

check_local(key, duration) verifica e define o cooldown numa chamada. Também pode usar local_ready(key) seguido de set_local(duration, key) quando o ataque começar; verificar apenas a disponibilidade nunca inicia o cooldown. Os poderes do boss partilham as chaves explícitas, por isso use um prefixo específico de cada poder. Os metadados YAML não bloqueiam automaticamente a execução dos hooks Lua.

9. O boss continua a usar o poder depois de morrer​

As tarefas de context.scheduler são canceladas quando o runtime do poder fecha. Cancele os ciclos de ataque em on_exit_combat se tiverem de terminar antes da remoção. Verifique também o auxiliar chamado: alguns auxiliares Java iniciam as suas próprias tarefas. Por exemplo, world:run_fake_gold_nugget_damage(...) já executa um ciclo por tick com o seu próprio limite de tempo e só deve ser chamado uma vez por lote de projéteis.


Ler as mensagens de erro​

Quando um poder Lua falha durante a execução, a consola mostra um bloco com o prefixo [Lua], identificando o ficheiro e o hook ou callback. Inclui o número de linha quando o consegue extrair do erro original. Leia a mensagem completa antes de depurar. Em ficheiros com -- @include, as linhas referem-se ao código expandido.

Um erro típico tem este aspeto:

[Lua] Error in 'push_zone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> You tried to call a method or function that doesn't exist.
[Lua] -> Check the method name for typos, or make sure you're using ':' (colon) for method calls, not '.' (dot).
[Lua] -> Script has been disabled for this entity to prevent further errors.

O sistema traduz os erros comuns de Lua para linguagem simples. Eis os mais comuns:

Erro Lua brutoO que a consola lhe diz
attempt to call nilTentou chamar um método ou função que não existe. Verifique se há erros de escrita no nome do método, ou certifique-se de que está a usar : (dois-pontos) para chamadas de método, não . (ponto).
index expected, got nilTentou aceder a um campo de algo que é nil. Verifique se código anterior o inicializou.
attempt to indexTentou aceder a uma propriedade de um valor nil ou inválido.
bad argumentMostra os detalhes específicos da incompatibilidade do argumento (tipo esperado vs. tipo real).
Orçamento excedidoA mensagem identifica o limite de instruções, tempo de CPU ou tempo decorrido alternativo; consulte a secção acima.
dica

A instância do script que falha é desativada para essa entidade. Depois de corrigir o ficheiro, é preciso recarregar a definição e criar um runtime novo; uma instância desativada sem alterações não volta a tentar o hook que falhou em cada tick.


Não assuma que existem aliases não documentados​

A API Lua expõe um conjunto específico de nomes de métodos. Se está a escrever poderes à mão ou com assistência de IA, não assuma que existem nomes abreviados ou alternativos. Os seguintes são exemplos de nomes que não existem e que irão causar erros:

  • show_temporary_boss_bar() -- use player:show_boss_bar(title, color, style, duration) em vez disso.
  • run_command_as_player() -- use player:run_command(command) em vez disso.
  • em.location(...) -- o nome do método está errado. Use em.create_location(x, y, z) em vez disso, ou context.boss:get_location() / context.player.current_location.
  • em.vector(...) -- o nome do método está errado. Use em.create_vector(x, y, z) em vez disso, ou tabelas {x=0, y=1, z=0} simples.
  • em.zone.sphere(...) -- o nome do método está errado. Use em.zone.create_sphere_zone(radius) em vez disso, ou uma tabela de definição de zona como {kind = "sphere", radius = 5, origin = location}.
  • entity:teleport_to(...) -- use entity:teleport_to_location(location).
  • Nos wrappers de entidades específicos dos bosses EliteMobs, use entity:set_velocity_vector(vector). Os wrappers partilhados do MagmaCore também expõem set_velocity(...); verifique que wrapper o seu contexto devolve.
  • entity:set_facing(...) -- use entity:face_direction_or_location(direction_or_location).

Consulte a referência da API do wrapper que utiliza: Bosses e Entidades, Mundo e Ambiente, Zonas e Alvos ou a API partilhada. Contextos semelhantes podem expor métodos diferentes; escrever um alias plausível não o torna disponível.


Conselhos de migração para autores de EliteScript​

Se já escreve bons EliteScripts, a forma mais fácil de aprender os poderes em Lua é:

  1. Continue a pensar em termos de eventos, alvos, zonas, vetores relativos e partículas. Os conceitos são os mesmos -- só a sintaxe muda. Os eventos do EliteScript tornam-se nomes de hook como on_spawn ou on_boss_damaged_by_player. Os alvos e zonas são passados como tabelas para context.script usando os mesmos nomes de campo documentados nas páginas Zonas do EliteScript e Alvos do EliteScript.

  2. Mova o seu fluxo de controlo para Lua. Sorteios aleatórios, funções auxiliares partilhadas, ciclos, estado persistente (context.state) e agendamento de tarefas (context.scheduler) são as coisas que o Lua acrescenta e que o EliteScript puro não consegue fazer facilmente. Comece por converter um poder com ramificações ou condições para Lua, mantendo tudo o resto igual.

  3. Use context.script para a seleção de alvos e geometria de zonas. As suas tabelas usam os parsers do EliteScript para targetType, shape, Target, Target2, FinalTarget, range, offset, relativeOffset, coverage e filter. Mantenha a documentação EliteScript aberta para os nomes e valores predefinidos, mas leia também as limitações dos objetos Lua. Os objetos Lua estáticos não aplicam a cache track: false da ação YAML e não têm dados de ações herdadas. offset e relativeOffset aplicam-se às localizações resolvidas, incluindo LOCATION, LOCATIONS e as localizações amostradas de zonas. Use uma origem fixa explícita quando a área tiver de permanecer no mesmo sítio.

  4. Tenha atenção aos dois sistemas de zonas. context.script:zone({shape = "SPHERE", ...}) usa o motor EliteScript com enums em maiúsculas e tabelas Target. context.zones usa kind em minúsculas e localizações origin/destination simples. Misturar estes estilos de chaves pode produzir uma zona vazia ou inesperada, avisos ou um erro de execução.


Caminho de progressão para iniciantes​

Se quer aprender este sistema do zero, esta progressão funciona bem:

  1. Escreva um ficheiro apenas com api_version = 1 e on_spawn.
  2. Faça o boss enviar uma mensagem ou tocar um som.
  3. Adicione um cooldown com context.cooldowns.
  4. Adicione um hook acionado pelo jogador, como on_boss_damaged_by_player.
  5. Adicione uma ação atrasada com context.scheduler:run_after(...).
  6. Adicione uma consulta de zona Lua nativa simples ou um context.script:target(...) simples.
  7. Só então avance para ataques rotativos, máquinas de estados e mecânicas de múltiplos passos.

Cada passo constrói sobre o anterior, e pode testar em todas as fases. Não tente escrever um boss de múltiplas fases como o seu primeiro poder Lua.


Próximos passos​

  • Começar -- estrutura de ficheiros, hooks, primeiro poder passo a passo, modelos para copiar e colar
  • Scripts de NPC -- scripts de proximidade, interação e ciclo de vida de NPCs
  • Exemplos e Padrões -- poderes funcionais completos que pode estudar e adaptar