Scripting Lua: Exemplos e padrões
Esta página contém exemplos completos e funcionais de poderes Lua do EliteMobs, além de padrões práticos, melhores práticas e dicas. Cada exemplo inclui uma explicação do que faz e por quê.
Se você é novo em poderes Lua, comece com Primeiros passos. Para detalhes completos da API, consulte a Referência da API, Boss e entidades, Mundo e ambiente, Zonas e alvos e Enums.
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.
Arquivo 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
-
Criação de zona --
context.script:zone(...)cria uma forma de cone usando os mesmos nomes de campo que as Zonas do EliteScript.Targetdefine a origem do cone (o próprio boss, deslocado 1 bloco acima), eTarget2define o destino (jogadores mais próximos dentro de 20 blocos).radiuscontrola a abertura do cone. -
Geração de partículas --
cone:full_target(0.4)retorna um handle de alvo que resolve para todas as localizações dentro do cone com 40% de cobertura. -
Dano --
context.script:damage(cone:full_target(), 1.0, 1.5)atinge todas as entidades vivas dentro do cone completo. O primeiro número (1.0) é a quantidade de dano base, e o segundo (1.5) é o multiplicador de dano aplicado aos jogadores.
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.
Arquivo 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
- Inicialização de estado --
on_spawndefinecontext.state.startedcomofalseecontext.state.loop_task_idcomonil. A tabelastatepersiste durante toda a vida útil desta instância do boss. - Guarda de combate --
on_enter_combatverificacontext.state.startedantes de iniciar o loop, prevenindo loops múltiplos sobrepostos. - Padrão de scheduler --
context.scheduler:run_every(100, callback)executa o callback a cada 100 ticks (5 segundos). O callback recebe um contexto fresco. - Limpeza na saída --
on_exit_combatcancela a tarefa repetitiva e reinicia o estado.
Se você iniciar uma tarefa repetitiva em on_enter_combat, sempre cancele-a em on_exit_combat. Esquecer de cancelar deixa uma tarefa em segundo plano executando até o boss desaparecer.
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.
Arquivo de poder completo (clique para expandir)
return {
api_version = 1,
on_player_damaged_by_boss = function(context)
if context.player == nil then return end
if not context.cooldowns:check_local("fire_touch", 60) then return end
context.player:set_fire_ticks(60)
context.world:spawn_particle_at_location(context.player.current_location, { particle = "FLAME", amount = 20, speed = 0.1 })
context.player:send_message("&cThe boss's touch burns!")
context.cooldowns:set_global(40)
end
}
Explicação
- Guarda nil --
context.playerpode sernilem casos extremos raros. Sempre verifique antes de usar. - Cooldown local --
context.cooldowns:check_local("fire_touch", 60)verifica e define atomicamente o cooldown. - Ticks de fogo --
context.player:set_fire_ticks(60)coloca o jogador em chamas por 60 ticks (3 segundos). - Partículas --
context.world:spawn_particle_at_location(location, spec)gera partículas em uma localização. - Mensagem --
context.player:send_message(text)envia uma mensagem de chat com códigos de cor. - Cooldown global --
context.cooldowns:set_global(40)coloca todos os poderes deste boss em cooldown de 40 ticks.
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.
Arquivo 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)
if context.state.aoe_task_id ~= nil then return end
context.state.aoe_task_id = context.scheduler:run_every(60, function(tick_context)
if not tick_context.boss.exists then return end
if not tick_context.cooldowns:check_local("pulse_aoe", 60) then return end
local zone_def = { kind = "sphere", radius = 8, origin = tick_context.boss:get_location() }
local victims = tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #victims do
victims[i]:deal_custom_damage(4.0)
tick_context.world:spawn_particle_at_location(victims[i].current_location, { particle = "DUST", amount = 15, speed = 0, red = 128, green = 0, blue = 255 })
end
tick_context.world:spawn_particle_at_location(tick_context.boss:get_location(), { particle = "SPELL_MOB", amount = 40, speed = 0.1 })
tick_context.cooldowns:set_global(60)
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
- Configuração de estado --
on_spawninicializaaoe_task_idcomonil. - Ataque repetitivo --
on_enter_combatinicia uma tarefa repetitiva a cada 60 ticks (3 segundos). - Definição de zona -- A tabela
zone_defusa a sintaxe de zona nativa Lua comkind,radiuseorigin. - Consulta de entidades -- Retorna um array Lua de todos os jogadores dentro da esfera.
- Causar dano --
victim:deal_custom_damage(4.0)causa 4 pontos de dano atribuídos ao boss. - Feedback de partículas -- Partículas de poeira roxa em cada vítima, partículas ambientais na posição do boss.
- Limpeza --
on_exit_combatcancela a tarefa e limpa o estado.
Este exemplo usa zonas nativas Lua (context.zones:get_entities_in_zone()). Os utilitários de script (context.script:zone(...)) usam nomes de campo do EliteScript como shape, Target e Target2. Ambos funcionam -- use zonas nativas para formas simples e context.script para resolução avançada de alvos.
Exemplo: Mecânica de boss multifase
O que este exemplo ensina: Uso de estado para rastrear fases do boss e mudar comportamento em limiares de saúde.
Arquivo de poder completo (clique para expandir)
local function phase_one_attack(context)
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)
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
return {
api_version = 1,
on_spawn = function(context)
context.state.phase = 1
context.state.attack_task_id = nil
context.state.phase_switched = false
end,
on_enter_combat = function(context)
if context.state.attack_task_id ~= nil then return end
context.state.attack_task_id = context.scheduler:run_every(100, function(tick_context)
if not tick_context.boss.exists then return end
if not tick_context.cooldowns:check_local("phase_attack", 100) then return end
phase_one_attack(tick_context)
end)
end,
on_game_tick = function(context)
if not context.cooldowns:check_local("phase_check", 20) then return end
if context.state.phase ~= 1 then return end
local health_ratio = context.boss.health / context.boss.maximum_health
if health_ratio <= 0.5 then
context.state.phase = 2
context.state.phase_switched = true
context.log:info("Boss entering phase 2 at " .. tostring(math.floor(health_ratio * 100)) .. "% health")
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end
context.boss:play_model_animation("transform")
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
context.state.attack_task_id = context.scheduler:run_every(40, function(tick_context)
if not tick_context.boss.exists then return end
if not tick_context.cooldowns:check_local("phase_attack", 40) then return end
phase_two_attack(tick_context)
end)
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
- Inicialização de estado --
on_spawndefinephasecomo1,attack_task_idcomonilephase_switchedcomofalse. - Loop de ataque fase 1 --
on_enter_combatinicia uma tarefa repetitiva que executaphase_one_attacka cada 100 ticks. - Verificação de fase em on_game_tick -- Verifica um cooldown de 20 ticks para evitar lógica pesada a cada tick.
- Limiar de saúde --
context.boss.health / context.boss.maximum_healthdá a saúde atual como fração. A 50% ou menos, a transição começa. - 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).
- Ataques fase 2 -- Esfera maior (8 blocos), dano ligeiramente menor por golpe mas muito mais frequente, aplica efeito de lentidão, usa partículas de poeira vermelha e som de wither.
- Registro --
context.log:info(...)escreve no console do servidor. - Limpeza --
on_exit_combatcancela qualquer loop de ataque ativo, independentemente da fase.
on_game_tick executa a cada tick do servidor (20 vezes por segundo). Sempre proteja trabalho pesado atrás de uma verificação de cooldown. Se seu hook exceder 50ms, o EliteMobs desativará automaticamente o poder.
Dicas para geração com IA
Se quer que a IA gere poderes Lua de forma confiável, certifique-se de que o prompt inclua: nome exato do hook, zonas nativas Lua ou utilitários de script (especifique qual), cooldowns locais e globais com chave e duração em ticks, nomes de animação de modelo personalizado, seleção de alvo, tipo de efeito, e apenas nomes de métodos documentados.
Bom exemplo de prompt
Escreva um poder Lua que use
on_enter_combatpara iniciar uma tarefa repetitiva a cada 80 ticks. A cada tick, crie uma zona de esfera nativa Lua (raio 6) centrada no boss, consulte os jogadores dentro e cause 2.0 de dano personalizado a cada um. Use uma chave de cooldown local"pulse"com duração 80. Cancele a tarefa emon_exit_combat. Gere partículas DUST (red=0, green=255, blue=100) em cada vítima.
Lista de verificação QC
- O arquivo retorna exatamente uma tabela com
api_version = 1. - Nomes de hook coincidem exatamente com a lista de hooks.
context.playeré protegido com== nilantes do uso.- Campos de
context.statesão inicializados emon_spawn. - Cada
run_everytem umcancel_taskcorrespondente emon_exit_combat. - Callbacks do scheduler usam o parâmetro de contexto do callback, não o
contextexterno. - Chaves de cooldown são strings descritivas e durações estão em ticks.
- Hooks
on_game_tickprotegem trabalho pesado atrás de verificação de cooldown. - Todos os nomes de métodos existem na Referência da API.
- Tabelas de utilitários de script usam nomes de campo do EliteScript.
- Definições de zona nativas usam
kind,radius,origin,destination, etc. - O poder não chama operações bloqueantes dentro de hooks ou callbacks.
- Especificações de partículas usam nomes de enum de partículas Bukkit válidos em UPPER_CASE.
Melhores práticas
-
Comece com um hook pequeno e verifique. Escreva um unico
on_spawnque envie uma mensagem de log. Confirme que dispara. Depois construa a partir dai. -
Mantenha funcoes auxiliares locais. Declare helpers como
local function pick_action(context)acima da tabela de retorno. Isso os mantem fora do escopo global e evita colisoes com outros poderes Lua carregados no mesmo runtime. -
Coloque geometria nos utilitarios de script. Se voce precisa de cones, raios rotativos, raios em translacao ou zonas animadas, use
context.script:zone(...)com nomes de campo do EliteScript. Os utilitarios de script reutilizam o motor de zonas testado em batalha do EliteScript. -
Use
context.statepara estado em tempo de execucao. Nao use variaveis globais Lua.context.statee limitado a uma unica instancia de boss e persiste entre hooks durante a vida desse boss. -
Use chaves de cooldown locais nomeadas. Em vez de numeros simples, use chaves descritivas como
"fire_touch"ou"aoe_pulse". Isso facilita a depuracao e previne colisoes acidentais entre diferentes cooldowns no mesmo poder. -
Mantenha
on_game_tickleve. Sempre proteja atras de uma verificacao de cooldown. Se sua logica executa a cada tick, deve completar em bem menos de 50ms ou o poder sera desativado. -
Cancele tarefas repetitivas quando terminar. Cada
run_everydeve ter umcancel_taskcorrespondente emon_exit_combat(e possivelmenteon_death). Tarefas nao canceladas desperdicam CPU e podem causar erros de referencia nula apos o boss desaparecer. -
Use contextos frescos nos callbacks do scheduler. Callbacks do scheduler (
run_every,run_after) recebem um contexto fresco como parametro. Sempre use esse parametro -- nao ocontextexterno -- porque o contexto externo pode conter snapshots obsoletos. -
Registre transicoes de estado com
context.log:info(). Durante o desenvolvimento, adicione logging para mudancas de fase, inicios de cooldown e inicios/paradas do scheduler. Remova ou mude paracontext.log:debug()antes do deploy. -
Reutilize documentacao existente do EliteScript. As paginas de Zonas, Alvos, Vetores relativos e Condicoes documentam os mesmos nomes de campo que os utilitarios de script aceitam. Nao duplique essa informacao no seu poder Lua -- simplesmente faca referencia a ela.
Erros comuns de iniciante
-
Usar o
contextexterno dentro de um callback do scheduler. O contexto externo captura um snapshot no momento em que o hook foi executado. Dentro de um callbackrun_everyourun_after, sempre use o parametro proprio do callback (ex.,tick_context), que lhe da um snapshot fresco. -
Esquecer de cancelar tarefas repetitivas. Se voce inicia um
run_everyemon_enter_combatmas nunca o cancela, a tarefa continua rodando ate o boss ser removido do servidor, mesmo apos o combate terminar. -
Nao inicializar estado em
on_spawn. Se voce lecontext.state.phaseemon_game_tickmas nunca o define emon_spawn, seranile suas comparacoes se comportarao inesperadamente. -
Verificar
context.playersem guarda nil. Em hooks comoon_player_damaged_by_boss, o jogador esta quase sempre disponivel -- mas "quase sempre" nao e "sempre". Um unico guarda nil faltando pode crashar o poder. -
Usar nomes de campo estilo Lua em chamadas de utilitarios de script. Os utilitarios de script esperam
targetType,Target,Target2,shape-- naotarget_type,target,target2,zone_shape. Nomes nao correspondentes silenciosamente nao produzem resultados. -
Executar logica pesada em
on_game_ticksem gate de cooldown. Este hook dispara a cada tick do servidor. Mesmo aritmetica simples repetida 20 vezes por segundo em muitos bosses se acumula. -
Inventar nomes de metodos. Se um metodo nao esta listado na Referencia da API, nao existe. Erros comuns incluem escrever
entity:teleport(loc)em vez deentity:teleport_to_location(loc), ouplayer:set_velocity(vec)em vez deplayer:set_velocity_vector(vec). -
Usar
context.boss.healthpara definir saude.context.boss.healthe um snapshot somente leitura. Para curar o boss, usecontext.boss:restore_health(amount). -
Esquecer
api_version = 1. A tabela retornada deve incluir este campo, ou EliteMobs nao carregara o poder.
Próximos passos
- Primeiros passos -- estrutura de arquivos, hooks, explicação do primeiro poder
- Hooks e ciclo de vida -- referência completa de hooks e contexto
- Boss e entidades -- métodos de boss, jogador e entidade
- Mundo e ambiente -- partículas, sons, raios, blocos
- Zonas e alvos -- zonas nativas e zonas de utilitários de script
- Enums -- valores válidos para Particle, Sound, Material e outras constantes de string
- Solução de problemas -- problemas comuns, dicas de depuração e conselhos de migração
