Saltar para o conteúdo principal

Scripting Lua: Scripts de NPC

webapp_banner.jpg

Os scripts Lua de NPC do EliteMobs são ficheiros .lua autónomos que se ligam a configurações de NPC. São separados dos poderes Lua de boss: os poderes de boss ficam em plugins/EliteMobs/powers/, enquanto os scripts de NPC ficam em plugins/EliteMobs/npc_scripts/.

Os scripts de NPC usam o runtime partilhado do MagmaCore, também usado pelos poderes de boss e props do FreeMinecraftModels. Recebem context.world, context.zones, context.scheduler, context.cooldowns e context.log, além de uma tabela context.npc específica. Os dados de jogador e evento dependem do hook. Os poderes de boss têm versões próprias de várias tabelas; use as assinaturas da API partilhada ao escrever scripts de NPC.

Funcionalidade Experimental

Os scripts Lua de NPC ainda são experimentais. Os hooks específicos de NPC e os auxiliares de context.npc podem mudar. As tabelas partilhadas (context.world, context.zones, context.scheduler, context.cooldowns, context.log, context.event, context.player) são as mesmas documentadas no Motor de Scripting e na Referência da API Lua.


Localização dos ficheiros​

Crie os ficheiros de script de NPC em:

plugins/
EliteMobs/
npc_scripts/
wave.lua

As subpastas são percorridas, recursivamente. No entanto, os scripts são registados apenas pelo nome do ficheiro, por isso npc_scripts/wave.lua e npc_scripts/town/wave.lua colidem -- mantenha os nomes base únicos em toda a árvore.

A descoberta seleciona um ficheiro por nome base, sem distinguir maiúsculas, numa ordem estável de caminhos absolutos. Quando as cópias diferem, regista os caminhos selecionados e ignorados. Um ficheiro selecionado com erros não é substituído por um duplicado ignorado. As referências da configuração continuam a exigir as maiúsculas exatas do ficheiro selecionado.

A extensão .lua é opcional numa configuração de NPC: tanto - wave como - wave.lua resolvem para wave.lua. Se uma configuração de NPC referenciar um script que não existe, o EliteMobs regista um aviso e o NPC continua a aparecer.


Associar scripts a NPCs​

Adicione uma lista scripts: à configuração do NPC:

scripts:
- wave.lua

Vários scripts podem ser associados a um só NPC:

scripts:
- wave.lua
- greeting_particles.lua

Os scripts correm por prioridade crescente. priority assume 0 por omissão. Prioridades iguais conservam a ordem da lista scripts:. Isto ordena a entrega de eventos do NPC, não tarefas agendadas independentes.


Estrutura do script​

Todos os scripts de NPC têm de devolver uma tabela:

return {
api_version = 1,
priority = 0,

on_spawn = function(context)
context.state.spawned = true
context.npc:play_model_animation("idle")
end
}

Apenas estes campos de topo são aceites:

CampoTipoNotas
api_versionnumberObrigatório. Tem de ser 1.
prioritynumberOpcional. Valores mais baixos correm primeiro.
on_spawnfunctionCorre depois de o NPC aparecer.
on_removefunctionCorre durante a remoção normal do NPC, antes do encerramento dos scripts e da remoção do corpo.
on_game_tickfunctionCorre a cada tick depois de o runtime do script ser inicializado e o relógio ficar ativo. Mantenha este trabalho leve.
on_npc_interactfunctionCorre quando o processamento normal de interação chega ao passo dos scripts; veja as limitações abaixo.
on_npc_proximity_enterfunctionCorre uma vez quando um jogador entra no raio de ativação deste NPC.
on_npc_proximity_leavefunctionCorre uma vez quando um jogador sai do raio de ativação deste NPC.
on_zone_enterfunctionCorre quando um jogador entra numa zona que este script está a vigiar (ver context.zones).
on_zone_leavefunctionCorre quando um jogador sai de uma zona vigiada.

A vigilância de zonas só segue jogadores -- mobs e outras entidades nunca acionam on_zone_enter / on_zone_leave.

Chaves de topo desconhecidas são rejeitadas durante o carregamento do script. As funções auxiliares devem ser declaradas como funções local acima da tabela devolvida.

O carregador executa o código ao nível do ficheiro para validação. Cada instância de script do NPC volta a executá-lo na sua própria sandbox. Não use o contexto dos hooks durante esta inicialização. Cada script tem globais e context.state independentes, mesmo no mesmo NPC.

