Saltar para o conteúdo principal

Scripting Lua: Exemplos e padrões

Esta página contém exemplos completos de poderes Lua do EliteMobs e respetivas explicações. Guarde cada exemplo no seu próprio ficheiro .lua, associe-o a um boss de teste, execute /em reload e invoque um novo boss após editar. As animações slam, roar, frenzy e transform têm de existir no modelo personalizado do boss; não são animações integradas. Um boss sem modelo personalizado ignora essas chamadas. Os tempos abaixo pressupõem 20 TPS.

Se está a começar com poderes Lua, consulte Primeiros passos. Para scripts de NPC, consulte Scripts de NPC. Para a API completa dos poderes de boss, consulte a Referência da API, Boss e entidades, Mundo e ambiente, Zonas e alvos e Enums.

webapp_banner.jpg


Exemplo: Seleção de alvos baseada em zonas com utilitários de script​

O que este exemplo ensina: Como usar context.script para criar geometria de zona estilo EliteScript a partir de Lua, gerar partículas e causar dano a entidades na zona.

Ficheiro de poder completo (clique para expandir)
return {
api_version = 1,

on_boss_damaged_by_player = function(context)
local cone = context.script:zone({
shape = "CONE",
Target = {
targetType = "SELF",
offset = "0,1,0"
},
Target2 = {
targetType = "NEARBY_PLAYERS",
range = 20
},
radius = 5
})

context.script:spawn_particles(
cone:full_target(0.4),
{
particle = "FLAME",
amount = 1,
speed = 0.05
}
)

context.script:damage(cone:full_target(), 1.0, 1.5)
end
}

Explicação​

  1. Criação da zona -- context.script:zone(...) usa os campos das Zonas do EliteScript. Target coloca a ponta do cone um bloco acima do boss. A primeira posição resolvida de Target2 define o centro da base e, portanto, o comprimento do cone; radius = 5 define o raio da base. NEARBY_PLAYERS procura numa caixa que se estende 20 blocos em cada eixo, sem garantir o jogador mais próximo. Use DIRECT_TARGET neste hook de dano para apontar ao atacante. Sem jogador resolvido não existe cone; as extremidades têm de ser distintas e estar no mesmo mundo.

  2. Geração de partículas -- cone:full_target(0.4) devolve um objeto de alvo que seleciona pontos da zona com probabilidade de 40% em cada chamada. Não garante uma contagem exata nem filtra os destinatários do dano. A especificação usa os campos EliteScript particle, amount e speed. O auxiliar desloca as posições das partículas de zona meio bloco em X e Z; as partículas não representam limites exatos de impacto em entidades.

  3. Dano -- context.script:damage(cone:full_target(), 1.0, 1.5) atinge os jogadores resolvidos dentro dos cones porque o filtro de zona predefinido é PLAYER. O primeiro número (1.0) é o dano base submetido e o segundo (1.5) é o multiplicador EliteMobs aplicado aos jogadores. O processamento de combate determina o dano final.

Este exemplo executa em cada golpe encaminhado: não tem cooldown local nem global, nem espera de aviso entre partículas e dano. Acrescente verificações explícitas ao combiná-lo com outros ataques. Cada consulta de zona estática volta a resolver os alvos; um objeto de zona Lua não aplica automaticamente condições ou caches de alvos das ações YAML.

Nomes de campo do EliteScript

As tabelas de zona e alvo passadas a context.script usam nomes de campo do EliteScript (targetType, shape, Target, Target2, range, offset, coverage). Para a lista completa, consulte Zonas do EliteScript e Alvos do EliteScript.


Exemplo: Loop de ataque com estado e scheduler​

O que este exemplo ensina: Uso de context.state para rastrear estado em tempo de execução, context.scheduler para tarefas repetitivas e ciclo de vida adequado de entrada/saída de combate.

Ficheiro de poder completo (clique para expandir)
local function pick_action(context)
local roll = math.random(1, 2)

if roll == 1 then
context.boss:play_model_animation("slam")
else
context.boss:play_model_animation("roar")
end
end

