X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=mods%2Ftnt%2Finit.lua;h=2f0ffdc413ec7d6034c9c367c358f9091aad38b0;hb=8d3d558e424f8b5221834bed3780a440ac6b75ab;hp=981cc9ee7be6dcdd009f9d64d52b571d0e733e18;hpb=36d18cb16163405234f92d17897b66d12487476a;p=oweals%2Fminetest_game.git diff --git a/mods/tnt/init.lua b/mods/tnt/init.lua index 981cc9ee..2f0ffdc4 100644 --- a/mods/tnt/init.lua +++ b/mods/tnt/init.lua @@ -1,4 +1,4 @@ - +tnt = {} -- Default to enabled in singleplayer and disabled in multiplayer local singleplayer = minetest.is_singleplayer() local setting = minetest.setting_getbool("enable_tnt") @@ -49,23 +49,22 @@ local function eject_drops(drops, pos, radius) local drop_pos = vector.new(pos) for _, item in pairs(drops) do local count = item:get_count() - local max = item:get_stack_max() - if count > max then - item:set_count(max) - end while count > 0 do - if count < max then - item:set_count(count) - end + local take = math.max(1,math.min(radius * radius, + count, + item:get_stack_max())) rand_pos(pos, drop_pos, radius) - local obj = minetest.add_item(drop_pos, item) + local dropitem = ItemStack(item) + dropitem:set_count(take) + local obj = minetest.add_item(drop_pos, dropitem) if obj then obj:get_luaentity().collect = true - obj:setacceleration({x=0, y=-10, z=0}) - obj:setvelocity({x=math.random(-3, 3), y=10, - z=math.random(-3, 3)}) + obj:setacceleration({x = 0, y = -10, z = 0}) + obj:setvelocity({x = math.random(-3, 3), + y = math.random(0, 10), + z = math.random(-3, 3)}) end - count = count - max + count = count - take end end end @@ -85,27 +84,27 @@ local function add_drop(drops, item) end end -local fire_node = {name="fire:basic_flame"} -local function destroy(drops, pos, cid) - if minetest.is_protected(pos, "") then - return +local function destroy(drops, npos, cid, c_air, c_fire, on_blast_queue, ignore_protection, ignore_on_blast) + if not ignore_protection and minetest.is_protected(npos, "") then + return cid end + local def = cid_data[cid] - if def and def.on_blast then - def.on_blast(vector.new(pos), 1) - return - end - if def and def.flammable then - minetest.set_node(pos, fire_node) + + if not def then + return c_air + elseif not ignore_on_blast and def.on_blast then + on_blast_queue[#on_blast_queue + 1] = {pos = vector.new(npos), on_blast = def.on_blast} + return cid + elseif def.flammable then + return c_fire else - minetest.remove_node(pos) - if def then - local node_drops = minetest.get_node_drops(def.name, "") - for _, item in ipairs(node_drops) do - add_drop(drops, item) - end + local node_drops = minetest.get_node_drops(def.name, "") + for _, item in ipairs(node_drops) do + add_drop(drops, item) end + return c_air end end @@ -122,61 +121,179 @@ local function calc_velocity(pos1, pos2, old_vel, power) -- Add old velocity vel = vector.add(vel, old_vel) + + -- randomize it a bit + vel = vector.add(vel, { + x = math.random() - 0.5, + y = math.random() - 0.5, + z = math.random() - 0.5, + }) + + -- Limit to terminal velocity + dist = vector.length(vel) + if dist > 250 then + vel = vector.divide(vel, dist / 250) + end return vel end -local function entity_physics(pos, radius) - -- Make the damage radius larger than the destruction radius - radius = radius * 2 +local function entity_physics(pos, radius, drops) local objs = minetest.get_objects_inside_radius(pos, radius) for _, obj in pairs(objs) do local obj_pos = obj:getpos() - local obj_vel = obj:getvelocity() local dist = math.max(1, vector.distance(pos, obj_pos)) - if obj_vel ~= nil then - obj:setvelocity(calc_velocity(pos, obj_pos, - obj_vel, radius * 10)) - end - local damage = (4 / dist) * radius - obj:set_hp(obj:get_hp() - damage) + if obj:is_player() then + -- currently the engine has no method to set + -- player velocity. See #2960 + -- instead, we knock the player back 1.0 node, and slightly upwards + local dir = vector.normalize(vector.subtract(obj_pos, pos)) + local moveoff = vector.multiply(dir, dist + 1.0) + local newpos = vector.add(pos, moveoff) + local newpos = vector.add(newpos, {x = 0, y = 0.2, z = 0}) + obj:setpos(newpos) + + obj:set_hp(obj:get_hp() - damage) + else + local do_damage = true + local do_knockback = true + local entity_drops = {} + local luaobj = obj:get_luaentity() + local objdef = minetest.registered_entities[luaobj.name] + + if objdef and objdef.on_blast then + do_damage, do_knockback, entity_drops = objdef.on_blast(luaobj, damage) + end + + if do_knockback then + local obj_vel = obj:getvelocity() + obj:setvelocity(calc_velocity(pos, obj_pos, + obj_vel, radius * 10)) + end + if do_damage then + if not obj:get_armor_groups().immortal then + obj:punch(obj, 1.0, { + full_punch_interval = 1.0, + damage_groups = {fleshy = damage}, + }, nil) + end + end + for _, item in ipairs(entity_drops) do + add_drop(drops, item) + end + end end end -local function add_effects(pos, radius) +local function add_effects(pos, radius, drops) + minetest.add_particle({ + pos = pos, + velocity = vector.new(), + acceleration = vector.new(), + expirationtime = 0.4, + size = radius * 10, + collisiondetection = false, + vertical = false, + texture = "tnt_boom.png", + }) minetest.add_particlespawner({ - amount = 128, - time = 1, + amount = 64, + time = 0.5, minpos = vector.subtract(pos, radius / 2), maxpos = vector.add(pos, radius / 2), - minvel = {x=-20, y=-20, z=-20}, - maxvel = {x=20, y=20, z=20}, + minvel = {x = -10, y = -10, z = -10}, + maxvel = {x = 10, y = 10, z = 10}, minacc = vector.new(), maxacc = vector.new(), minexptime = 1, - maxexptime = 3, - minsize = 8, - maxsize = 16, + maxexptime = 2.5, + minsize = radius * 3, + maxsize = radius * 5, 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_blast.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 = 64, + 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}, + maxacc = {x = 0, y = -10, z = 0}, + minexptime = 0.8, + maxexptime = 2.0, + minsize = radius * 0.66, + maxsize = radius * 2, + texture = texture, + collisiondetection = true, + }) end -local function burn(pos) +function tnt.burn(pos) local name = minetest.get_node(pos).name - if name == "tnt:tnt" then - minetest.sound_play("tnt_ignite", {pos=pos}) - minetest.set_node(pos, {name="tnt:tnt_burning"}) + local group = minetest.get_item_group(name, "tnt") + if group > 0 then + minetest.sound_play("tnt_ignite", {pos = pos}) + minetest.set_node(pos, {name = name .. "_burning"}) minetest.get_node_timer(pos):start(1) elseif name == "tnt:gunpowder" then - minetest.sound_play("tnt_gunpowder_burning", {pos=pos, gain=2}) - minetest.set_node(pos, {name="tnt:gunpowder_burning"}) - minetest.get_node_timer(pos):start(1) + minetest.set_node(pos, {name = "tnt:gunpowder_burning"}) end end -local function explode(pos, radius) +local function tnt_explode(pos, radius, ignore_protection, ignore_on_blast) local pos = vector.round(pos) + -- scan for adjacent TNT nodes first, and enlarge the explosion + local vm1 = VoxelManip() + local p1 = vector.subtract(pos, 2) + local p2 = vector.add(pos, 2) + local minp, maxp = vm1:read_from_map(p1, p2) + local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp}) + local data = vm1:get_data() + local count = 0 + local c_tnt = minetest.get_content_id("tnt:tnt") + local c_tnt_burning = minetest.get_content_id("tnt:tnt_burning") + local c_tnt_boom = minetest.get_content_id("tnt:boom") + local c_air = minetest.get_content_id("air") + + for z = pos.z - 2, pos.z + 2 do + for y = pos.y - 2, pos.y + 2 do + local vi = a:index(pos.x - 2, y, z) + for x = pos.x - 2, pos.x + 2 do + local cid = data[vi] + if cid == c_tnt or cid == c_tnt_boom or cid == c_tnt_burning then + count = count + 1 + data[vi] = c_air + end + vi = vi + 1 + end + end + end + + vm1:set_data(data) + vm1:write_to_map() + + -- recalculate new radius + radius = math.floor(radius * math.pow(count, 1/3)) + + -- perform the explosion local vm = VoxelManip() local pr = PseudoRandom(os.time()) local p1 = vector.subtract(pos, radius) @@ -186,22 +303,21 @@ local function explode(pos, radius) local data = vm:get_data() local drops = {} - local p = {} - - local c_air = minetest.get_content_id("air") + local on_blast_queue = {} + local c_fire = minetest.get_content_id("fire:basic_flame") for z = -radius, radius do for y = -radius, radius do local vi = a:index(pos.x + (-radius), pos.y + y, pos.z + z) for x = -radius, radius do - if (x * x) + (y * y) + (z * z) <= - (radius * radius) + pr:next(-radius, radius) then + local r = vector.length(vector.new(x, y, z)) + if (radius * radius) / (r * r) >= (pr:next(80, 125) / 100) then local cid = data[vi] - p.x = pos.x + x - p.y = pos.y + y - p.z = pos.z + z + local p = {x = pos.x + x, y = pos.y + y, z = pos.z + z} if cid ~= c_air then - destroy(drops, p, cid) + data[vi] = destroy(drops, p, cid, c_air, c_fire, + on_blast_queue, ignore_protection, + ignore_on_blast) end end vi = vi + 1 @@ -209,69 +325,61 @@ local function explode(pos, radius) end end - return drops -end - + vm:set_data(data) + vm:write_to_map() + vm:update_map() + vm:update_liquids() + + -- call nodeupdate for everything within 1.5x blast radius + for z = -radius * 1.5, radius * 1.5 do + for x = -radius * 1.5, radius * 1.5 do + for y = -radius * 1.5, radius * 1.5 do + local s = vector.add(pos, {x = x, y = y, z = z}) + local r = vector.distance(pos, s) + if r / radius < 1.4 then + nodeupdate(s) + end + end + end + end -local function boom(pos) - minetest.sound_play("tnt_explode", {pos=pos, gain=1.5, max_hear_distance=2*64}) - minetest.set_node(pos, {name="tnt:boom"}) - minetest.get_node_timer(pos):start(0.5) + for _, data in ipairs(on_blast_queue) do + local dist = math.max(1, vector.distance(data.pos, pos)) + local intensity = (radius * radius) / (dist * dist) + local node_drops = data.on_blast(data.pos, intensity) + if node_drops then + for _, item in ipairs(node_drops) do + add_drop(drops, item) + end + end + end - local drops = explode(pos, radius) - entity_physics(pos, radius) - eject_drops(drops, pos, radius) - add_effects(pos, radius) + return drops, radius end -minetest.register_node("tnt:tnt", { - description = "TNT", - tiles = {"tnt_top.png", "tnt_bottom.png", "tnt_side.png"}, - is_ground_content = false, - groups = {dig_immediate=2, mesecon=2}, - sounds = default.node_sound_wood_defaults(), - on_punch = function(pos, node, puncher) - if puncher:get_wielded_item():get_name() == "default:torch" then - minetest.sound_play("tnt_ignite", {pos=pos}) - minetest.set_node(pos, {name="tnt:tnt_burning"}) - end - end, - on_blast = function(pos, intensity) - burn(pos) - end, - mesecons = {effector = {action_on = boom}}, -}) - -minetest.register_node("tnt:tnt_burning", { - tiles = { - { - name = "tnt_top_burning_animated.png", - animation = { - type = "vertical_frames", - aspect_w = 16, - aspect_h = 16, - length = 1, - } - }, - "tnt_bottom.png", "tnt_side.png"}, - light_source = 5, - drop = "", - sounds = default.node_sound_wood_defaults(), - on_construct = function(pos) - minetest.get_node_timer(pos):start(4) - end, - on_timer = boom, - -- unaffected by explosions - on_blast = function() end, -}) +function tnt.boom(pos, def) + minetest.sound_play("tnt_explode", {pos = pos, gain = 1.5, max_hear_distance = 2*64}) + minetest.set_node(pos, {name = "tnt:boom"}) + local drops, radius = tnt_explode(pos, def.radius, def.ignore_protection, + def.ignore_on_blast) + -- append entity drops + local damage_radius = (radius / def.radius) * def.damage_radius + entity_physics(pos, damage_radius, drops) + if not def.disable_drops then + eject_drops(drops, pos, radius) + end + add_effects(pos, radius, drops) +end minetest.register_node("tnt:boom", { - drawtype = "plantlike", - tiles = {"tnt_boom.png"}, + drawtype = "airlike", light_source = default.LIGHT_MAX, walkable = false, drop = "", - groups = {dig_immediate=3}, + groups = {dig_immediate = 3}, + on_construct = function(pos) + minetest.get_node_timer(pos):start(0.4) + end, on_timer = function(pos, elapsed) minetest.remove_node(pos) end, @@ -293,16 +401,16 @@ minetest.register_node("tnt:gunpowder", { type = "fixed", fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2}, }, - groups = {dig_immediate=2,attached_node=1,connect_to_raillike=minetest.raillike_group("gunpowder")}, + groups = {dig_immediate = 2, attached_node = 1, connect_to_raillike = minetest.raillike_group("gunpowder")}, sounds = default.node_sound_leaves_defaults(), - + on_punch = function(pos, node, puncher) if puncher:get_wielded_item():get_name() == "default:torch" then - burn(pos) + tnt.burn(pos) end end, on_blast = function(pos, intensity) - burn(pos) + tnt.burn(pos) end, }) @@ -353,14 +461,14 @@ minetest.register_node("tnt:gunpowder_burning", { fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2}, }, drop = "", - groups = {dig_immediate=2,attached_node=1,connect_to_raillike=minetest.raillike_group("gunpowder")}, + groups = {dig_immediate = 2, attached_node = 1, connect_to_raillike = minetest.raillike_group("gunpowder")}, sounds = default.node_sound_leaves_defaults(), on_timer = function(pos, elapsed) for dx = -1, 1 do for dz = -1, 1 do for dy = -1, 1 do if not (dx == 0 and dz == 0) then - burn({ + tnt.burn({ x = pos.x + dx, y = pos.y + dy, z = pos.z + dz, @@ -373,14 +481,18 @@ minetest.register_node("tnt:gunpowder_burning", { end, -- unaffected by explosions on_blast = function() end, + on_construct = function(pos) + minetest.sound_play("tnt_gunpowder_burning", {pos = pos, gain = 2}) + minetest.get_node_timer(pos):start(1) + end, }) minetest.register_abm({ - nodenames = {"tnt:tnt", "tnt:gunpowder"}, + nodenames = {"group:tnt", "tnt:gunpowder"}, neighbors = {"fire:basic_flame", "default:lava_source", "default:lava_flowing"}, interval = 4, chance = 1, - action = burn, + action = tnt.burn, }) minetest.register_craft({ @@ -397,3 +509,80 @@ minetest.register_craft({ {"", "group:wood", ""} } }) + +function tnt.register_tnt(def) + local name = "" + if not def.name:find(':') then + name = "tnt:" .. def.name + else + name = def.name + def.name = def.name:match(":([%w_]+)") + end + if not def.tiles then def.tiles = {} end + local tnt_top = def.tiles.top or def.name .. "_top.png" + local tnt_bottom = def.tiles.bottom or def.name .. "_bottom.png" + local tnt_side = def.tiles.side or def.name .. "_side.png" + local tnt_burning = def.tiles.burning or def.name .. "_top_burning_animated.png" + if not def.damage_radius then def.damage_radius = def.radius * 2 end + + minetest.register_node(":" .. name, { + description = def.description, + tiles = {tnt_top, tnt_bottom, tnt_side}, + is_ground_content = false, + groups = {dig_immediate = 2, mesecon = 2, tnt = 1}, + sounds = default.node_sound_wood_defaults(), + on_punch = function(pos, node, puncher) + if puncher:get_wielded_item():get_name() == "default:torch" then + minetest.set_node(pos, {name = name .. "_burning"}) + end + end, + on_blast = function(pos, intensity) + minetest.after(0.1, function() + tnt.boom(pos, def) + end) + end, + mesecons = {effector = + {action_on = + function(pos) + tnt.boom(pos, def) + end + } + }, + }) + + minetest.register_node(":" .. name .. "_burning", { + tiles = { + { + name = tnt_burning, + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 1, + } + }, + tnt_bottom, tnt_side + }, + light_source = 5, + drop = "", + sounds = default.node_sound_wood_defaults(), + groups = {falling_node = 1}, + on_timer = function(pos, elapsed) + tnt.boom(pos, def) + end, + -- unaffected by explosions + on_blast = function() end, + on_construct = function(pos) + minetest.sound_play("tnt_ignite", {pos = pos}) + minetest.get_node_timer(pos):start(4) + nodeupdate(pos) + end, + }) +end + +tnt.register_tnt({ + name = "tnt:tnt", + description = "TNT", + radius = radius, +}) +