local S = minetest.get_translator("mcl_tnt") local tnt_griefing = minetest.settings:get_bool("mcl_tnt_griefing", true) local mod_death_messages = minetest.get_modpath("mcl_death_messages") local function spawn_tnt(pos, entname) minetest.sound_play("tnt_ignite", {pos = pos,gain = 1.0,max_hear_distance = 15,}, true) local tnt = minetest.add_entity(pos, entname) tnt:set_armor_groups({immortal=1}) return tnt end local function activate_if_tnt(nname, np, tnt_np, tntr) if nname == "mcl_tnt:tnt" then local e = spawn_tnt(np, nname) e:set_velocity({x=(np.x - tnt_np.x)*5+(tntr / 4), y=(np.y - tnt_np.y)*5+(tntr / 3), z=(np.z - tnt_np.z)*5+(tntr / 4)}) minetest.remove_node(np) minetest.check_for_falling(np) end end local function do_tnt_physics(tnt_np, tntr, tnt_obj) local objs = minetest.get_objects_inside_radius(tnt_np, tntr) for k, obj in pairs(objs) do local ent = obj:get_luaentity() local v = obj:get_velocity() local p = obj:get_pos() if ent and ent.name == "mcl_tnt:tnt" and v ~= nil then obj:set_velocity({x=(p.x - tnt_np.x) + (tntr / 2) + v.x, y=(p.y - tnt_np.y) + tntr + v.y, z=(p.z - tnt_np.z) + (tntr / 2) + v.z}) else if v ~= nil and not obj:is_player() then obj:set_velocity({x=(p.x - tnt_np.x) + (tntr / 4) + v.x, y=(p.y - tnt_np.y) + (tntr / 2) + v.y, z=(p.z - tnt_np.z) + (tntr / 4) + v.z}) end local dist = math.max(1, vector.distance(tnt_np, p)) local damage = (4 / dist) * tntr if obj:is_player() == true then if mod_death_messages then mcl_death_messages.player_damage(obj, S("@1 was caught in an explosion.", obj:get_player_name())) end end local puncher = tnt_obj or obj obj:punch(puncher, nil, { damage_groups = { fleshy = damage }}) end end end tnt = {} tnt.ignite = function(pos) minetest.remove_node(pos) spawn_tnt(pos, "mcl_tnt:tnt") minetest.check_for_falling(pos) end -- Add smoke particle of entity at pos. -- Intended to be called every step tnt.smoke_step = function(pos) minetest.add_particle({ pos = {x=pos.x,y=pos.y+0.5,z=pos.z}, velocity = vector.new(math.random() * 0.2 - 0.1, 1.0 + math.random(), math.random() * 0.2 - 0.1), acceleration = vector.new(0, -0.1, 0), expirationtime = 0.15 + math.random() * 0.25, size = 1.0 + math.random(), collisiondetection = false, texture = "tnt_smoke.png" }) end tnt.BOOMTIMER = 4 tnt.BLINKTIMER = 0.25 local TNT_RANGE = 3 local sounds if minetest.get_modpath("mcl_sounds") then sounds = mcl_sounds.node_sound_wood_defaults() end local tnt_mesecons if minetest.get_modpath("mesecons") then tnt_mesecons = {effector = { action_on = tnt.ignite, rules = mesecon.rules.alldirs, }} end local longdesc if tnt_griefing then longdesc = S("An explosive device. When it explodes, it will hurt living beings and destroy blocks around it. TNT has an explosion radius of @1. With a small chance, blocks may drop as an item (as if being mined) rather than being destroyed. TNT can be ignited by tools, explosions, fire, lava and redstone signals.", TNT_RANGE) else longdesc = S("An explosive device. When it explodes, it will hurt living beings. TNT has an explosion radius of @1. TNT can be ignited by tools, explosions, fire, lava and redstone signals.", TNT_RANGE) end minetest.register_node("mcl_tnt:tnt", { tiles = {"default_tnt_top.png", "default_tnt_bottom.png", "default_tnt_side.png", "default_tnt_side.png", "default_tnt_side.png", "default_tnt_side.png"}, is_ground_content = false, stack_max = 64, description = S("TNT"), paramtype = "light", sunlight_propagates = true, _tt_help = S("Ignited by tools, explosions, fire, lava, redstone power").."\n"..S("Explosion radius: @1", tostring(TNT_RANGE)), _doc_items_longdesc = longdesc, _doc_items_usagehelp = S("Place the TNT and ignite it with one of the methods above. Quickly get in safe distance. The TNT will start to be affected by gravity and explodes in 4 seconds."), groups = { dig_immediate = 3, tnt = 1, enderman_takable=1 }, mesecons = tnt_mesecons, on_blast = function(pos) local e = spawn_tnt(pos, "mcl_tnt:tnt") e:get_luaentity().timer = tnt.BOOMTIMER - (0.5 + math.random()) return true end, _on_ignite = function(player, pointed_thing) tnt.ignite(pointed_thing.under) return true end, _on_dispense = function(stack, pos, droppos, dropnode, dropdir) -- Place and ignite TNT if minetest.registered_nodes[dropnode.name].buildable_to then minetest.set_node(droppos, {name = stack:get_name()}) tnt.ignite(droppos) end end, sounds = sounds, }) local TNT = { -- Static definition physical = true, -- Collides with things --weight = -100, collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, visual = "cube", textures = {"default_tnt_top.png", "default_tnt_bottom.png", "default_tnt_side.png", "default_tnt_side.png", "default_tnt_side.png", "default_tnt_side.png"}, -- Initial value for our timer timer = 0, blinktimer = 0, tnt_knockback = true, blinkstatus = true,} function TNT:on_activate(staticdata) local phi = math.random(0, 65535) / 65535 * 2*math.pi local hdir_x = math.cos(phi) * 0.02 local hdir_z = math.sin(phi) * 0.02 self.object:set_velocity({x=hdir_x, y=2, z=hdir_z}) self.object:set_acceleration({x=0, y=-10, z=0}) self.object:set_texture_mod("^mcl_tnt_blink.png") end local function add_effects(pos, radius, drops) minetest.add_particlespawner({ amount = 64, time = 0.5, minpos = vector.subtract(pos, radius / 2), maxpos = vector.add(pos, radius / 2), minvel = {x = -10, y = -10, z = -10}, maxvel = {x = 10, y = 10, z = 10}, minacc = vector.new(), maxacc = vector.new(), minexptime = 1, maxexptime = 2.5, minsize = radius * 1, maxsize = radius * 3, texture = "tnt_smoke.png", }) -- we just dropped some items. Look at the items entities and pick -- one of them to use as texture local texture = "tnt_smoke.png" --fallback texture local most = 0 for name, stack in pairs(drops) do local count = stack:get_count() if count > most then most = count local def = minetest.registered_nodes[name] if def and def.tiles and def.tiles[1] then texture = def.tiles[1] end end end minetest.add_particlespawner({ amount = 32, time = 0.1, minpos = vector.subtract(pos, radius / 2), maxpos = vector.add(pos, radius / 2), minvel = {x = -3, y = 0, z = -3}, maxvel = {x = 3, y = 5, z = 3}, minacc = {x = 0, y = -10, z = 0}, minexptime = 0.8, maxexptime = 2.0, minsize = radius * 0.66, maxsize = radius * 2, texture = texture, collisiondetection = true, }) end function TNT:on_step(dtime) local pos = self.object:get_pos() tnt.smoke_step(pos) self.timer = self.timer + dtime self.blinktimer = self.blinktimer + dtime if self.blinktimer > tnt.BLINKTIMER then self.blinktimer = self.blinktimer - tnt.BLINKTIMER if self.blinkstatus then self.object:set_texture_mod("") else self.object:set_texture_mod("^mcl_tnt_blink.png") end self.blinkstatus = not self.blinkstatus end if self.timer > tnt.BOOMTIMER then mcl_explosions.explode(self.object:get_pos(), 4, { drop_chance = 1.0 }, self.object) self.object:remove() end end tnt.boom = function(pos, info, tnt_obj) if not info then info = {} end local range = info.radius or TNT_RANGE local damage_range = info.damage_radius or TNT_RANGE pos.x = math.floor(pos.x+0.5) pos.y = math.floor(pos.y+0.5) pos.z = math.floor(pos.z+0.5) do_tnt_physics(pos, range, tnt_obj) local meta = minetest.get_meta(pos) local sound if not info.sound then sound = "tnt_explode" else sound = info.sound end if info.is_tnt == nil then info.is_tnt = true end minetest.sound_play(sound, {pos = pos,gain = 1.0,max_hear_distance = 16,}, true) local node = minetest.get_node(pos) if minetest.get_item_group("water") == 1 or minetest.get_item_group("lava") == 1 then -- Cancel the Explosion return end for x=-range,range do for y=-range,range do for z=-range,range do if x*x+y*y+z*z <= range * range + range then local np={x=pos.x+x,y=pos.y+y,z=pos.z+z} local n = minetest.get_node(np) local def = minetest.registered_nodes[n.name] -- Simple blast resistance check (for now). This keeps the important blocks like bedrock, command block, etc. intact. -- TODO: Implement the real blast resistance algorithm if def and n.name ~= "air" and n.name ~= "ignore" and (def._mcl_blast_resistance == nil or def._mcl_blast_resistance < 1000) then activate_if_tnt(n.name, np, pos, 3) if (not tnt_griefing) and info.is_tnt ~= false then -- No-op -- Custom blast function defined by node. -- Node removal and drops must be handled manually. elseif def.on_blast then def.on_blast(np, 1.0) -- Default destruction handling: Remove nodes, drop items else minetest.remove_node(np) minetest.check_for_falling(np) if n.name ~= "mcl_tnt:tnt" and math.random() > 0.9 then local drop = minetest.get_node_drops(n.name, "") for _,item in ipairs(drop) do if type(item) == "string" then if math.random(1,100) > 40 then local obj = minetest.add_item(np, item) end end end end end end end end end add_effects(pos, range, {}) end end minetest.register_entity("mcl_tnt:tnt", TNT) if minetest.get_modpath("mcl_mobitems") then minetest.register_craft({ output = "mcl_tnt:tnt", recipe = { {'mcl_mobitems:gunpowder','group:sand','mcl_mobitems:gunpowder'}, {'group:sand','mcl_mobitems:gunpowder','group:sand'}, {'mcl_mobitems:gunpowder','group:sand','mcl_mobitems:gunpowder'} } }) end if minetest.get_modpath("doc_identifier") then doc.sub.identifier.register_object("mcl_tnt:tnt", "nodes", "mcl_tnt:tnt") end