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)
-- Guard: the player may be nil in edge cases
if context.player == nil 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
-- 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!")
-- Set the global power cooldown so other powers on this boss
-- don't all fire at the same instant
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)
-- 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
-- Check a local cooldown so this doesn't stack with other effects
if not tick_context.cooldowns:check_local("pulse_aoe", 60) then
return
end
-- 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 visual ring particles at the boss
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "WITCH", amount = 40, speed = 0.1 }
)
-- Set global cooldown
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 --
tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })retorna um array Lua com todas as tabelas de jogador dentro da esfera. A opçãofilteraceita"players","elites","mobs"ou"living"(padrão). - 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)
-- Slow, heavy slam
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)
-- Fast, frantic multi-hit
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
context.state.phase_switched = false
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.state.phase_switched = true
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
- Inicialização de estado --
on_spawndefinephasecomo1,attack_task_idcomonilephase_switchedcomofalse. - Ciclo de ataque --
on_enter_combatretoma a fase atual através destart_attack_loop: a fase 1 ataca a cada 100 ticks e a fase 2 a cada 40. Sair e voltar a entrar em combate mantém a fase. O ataque da fase 1 cria uma esfera de 5 blocos, causa dano aos jogadores no interior e reproduz uma animação de golpe com partículas de explosão. - Verificação de fase em on_game_tick --
on_game_ticktermina imediatamente fora de combate e depois verifica um cooldown de 20 ticks ("phase_check"). Se o chefe já estiver na fase 2, também termina antecipadamente. - 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 é executado a cada tick do servidor (20 vezes por segundo). Limite as operações dispendiosas com um cooldown, como check_local("phase_check", 20). Exceder o orçamento de execução desativa 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 -- 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)comkind,radius,origin. Utilitários de script usamcontext.script:zone(...)com nomes de campo do EliteScript comoshape,Target,Target2. - Cooldowns locais e globais -- especifique o nome da chave de cooldown e a duração em ticks.
context.cooldowns:check_local(key, ticks)para cooldowns por boss,context.cooldowns:set_global(ticks)para o cooldown de poder compartilhado. - 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 apenas nomes de método documentados -- se não está na página de Referência da API, não existe.
- 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. - Callbacks do scheduler recebem um contexto novo -- o parâmetro do callback é um contexto novo, não o externo. Sempre use
tick_context(ou o nome que você der ao parâmetro) dentro dos callbacks.
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.
Restrições extras a incluir
- "Retorne 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 checar e definir de uma vez."
Lista de verificação QC
Use esta lista para verificar um poder Lua antes de implantá-lo:
- O arquivo retorna exatamente uma tabela com
api_version = 1. - Cada nome de hook coincide exatamente com uma entrada da lista de hooks (por exemplo,
on_player_damaged_by_boss, e nãoon_player_hit). context.playeré protegido com== nilantes do uso, nos hooks em que pode ser nil.- Campos de
context.statesão inicializados emon_spawn. - Cada chamada
context.scheduler:run_every(...)tem umcontext.scheduler:cancel_task(...)correspondente emon_exit_combat. - Callbacks do scheduler usam o parâmetro de contexto do próprio callback, não o
contextexterno. - Chaves de cooldown são strings descritivas (por exemplo,
"fire_pulse") 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 -- sem aliases inventados.
- Tabelas de utilitários de script usam nomes de campo do EliteScript (
targetType,shape,Target, etc.), e não nomes em estilo Lua. - Definições de zona nativas usam
kind,radius,origin,destination, etc. - O poder não chama operações bloqueantes ou de longa duração dentro de um hook ou callback.
- Especificações de partículas usam nomes de enum de partículas Bukkit válidos em UPPER_CASE (por exemplo,
"FLAME","DUST","EXPLOSION").
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. Limite as operações dispendiosas com um cooldown e fique bem abaixo do orçamento de execução. -
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