return {
api_version = 1,

on_spawn = function(context)
context.state.started = false
context.state.loop_task_id = nil
end,

on_enter_combat = function(context)
if context.state.started then
return
end

context.state.started = true

context.state.loop_task_id = context.scheduler:run_every(100, function(loop_context)
if loop_context.boss.exists then
pick_action(loop_context)
end
end)
end,

on_exit_combat = function(context)
if context.state.loop_task_id ~= nil then
context.scheduler:cancel_task(context.state.loop_task_id)
context.state.loop_task_id = nil
end
context.state.started = false
end
}

Explicação​

  1. Inicialização do estado -- on_spawn define context.state.started como false e context.state.loop_task_id como nil. A tabela state persiste entre hooks desta instância do poder neste boss. Os outros poderes têm tabelas de estado separadas.

  2. Proteção de combate -- on_enter_combat verifica context.state.started antes de iniciar o ciclo. Impede ciclos sobrepostos se o evento disparar mais de uma vez.

  3. Padrão do scheduler -- context.scheduler:run_every(100, callback) agenda a primeira chamada sem atraso e depois repete a cada 100 ticks (5 segundos). Não espera cinco segundos antes de começar. Use run_repeating(100, 100, callback) se quiser essa espera inicial. O callback recebe um contexto novo sem o evento ou jogador que o originou, pelo que loop_context.boss é uma nova fotografia do estado. Guarde o identificador numérico devolvido no estado. Este exemplo apenas escolhe uma animação; não causa dano por si mesmo.

  4. Limpeza na saída -- on_exit_combat cancela a repetição usando o identificador guardado e repõe o estado. Sem essa limpeza, o scheduler continua depois de o combate terminar.

Sempre cancele tarefas do scheduler

Cancele repetições exclusivas de combate em on_exit_combat. Sem isso, continuam agendadas depois do combate até serem canceladas ou a instância do poder fechar. Instâncias pausadas ignoram callbacks sem perder a tarefa. Um erro não tratado num callback fecha a instância e cancela as suas tarefas.


Exemplo: Efeito de fogo ao atingir​

O que este exemplo ensina: Um simples poder de "ao atingir aplicar efeito" -- o padrão mais comum para poderes de combate.

Ficheiro de poder completo (clique para expandir)
return {
api_version = 1,

on_player_damaged_by_boss = function(context)
-- Guard: the player may be nil in edge cases
if context.player == nil then
return
end

if not context.cooldowns:global_ready() then
return
end

-- Check and set a 60-tick (3 second) local cooldown in one call
if not context.cooldowns:check_local("fire_touch", 60) then
return
end

-- Coordinate with other powers that check the shared cooldown
context.cooldowns:set_global(40)

-- Set the player on fire for 60 ticks (3 seconds)
context.player:set_fire_ticks(60)

-- Visual feedback: spawn flame particles at the player's location
context.world:spawn_particle_at_location(
context.player.current_location,
{ particle = "FLAME", amount = 20, speed = 0.1 }
)

-- Tell the player what happened
context.player:send_message("&cThe boss's touch burns!")
end
}

Explicação​

  1. Verificação de nil -- context.player resolve o jogador que é interveniente ou alvo direto do evento atual. Verifique-o antes de usar, sobretudo ao adaptar o código a outro hook. Os callbacks agendados não herdam esse jogador.

  2. Cooldown local -- Depois de verificar global_ready(), context.cooldowns:check_local("fire_touch", 60) verifica a chave e define imediatamente 60 ticks se estiver pronta. Caso contrário devolve false e termina o hook. Os poderes deste boss partilham a chave; bosses diferentes são independentes. Prefixe as chaves com o nome do poder ao combinar exemplos.

  3. Ticks de fogo -- context.player:set_fire_ticks(60) define a duração de fogo Bukkit como 60 ticks (3 segundos), após verificar que o jogador é válido, está vivo, ligado, no mundo de origem e, dentro de uma instância de masmorra, é um participante atual. Substitui a duração em vez de acrescentar 60 ticks. Outras mecânicas podem apagar o fogo; a mensagem e as partículas não provam que o efeito foi aceite.

  4. Partículas -- context.world:spawn_particle_at_location(location, spec) gera partículas numa posição. A tabela aceita particle (nome do enum Bukkit), amount e speed.

  5. Mensagem -- context.player:send_message(text) envia uma mensagem de chat com cores. Códigos Minecraft como &c (vermelho) funcionam automaticamente.

  6. Cooldown global -- context.cooldowns:set_global(40) define o cooldown partilhado do boss como 40 ticks (2 segundos), antes dos efeitos. Este exemplo verifica-o antes de consumir o cooldown local. Outros hooks e callbacks Lua também têm de chamar global_ready() para esperar; definir o global não suspende todos os callbacks Lua.


