
local builtin_item = minetest.registered_entities["__builtin:item"]

pmb_item_ent = {}

pmb_item_ent.magnet_radius = 3
pmb_item_ent.magnet_speed = 8
pmb_item_ent.grace_period = 2
pmb_item_ent.key = "sneak"

local function get_eyepos(player, y)
    local eyepos = vector.add(player:get_pos(), vector.multiply(player:get_eye_offset(), 0.1))
    eyepos.y = eyepos.y + 1.6 + y
    return eyepos
end

local new_item_ent = {
    _picking_up_by = nil,
    set_item = function(self, item_name)
        builtin_item.set_item(self, item_name)
        self._get_custom_params(self)
    end,
    on_step = function(self, dtime, ...)
        builtin_item.on_step(self, dtime, ...)
        self:_do_magnet(dtime)
    end,
    _set_magnet = function(self, player)
        self._picking_up_by = player
        self:disable_physics()

        local dir = vector.direction(self.object:get_pos(), player:get_pos())
        local vel = vector.multiply(dir, 2)

        self.object:set_velocity(vel)
    end,
    _do_magnet = function(self, dtime, first)
        if self._picking_up_by then
            local player = self._picking_up_by
            local pos = self.object:get_pos()
            local target = get_eyepos(player, -0.7)
            local dist = vector.distance(vector.new(pos.x, target.y, pos.z), target)

            if dist < 0.2 then
                local inv = player:get_inventory()
                local stack = ItemStack(self.itemstring)
                stack:set_count(1)
                if inv:room_for_item("main", stack) then
                    minetest.sound_play("pmb_pickup_item", {
                        gain = 0.3 + math.random() * 0.1,
                        to_player = player:get_player_name(),
                        pitch = 0.8 + math.random() * 0.2
                    })
                    return self.on_punch(self, player)
                else
                    self._picking_up_by = nil
                    self:enable_physics()
                    self.object:set_velocity(vector.new(0,0,0))
                end
            elseif dist < pmb_item_ent.magnet_radius then
                local dir = vector.direction(pos, target)
                dir.y = dir.y + (0.1)
                local vel = vector.multiply(dir, pmb_item_ent.magnet_speed)

                local lvel = self.object:get_velocity()
                lvel = vector.multiply(lvel, -0.07)
                vel = vector.add(lvel, vel)

                self.object:set_velocity(vel)
            elseif dist > pmb_item_ent.magnet_radius + 0.3 then
                self:enable_physics()
                self._picking_up_by = nil
            end
        elseif not self.enable_physics then
            self:enable_physics()
        end
    end,
    _get_custom_params = function(self)
        local stack = ItemStack(self.itemstring)
        local def = minetest.registered_items[stack:get_name()]
        if def and def._override_item_entity then
            local ov = def._override_item_entity
            self.object:set_properties(def._override_item_entity)
        end
    end,
    on_activate = function(self, staticdata, dtime_s)
        builtin_item.on_activate(self, staticdata, dtime_s)
    end,
    on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage, ...)
        -- just delete empty ones
        if self.itemstring == "" then
            self.object:remove()
            return
        end

        if tool_capabilities and tool_capabilities.damage_groups.projectile then
            return
        end

        local itemstack = ItemStack(self.itemstring)
        local itemname = itemstack:get_name()

        -- if the item has an on_pickup function, use it
        local def = minetest.registered_items[itemname]
        if def and def.on_pickup then
            local ret = def.on_pickup(itemstack, puncher, {type = "object", ref = self.object}, ...)
            if ret then
                itemstack = ItemStack(ret)
            end
        end

        -- if nothing left, delete
        if itemstack:is_empty() then
            self.itemstring = ""
            self.object:remove()
        else
            -- otherwise set this to the new stack
            self:set_item(itemstack)
        end
    end,
    get_staticdata = function(self)
        local data = {
            itemstring = self.itemstring,
            age = self.age,
            dropped_by = self.dropped_by,
            --
        }
        return minetest.serialize(data)
    end,
}

setmetatable(new_item_ent, { __index = builtin_item })
minetest.register_entity(":__builtin:item", new_item_ent)

local pl = {}

local function process_item_ent(self, player)
    if self._picking_up_by then return end
    if self.age < pmb_item_ent.grace_period then return end

    local inv = player:get_inventory()
    local stack = ItemStack(self.itemstring)
    stack:set_count(1)
    if not inv:room_for_item("main", stack) then return end

    self._set_magnet(self, player)
end

minetest.register_globalstep(function(dtime)
    for i, player in pairs(minetest.get_connected_players()) do
        if not pl[player] then pl[player] = {t=1} end
        if pl[player].t > 0 then
            pl[player].t = pl[player].t - dtime
        else
            local pi = (player_info and player_info.get(player)) or {ctrl=player:get_player_control()} or {ctrl={}}
            if pi and pi.ctrl[pmb_item_ent.key] then
                pl[player].t = 0.5

                local pos = player:get_pos()
                local objects = minetest.get_objects_inside_radius(pos, pmb_item_ent.magnet_radius)
                for k, obj in pairs(objects) do
                    local ent = obj:get_luaentity()
                    if ent and ent.name == "__builtin:item" then
                        process_item_ent(ent, player)
                    end
                end
            end
        end
    end
end)


