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.
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
-
Criação da zona --
context.script:zone(...)usa os campos das Zonas do EliteScript.Targetcoloca a ponta do cone um bloco acima do boss. A primeira posição resolvida deTarget2define o centro da base e, portanto, o comprimento do cone;radius = 5define o raio da base.NEARBY_PLAYERSprocura numa caixa que se estende 20 blocos em cada eixo, sem garantir o jogador mais próximo. UseDIRECT_TARGETneste 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. -
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 EliteScriptparticle,amountespeed. 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. -
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.
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
-
Inicialização do estado --
on_spawndefinecontext.state.startedcomofalseecontext.state.loop_task_idcomonil. A tabelastatepersiste entre hooks desta instância do poder neste boss. Os outros poderes têm tabelas de estado separadas. -
Proteção de combate --
on_enter_combatverificacontext.state.startedantes de iniciar o ciclo. Impede ciclos sobrepostos se o evento disparar mais de uma vez. -
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. Userun_repeating(100, 100, callback)se quiser essa espera inicial. O callback recebe um contexto novo sem o evento ou jogador que o originou, pelo queloop_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. -
Limpeza na saída --
on_exit_combatcancela a repetição usando o identificador guardado e repõe o estado. Sem essa limpeza, o scheduler continua depois de o combate terminar.
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
-
Verificação de nil --
context.playerresolve 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. -
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 devolvefalsee 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. -
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. -
Partículas --
context.world:spawn_particle_at_location(location, spec)gera partículas numa posição. A tabela aceitaparticle(nome do enum Bukkit),amountespeed. -
Mensagem --
context.player:send_message(text)envia uma mensagem de chat com cores. Códigos Minecraft como&c(vermelho) funcionam automaticamente. -
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 chamarglobal_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
-
Configuração do estado --
on_spawninicializaaoe_task_idcomonil. Este identificador guardará a referência da tarefa repetitiva. -
Ataque repetitivo --
on_enter_combatinicia sem atraso inicial e repete a cada 60 ticks (3 segundos). A proteção evita ciclos duplicados. Cada callback verifica o global, verifica e definepulse_aoee depois fixa o global em 60 ticks antes dos efeitos. Só coordena poderes que verificam essas chaves; não bloqueia automaticamente todos os scripts. -
Definição da zona --
zone_defusa a sintaxe nativa Lua.kindindica a forma ("sphere"),radiuso tamanho eorigina posição atual do boss quando o callback executa. Por isso, a zona acompanha o boss à medida que se move. -
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. -
Causar dano --
victim:deal_custom_damage(4.0)submete 4 pontos de dano base atribuídos ao boss através deBossCustomAttackDamage. 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 decontext.script:damage. -
Partículas -- O pó roxo aparece na posição guardada de cada jogador encontrado, mesmo que o dano seja rejeitado. Uma emissão
WITCHaparece 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 em1. -
Limpeza --
on_exit_combatcancela a repetição e limpa o estado, seguindo o padrão anterior.
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
- Inicialização de estado --
on_spawndefinephasecomo1eattack_task_idcomonilneste runtime de poder. O estado não é partilhado com outros poderes nem guardado entre recarregamentos. - Ciclo de ataque --
on_enter_combatretoma a fase atual através destart_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 cooldownphase_attackesteja 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. - 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 boss 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 da fase 2 --
phase_two_attackusa 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. - Registo --
context.log:info(...)escreve na consola do servidor. - Limpeza --
on_exit_combatcancela qualquer loop de ataque ativo, independentemente da fase.
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)comkind,radius,origin. Utilitários de script usamcontext.script:zone(...)com nomes de campo do EliteScript comoshape,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 explicitamenteglobal_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_everytem atraso inicial zero;run_repeatingrecebe 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_combatpara 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 emon_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:
- O ficheiro devolve 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 tarefa repetitiva exclusiva do combate, criada com
run_everyourun_repeating, tem um cancelamento correspondente emon_exit_combat; o atraso inicial é escolhido intencionalmente. - Os callbacks do scheduler usam o próprio contexto e não presumem que exista um jogador ou evento de origem.
- 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.
- 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"). - 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_spawnque 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.statepara 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_tickleve. 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_repeatingerun_afterrecebem 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
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, 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_everyemon_enter_combatmas 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 lercontext.state.phaseemon_game_tickmas nunca o definir emon_spawn, seránile as suas comparações comportar-se-ão de forma inesperada. -
Usar
context.playersem 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,Target2eshape. Alternativas com sublinhado, comotarget_typeezone_shape, não são suportadas. O parser de zonas aceitatargetetarget2em minúsculas, mas isso não torna válidos aliases arbitrários em estilo Lua. -
Executar lógica pesada em
on_game_ticksem 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)eplayer: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.healthpara definir a saúde.context.boss.healthé um snapshot apenas de leitura. Para curar o boss, usecontext.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
- Primeiros passos -- estrutura de ficheiros, hooks, explicação do primeiro poder
- Scripts de NPC -- scripts de proximidade e interação com NPCs
- 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