Exemplo: Poder AoE baseado em zonas usando zonas nativas Lua​

O que este exemplo ensina: Criar e consultar zonas nativas Lua para causar dano a jogadores em uma área.

Ficheiro de poder completo (clique para expandir)
return {
api_version = 1,

on_spawn = function(context)
context.state.aoe_task_id = nil
end,

on_enter_combat = function(context)
-- Prevent duplicate loops
if context.state.aoe_task_id ~= nil then
return
end

context.state.aoe_task_id = context.scheduler:run_every(60, function(tick_context)
-- Make sure the boss is still alive
if not tick_context.boss.exists then
return
end

if not tick_context.cooldowns:global_ready() then
return
end

-- Check and set this boss's named cooldown before applying effects
if not tick_context.cooldowns:check_local("pulse_aoe", 60) then
return
end
tick_context.cooldowns:set_global(60)

-- Build a sphere zone centered on the boss's current position
local zone_def = {
kind = "sphere",
radius = 8,
origin = tick_context.boss:get_location()
}

-- Find all players inside the sphere
local victims = tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })

-- Damage and show particles on each player found
for i = 1, #victims do
local victim = victims[i]
victim:deal_custom_damage(4.0)

tick_context.world:spawn_particle_at_location(
victim.current_location,
{ particle = "DUST", amount = 15, speed = 0, red = 128, green = 0, blue = 255 }
)
end

-- Spawn a particle burst at the boss; this does not draw the sphere
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "WITCH", amount = 40, speed = 0.1 }
)
end)
end,

on_exit_combat = function(context)
if context.state.aoe_task_id ~= nil then
context.scheduler:cancel_task(context.state.aoe_task_id)
context.state.aoe_task_id = nil
end
end
}

Explicação​

  1. Configuração do estado -- on_spawn inicializa aoe_task_id como nil. Este identificador guardará a referência da tarefa repetitiva.

  2. Ataque repetitivo -- on_enter_combat inicia sem atraso inicial e repete a cada 60 ticks (3 segundos). A proteção evita ciclos duplicados. Cada callback verifica o global, verifica e define pulse_aoe e depois fixa o global em 60 ticks antes dos efeitos. Só coordena poderes que verificam essas chaves; não bloqueia automaticamente todos os scripts.

  3. Definição da zona -- zone_def usa a sintaxe nativa Lua. kind indica a forma ("sphere"), radius o tamanho e origin a posição atual do boss quando o callback executa. Por isso, a zona acompanha o boss à medida que se move.

  4. Consulta de entidades -- tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" }) devolve um array Lua indexado a partir de um, usando a interseção de entidades da forma, não a distância exata aos pés. A pesquisa usa o mundo atual do boss e não filtra jogadores por modo de jogo ou participação no encontro. Aceita "players", "elites", "mobs" ou "living" (predefinição); o próprio boss fica excluído.

  5. Causar dano -- victim:deal_custom_damage(4.0) submete 4 pontos de dano base atribuídos ao boss através de BossCustomAttackDamage. Ignora alvos invulneráveis ou mortos e jogadores fora de Sobrevivência ou Aventura. No percurso elite para jogador, contorna a fórmula normal e a redução de bloqueio, removendo as reduções de armadura vanilla. Listeners, cancelamento e absorção ainda podem alterar a saúde perdida. O auxiliar repõe o cooldown de dano nativo do alvo após submeter; não segue o mesmo percurso de context.script:damage.

  6. Partículas -- O pó roxo aparece na posição guardada de cada jogador encontrado, mesmo que o dano seja rejeitado. Uma emissão WITCH aparece no boss; não desenha um anel nem o limite da esfera. Estas tabelas compactas usam dispersão de posição zero por predefinição e tamanho de pó fixo em 1.

  7. Limpeza -- on_exit_combat cancela a repetição e limpa o estado, seguindo o padrão anterior.