A inicialização do runtime dos NPCs é diferida. Atualmente, o NPC constrói cada instância e envia on_spawn, mas não chama o método de início imediato start(). Um ficheiro que contenha apenas on_game_tick não começa, por isso, a correr sozinho. Defina on_spawn = function(context) end para o inicializar no spawn, ou deixe que outro hook declarado o inicialize mais tarde.

Um erro Lua fecha a instância afetada e cancela o trabalho que lhe pertence. Eventos posteriores descartam a instância fechada sem a recriar. Recarregue a definição corrigida e crie uma nova instância. O estado não persiste entre remoções ou reinícios; on_remove é um hook de remoção do NPC, não um callback geral de erro ou encerramento.


Hooks de proximidade​

Os hooks de proximidade de NPC usam o valor de configuração activationRadius do NPC.

HookDispara quando
on_npc_proximity_enterUm jogador passa de fora do raio de ativação do NPC para dentro dele.
on_npc_proximity_leaveUm jogador passa de dentro do raio de ativação do NPC para fora dele.

O scanner do servidor verifica cada par NPC/jogador a cada 100 ticks, normalmente cinco segundos a 20 TPS, usando distância esférica desde o corpo atual do NPC. Um jogador já dentro no primeiro varrimento conta como entrada. Uma visita rápida entre varrimentos pode passar despercebida. Estar perto de um NPC não bloqueia a entrada junto de outro, e permanecer detetado dentro não repete o evento.

NPCs em movimento ativo de patrulha ou em edição são ignorados. Um par anteriormente detetado pode, por isso, gerar uma saída ao começar o movimento e uma entrada quando a deteção regressa. A remoção e a saída do jogador do servidor não garantem um hook de saída: são necessários o NPC registado e um jogador ligado. Se não houver NPCs ou jogadores ligados, o acompanhamento é limpo sem eventos de saída. Um activationRadius menor ou igual a zero desativa a proximidade; o alcance separado de 30 blocos dos indicadores de missão não amplia a ativação Lua.

O comportamento normal de saudação, diálogo e indicador de missão continua a funcionar. O hook Lua acrescenta comportamento por cima dele.


API de scripting partilhada​

Como os scripts de NPC correm no runtime unificado, todos os hooks de NPC recebem também as tabelas de context partilhadas do MagmaCore usadas pelos scripts do FreeMinecraftModels. Os poderes de boss do EliteMobs correm no mesmo runtime, mas usam variantes específicas de boss para várias tabelas. As listas completas de métodos estão na Referência da API Lua e no Motor de Scripting:

TabelaO que faz
context.worldEfeitos e consultas de mundo: strike_lightning, spawn_particle, play_sound, set_block_at, place_temporary_block, spawn_entity, spawn_firework, get_nearby_entities, get_nearby_players, raycast, entre outros. São aceites tanto a forma por coordenadas (strike_lightning(x, y, z)) como a forma por tabela de localização (strike_lightning_at_location(loc)).
context.zonesCria zonas com create_sphere(x, y, z, radius), create_cylinder(x, y, z, radius, height) ou create_cuboid(x, y, z, xSize, ySize, zSize). Devolve um identificador numérico ou nil sem mundo do proprietário. watch(handle, on_enter, on_leave) ativa os hooks superiores correspondentes quando recebe uma função; a função fornecida não é chamada diretamente. unwatch(handle) remove a zona e termina o acompanhamento.
context.schedulerrun_later(ticks, fn), run_repeating(delay, interval, fn), cancel(task_id).
context.cooldownsTempos de recarga partilhados do MagmaCore: local_ready, local_remaining, check_local, set_local, global_ready, set_global.
context.loginfo(msg), warn(msg), error(msg) — escreve na consola do servidor.
context.eventDados do evento ou do ator quando o hook fornece algum. Ver abaixo.
context.playerO jogador que interage/aciona, quando presente. Ver abaixo.
context.stateUma tabela Lua simples que persiste para esta instância de script de NPC até o NPC ser removido.

