

local function sqdist(p, l)
    return (
        (p.x - l.x) * (p.x - l.x) +
        (p.y - l.y) * (p.y - l.y) +
        (p.z - l.z) * (p.z - l.z)
    )
end

pmb_combat.explosions = {}

local function hit_object(pos, obj, def, range)
    -- minetest.log("hitting obj")

    -- if minetest.is_player(obj) then minetest.log("player!") end

    local p = obj:get_pos() or pos
    local d = 1 - (vector.distance(p, pos) / range)
    local dg = {}
    for l, v in pairs(def.damage_groups) do
        dg[l] = v * d
    end
    obj:punch(def.owner or obj, 1, {
        full_punch_interval = 1,
        damage_groups = dg,
    }, vector.direction(pos, p))
end

local function can_dig_node(groupcaps, node)
    local can_drop = true
    for group, def in pairs(groupcaps or {}) do
        local gval = minetest.get_item_group(node.name, group)
        if gval > 0 then
            can_drop = false
            if ((not def.max_drop_level)
            or (def.max_drop_level and def.max_drop_level >= gval)) then
                can_drop = true
                break
            end
        end
    end
    return can_drop
end

function pmb_combat.explosions.explode(pos, def)
    def.radius = def.radius or 3
    local particles = def.particles

    local minp = vector.offset(pos, -def.radius, -def.radius, -def.radius)
    local maxp = vector.offset(pos,  def.radius,  def.radius,  def.radius)

    local damage_range = def.radius * (def.damage_range_factor or 1)
    if def.damage_groups then
        for i, obj in ipairs(minetest.get_objects_inside_radius(pos, damage_range)) do
            if obj ~= def.owner then
                hit_object(pos, obj, def, damage_range)
            end
        end
    end

    if def.no_terrain_damage then return end

    for z = minp.z, maxp.z, 1 do
    for x = minp.x, maxp.x, 1 do
    for y = maxp.y, minp.y, -1 do
    repeat
        local p = vector.new(x, y, z)
        local dist2 = (p.x-pos.x)^2 + (p.y-pos.y)^2 + (p.z-pos.z)^2

        -- minetest.dig_node will not work if it's exactly 0 or something idk #no-bugs
        if p.x == 0 then p.x = 0.001 end
        if p.y == 0 then p.y = 0.001 end
        if p.z == 0 then p.z = 0.001 end

        if (not def.cubic_radius) and (dist2 > def.radius^2) then break end

        local node = minetest.get_node(p)
        if node.name == "air" then break end
        if def.groupcaps and not can_dig_node(def.groupcaps, node) then break end

        local igroup = minetest.get_item_group(node.name, "blast_resistance")
        if (igroup > 0) and (igroup^2 > def.radius^2 - dist2) or (igroup == -1) then break end

        local ndef = minetest.registered_nodes[node.name] or {}
        if ndef._pmb_on_explode then
            ndef._pmb_on_explode(p)
        end

        if def.no_drops then
            minetest.set_node(p, {name="air"})
            pmb_node_update.update_node(p, "dig", nil, 15, nil, {}, nil)
            -- minetest.log("destroying "..node.name)
        else
            minetest.dig_node(p, nil)
        end

        if particles then
            for i, particledef in pairs(particles) do
                minetest.add_particlespawner(particledef)
            end
        end
    until true
    end
    end
    end
end

