local utils = ... local S = utils.S local explode = { } if minetest.global_exists ("fire") then explode.fire_supported = true function explode.set_fire (pos, burn_all) local node = utils.get_far_node (pos) if not node then return end if node.name ~= "air" then local def = minetest.registered_nodes[node.name] if not def or not def.buildable_to then return end end local dirs = { { x = 0, y = -1, z = 0 }, { x = -1, y = 0, z = 0 }, { x = 0, y = 0, z = -1 }, { x = 1, y = 0, z = 0 }, { x = 0, y = 0, z = 1 } } for i = 1, #dirs do node = utils.get_far_node (vector.add (pos, dirs[i])) if node and node.name ~= "air" and node.name ~= "fire:basic_flame" then local def = minetest.registered_nodes[node.name] if def and def.liquidtype == "none" then if (def.groups and def.groups.flammable) or burn_all then minetest.set_node (pos, { name = "fire:basic_flame" }) return end end end end end else explode.fire_supported = false function explode.set_fire (pos, burn_all) end end local function is_same_item (stack1, stack2) local copy1 = ItemStack (stack1) local copy2 = ItemStack (stack2) if copy1 and copy2 then copy1:set_count (1) copy2:set_count (1) if copy1:to_string () == copy2:to_string () then return true end end return false end local function dig_node (pos, toolname) local node = utils.get_far_node (pos) local dig = false local drops = nil if toolname == true then dig = true toolname = nil end if node and node.name ~= "air" then local def = utils.find_item_def (node.name) if not dig then if def and def.can_dig then local result, can_dig = pcall (def.can_dig, pos) dig = ((not result) or (result and (can_dig == nil or can_dig == true))) else dig = true end end if dig then local items = minetest.get_node_drops (node, toolname) if items then drops = { } for i = 1, #items do drops[i] = ItemStack (items[i]) end if def and def.preserve_metadata then def.preserve_metadata (pos, node, minetest.get_meta (pos), drops) end end minetest.remove_node (pos) end end return drops end local function add_drops (drops, drop) if drops and drop then for i = 1, #drop do local item = ItemStack (drop[i]) if item and not item:is_empty () then local existing = drops[item:get_name ()] if existing and is_same_item (item, existing) then existing:set_count (existing:get_count () + item:get_count ()) else drops[item:get_name ()] = item end end end end end local function explode_node (pos, dig_chance, intensity, drops, filter) if not utils.is_protected (pos, nil) then dig_chance = math.min (math.max (dig_chance, 0), 100) if math.random (100) <= dig_chance then local node = utils.get_far_node (pos) local blasted = false if node and node.name ~= "air" then local def = minetest.registered_nodes[node.name] if def then if def.diggable == false then return false end for k, v in pairs (filter) do if def[k] == nil then if filter[k.."_undefined"] == false then return false end elseif def[k] ~= v then return false end end if def.on_blast then def.on_blast (pos, intensity) blasted = true end end if not blasted then local drop = dig_node (pos, true) add_drops (drops, drop) end minetest.check_for_falling ({ x = pos.x, y = pos.y + 1, z = pos.z }) return true end end end return false end local function burn_node (pos, fire_chance, burn_all) if not utils.is_protected (pos, nil) then fire_chance = math.min (math.max (fire_chance, 0), 100) if math.random (100) <= fire_chance then explode.set_fire (pos, burn_all) end end end local function entity_is_drop (obj) return obj.get_luaentity and obj:get_luaentity () and obj:get_luaentity ().name and obj:get_luaentity ().name == "__builtin:item" end local function explode_entities (pos, radius, damage, drops) local objs = minetest.get_objects_inside_radius (pos, radius) for _, obj in pairs (objs) do local obj_pos = obj:get_pos () local dir = vector.direction (pos, obj_pos) local dist = vector.length (vector.subtract (obj_pos, pos)) local vel = vector.multiply (dir, ((radius + 1) - dist) / (radius + 1) * damage * 5) if entity_is_drop (obj) then obj:add_velocity (vel) elseif not obj:get_armor_groups ().immortal then local ent_damage = ((radius - dist) / radius * damage / 2) + (damage / 2) local reason = { type = "set_hp", from = "lwcomponents" } if obj:is_player() then obj:add_velocity (vel) obj:set_hp (obj:get_hp() - ent_damage, reason) else local luaobj = obj:get_luaentity() -- object might have disappeared somehow if luaobj then local do_damage = true local do_knockback = true local entity_drops = {} local objdef = minetest.registered_entities[luaobj.name] if objdef and objdef.on_blast then do_damage, do_knockback, entity_drops = objdef.on_blast (luaobj, ent_damage) end if do_knockback then obj:add_velocity (vel) end if do_damage then obj:set_hp (obj:get_hp() - ent_damage, reason) end add_drops (drops, entity_drops) end end end end end local function spray_drops (pos, drops, damage) local max_vel = damage * 2.5 for k, stack in pairs (drops) do local vel = { x = math.random (max_vel) - (max_vel / 2), y = math.random (max_vel) - (max_vel / 2), z = math.random (max_vel) - (max_vel / 2) } local drop = minetest.add_item (pos, stack) if drop then drop:set_velocity (vel) end end end local function add_effects (pos, radius, drops) minetest.add_particle ({ pos = pos, velocity = vector.new (), acceleration = vector.new (), expirationtime = 0.4, size = 30, -- radius * 10, collisiondetection = false, vertical = false, texture = "lwcomponents_boom.png", glow = 15, }) 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 = 9, -- radius * 3, maxsize = 15, -- radius * 5, texture = "lwcomponents_smoke.png", }) -- we just dropped some items. Look at the items entities and pick -- one of them to use as texture local texture = "lwcomponents_blast.png" --fallback texture local node local most = 0 if drops then 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 then node = { name = name } end if def and def.tiles and def.tiles[1] then if type (def.tiles[1]) == "table" then texture = def.tiles[1].name or "lwcomponents_blast.png" elseif type (def.tiles[1]) == "string" then texture = def.tiles[1] end end 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 = 1, -- radius * 0.33, maxsize = 3, -- radius, texture = texture, -- ^ only as fallback for clients without support for `node` parameter node = node, collisiondetection = true, }) end function utils.boom (pos, -- center of explosion node_radius, node_chance, -- radius and chance in 100 fire_radius, fire_chance, -- radius and chance in 100 entity_radius, entity_damage, -- radius and max damage applied disable_drops, -- true to disable drops node_filter, -- node filter table as { buildable_to = true, buildable_to_undefined = false, ... } burn_all, -- true to set fire to anything, otherwise only flammable sound) -- sound on blast, if nil plays default pos = vector.round (pos) node_radius = math.floor (node_radius or 1) fire_radius = math.floor (fire_radius or node_radius) entity_radius = math.floor (entity_radius or node_radius * 2) node_chance = node_chance or 80 fire_chance = fire_chance or 30 entity_damage = math.floor (entity_damage or entity_radius) disable_drops = disable_drops == true node_filter = node_filter or { } burn_all = burn_all == true sound = sound or "lwcannon" local drops = { } local effects_radius = (node_radius > 0 and node_radius) or entity_radius local center_free = false if not utils.is_protected (pos, nil) then local center_node = utils.get_far_node (pos) if not node or node.name == "air" then center_free = true end end if node_radius > 0 and node_chance > 0 then local extents = node_radius * 2 for y = -extents, extents, 1 do for z = -extents, extents, 1 do for x = -extents, extents, 1 do local node_pos = { x = x + pos.x, y = y + pos.y, z = z + pos.z } local length = vector.length ({ x = x, y = y, z = z }) if node_chance > 0 and length <= node_radius then if explode_node (node_pos, node_chance, 1.0, drops, node_filter) then if vector.equals (pos, node_pos) then center_free = true end end end end end end end if fire_radius > 0 and fire_chance > 0 then local extents = fire_radius * 2 for y = -extents, extents, 1 do for z = -extents, extents, 1 do for x = -extents, extents, 1 do local node_pos = { x = x + pos.x, y = y + pos.y, z = z + pos.z } local length = vector.length ({ x = x, y = y, z = z }) if fire_chance > 0 and length <= fire_radius then burn_node (node_pos, fire_chance, burn_all) end end end end end minetest.sound_play (sound, { pos = pos, gain = 2.5, max_hear_distance = math.min (effects_radius * 20, 128) }, true) if center_free then minetest.set_node (pos, { name = "lwcomponents:boom" }) end explode_entities (pos, entity_radius, entity_damage, drops) if not disable_drops then spray_drops (pos, drops, entity_damage) end add_effects (pos, effects_radius, drops) minetest.log ("action", "A Shell explosion occurred at " .. minetest.pos_to_string (pos) .. " with radius " .. entity_radius) end minetest.register_node ("lwcomponents:boom", { description = S("Boom"), drawtype = "airlike", tiles = { "lwcomponents_boom.png" }, inventory_image = "lwcomponents_boom.png", wield_image = "lwcomponents_boom.png", light_source = default.LIGHT_MAX, use_texture_alpha = "blend", sunlight_propagates = true, walkable = false, pointable = false, diggable = false, climbable = false, buildable_to = true, floodable = true, is_ground_content = false, drop = "", paramtype = "light", param1 = 255, post_effect_color = { a = 128, r = 255, g = 0, b = 0 }, groups = { dig_immediate = 3, not_in_creative_inventory = 1 }, on_construct = function (pos) minetest.get_node_timer (pos):start (0.5) end, on_timer = function (pos, elapsed) minetest.remove_node (pos) return false end, -- unaffected by explosions on_blast = function() end, }) --