As zonas partilhadas usam coordenadas absolutas no mundo do NPC no momento da criação; não seguem o NPC em movimento. A criação regista imediatamente um varrimento por tick dos jogadores do mundo atual do proprietário, antes de watch instalar os handlers. Passe uma função para cada direção que pretenda ativar e implemente o hook on_zone_enter ou on_zone_leave correspondente na tabela devolvida. Estes hooks recebem context.player e uma tabela de evento do ator, mas nenhum identificador de zona. unwatch elimina o identificador por completo; crie outra zona para voltar a vigiar. Jogadores já dentro no primeiro varrimento contam como entradas; um jogador desligado não recebe um callback de saída.

Exemplo: relâmpago ao interagir​

return {
api_version = 1,

on_npc_interact = function(context)
-- NPC scripts can now reach the full world API.
context.world:strike_lightning_at_location(context.npc:get_location())
end
}

context.npc​

context.npc está disponível em todos os hooks de NPC.

Campos​

CampoTipoNotas
namestringNome de exibição do NPC, vindo da configuração.
filenamestringNome do ficheiro de configuração do NPC.
uuidstringUUID do NPC em tempo de execução.
activation_radiusnumberRaio de ativação configurado.
current_locationtabela de localizaçãoLocalização instantânea quando a entidade de suporte existe.
entity_typestringTipo de entidade do Bukkit quando a entidade de suporte existe.

Métodos​

MétodoArgumentosDevolveNotas
is_valid()-booleanSe o NPC ainda tem uma entidade de suporte válida.
get_location()-tabela de localizaçãoLocalização atual do NPC, ou a localização de spawn se a entidade não estiver disponível.
get_eye_location()-tabela de localizaçãoLocalização atual dos olhos, ou a localização de spawn como alternativa.
get_activation_radius()-numberRaio de ativação atualmente configurado.
get_nearby_players(radius)numbertableWrappers de jogador dentro da distância esférica no mundo do NPC. Sem filtro de modo de jogo; use um raio não negativo.
face_direction_or_location(target)vetor, localização ou tabela de entidadenilVira-se numa direção ou para uma posição do mesmo mundo. Tabelas com world ou current_location são posições; coordenadas isoladas são direções. Não faz nada durante movimento ativo da patrulha.
say_greeting(player?)jogador, UUID, nome ou nilnilEnvia uma saudação configurada. Por omissão, usa o jogador que acionou, quando disponível.
say_dialog(player?)jogador, UUID, nome ou nilnilEnvia o diálogo configurado. Por omissão, usa o jogador que acionou, quando disponível.
say_farewell(player?)jogador, UUID, nome ou nilnilEnvia o texto de despedida configurado. Por omissão, usa o jogador que acionou, quando disponível.
play_model_animation(name)stringnilReproduz uma animação de modelo personalizado, se existir. Caso contrário, não faz nada de forma segura.
patrol_pause()-booleanoPausa a patrulha configurada.
patrol_resume()-booleanoTermina uma espera ou caminhada temporária e retoma a patrulha.
walk_to(x, y, z)três númerosbooleanoCaminha até um deslocamento desde o spawn configurado e retoma a patrulha. Rotas longas são resolvidas automaticamente.
hold(x, y, z)três númerosbooleanoCaminha até um deslocamento desde a origem configurada e permanece ali.
teleport(x, y, z)três númerosbooleanoTeleporta para um deslocamento desde a origem configurada quando o destino processa entidades.
remove()-nilRemove o corpo atual pelo ciclo normal com motivo OTHER; não apaga a colocação configurada. Não chame em on_remove, pois voltaria a entrar na remoção.

Os métodos de movimento devolvem false se o NPC não tiver uma patrulha configurada ou se o pedido não puder ser aceito. Consulte Patrulhas de NPCs e chefes.


context.player​

context.player está disponível em on_npc_interact, on_npc_proximity_enter, on_npc_proximity_leave e nos hooks de entrada/saída de zonas vigiadas. É nil nos hooks de ciclo de vida sem jogador.

É o wrapper de jogador partilhado do MagmaCore, também usado por FMM. Expõe vida, efeitos de poção, send_message, show_title, show_action_bar, get_held_item, raycasting e outros métodos. Os poderes de boss têm o seu próprio wrapper; as listas de métodos não são intercambiáveis. Consulte a Referência da API Lua para a lista completa. Os membros mais usados são:

Campo / MétodoNotas
nameNome do jogador.
uuidUUID do jogador.
current_locationLocalização lida em tempo real a cada acesso ao campo. Uma tabela de localização guardada anteriormente mantém-se como instantâneo.
get_eye_location()Localização atual dos olhos do jogador.
send_message(text)Envia uma mensagem de chat. Suporta códigos de cor.

