Scripts Lua: exemplos e padrões
Esta página apresenta exemplos de scripts de elementos decorativos do FreeMinecraftModels, com explicações e boas práticas. Forneça os modelos e as animações referidos e verifique o comportamento no servidor antes de os usar num mapa.
Se está a começar a criar scripts, consulte Primeiros passos. Para conhecer a API em detalhe, consulte a API de elementos decorativos.
Exemplo: elemento decorativo invulnerável
O que este exemplo ensina: O script útil mais simples: cancelar o dano predefinido do clique esquerdo.
Guarde este exemplo como invulnerable.lua na pasta de scripts e recarregue os scripts antes de o associar a um elemento decorativo.
Ficheiro 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
-
Escolha do evento —
on_left_clické executado quando um jogador dá um soco no elemento decorativo, clicando com o botão esquerdo. O evento Lua representa o sinalizador de cancelamento usado pelo FMM para o seu tratamento predefinido de dano em elementos decorativos. -
Verificação do evento —
context.eventdeve estar sempre presente neste evento, mas verificá-lo é uma boa prática. -
Cancelamento —
context.event.cancel()impede que o FMM aplique o dano predefinido deste clique. Não concede invulnerabilidade geral contra todas as fontes de dano.
Utilização
Adicione o seguinte à configuração .yml do elemento decorativo:
isEnabled: true
scripts:
- invulnerable.lua
Exemplo: porta interativa
O que este exemplo ensina: Alternar o estado com o clique direito, reproduzir e parar animações e usar context.state para registar se a porta está aberta ou fechada.
Ficheiro 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)
-- Prevent default left-click damage
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
-
Constantes ao nível do ficheiro —
OPEN_ANIMATIONeCLOSE_ANIMATIONsão definidas antes da tabela devolvida. Assim, é fácil adaptá-las a ficheiros de modelos que usem outros nomes de animação. -
Inicialização do estado —
on_spawndefinecontext.state.is_open = false. O estado mantém-se entre os eventos desta associação de script ao elemento decorativo. Outros scripts associados ao mesmo elemento têm o seu próprio estado. -
Proteção contra cliques — O evento
on_left_clickcancela o dano predefinido desse clique. -
Alternância —
on_right_clickverificacontext.state.is_open, pede a paragem e a animação adequada, inverte o sinalizador e reproduz um som. O sinalizador regista o pedido, não confirma a reprodução. Comblend = true, o pedido entra na fila;stop_animation()não interrompe o estado terminaldeath. Consulte Animações. Este exemplo não altera barreiras nem colisões e não garante que se possa atravessar a porta. -
Os sons aceitam nomes de enumerações Bukkit, independentemente de maiúsculas e minúsculas, ou chaves de som válidas como
minecraft:block.note_block.harpe chaves personalizadas de pacotes de recursos. O cliente tem de ter o som no seu pacote de recursos.
Os nomes "open" e "close" têm de corresponder às definições no modelo. Se não existirem, play_animation() devolve false, mas este exemplo continua a inverter o sinalizador e a reproduzir o som. Verifique o resultado se o estado depender da aceitação do pedido. Mesmo true não confirma a reprodução visível.
Exemplo: ativação por proximidade com partículas
O que este exemplo ensina: Criar zonas, observar as entradas e saídas dessas zonas, produzir efeitos de partículas e libertar os recursos utilizados.
Ficheiro 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
-- Pass placeholder functions to enable on_zone_enter and on_zone_leave.
-- The actual logic lives in the hooks below.
context.zones:watch(handle, function() end, function() 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
-- Flame particles when players are inside
tick_context.world:spawn_particle("FLAME", 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)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_zone_enter = function(context)
context.state.players_in_zone = (context.state.players_in_zone or 0) + 1
end,
on_zone_leave = function(context)
context.state.players_in_zone = math.max(0, (context.state.players_in_zone or 0) - 1)
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
-
Constantes —
ZONE_RADIUSePARTICLE_INTERVALficam ao nível do ficheiro para facilitar os ajustes. -
Inicialização do estado — Antes de executar qualquer outra operação,
on_spawninicializa todos os campos de estado comnilou0. -
Criação da zona —
context.zones:create_sphere()cria uma zona esférica centrada na posição do elemento no momento deon_spawn. O identificador numérico referencia essa zona fixa. As partículas consultam a posição atual do elemento, mas a zona não o acompanha. Use um elemento imóvel ou atualize a zona quando o mover. -
Observação da zona —
context.zones:watch(handle, function() end, function() end)ativa os eventoson_zone_entereon_zone_leave. Estes eventos incrementam e decrementam um contador guardado emcontext.state; também recebem o jogador que atravessa o limite emcontext.player/context.event.player, caso pretenda um comportamento específico por jogador. -
Ciclo de partículas — Uma tarefa repetida cria um anel de partículas a cada 10 ticks do servidor, meio segundo a 20 TPS. O tipo de partícula varia consoante existam jogadores dentro da zona.
-
Limpeza —
on_destroycancela a tarefa repetida e deixa de observar a zona. Embora ambos sejam limpos automaticamente quando o elemento decorativo é removido, a limpeza explícita é uma boa prática.
Criar muitas partículas em cada tick pode afetar o desempenho. Use um intervalo razoável de 10 a 20 ticks e mantenha o número de partículas baixo. O exemplo usa PARTICLE_INTERVAL = 10, duas vezes por segundo a 20 TPS, com 12 partículas por anel.
Exemplo: elemento decorativo que emite som
O que este exemplo ensina: Reproduzir sons ao interagir, usar context.cooldowns e impedir interações demasiado rápidas.
Ficheiro 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_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Prevent spam
if not context.cooldowns:check_local("sound", COOLDOWN_TICKS) 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)
end
}
Explicação
-
Intervalo entre utilizações —
context.cooldowns:check_local("sound", COOLDOWN_TICKS)verifica o intervalo e inicia-o imediatamente se estiver disponível. Todos os jogadores partilham este intervalo na mesma associação de script. Usa tempo decorrido, com 50 ms por tick, ao contrário dos ticks efetivos do servidor usados pelo agendador. -
Os sons aceitam nomes de enumerações Bukkit, independentemente de maiúsculas e minúsculas, ou chaves de som válidas como
minecraft:block.note_block.harpe chaves personalizadas de pacotes de recursos. O cliente tem de ter o som no seu pacote de recursos. -
Resposta visual — Quando o som é reproduzido, surgem partículas de notas por cima do elemento decorativo para sinalizar a interação.
-
Proteção contra cliques — O evento
on_left_clickcancela o dano predefinido desse clique.
Use context.cooldowns para intervalos de espera normais. Reserve context.state em conjunto com scheduler:run_later() para os casos em que precisa de alterar o estado de uma forma específica quando a espera termina.
Exemplo: elemento decorativo com animação ambiente
O que este exemplo ensina: Iniciar uma animação em ciclo quando o elemento é criado e emitir partículas a intervalos medidos em ticks.
Ficheiro 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
-
Início automático da animação —
on_spawnreproduz imediatamente a animação"idle"em ciclo. O valorfalsedo parâmetro de transição faz a mudança de imediato. O estado integradoidlerepete-se independentemente do argumento de repetição fornecido. -
Partículas ambiente — Uma tarefa repetida cria partículas de mesa de encantamentos por cima do elemento decorativo a cada 2 segundos, produzindo um ambiente mágico.
-
Limpeza —
on_destroycancela a tarefa de partículas.
Exemplo: cadeira onde os jogadores se podem sentar
O que este exemplo ensina: Montar um jogador num elemento decorativo com o clique direito, desmontá-lo com o clique esquerdo e usar context.event.player para obter o jogador que está a interagir.
Ficheiro de script completo (clique para expandir)
return {
api_version = 1,
on_right_click = function(context)
-- Handle seating here instead of also using the default mount fallback
if context.event then
context.event.cancel()
end
local player = context.event and context.event.player
if not player then return end
-- Check if the player is already sitting on this prop
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
-- Player is already seated, do nothing on right-click
return
end
end
-- Mount the player on the chair
context.prop:mount(player)
local loc = context.prop.current_location
if loc then
context.world:play_sound("BLOCK_WOOD_PLACE", loc.x, loc.y, loc.z, 0.8, 1.2)
end
end,
on_left_click = function(context)
-- Cancel default left-click damage
if context.event then
context.event.cancel()
end
local player = context.event and context.event.player
if not player then return end
-- Check if the player is sitting and dismount them
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
context.prop:dismount(player)
local loc = context.prop.current_location
if loc then
context.world:play_sound("BLOCK_WOOD_BREAK", loc.x, loc.y, loc.z, 0.8, 1.0)
end
return
end
end
end
}
Explicação
-
Clique direito para sentar —
on_right_clickobtém o jogador através decontext.event.player, verifica se já é passageiro para evitar uma segunda montagem e chamacontext.prop:mount(player)para pedir o primeiro assento disponível nos pontos de montagemm_. -
Clique esquerdo para levantar —
on_left_clickcancela o dano predefinido desse clique e verifica se o jogador que deu o soco é passageiro. Se for,context.prop:dismount(player)retira-o do assento. -
Verificação dos passageiros —
context.prop:get_passengers()devolve uma lista de tabelas de entidades. A comparação dos UUIDs permite encontrar o jogador na lista. -
Resposta sonora — Os sons acompanham os pedidos de montagem e desmontagem colocados na fila; não confirmam que tenham sido executados. O script cancela o comportamento predefinido de montagem para evitar uma segunda tentativa. É necessário um ponto de montagem
m_livre.
Exemplo: santuário de bênçãos
O que este exemplo ensina: Verificar o item que o jogador segura, consumir itens, aplicar efeitos de poção aleatórios, gerir intervalos de espera e produzir respostas visuais e sonoras.
Ficheiro de script completo (clique para expandir)
local COOLDOWN_TICKS = 600 -- 30 seconds between uses
local BLESSINGS = {
{ effect = "speed", name = "Swiftness" },
{ effect = "strength", name = "Strength" },
{ effect = "regeneration", name = "Regeneration" },
{ effect = "resistance", name = "Resistance" },
{ effect = "jump_boost", name = "Leap" },
{ effect = "haste", name = "Haste" },
}
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Check cooldown
if context.state.on_cooldown then
player:send_message("&eThe shrine is recharging... Please wait.")
return
end
-- Check if the player is holding a gold ingot
local held = player:get_held_item()
if not held or held.type ~= "GOLD_INGOT" then
player:send_message("&eThe shrine demands a gold offering...")
return
end
-- Consume one gold ingot
player:consume_held_item(1)
-- Play blessing effects
local loc = context.prop.current_location
if loc then
context.world:spawn_particle("ENCHANT", loc.x, loc.y + 1.5, loc.z, 30, 0.5, 0.5, 0.5, 0.5)
context.world:spawn_particle("HAPPY_VILLAGER", loc.x, loc.y + 1, loc.z, 10, 0.3, 0.3, 0.3, 0)
context.world:play_sound("BLOCK_BEACON_ACTIVATE", loc.x, loc.y, loc.z, 1.0, 1.5)
end
-- Apply a random blessing
local chosen = BLESSINGS[math.random(#BLESSINGS)]
player:add_potion_effect(chosen.effect, 600, 1) -- 30 seconds, level II
player:send_message("&aThe shrine blesses you with " .. chosen.name .. "!")
-- 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
-
Verificação do item —
player:get_held_item()devolve uma tabela comtype,amountedisplay_namepara o item na mão principal, ounilse estiver vazia.held.typeé o nome do material Bukkit em maiúsculas, pelo que o comparamos com"GOLD_INGOT". -
Consumo do item —
player:consume_held_item(1)coloca a remoção da mão principal numa fila. A mão é consultada quando a tarefa é executada e a chamada não devolve um resultado de pagamento. Esta demonstração não é uma troca atómica: mudar o item na mão pode alterar o que é consumido, enquanto a bênção é aplicada imediatamente. -
Efeito positivo aleatório — A tabela
BLESSINGS, definida ao nível do ficheiro, lista os efeitos positivos disponíveis.math.random(#BLESSINGS)escolhe um deles aleatoriamente.player:add_potion_effect(effect, duration, amplifier)aplica-o:600ticks correspondem a 30 segundos e o amplificador1corresponde ao nível II. -
Intervalo de espera — Todos os jogadores partilham o sinalizador desta associação de script. A função agendada repõe-no após 600 ticks do servidor, 30 segundos a 20 TPS. Para medir tempo decorrido, use
context.cooldowns, como no exemplo do som. -
Resposta visual e sonora — As partículas de encantamento e de aldeão feliz, juntamente com o som de ativação de um sinalizador, dão a sensação de uma bênção divina.
Exemplo: santuário amaldiçoado
O que este exemplo ensina: Aplicar efeitos de poção negativos, lançar raios, criar entidades e escolher comportamentos consoante a oferenda do jogador.
Ficheiro de script completo (clique para expandir)
local COOLDOWN_TICKS = 600 -- 30 seconds between uses
local BUFFS = {
{ effect = "speed", name = "Swiftness" },
{ effect = "strength", name = "Strength" },
{ effect = "regeneration", name = "Regeneration" },
{ effect = "resistance", name = "Resistance" },
}
local CURSES = {
{ effect = "slowness", name = "Slowness" },
{ effect = "weakness", name = "Weakness" },
{ effect = "poison", name = "Poison" },
{ effect = "mining_fatigue", name = "Mining Fatigue" },
}
local ZOMBIE_COUNT = 4
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Check cooldown
if context.state.on_cooldown then
player:send_message("&7The dark shrine pulses with residual energy...")
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Check if the player is holding a gold ingot
local held = player:get_held_item()
if not held or held.type ~= "GOLD_INGOT" then
-- No offering -- punish the player!
player:send_message("&4The shrine demands tribute! You dare approach empty-handed?!")
-- Strike lightning on the player
local player_loc = player.current_location
if player_loc then
context.world:strike_lightning(player_loc.x, player_loc.y, player_loc.z)
end
-- Spawn a horde of zombies around the shrine
for i = 1, ZOMBIE_COUNT do
local angle = math.rad((360 / ZOMBIE_COUNT) * i)
local spawn_x = loc.x + math.cos(angle) * 3
local spawn_z = loc.z + math.sin(angle) * 3
context.world:spawn_entity("zombie", spawn_x, loc.y, spawn_z)
end
-- Apply a random curse
local chosen_curse = CURSES[math.random(#CURSES)]
player:add_potion_effect(chosen_curse.effect, 400, 1) -- 20 seconds, level II
player:send_message("&cThe shrine curses you with " .. chosen_curse.name .. "!")
-- Ominous effects
context.world:spawn_particle("SMOKE", loc.x, loc.y + 1, loc.z, 30, 0.5, 0.5, 0.5, 0.05)
context.world:play_sound("ENTITY_WITHER_AMBIENT", loc.x, loc.y, loc.z, 1.0, 0.5)
else
-- Gold offered -- reward the player
player:consume_held_item(1)
-- Apply a random buff
local chosen_buff = BUFFS[math.random(#BUFFS)]
player:add_potion_effect(chosen_buff.effect, 600, 1) -- 30 seconds, level II
player:send_message("&aThe dark shrine accepts your offering. You are blessed with " .. chosen_buff.name .. "!")
-- Positive feedback
context.world:spawn_particle("ENCHANT", loc.x, loc.y + 1.5, loc.z, 30, 0.5, 0.5, 0.5, 0.5)
context.world:play_sound("BLOCK_BEACON_ACTIVATE", loc.x, loc.y, loc.z, 1.0, 0.8)
end
-- Set cooldown regardless of path
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
Explicação
-
Decisão com base na oferenda — O script consulta
player:get_held_item()e segue um de dois caminhos: castiga o jogador se não tiver ouro ou recompensa-o se tiver. -
Raio —
context.world:strike_lightning(x, y, z)lança um raio real, que causa dano, na localização do jogador. A posição do jogador é obtida através deplayer.current_location. -
Criação de zombies —
context.world:spawn_entity("zombie", x, y, z)cria zombies normais, distribuídos num círculo à volta do santuário. Não verifica o terreno nem limita quantos continuam vivos. Remover o elemento decorativo não remove estes zombies. -
Efeitos de poção negativos —
player:add_potion_effect("poison", 400, 1)aplica Veneno II durante 20 segundos. Os nomes dos efeitos são cadeias de texto em minúsculas que correspondem aos nomes dePotionEffectTypedo Bukkit. -
Recompensa — Quando é oferecido ouro, o santuário coloca o consumo da mão principal na fila e aplica um efeito positivo aleatório. Tal como no santuário de bênçãos, esta demonstração não é uma troca atómica.
-
Intervalo de espera — Ambos os caminhos usam o mesmo sinalizador, partilhado por todos os jogadores nesta associação de script. É reposto após 600 ticks do servidor, 30 segundos a 20 TPS.
Criar várias entidades ao mesmo tempo pode afetar o desempenho do servidor. Mantenha a quantidade baixa, entre 4 e 6, e considere acrescentar um intervalo por jogador se muitos jogadores usarem o santuário em simultâneo.
Exemplo: globo giratório
O que este exemplo ensina: Reproduzir uma animação com duração limitada ao interagir, agendar a sua paragem e usar efeitos sonoros mecânicos.
Ficheiro de script completo (clique para expandir)
local SPIN_ANIMATION = "spin"
local SPIN_DURATION = 100 -- 5 seconds in ticks
return {
api_version = 1,
on_spawn = function(context)
context.state.is_spinning = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Prevent starting a new spin while already spinning
if context.state.is_spinning then
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Start the spin animation (non-looping)
context.prop:play_animation(SPIN_ANIMATION, true, false)
context.state.is_spinning = true
-- Play a mechanical clicking sound
context.world:play_sound("BLOCK_CHAIN_PLACE", loc.x, loc.y, loc.z, 1.0, 1.5)
-- Schedule the animation to stop after 5 seconds
context.scheduler:run_later(SPIN_DURATION, function(later_context)
later_context.prop:stop_animation()
later_context.state.is_spinning = false
local stop_loc = later_context.prop.current_location
if stop_loc then
later_context.world:play_sound("BLOCK_CHAIN_FALL", stop_loc.x, stop_loc.y, stop_loc.z, 1.0, 0.8)
end
end)
end
}
Explicação
-
Verificação do estado —
context.state.is_spinningimpede pedidos de rotação sobrepostos. O sinalizador é ativado quando a rotação começa e desativado quando a paragem agendada é executada. -
Animação com duração limitada —
play_animation(SPIN_ANIMATION, true, false)coloca na fila uma animação sem repetição. A paragem agendada ocorre 100 ticks do servidor após o clique, 5 segundos a 20 TPS, e não após o início da reprodução. Uma fila ocupada pode fazer a paragem cortar ou impedir a animação. O estado terminaldeathnão pode ser parado desta forma. O exemplo ativa o sinalizador mesmo que o pedido devolvafalse. -
Sons mecânicos —
BLOCK_CHAIN_PLACEproduz um som de clique mecânico no início;BLOCK_CHAIN_FALLproduz um som de desaceleração no fim. Ajuste a altura do som conforme pretendido. -
Contexto da função agendada — A função passada a
run_laterusalater_context, em vez decontext, para todos os acessos ao estado e ao mundo. Este é o padrão correto para funções agendadas.
O nome da animação "spin" tem de corresponder ao definido no ficheiro do modelo. Se o modelo usar outro nome, como "rotate" ou "turn", atualize a constante SPIN_ANIMATION em conformidade.
Exemplo: elemento decorativo que assusta
O que este exemplo ensina: Ativar comportamentos por zonas de proximidade, produzir um susto com um intervalo de espera longo e combinar som, partículas e animação para criar um efeito marcante.
Ficheiro de script completo (clique para expandir)
local SCARE_RADIUS = 3
local COOLDOWN_TICKS = 1200 -- 60 seconds between scares
local SCARE_ANIMATION = "jumpscare"
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
context.state.zone_handle = nil
local loc = context.prop.current_location
if loc == nil then return end
-- Create a small sphere zone around the prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, SCARE_RADIUS)
context.state.zone_handle = handle
-- Watch for players entering the zone. The actual scare logic
-- lives in on_zone_enter below.
context.zones:watch(handle, function() end, nil)
end,
on_zone_enter = function(context)
if context.state.on_cooldown then
return
end
local scare_loc = context.prop.current_location
if scare_loc == nil then return end
-- Play the jumpscare animation
context.prop:stop_animation()
context.prop:play_animation(SCARE_ANIMATION, false, false)
-- Scary sound
context.world:play_sound(
"ENTITY_GHAST_SCREAM",
scare_loc.x, scare_loc.y, scare_loc.z,
1.0, 0.7
)
-- Burst of smoke particles
context.world:spawn_particle(
"CAMPFIRE_SIGNAL_SMOKE",
scare_loc.x, scare_loc.y + 1, scare_loc.z,
20, 0.5, 0.5, 0.5, 0.05
)
-- Set cooldown so it does not trigger again immediately
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_destroy = function(context)
-- 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
-
Zona de proximidade —
context.zones:create_sphere()cria uma zona fixa com 3 blocos de raio na posição do elemento emon_spawn.context.zones:watch(handle, function() end, nil)ativaon_zone_enterquando um jogador entra. Se mover o elemento, mova ou recrie a zona. -
Efeitos do susto — O evento
on_zone_enterreproduz uma animação"jumpscare"e um grito de ghast, e cria partículas de fumo de fogueira. A combinação produz um efeito súbito que surpreende o jogador. -
Intervalo de 60 segundos — Todos os jogadores partilham o sinalizador desta associação de script. É reposto após 1200 ticks do servidor, 60 segundos a 20 TPS. Depois disso, uma nova entrada na zona pode ativar o susto.
-
Sem evento de saída — Passar
nilcomo terceiro argumento decontext.zones:watch()indica que a saída dos jogadores da zona não precisa de ser tratada. -
Limpeza —
on_destroydeixa de observar a zona. Embora as zonas sejam limpas automaticamente quando o elemento decorativo é removido, a limpeza explícita é uma boa prática.
Para obter o melhor efeito, esconda o elemento decorativo atrás de uma esquina ou numa zona escura. O raio de 3 blocos garante que o jogador está perto antes da ativação. Ajuste SCARE_RADIUS e COOLDOWN_TICKS conforme pretendido.
Exemplo: elemento decorativo que cria goblins
O que este exemplo ensina: Usar prop:spawn_elitemobs_boss() para criar um boss personalizado ao interagir com um elemento decorativo e tratar a ausência do EliteMobs de forma controlada.
Ficheiro de script completo (clique para expandir)
local BOSS_FILE = "goblin_warrior.yml"
local COOLDOWN_TICKS = 200 -- 10 seconds between spawns
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Prevent spawn spam
if context.state.on_cooldown then
player:send_message("&7The spawner is recharging...")
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Try to spawn the EliteMobs boss
local boss = context.prop:spawn_elitemobs_boss(BOSS_FILE, loc.x, loc.y + 1, loc.z)
if boss then
-- Success -- play spawn effects
context.world:spawn_particle("FLAME", loc.x, loc.y + 1, loc.z, 20, 0.5, 0.5, 0.5, 0.05)
context.world:play_sound("ENTITY_EVOKER_PREPARE_SUMMON", loc.x, loc.y, loc.z, 1.0, 1.0)
player:send_message("&cA goblin warrior emerges!")
else
-- EliteMobs is not installed or the boss file was not found
context.log:warn("Could not spawn boss '" .. BOSS_FILE .. "' -- is EliteMobs installed?")
player:send_message("&7The spawner fizzles... (EliteMobs not available)")
-- Fizzle particles as visual feedback
context.world:spawn_particle("SMOKE", loc.x, loc.y + 1, loc.z, 10, 0.3, 0.3, 0.3, 0.02)
context.world:play_sound("BLOCK_FIRE_EXTINGUISH", loc.x, loc.y, loc.z, 0.8, 1.2)
end
-- 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
-
Criação do boss —
context.prop:spawn_elitemobs_boss(filename, x, y, z)cria um boss personalizado do EliteMobs nas coordenadas indicadas. O nome do ficheiro tem de corresponder a um ficheiro.ymlna pastacustombossesdo EliteMobs. -
Tratamento da falha —
spawn_elitemobs_boss()devolvenilquando a integração não consegue devolver um boss vivo, incluindo plugin indisponível, configuração em falta ou falha na criação. O script regista um aviso e produz partículas e uma mensagem. A mensagem sugere verificar a instalação ou o ficheiro; não identifica a causa exata. -
Deslocamento da posição —
loc.y + 1cria o boss um bloco acima da origem do elemento. Isto não verifica terreno nem colisões. Escolha um ponto de criação com espaço livre. -
Intervalo de espera — Todos os jogadores partilham uma tentativa a cada 200 ticks do servidor nesta associação de script, 10 segundos a 20 TPS. Isto não limita os bosses vivos nem os remove quando o elemento desaparece. Acrescente os limites necessários ao encontro.
-
Distinção visual e sonora — Em caso de sucesso, as partículas de chama e o som de invocação do evocador destacam a criação do boss. Em caso de falha, o fumo e o som de apagar fogo deixam claro que algo correu mal, sem que o jogador precise de consultar a consola.
O nome do ficheiro do boss, por exemplo "goblin_warrior.yml", tem de corresponder a uma configuração de boss personalizado existente no EliteMobs. Se distribuir um mapa ou uma masmorra que use este script, inclua o ficheiro de configuração do boss e documente a dependência do EliteMobs.
Efeitos de itens
Os exemplos de scripts de itens equipados foram retirados. Para os efeitos de itens atuais, use definições de itens personalizados e encantamentos personalizados.
Boas práticas
-
Comece por um evento pequeno e verifique-o. Escreva apenas um
on_spawnque envie uma mensagem para o registo. Confirme que é executado e depois desenvolva o script a partir daí. -
Mantenha as funções auxiliares locais. Declare funções como
local function toggle_door(context)antes da tabela devolvida, para as manter fora do âmbito global. -
Inicialize todo o estado em
on_spawn. Se consultarcontext.state.is_openemon_right_clicksem o definir emon_spawn, o valor seránile as comparações podem ter resultados inesperados. -
Cancele as tarefas repetidas quando terminarem. Cada
run_repeatingdeve ter umcancelcorrespondente emon_destroy. As tarefas que ficam a executar sem necessidade desperdiçam CPU. -
Use o contexto atualizado da função agendada. As funções agendadas recebem um novo parâmetro de contexto. Use sempre esse parâmetro dentro da função, em vez do
contextexterior. -
Mantenha
on_game_tickleve. Se definir este evento, ele será executado em cada tick do servidor, 20 vezes por segundo a 20 TPS. Limite o trabalho dispendioso comcontext.cooldownsou com um intervalo do agendador. -
Controle o dano do clique esquerdo de forma explícita. Inclua o cancelamento em
on_left_clickquando os socos não devam causar o dano predefinido no elemento decorativo; isto não bloqueia todas as fontes de dano. -
Os nomes de partículas aceitam
FLAMEouflame; a API normaliza maiúsculas e minúsculas antes de procurar a partícula Bukkit. Os sons aceitam nomes de enumerações Bukkit, independentemente de maiúsculas e minúsculas, ou chaves de som válidas comominecraft:block.note_block.harpe chaves personalizadas de pacotes de recursos. O cliente tem de ter o som no seu pacote de recursos.
Erros comuns de principiantes
-
Usar o
contextexterior dentro de uma função agendada. O contexto exterior contém uma imagem do estado no momento em que o evento foi executado. Dentro das funções agendadas, use sempre o respetivo parâmetro de contexto. -
Esquecer-se de cancelar tarefas repetidas. Se iniciar um
run_repeatingemon_spawne nunca o cancelar, a tarefa continuará a executar até o elemento decorativo ser removido. -
Não inicializar o estado em
on_spawn. Lercontext.state.xantes de o definir devolvenil, o que pode quebrar a lógica sem produzir um erro evidente. -
Usar nomes de animação incorretos. Se
play_animation("open")devolverfalse, o nome da animação não corresponde ao que existe no ficheiro do modelo. Consulte o modelo para obter os nomes exatos. -
Os nomes de partículas aceitam
FLAMEouflame; a API normaliza maiúsculas e minúsculas antes de procurar a partícula Bukkit. Os sons aceitam nomes de enumerações Bukkit, independentemente de maiúsculas e minúsculas, ou chaves de som válidas comominecraft:block.note_block.harpe chaves personalizadas de pacotes de recursos. O cliente tem de ter o som no seu pacote de recursos. -
Esquecer-se de
api_version = 1. A tabela devolvida tem de incluir este campo; caso contrário, o FMM não carrega o script. -
Colocar funções auxiliares na tabela devolvida. Declare-as como funções locais antes de
return. Além deapi_versione do campo opcionalpriority, a tabela só aceita funções de eventos suportados. O FMM executa os scripts pela ordem da lista configurada;prioritynão altera essa ordem.
Lista de verificação de qualidade
Use esta lista para verificar um script de elemento decorativo antes de o colocar em produção:
- O ficheiro devolve exatamente uma tabela com
api_version = 1. - O nome de cada evento corresponde exatamente a uma entrada na lista de eventos de elementos decorativos. Os eventos de encantamentos pertencem a um sistema de execução separado.
- Verifique
context.eventecontext.event.cancelantes de cancelar fora de um evento de clique conhecido. - Os campos de
context.statesão inicializados emon_spawn. - Cada chamada
scheduler:run_repeating(...)tem umscheduler:cancel(...)correspondente emon_destroy. - As funções agendadas usam o seu próprio parâmetro de contexto, em vez do
contextexterior. - Os eventos
on_game_tickcondicionam o trabalho dispendioso a uma verificação. - Todos os nomes de métodos existem na referência da API de elementos decorativos; não são usados nomes alternativos inventados.
- Os nomes de partículas aceitam
FLAMEouflame; a API normaliza maiúsculas e minúsculas antes de procurar a partícula Bukkit. Os sons aceitam nomes de enumerações Bukkit, independentemente de maiúsculas e minúsculas, ou chaves de som válidas comominecraft:block.note_block.harpe chaves personalizadas de pacotes de recursos. O cliente tem de ter o som no seu pacote de recursos. - O script não executa operações bloqueantes ou demoradas dentro de um evento ou de uma função agendada.
Dicas para gerar scripts com IA
Para que uma IA gere scripts de elementos decorativos de forma fiável, inclua no pedido:
- O nome exato do evento — Por exemplo,
on_right_click, em vez de apenas «quando o jogador clica no elemento decorativo». - Os nomes das animações do ficheiro do modelo — A IA não os pode adivinhar; forneça-os.
- Os nomes das enumerações dos sons — Por exemplo,
"BLOCK_NOTE_BLOCK_HARP", em vez de «som de harpa». - Os nomes das enumerações das partículas — Por exemplo,
"FLAME", em vez de «partículas de fogo». - Se os cliques esquerdos devem causar o dano predefinido — Cancele esse tratamento com
on_left_clickecontext.event.cancel(). - A exigência de usar métodos verificados — Prefira os métodos da referência da API de elementos decorativos. Se um método não estiver listado, verifique a sua implementação antes de o usar; não invente nomes alternativos.
Exemplo de um bom pedido
Escreve um script de elemento decorativo FMM que reproduza a animação "activate" com o clique direito, cancele o dano predefinido do clique esquerdo, crie partículas FLAME na localização do elemento ao clicar, reproduza o som BLOCK_LEVER_CLICK e imponha um intervalo de 2 segundos entre cliques através de context.cooldowns:check_local("activate", 40).
Compatibilidade do conteúdo de itens
Os pacotes de itens antigos que usam scripts: precisam de definições de itens e conteúdo de encantamentos atualizados. O FMM ignora essas definições retiradas e deixa os ficheiros inalterados. Adicionar um campo material: já não ativa um script de item.