Zonas nativas vs. zonas de utilitários de script

Este exemplo usa zonas nativas Lua (context.zones:get_entities_in_zone()), que recebem uma tabela simples com kind, radius, origin, etc. Os utilitários (context.script:zone(...)) usam campos EliteScript como shape, Target e Target2. Ambos funcionam: use zonas nativas para formas simples e context.script para a resolução avançada de alvos do EliteScript.


Exemplo: Mecânica de boss multifase​

O que este exemplo ensina: Uso de estado Lua para acompanhar fases de ataque e mudar o comportamento num limiar de saúde. Isto não usa as fases YAML que substituem o boss, nem restaura a sua saúde. Curá-lo depois da transição não o faz regressar à fase 1; sair de combate mantém a fase até o runtime ser recriado.

Ficheiro de poder completo (clique para expandir)
local function phase_one_attack(context)
-- One damage pulse per callback; the animation does not delay it
context.boss:play_model_animation("slam")

local zone_def = {
kind = "sphere",
radius = 5,
origin = context.boss:get_location()
}

local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(3.0)
end

context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "EXPLOSION", amount = 3, speed = 0 }
)
end

local function phase_two_attack(context)
-- One pulse per callback, repeated more frequently in phase 2
context.boss:play_model_animation("frenzy")

local zone_def = {
kind = "sphere",
radius = 8,
origin = context.boss:get_location()
}

local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(2.0)
targets[i]:apply_potion_effect("SLOWNESS", 40, 1)
end

context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "DUST", amount = 30, speed = 0.2, red = 255, green = 0, blue = 0 }
)

context.world:play_sound_at_location(
context.boss:get_location(),
"entity.wither.ambient",
1.0, 1.5
)
end

local function start_attack_loop(context)
if context.state.attack_task_id ~= nil then return end
local phase = context.state.phase or 1
local interval = phase == 2 and 40 or 100
local attack = phase == 2 and phase_two_attack or phase_one_attack
context.state.attack_task_id = context.scheduler:run_every(interval, function(tick_context)
if not tick_context.boss.exists or not tick_context.boss.is_in_combat then return end
if tick_context.cooldowns:check_local("phase_attack", interval) then
attack(tick_context)
end
end)
end

return {
api_version = 1,

on_spawn = function(context)
context.state.phase = 1
context.state.attack_task_id = nil
end,

on_enter_combat = function(context)
start_attack_loop(context)
end,

on_game_tick = function(context)
-- Only check phase transition every 20 ticks (1 second) to stay lightweight
if not context.boss.is_in_combat then return end
if not context.cooldowns:check_local("phase_check", 20) then
return
end

-- Skip if already in phase 2
if context.state.phase ~= 1 then
return
end

-- Check health ratio
local health_ratio = context.boss.health / context.boss.maximum_health

if health_ratio <= 0.5 then
-- Transition to phase 2
context.state.phase = 2

context.log:info("Boss entering phase 2 at " .. tostring(math.floor(health_ratio * 100)) .. "% health")

-- Cancel the old attack loop
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end

-- Play transition effects
context.boss:play_model_animation("transform")

-- Announce the phase change to nearby players
local nearby = context.players.nearby_players(40)
for i = 1, #nearby do
nearby[i]:send_message("&4&lThe boss enters a frenzy!")
nearby[i]:show_title("&4Phase 2", "&cThe boss is enraged!", 10, 40, 10)
end

start_attack_loop(context)
end
end,

on_exit_combat = function(context)
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end
end
}