Verifique sempre se context.player é nil antes de o usar em funções auxiliares partilhadas.

Aqui o entity_type está em minúsculas

Nas tabelas de entidade partilhadas do MagmaCore, entity_type é o nome do Bukkit em minúsculas ("player", "zombie"). Só context.npc.entity_type e as tabelas de entidade dos poderes de boss do EliteMobs usam a forma em maiúsculas. Compare sem distinguir maiúsculas de minúsculas se um script tiver de funcionar com ambos.

Campos do EliteMobs adicionados a todas as tabelas de entidade partilhadas​

Enquanto está ativo, o EliteMobs acrescenta campos às tabelas de entidade partilhadas do MagmaCore, incluindo context.player, os wrappers de context.npc:get_nearby_players(...) e os scripts de props do FreeMinecraftModels:

Campo / MétodoTipoNotas
is_elitebooleantrue se o EliteMobs regista a entidade como um elite
is_custom_bossbooleantrue se for um Custom Boss (sempre false quando is_elite é false)
is_significant_bossbooleantrue para um Custom Boss com multiplicador de vida superior a 1. Não verifica se foi invocado como reforço.
elitetable ou nilPresente apenas em elites. Ver abaixo
elite_playertabela ou nilNos wrappers partilhados de jogador criados pelo EliteMobs, expõe tier quando o inventário elite está carregado. Cada acesso cria uma tabela nova; uma tabela guardada conserva o valor anterior.
can_receive_hostile_effect(actor_uuid)booleanVerifica um ator jogador pelas regras partilhadas de alvos hostis e pela autorização de armas mágicas do EliteMobs. Passe uma string UUID válida.

A subtabela elite:

Campo / MétodoTipoNotas
elite.levelnumberNível do elite
elite.namestring ou nilNome de exibição do elite
elite.healthnumberVida atual do elite (leitura em tempo real)
elite.max_healthnumberVida máxima do elite (leitura em tempo real)
elite.is_custom_bossbooleanO mesmo valor do campo de nível superior
elite.health_multipliernumberMultiplicador de vida configurado
elite.damage_multipliernumberMultiplicador de dano configurado
elite:remove()—Faz desaparecer o elite
-- Warn the approaching player if a real boss is loose near this NPC
on_npc_proximity_enter = function(context)
if context.player == nil then return end
local here = context.npc:get_location()
local nearby = context.world:get_nearby_entities(here.x, here.y, here.z, 40)
for i = 1, #nearby do
if nearby[i].is_significant_boss then
context.player:send_message("&cA boss is nearby: " .. tostring(nearby[i].elite.name))
return
end
end
end

Os wrappers dos poderes de boss usam um construtor e um conjunto de campos diferentes. Alguns nomes coincidem, como is_elite, mas a subtabela partilhada elite não substitui os métodos dos poderes de boss. Consulte Bosses e Entidades. O FMM tem o seu próprio adaptador de EliteMobs; não assuma que todos os campos adicionais de um wrapper criado pelo EliteMobs existem no FMM.


context.event​

context.event é nil apenas quando o hook não tem evento nem ator. É a tabela de evento partilhada do MagmaCore:

Campo / MétodoNotas
is_cancelledInstantâneo do cancelamento ao criar a tabela; false para eventos não canceláveis. Não muda após cancel() ou uncancel().
cancel()Só existe se o evento for cancelável. Cancela esse evento.
uncancel()Só existe se o evento for cancelável. Limpa o seu cancelamento.
playerO ator do evento (por exemplo, o jogador que interage), como wrapper de jogador, quando presente.

Para o jogador que interage ou aciona a proximidade, prefira context.player (está definido nesses hooks).

Atualmente, on_npc_interact fornece o jogador sem um evento Bukkit. A tabela contém, por isso, player e is_cancelled: false, mas não cancel ou uncancel. Um script não pode cancelar o menu normal do NPC por esta tabela.

O caminho de interação ignora eventos Bukkit já cancelados, bloqueia etiquetas de nome e NPCs em edição e limita as interações a uma por tick por jogador. Um objetivo de diálogo de missão que consuma a interação retorna antes do hook Lua. Nos restantes casos, o hook corre antes do menu ou ação normal. Os eventos de proximidade também não são canceláveis; os hooks de zona fornecem um ator sem evento Bukkit. Estes caminhos também não oferecem cancel() ou uncancel().


