Scripting Lua: Exemplos e Padrões
Esta página contém exemplos completos e funcionais de scripts de props do FreeMinecraftModels, além de padrões práticos e boas práticas. Cada exemplo inclui uma explicação detalhada do que faz e porquê.
Se você é novo no scripting de props, comece com Primeiros Passos. Para detalhes completos da API, consulte a API de Props.
Exemplo: Prop Invulnerável
O que este exemplo ensina: O script útil mais simples -- cancelar dano para que o prop não possa ser destruído.
Este é o script pré-definido que acompanha o FreeMinecraftModels.
Arquivo de script completo (clique para expandir)
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
Explicação passo a passo
-
Escolha do hook --
on_left_clickdispara quando um jogador ataca (clique esquerdo) o prop. Internamente, trata-se de umEntityDamageByEntityEventno armor stand subjacente do prop. -
Verificação do evento --
context.eventdeveria estar sempre presente neste hook, mas a verificação é uma boa prática. -
Cancelamento --
context.event.cancel()cancela o evento de dano, impedindo que o armor stand sofra dano e seja destruído.
Uso
Adicione à configuração .yml do seu prop:
isEnabled: true
scripts:
- invulnerable.lua
Exemplo: Porta Interativa
O que este exemplo ensina: Alternar estado com clique direito, reproduzir e parar animações, e usar context.state para rastrear se a porta está aberta ou fechada.
Arquivo de script completo (clique para expandir)
local OPEN_ANIMATION = "open"
local CLOSE_ANIMATION = "close"
return {
api_version = 1,
on_spawn = function(context)
context.state.is_open = false
end,
on_left_click = function(context)
-- Make the door invulnerable
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local loc = context.prop.current_location
if context.state.is_open then
-- Close the door
context.prop:stop_animation()
context.prop:play_animation(CLOSE_ANIMATION, true, false)
context.state.is_open = false
if loc then
context.world:play_sound(
"BLOCK_WOODEN_DOOR_CLOSE",
loc.x, loc.y, loc.z,
1.0, 1.0
)
end
else
-- Open the door
context.prop:stop_animation()
context.prop:play_animation(OPEN_ANIMATION, true, false)
context.state.is_open = true
if loc then
context.world:play_sound(
"BLOCK_WOODEN_DOOR_OPEN",
loc.x, loc.y, loc.z,
1.0, 1.0
)
end
end
end
}
Explicação passo a passo
-
Constantes no escopo do arquivo --
OPEN_ANIMATIONeCLOSE_ANIMATIONsão definidas acima da tabela return. Isso facilita a alteração para diferentes arquivos de modelo que possam usar nomes de animação diferentes. -
Inicialização do estado --
on_spawndefinecontext.state.is_open = false. O estado persiste entre todos os hooks para esta instância do prop. -
Invulnerabilidade -- O hook
on_left_clickcancela o dano para que a porta não possa ser quebrada acidentalmente. -
Lógica de alternância --
on_right_clickverificacontext.state.is_open, para qualquer animação atual, reproduz a animação apropriada, inverte o estado e reproduz um som. A chamada astop_animation()antes deplay_animation()garante transições limpas. -
Feedback sonoro --
context.world:play_sound()reproduz o nome do enum Sound do Bukkit na posição do prop. Os nomes de sons devem ser nomes de enum em UPPER_CASE.
Nomes de animação como "open" e "close" devem corresponder ao que está definido no arquivo do modelo. Se a animação não for encontrada, play_animation() retorna false e nada acontece. Verifique seu arquivo de modelo para os nomes exatos de animação.
Exemplo: Gatilho de Proximidade com Partículas
O que este exemplo ensina: Criação de zonas, monitoramento de zonas para eventos de entrada/saída, efeitos de partículas e limpeza.
Arquivo de script completo (clique para expandir)
local ZONE_RADIUS = 8
local PARTICLE_INTERVAL = 10 -- ticks between particle bursts
return {
api_version = 1,
on_spawn = function(context)
context.state.zone_handle = nil
context.state.particle_task = nil
context.state.players_in_zone = 0
local loc = context.prop.current_location
if loc == nil then return end
-- Create a sphere zone around the prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, ZONE_RADIUS)
context.state.zone_handle = handle
-- Watch for enter/leave
context.zones:watch(
handle,
function(player)
-- on_enter
context.state.players_in_zone = (context.state.players_in_zone or 0) + 1
end,
function(player)
-- on_leave
context.state.players_in_zone = math.max(0, (context.state.players_in_zone or 0) - 1)
end
)
-- Start a repeating particle effect at the zone boundary
context.state.particle_task = context.scheduler:run_repeating(0, PARTICLE_INTERVAL, function(tick_context)
local prop_loc = tick_context.prop.current_location
if prop_loc == nil then return end
-- Spawn particles in a ring at the zone boundary
for angle = 0, 350, 30 do
local rad = math.rad(angle)
local px = prop_loc.x + math.cos(rad) * ZONE_RADIUS
local pz = prop_loc.z + math.sin(rad) * ZONE_RADIUS
if (tick_context.state.players_in_zone or 0) > 0 then
-- Red particles when players are inside
tick_context.world:spawn_particle("DUST", px, prop_loc.y + 0.5, pz, 1, 0, 0, 0, 0)
else
-- Green particles when zone is empty
tick_context.world:spawn_particle("HAPPY_VILLAGER", px, prop_loc.y + 0.5, pz, 1, 0, 0, 0, 0)
end
end
end)
end,
on_left_click = function(context)
-- Make invulnerable
if context.event then
context.event.cancel()
end
end,
on_destroy = function(context)
-- Clean up the repeating task
if context.state.particle_task then
context.scheduler:cancel(context.state.particle_task)
context.state.particle_task = nil
end
-- Clean up the zone watch
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
context.state.zone_handle = nil
end
end
}
Explicação passo a passo
-
Constantes --
ZONE_RADIUSePARTICLE_INTERVALestão no escopo do arquivo para fácil ajuste. -
Inicialização do estado --
on_spawndefine todos os campos de estado comonil/0antes de fazer qualquer outra coisa. -
Criação de zona --
context.zones:create_sphere()cria uma zona esférica centrada no prop. O handle retornado é um ID numérico usado para referenciar esta zona posteriormente. -
Monitoramento de zona --
context.zones:watch()registra callbacks para entrada e saída de jogadores. Os callbacks incrementam e decrementam um contador armazenado emcontext.state. -
Loop de partículas -- Uma tarefa repetitiva gera partículas em anel ao redor do prop a cada meio segundo. O tipo de partícula muda conforme haja jogadores na zona ou não.
-
Limpeza --
on_destroycancela a tarefa repetitiva e para o monitoramento da zona. Embora ambos sejam limpos automaticamente quando o prop é removido, a limpeza explícita é uma boa prática.
Gerar muitas partículas a cada tick pode impactar o desempenho. Use um intervalo razoável (10-20 ticks) e mantenha as quantidades de partículas baixas. O exemplo acima usa PARTICLE_INTERVAL = 10 (duas vezes por segundo) com apenas 12 partículas por anel.
Exemplo: Prop Emissor de Som
O que este exemplo ensina: Reproduzir sons na interação, comportamento tipo cooldown usando state e scheduler, e prevenção de interações rápidas repetidas.
Arquivo de script completo (clique para expandir)
local SOUND_NAME = "BLOCK_NOTE_BLOCK_HARP"
local COOLDOWN_TICKS = 40 -- 2 seconds between sounds
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Make invulnerable
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Prevent spam
if context.state.on_cooldown then
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Play the sound
context.world:play_sound(SOUND_NAME, loc.x, loc.y, loc.z, 1.0, 1.0)
-- Show some particles
context.world:spawn_particle("NOTE", loc.x, loc.y + 1.5, loc.z, 5, 0.3, 0.3, 0.3, 0)
-- Set cooldown
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
Explicação passo a passo
-
Padrão de cooldown -- Como os scripts de props do FMM não possuem uma API
context.cooldownsintegrada como o EliteMobs, o exemplo implementa um cooldown simples usandocontext.state.on_cooldownescheduler:run_later(). A flag é definida comotruequando o som é reproduzido, e uma tarefa atrasada a redefine apósCOOLDOWN_TICKS. -
Reprodução de som --
context.world:play_sound()recebe o nome do enum Sound do Bukkit em UPPER_CASE, coordenadas, volume e tom. -
Feedback com partículas -- Partículas de nota aparecem acima do prop quando o som é reproduzido, fornecendo uma indicação visual.
-
Invulnerabilidade -- O hook
on_left_clickcancela o dano como de costume. -
Contexto do callback do scheduler -- O callback de
run_laterrecebelater_context, um contexto novo. Usamoslater_context.state(nãocontext.state) para redefinir a flag de cooldown. Como o estado é compartilhado, ambos apontam para a mesma tabela -- mas usar o parâmetro de contexto do callback é o hábito correto.
Os scripts de props do FMM não possuem a API context.cooldowns do EliteMobs. Use o padrão mostrado aqui: uma flag booleana em context.state combinada com scheduler:run_later() para redefini-la. Isso dá controle total sobre a duração e o comportamento do cooldown.
Exemplo: Prop Ambiental Animado
O que este exemplo ensina: Iniciar uma animação em loop ao surgir, com um emissor de partículas baseado em ticks.
Arquivo de script completo (clique para expandir)
return {
api_version = 1,
on_spawn = function(context)
-- Start the idle animation immediately, looping
context.prop:play_animation("idle", false, true)
-- Emit ambient particles every 40 ticks (2 seconds)
context.state.ambient_task = context.scheduler:run_repeating(0, 40, function(tick_context)
local loc = tick_context.prop.current_location
if loc == nil then return end
tick_context.world:spawn_particle(
"ENCHANT",
loc.x, loc.y + 1, loc.z,
10, 0.5, 0.5, 0.5, 0.05
)
end)
end,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end,
on_destroy = function(context)
if context.state.ambient_task then
context.scheduler:cancel(context.state.ambient_task)
end
end
}
Explicação passo a passo
-
Início automático da animação --
on_spawnreproduz imediatamente uma animação"idle"em loop. Ofalsepara blend significa que começa do zero sem misturar com uma animação anterior. Otruepara loop significa que se repete indefinidamente. -
Partículas ambientais -- Uma tarefa repetitiva gera partículas de mesa de encantamento acima do prop a cada 2 segundos, criando um efeito ambiental mágico.
-
Limpeza --
on_destroycancela a tarefa de partículas.
Boas Práticas
-
Comece com um hook pequeno e verifique. Escreva um único
on_spawnque envie uma mensagem de log. Confirme que ele dispara. Depois construa a partir daí. -
Mantenha funções auxiliares locais. Declare helpers como
local function toggle_door(context)acima da tabela return. Isso os mantém fora do escopo global. -
Inicialize todo o estado em
on_spawn. Se você lêcontext.state.is_openemon_right_clickmas nunca o define emon_spawn, ele seránile suas comparações podem se comportar inesperadamente. -
Cancele tarefas repetitivas quando terminar. Cada
run_repeatingdeve ter umcancelcorrespondente emon_destroy. Tarefas não canceladas desperdiçam CPU. -
Use contextos novos dos callbacks do scheduler. Callbacks do scheduler recebem um parâmetro de contexto novo. Sempre use esse parâmetro dentro do callback, não o
contextexterno. -
Mantenha
on_game_tickleve. Se você definir este hook, ele executa a cada tick do servidor (20 vezes por segundo). Proteja trabalho custoso atrás de uma verificação de cooldown baseada em estado. -
Torne props invulneráveis por padrão. A menos que você queira que o prop seja quebrável, inclua o cancelamento de dano de
on_left_clickem cada script. -
Use UPPER_CASE para enums do Bukkit. Nomes de sons e partículas devem usar o formato de constante enum do Bukkit (ex:
"FLAME", não"flame").
Erros Comuns de Iniciantes
-
Usar o
contextexterno dentro de um callback do scheduler. O contexto externo captura um snapshot do momento em que o hook foi executado. Dentro dos callbacks, sempre use o parâmetro próprio do callback. -
Esquecer de cancelar tarefas repetitivas. Se você inicia um
run_repeatingemon_spawnmas nunca o cancela, a tarefa executa até o prop ser removido. -
Não inicializar estado em
on_spawn. Lercontext.state.xantes de defini-lo retornanil, o que pode quebrar sua lógica silenciosamente. -
Nomes de animação errados. Se
play_animation("open")retornafalse, o nome da animação não corresponde ao que está no arquivo do modelo. Verifique o modelo para os nomes exatos. -
Nomes de sons/partículas em minúsculas.
"flame"não funciona -- use"FLAME". A API converte para UPPER_CASE internamente para partículas, mas nomes de enum de Sound devem ser exatos. -
Esquecer
api_version = 1. A tabela retornada deve incluir este campo, ou o FMM não carregará o script. -
Colocar funções dentro da tabela retornada que não são hooks. Funções auxiliares devem ser declaradas acima da instrução
return. Apenas nomes de hooks (on_spawn,on_right_click, etc.) são permitidos como chaves na tabela retornada.
Checklist QC
Use esta checklist para verificar um script de prop antes de implantá-lo:
- O arquivo retorna exatamente uma tabela com
api_version = 1. - Cada nome de hook corresponde exatamente a uma entrada na lista de hooks.
context.eventé verificado comif context.event thenantes de chamarcancel().- Os campos de
context.statesão inicializados emon_spawn. - Cada chamada a
scheduler:run_repeating(...)tem umscheduler:cancel(...)correspondente emon_destroy. - Callbacks do scheduler usam o parâmetro de contexto próprio do callback, não o
contextexterno. - Hooks
on_game_tickprotegem trabalho custoso atrás de uma verificação. - Todos os nomes de métodos existem na referência da API de Props -- sem aliases inventados.
- Nomes de sons e partículas usam nomes de enum UPPER_CASE do Bukkit.
- O script não chama operações bloqueantes ou de longa duração dentro de um hook ou callback.
Dicas para Geração com IA
Se você quer que uma IA gere scripts de props de forma confiável, certifique-se de que o prompt inclua:
- Nome exato do hook -- ex:
on_right_click, não "quando o jogador clica no prop". - Nomes de animação do arquivo do modelo -- a IA não consegue adivinhar esses nomes; forneça-os.
- Nomes de enum de som -- ex:
"BLOCK_NOTE_BLOCK_HARP", não "som de harpa". - Nomes de enum de partículas -- ex:
"FLAME", não "partículas de fogo". - Se o prop deve ser invulnerável -- se sim, incluir
on_left_clickcomcontext.event.cancel(). - Usar apenas nomes de métodos documentados -- se não está na página da API de Props, não existe.
Bom exemplo de prompt
Escreva um script de prop FMM que reproduza a animação "activate" ao clicar com botão direito, torne o prop invulnerável, gere partículas FLAME na posição do prop ao clicar, reproduza o som BLOCK_LEVER_CLICK e tenha um cooldown de 2 segundos entre cliques usando context.state e scheduler:run_later.
Próximos Passos
- Primeiros Passos -- estrutura de arquivos, hooks, explicação do primeiro script, templates
- API de Props -- referência completa da API para todas as tabelas de contexto
- Solução de Problemas -- problemas comuns, dicas de depuração