Explicação​

  1. Inicialização de estado -- on_spawn define phase como 1 e attack_task_id como nil neste runtime de poder. O estado não é partilhado com outros poderes nem guardado entre recarregamentos.
  2. Ciclo de ataque -- on_enter_combat retoma a fase atual através de start_attack_loop: a fase 1 usa um intervalo de 100 ticks e a fase 2 usa 40. Ambas agendam o primeiro callback sem atraso. Cada ataque ainda exige que o cooldown phase_attack esteja pronto; mudar de fase não elimina o cooldown anterior, por isso um callback pode saltar o ataque. Este exemplo não verifica o cooldown global. A fase 1 submete 3 de dano personalizado aos jogadores numa esfera de 5 blocos e reproduz animação e partículas sem esperar pela duração da animação.
  3. Verificação de fase em on_game_tick -- on_game_tick termina imediatamente fora de combate e depois verifica um cooldown de 20 ticks ("phase_check"). Se o boss já estiver na fase 2, também termina antecipadamente.
  4. Limiar de saúde -- context.boss.health / context.boss.maximum_health dá a saúde atual como fração. A 50% ou menos, a transição começa.
  5. Transição de fase -- O loop antigo é cancelado, animação de transformação é reproduzida, jogadores próximos recebem mensagem e título, e um novo loop mais rápido inicia (a cada 40 ticks em vez de 100).
  6. Ataques da fase 2 -- phase_two_attack usa uma esfera de 8 blocos e submete um golpe de 2 de dano por jogador consultado em cada callback bem-sucedido. Tenta aplicar Lentidão II durante 40 ticks (amplifier = 1), sujeita às verificações de participação dos efeitos descritas acima, depois mostra poeira vermelha e reproduz um som de wither. Não agenda vários golpes dentro do mesmo callback.
  7. Registo -- context.log:info(...) escreve na consola do servidor.
  8. Limpeza -- on_exit_combat cancela qualquer loop de ataque ativo, independentemente da fase.
Mantenha on_game_tick leve

Um runtime ativo e não pausado recebe on_game_tick a cada tick do servidor (20 vezes por segundo a 20 TPS). Limite as operações dispendiosas com um cooldown, como check_local("phase_check", 20). Esta verificação consulta a saúde periodicamente; um golpe fatal pode remover o boss antes de verificar a transição. Exceder o orçamento de execução fecha esse runtime de poder e cancela as suas tarefas.


Dicas para geração com IA​

Se quer que a IA gere poderes Lua de forma fiável, certifique-se de que o prompt inclua:

  • Nome exato do hook -- por exemplo, on_player_damaged_by_boss, e não "quando o boss acerta um jogador".
  • Zonas nativas Lua ou utilitários de script -- especifique qual. Zonas nativas usam context.zones:get_entities_in_zone(zone_def, opts) com kind, radius, origin. Utilitários de script usam context.script:zone(...) com nomes de campo do EliteScript como shape, Target, Target2.
  • Cooldowns locais e globais -- especifique uma chave com prefixo do poder e duração em ticks. context.cooldowns:check_local(key, ticks) verifica e define um cooldown nomeado por boss. Para coordenar com o cooldown global, verifique explicitamente global_ready() antes de consumir a chave local e defina a duração global antes dos efeitos.
  • Nomes de animação de modelo personalizado -- por exemplo, context.boss:play_model_animation("slam"). A IA não consegue adivinhar nomes de animação; forneça-os.
  • Seleção de alvo -- especifique se o poder mira context.player (o jogador do evento), context.players.nearby_players(range) ou uma consulta de zona.
  • Tipo de efeito -- dano (deal_custom_damage), poção (apply_potion_effect), fogo (set_fire_ticks), velocidade (set_velocity_vector, apply_push_vector), etc.
  • Use os métodos do wrapper correto -- forneça a referência da API do boss ou da API partilhada que se aplica e não invente aliases.
  • Especificações de utilitários de script usam nomes de campo do EliteScript -- targetType, shape, Target, Target2, range, offset, coverage, e não nomes em estilo Lua.
  • Temporização e contexto do scheduler -- especifique se o primeiro callback é imediato ou atrasado. run_every tem atraso inicial zero; run_repeating recebe um atraso separado. Use o contexto novo do callback; este não tem o jogador ou evento que o originou. Capture explicitamente um UUID de jogador ou uma posição se a mecânica precisar deles mais tarde.