Estado, scheduler e tempos de recarga​

context.state pertence a esta instância individual e dura até ela terminar. É separado do estado dos outros scripts do mesmo NPC.

context.scheduler é o scheduler partilhado do MagmaCore. Tanto os nomes do MagmaCore como os nomes run_after / run_every do EliteMobs funcionam — são aliases para o mesmo comportamento:

MétodoArgumentosNotas
run_later(ticks, callback) / run_after(ticks, callback)number, functionCorre uma vez após um atraso. Devolve um ID de tarefa.
run_repeating(delay, interval, callback)number, number, functionCorre repetidamente após um atraso inicial. Devolve um ID de tarefa.
run_every(interval, callback)number, functionCorre a cada interval ticks (atraso inicial 0). Devolve um ID de tarefa.
cancel(task_id) / cancel_task(task_id)numberCancela uma tarefa própria.

Os callbacks do scheduler recebem um context novo. Não recebem o context.player nem o context.event originais. Todas as tarefas próprias são canceladas automaticamente quando o NPC é removido.

context.cooldowns é a tabela de tempos de recarga partilhada do MagmaCore:

MétodoArgumentosDevolveNotas
local_ready(key?)stringbooleanVerdadeiro quando o tempo de recarga local expirou.
local_remaining(key?)stringnumberTicks restantes, ou 0 quando está pronto.
check_local(key?, duration)string, numberbooleanSe estiver pronto, inicia o tempo de recarga e devolve verdadeiro.
set_local(duration, key?)number, stringnilDefine ou reinicia o tempo de recarga.
global_ready()-booleanVerdadeiro quando o tempo de recarga global partilhado está pronto.
set_global(duration)numbernilInicia o tempo de recarga global.

Em check_local, a duração é sempre o segundo argumento: use check_local(nil, 60) para a chave predefinida. set_local recebe a duração primeiro. A chave predefinida inclui o nome do ficheiro. As chaves locais explícitas e o cooldown global são partilhados pelos scripts do mesmo adaptador de NPC; use chaves distintas salvo se pretender coordená-los. As durações usam tempo monotónico decorrido, a 50 ms por tick nominal, não ticks contados do servidor. Durações não positivas limpam o cooldown. Definir um cooldown global não bloqueia automaticamente hooks nem check_local; verifique global_ready() explicitamente.

API de tempos de recarga unificada

Os scripts de NPC usam agora a ordem de tempos de recarga partilhada do MagmaCore (check_local(key?, duration)), a mesma dos poderes de boss e dos scripts do FreeMinecraftModels. Versões experimentais anteriores de NPC usavam check_local(duration, key?) — atualize quaisquer scripts antigos para a ordem partilhada.


Exemplo: acenar ao aproximar​

Este script vira o NPC para o jogador cuja entrada foi detetada e reproduz wave. A entrada depende do varrimento de cinco segundos, não de cada pacote de movimento. O cooldown com chave limita a repetição perante notificações sucessivas de entrada; não aumenta a frequência do scanner.

return {
api_version = 1,
priority = 0,

on_npc_proximity_enter = function(context)
if context.player == nil then return end

if context.cooldowns:check_local("wave:" .. context.player.uuid, 60) then
context.npc:face_direction_or_location(context.player.current_location)
context.npc:play_model_animation("wave")
end
end
}

play_model_animation(name) não faz nada, de forma segura, quando o NPC não tem um modelo personalizado ou quando o modelo não tem essa animação.


Orientações de desempenho​

  • Mantenha os hooks on_game_tick leves. Depois de inicializado, o relógio corre a cada tick, normalmente 20 vezes por segundo. Um script sem este hook pode continuar a realizar varrimentos de zona por tick ou trabalho explicitamente agendado.
  • Prefira on_npc_proximity_enter e on_npc_proximity_leave para comportamento de proximidade, em vez de sondar os jogadores próximos a cada tick.
  • Use context.cooldowns:check_local(...) para limitar animações, sons e rajadas de partículas.
  • Use context.scheduler:run_repeating(...) com um intervalo razoável quando um comportamento não precisa de correr a cada tick.
  • Evite pesquisas grandes em Lua. context.npc:get_nearby_players(radius) é adequado para verificações locais pequenas, mas varrimentos amplos devem permanecer no runtime do plugin.