tntrun-cd2025/tnt.lua
2024-11-28 16:59:28 +01:00

199 lines
6.4 KiB
Lua

-- Delay in seconds after which TNT falls down when stepping on it directly
local TNT_FALL_DELAY_DIRECT = 0.25
-- Delay in seconds after which TNT falls down when stepping on it indirectly via sneaking
local TNT_FALL_DELAY_SNEAK = 0.5 -- it's intentional that it's a bit longer
-- Distance from TNT node a player can be so it is still considered "stepped on"
-- by the game. The distance takes X and Z into account separately but not Z.
-- Change this if Luanti changes the sneak distance; works only for sneak distance below 0.5
local SNEAK_DISTANCE = 0.3
-- Time in seconds after which to execute tnt_loop() again
local TNT_LOOP_TIME = 0.1
-- Time in seconds after which the TNT entity dies
local TIME_UNTIL_DESPAWN = 5
-- gravity for particles
local GRAVITY = 9.81
-- Animation settings for TNT fuse
local TNT_FUSE_ANIMATION_FRAMES = 4 -- number of frames
local TNT_FUSE_ANIMATION_FRAME_LENGTH = 0.25 -- length of a frame in seconds
-- Translation boilerplate
local S = minetest.get_translator("tntrun")
local tnt_entity_base_textures = {
"tntrun_tnt_top_burning_animated.png^[verticalframe:"..TNT_FUSE_ANIMATION_FRAMES..":0",
"tntrun_tnt_bottom.png", "tntrun_tnt_side.png", "tntrun_tnt_side.png", "tntrun_tnt_side.png", "tntrun_tnt_side.png"
}
minetest.register_entity("tntrun:tnt_falling", {
initial_properties = {
physical = true,
collide_with_objects = true,
pointable = true,
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
selectionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
visual = "cube",
visual_size = {x = 1, y = 1, z = 1},
textures = tnt_entity_base_textures,
static_save = false,
},
_timer = 0,
_sound_handle = nil,
_last_anim_frame = 0,
on_activate = function(self)
self.object:set_armor_groups({immortal=1})
local pitch = 1 + math.random(-50,50)*0.001
self._sound_handle = minetest.sound_play({name="tntrun_fuse"},
{object=self.object, max_hear_distance=16, gain=0.3, pitch=pitch})
end,
on_deactivate = function(self)
if self._sound_handle then
minetest.sound_stop(self._sound_handle)
end
end,
on_step = function(self, dtime, moveresult)
self.object:set_velocity({x = 0, y = -5, z = 0 })
-- Spawn particles when TNT is removed
local poof = function(pos)
-- TNT break particles
minetest.add_particlespawner({
amount = math.random(15, 30),
time = 0.01,
minpos = vector.add(pos, {x=-0.4, y=-0.5, z=-0.4}),
maxpos = vector.add(pos, {x=0.4, y=0.5, z=0.4}),
minvel = {x=-3.5, y=-0.1, z=-3.5},
maxvel = {x=3.5, y=1.0, z=3.5},
drag = { x=2, y=0, z=2 },
minacc = {x=0, y=-GRAVITY, z=0},
maxacc = {x=0, y=-GRAVITY, z=0},
minexptime = 0.2,
maxexptime = 0.7,
minsize = 1.5,
maxsize = 2.0,
collisiondetection = true,
vertical = false,
node = {name="tntrun:tnt"},
})
-- Smoke
minetest.add_particle({
pos = vector.add(pos, {x=0, y=0.2, z=0}),
velocity = { x=0, y=0.5+math.random(0,5)*0.1, z=0 },
expirationtime = 1.5,
size = 7.0,
collisiondetection = false,
vertical = false,
texture = "tntrun_smoke_puff.png",
})
end
local checkpos = self.object:get_pos()
local tntpos = table.copy(checkpos)
checkpos.y = checkpos.y - 1
local node_below = minetest.registered_nodes[minetest.get_node(checkpos).name]
-- Remove TNT when it collides with node (might be an unknown node, hence the first check)
if node_below and node_below.walkable then
poof(tntpos)
self.object:remove()
return
end
-- Remove TNT after timer
if self._timer > TIME_UNTIL_DESPAWN then
poof(tntpos)
self.object:remove()
return
else
self._timer = self._timer + dtime
end
-- Animate texture manually
-- TODO: Replace this code once Luanti allows to animate
-- entity textures natively.
local frametimer = self._timer * (1/TNT_FUSE_ANIMATION_FRAME_LENGTH)
local frame = math.floor(frametimer) % TNT_FUSE_ANIMATION_FRAMES
if frame ~= self._last_anim_frame then
local textures = table.copy(tnt_entity_base_textures)
textures[1] = "tntrun_tnt_top_burning_animated.png^[verticalframe:"..TNT_FUSE_ANIMATION_FRAMES..":"..frame
self.object:set_properties({
textures = textures,
})
-- Remember the last frame so we only update the
-- textures property if neccessary
self._last_anim_frame = frame
end
end,
})
local sounds
if minetest.get_modpath("default") then
sounds = default.node_sound_wood_defaults()
end
minetest.register_node("tntrun:tnt",{
tiles = {"tntrun_tnt_top.png", "tntrun_tnt_bottom.png", "tntrun_tnt_side.png"},
groups = {cracky = 3, not_in_creative_inventory = 1},
sounds = sounds,
description = S("TNT in Tntrun"),
drops = "tntrun:tnt",
on_punch = function(pos, node, puncher)
if puncher:get_wielded_item():get_name() == "tntrun:torch" then
minetest.remove_node(pos)
minetest.add_entity(pos, "tntrun:tnt_falling")
end
end,
})
minetest.register_craftitem("tntrun:torch",{
inventory_image = "tntrun_torch.png",
description = S("Torch for TNT"),
groups = { not_in_creative_inventory = 1 },
sound = {
punch_use_air = { name = "tntrun_swish", gain = 0.2 },
},
})
local function tnt_loop(arena)
if not arena.in_game then return end
for pl_name, _ in pairs(arena.players) do
local player = minetest.get_player_by_name(pl_name)
local pos = player:get_pos()
pos.y = pos.y - 1
if minetest.get_node(pos).name == "tntrun:tnt" then
-- TNT falls if player stands directly on it
pos.x = math.floor(pos.x+0.5)
pos.z = math.floor(pos.z+0.5)
minetest.after(TNT_FALL_DELAY_DIRECT, function()
if minetest.get_node(pos).name ~= "tntrun:tnt" then
return
end
minetest.remove_node(pos)
minetest.add_entity(pos, "tntrun:tnt_falling")
end)
elseif minetest.get_node(pos).name == "air" then
-- When player does not stand directly on TNT, check for TNT around the player
-- within SNEAK_DISTANCE
for x = -1,1 do
for z = -1,1 do
local posb = {x = pos.x+(x*SNEAK_DISTANCE), y = pos.y, z = pos.z+(z*SNEAK_DISTANCE)}
if minetest.get_node(posb).name == "tntrun:tnt" then
posb.x = math.floor(posb.x+0.5)
posb.z = math.floor(posb.z+0.5)
minetest.after(TNT_FALL_DELAY_SNEAK, function()
if minetest.get_node(posb).name ~= "tntrun:tnt" then
return
end
minetest.remove_node(posb)
minetest.add_entity(posb, "tntrun:tnt_falling")
end)
end
end
end
end
end
minetest.after(TNT_LOOP_TIME, function() tnt_loop(arena) end)
end
arena_lib.on_start("tntrun", function(arena)
tnt_loop(arena)
end)