Bom exemplo de prompt​

Escreva um poder Lua que use on_enter_combat para iniciar uma tarefa repetitiva com atraso inicial de 80 ticks e intervalo de 80 ticks. Em cada callback, crie uma esfera nativa Lua de raio 6 centrada no boss, consulte os jogadores e submeta 2.0 de dano personalizado a cada um. Verifique o cooldown global antes de verificar e definir a chave local "my_power:pulse" durante 80 ticks, depois defina o cooldown global para 80 antes dos efeitos. Cancele a tarefa em on_exit_combat. Gere partículas DUST (red=0, green=255, blue=100) em cada jogador consultado.

Restrições extras a incluir​

  • "Devolva uma única tabela com api_version = 1."
  • "Inicialize todos os campos de estado em on_spawn."
  • "Proteja os callbacks do scheduler com if not tick_context.boss.exists then return end."
  • "Cancele todas as tarefas repetitivas em on_exit_combat."
  • "Não invente nomes de método -- use apenas métodos da página de Referência da API."
  • "Use context.cooldowns:check_local(key, ticks) para verificar e definir de uma só vez."

Lista de verificação QC​

Use esta lista para verificar um poder Lua antes de o implementar:

  1. O ficheiro devolve exatamente uma tabela com api_version = 1.
  2. Cada nome de hook coincide exatamente com uma entrada da lista de hooks (por exemplo, on_player_damaged_by_boss, e não on_player_hit).
  3. context.player é protegido com == nil antes do uso, nos hooks em que pode ser nil.
  4. Campos de context.state são inicializados em on_spawn.
  5. Cada tarefa repetitiva exclusiva do combate, criada com run_every ou run_repeating, tem um cancelamento correspondente em on_exit_combat; o atraso inicial é escolhido intencionalmente.
  6. Os callbacks do scheduler usam o próprio contexto e não presumem que exista um jogador ou evento de origem.
  7. As chaves de cooldown incluem um prefixo com o nome do poder e as durações estão em ticks. A coordenação global inclui uma verificação explícita de disponibilidade.
  8. Hooks on_game_tick protegem trabalho pesado atrás de verificação de cooldown.
  9. Todos os nomes de métodos existem na Referência da API -- sem aliases inventados.
  10. Tabelas de utilitários de script usam nomes de campo do EliteScript (targetType, shape, Target, etc.), e não nomes em estilo Lua.
  11. Definições de zona nativas usam kind, radius, origin, destination, etc.
  12. O poder não chama operações bloqueantes ou de longa duração dentro de um hook ou callback.
  13. Especificações de partículas usam nomes de enum de partículas Bukkit válidos em UPPER_CASE (por exemplo, "FLAME", "DUST", "EXPLOSION").
  14. Recarregue os ficheiros alterados, invoque um boss novo e verifique o comportamento visível e a saída da consola num servidor de testes. Um ficheiro aceite pelo parser não prova que o ataque funciona.

Melhores práticas​

  • Comece com um hook pequeno e verifique. Escreva um único on_spawn que envie uma mensagem de log. Confirme que é executado. Depois desenvolva a partir daí.

  • Mantenha as funções auxiliares locais. Declare funções como local function pick_action(context) acima da tabela de retorno para tornar explícitas as dependências. Cada instância de poder já tem o seu próprio runtime Lua.

  • Coloque a geometria nos utilitários de script. Para cones, raios rotativos, raios em translação ou zonas animadas, use context.script:zone(...) com os nomes de campo do EliteScript. Estes utilitários reutilizam o motor de zonas do EliteScript.

  • Use context.state para o estado do runtime. O seu âmbito é um runtime de poder num boss e persiste entre os hooks desse runtime. Não é partilhado com outros poderes nem guardado entre recarregamentos.

  • Use chaves de cooldown locais nomeadas. Em vez de números simples, use chaves descritivas como "fire_touch" ou "aoe_pulse". As chaves explícitas são partilhadas por todos os poderes do mesmo boss. Adicione o nome do poder, como "my_power:fire_touch", para evitar colisões.

  • Mantenha on_game_tick leve. Limite as operações dispendiosas com um cooldown e fique bem abaixo do orçamento de execução.

  • Cancele as tarefas repetitivas quando terminar. Cancele os ciclos exclusivos do combate em on_exit_combat. As tarefas pertencentes ao runtime são canceladas automaticamente quando este fecha, mas continuam depois do combate se não forem explicitamente canceladas.

  • Use o contexto novo dos callbacks do scheduler. Os callbacks de run_every, run_repeating e run_after recebem um contexto novo sem o evento ou jogador que os agendou. Use esse argumento e retenha explicitamente qualquer UUID ou posição necessários; o contexto externo pode conter valores desatualizados.

  • Registe as transições de estado com context.log:info(). Durante o desenvolvimento, registe mudanças de fase, inícios de cooldown e arranques ou paragens do scheduler. Remova os logs ruidosos antes da publicação: context.log:debug() do boss também escreve ao nível info, com um prefixo diferente.

  • Reutilize as referências relevantes do EliteScript. Zonas, Alvos e Vetores relativos descrevem os parsers partilhados. As Condições YAML, as esperas e a cache ao nível das ações não são avaliadas automaticamente pelos utilitários Lua independentes; expresse essas verificações em Lua. Os alvos herdados e as posições de aterragem precisam de um contexto de ação YAML que estes handles não possuem.


Erros comuns de iniciante​

  • Usar o context externo dentro de um callback do scheduler. O contexto externo captura um snapshot no momento em que o hook foi executado. Dentro de um callback run_every ou run_after, use sempre o parâmetro próprio do callback (ex., tick_context), que lhe dá um snapshot novo.

  • Esquecer de cancelar tarefas repetitivas. Se iniciar um run_every em on_enter_combat mas nunca o cancelar, a tarefa continua a ser executada até o boss ser removido do servidor, mesmo depois de o combate terminar.

  • Não inicializar o estado em on_spawn. Se ler context.state.phase em on_game_tick mas nunca o definir em on_spawn, será nil e as suas comparações comportar-se-ão de forma inesperada.

  • Usar context.player sem verificar nil. Verifique quais hooks fornecem um jogador. Os callbacks agendados não têm nenhum por predefinição; a falta de uma verificação pode fechar o runtime do poder.

  • Usar nomes de campo em estilo Lua nas chamadas de utilitários de script. Use os campos documentados do EliteScript, como targetType, Target, Target2 e shape. Alternativas com sublinhado, como target_type e zone_shape, não são suportadas. O parser de zonas aceita target e target2 em minúsculas, mas isso não torna válidos aliases arbitrários em estilo Lua.

  • Executar lógica pesada em on_game_tick sem cooldown. Consultas de entidades de um mundo inteiro e geometria densa de partículas em muitos bosses são dispendiosas. Limite esse trabalho e mantenha cada callback dentro do seu orçamento de execução.

  • Misturar APIs de wrappers. Os wrappers específicos dos bosses EliteMobs usam entity:teleport_to_location(loc) e player:set_velocity_vector(vec). Os wrappers partilhados do MagmaCore têm aliases adicionais. Consulte a referência do próprio contexto antes de copiar um método de um exemplo de outro plugin.

  • Usar context.boss.health para definir a saúde. context.boss.health é um snapshot apenas de leitura. Para curar o boss, use context.boss:restore_health(amount).

  • Esquecer api_version = 1. A tabela devolvida deve incluir este campo, ou o EliteMobs não carregará o poder.


Próximos passos​