if not core.global_exists("mcl_campfires") then
    local PARTICLE_DISTANCE = 75

    local player_particlespawners = {}
    local food_entities = {}

    local campfire_spots = {
        vector.new(-0.25, -0.04, -0.25),
        vector.new(0.25, -0.04, -0.25),
        vector.new(0.25, -0.04, 0.25),
        vector.new(-0.25, -0.04, 0.25),
    }

    local function count_table(tbl)
        local count = 0
        if type(tbl) == "table" then for _, _ in pairs(tbl) do count = count + 1 end end
        return count
    end

    local function drop_items(pos)
        local ph = core.hash_node_position(vector.round(pos))
        if food_entities[ph] then
            for _, v in pairs(food_entities[ph]) do
                if v and v.object and v.object:get_pos() then
                    v.object:remove()
                    core.add_item(pos, v._item)
                end
            end
            food_entities[ph] = nil
        end
    end

    local function campfire_drops(pos, digger, drops, nodename)
        -- local wield_item = digger:get_wielded_item()
        local inv = digger:get_inventory()
        if not core.is_creative_enabled(digger:get_player_name()) then
            -- local is_book = wield_item:get_name() == "mcl_enchanting:book_enchanted"
            -- if mcl_enchanting.has_enchantment(wield_item, "silk_touch") and not is_book then
            --     core.add_item(pos, nodename)
            -- else
            core.add_item(pos, drops)
            -- end
        elseif inv:room_for_item("main", nodename) and not inv:contains_item("main", nodename) then
            inv:add_item("main", nodename)
        end
    end

    local function on_blast(pos)
        drop_items(pos)
        core.remove_node(pos)
    end

    local function light_campfire(pos)
        local campfire = core.get_node(pos)
        local name = campfire.name .. "_lit"
        core.set_node(pos, { name = name, param2 = campfire.param2 })
    end

    local function delete_entities(ph)
        if not food_entities[ph] then return end
        for _, v in pairs(food_entities[ph]) do
            if v and v.object then
                v:remove()
            end
        end
        food_entities[ph] = nil
    end

    local function get_free_spot(ph)
        if not food_entities[ph] then
            food_entities[ph] = {}
            return 1
        end
        for i = 1, 4 do
            local v = food_entities[ph][i]
            if not v or not v.object or not v.object:get_pos() then
                food_entities[ph][i] = nil
                return i
            end
        end
    end

    -- on_rightclick function to take items that are cookable in a campfire, and put them in the campfire inventory
    local function take_item(pos, _, player, itemstack)
        if core.get_item_group(itemstack:get_name(), "campfire_cookable") ~= 0 or core.get_item_group(itemstack:get_name(), "food_meat_raw") ~= 0 then
            local cookable = core.get_craft_result({ method = "cooking", width = 1, items = { itemstack } })
            if cookable then
                local ph = core.hash_node_position(vector.round(pos))
                local spot = get_free_spot(ph)
                if not spot then return end

                local o = core.add_entity(pos + campfire_spots[spot], "campfires:food_entity")
                o:set_properties({
                    wield_item = itemstack:get_name(),
                })
                local l = o:get_luaentity()
                l._campfire_poshash = ph
                l._start_time = core.get_gametime()
                l._cook_time = cookable.time * 3 --apparently it always takes 30 secs in mc?
                l._item = itemstack:get_name()
                l._drop = cookable.item:get_name()
                l._spot = spot
                food_entities[ph][spot] = l
                if not core.is_creative_enabled(player:get_player_name()) then
                    itemstack:take_item()
                end
                return itemstack
            end
        end
    end

    local function register_campfire(name, def)
        -- Define Campfire
        core.register_node(name, {
            description = def.description,
            _tt_help = "Cooks food and keeps bees happy.",
            _doc_items_longdesc =
            "Campfires have multiple uses, including keeping bees happy, cooking raw meat and fish, and as a trap.",
            inventory_image = def.inv_texture,
            wield_image = def.inv_texture,
            drawtype = "mesh",
            mesh = "mcl_campfires_campfire.obj",
            tiles = { { name = "mcl_campfires_log.png" }, },
            use_texture_alpha = "clip",
            groups = { oddly_breakable_by_hand = 1, choppy = 1, material_wood = 1, not_in_creative_inventory = 1, campfire = 1, unmovable_by_piston = 1 },
            paramtype = "light",
            paramtype2 = "4dir",
            on_ignite = function(pos, user)
                light_campfire(pos)
                return true
            end,
            -- _on_arrow_hit = function(pos, arrowent)
            --     if mcl_burning.is_burning(arrowent.object) then
            --         light_campfire(pos)
            --     end
            -- end,
            drop = "",
            sounds = stoneage.sounds.campfire,
            selection_box = {
                type = 'fixed',
                fixed = { -.5, -.5, -.5, .5, -.05, .5 }, --left, bottom, front, right, top
            },
            collision_box = {
                type = 'fixed',
                fixed = { -.5, -.5, -.5, .5, -.05, .5 },
            },
            -- _mcl_blast_resistance = 2,
            -- _mcl_hardness = 2,
            after_dig_node = function(pos, _, _, digger)
                campfire_drops(pos, digger, def.drops, name .. "_lit")
            end,
        })

        --Check for a protection violation on a single position.
        local function check_position_protection(position, player)
            local pname = player and player:get_player_name() or ""

            if core.is_protected(position, pname) then
                core.record_protection_violation(position, pname)
                return true
            end

            return false
        end

        local function get_wear(toolname, diggroup)
            local tdef = core.registered_tools[toolname]
            local uses = tdef.tool_capabilities.groupcaps[diggroup].uses
            return math.ceil(65535 / uses)
        end


        --Define Lit Campfire
        core.register_node(name .. "_lit", {
            description = def.description,
            _tt_help = "Cooks food and keeps bees happy.",
            _doc_items_longdesc =
            "Campfires have multiple uses, including keeping bees happy, cooking raw meat and fish, and as a trap.",
            inventory_image = def.inv_texture,
            wield_image = def.inv_texture,
            drawtype = "mesh",
            mesh = "mcl_campfires_campfire.obj",
            tiles = { {
                name = def.fire_texture,
                animation = {
                    type = "vertical_frames",
                    aspect_w = 32,
                    aspect_h = 16,
                    length = 2.0
                }
            }
            },
            overlay_tiles = { {
                name = def.lit_logs_texture,
                animation = {
                    type = "vertical_frames",
                    aspect_w = 32,
                    aspect_h = 16,
                    length = 2.0,
                }
            }
            },
            use_texture_alpha = "clip",
            groups = { oddly_breakable_by_hand = 1, choppy = 1, material_wood = 1, lit_campfire = 1, deco_block = 1, unmovable_by_piston = 1 },
            paramtype = "light",
            paramtype2 = "4dir",
            on_destruct = function(pos)
                local ph = core.hash_node_position(vector.round(pos))
                for k, v in pairs(player_particlespawners) do
                    if v[ph] then
                        core.delete_particlespawner(v[ph])
                        player_particlespawners[k][ph] = nil
                    end
                end
            end,
            on_rightclick = function(pos, node, player, itemstack, pointed_thing)
                if core.get_item_group(itemstack:get_name(), "shovel") ~= 0 then
                    local protected = check_position_protection(pos, player)
                    if not protected then
                        if not core.is_creative_enabled(player:get_player_name()) then
                            -- Add wear (as if digging a crumbly node)
                            local toolname = itemstack:get_name()
                            local wear = get_wear(toolname, "crumbly")
                            if wear then
                                itemstack:add_wear(wear)
                            end
                        end
                        node.name = name
                        core.set_node(pos, node)
                        core.sound_play("fire_extinguish_flame", { pos = pos, gain = 0.25, max_hear_distance = 16 },
                            true)
                    end
                elseif core.get_item_group(itemstack:get_name(), "campfire_cookable") ~= 0 or core.get_item_group(itemstack:get_name(), "food_meat_raw") ~= 0 then
                    take_item(pos, node, player, itemstack)
                elseif itemstack and player and pointed_thing then
                    core.item_place_node(itemstack, player, pointed_thing)
                end

                return itemstack
            end,
            drop = "",
            light_source = def.lightlevel,
            sounds = stoneage.sounds.campfire,
            selection_box = {
                type = "fixed",
                fixed = { -.5, -.5, -.5, .5, -.05, .5 }, --left, bottom, front, right, top
            },
            collision_box = {
                type = "fixed",
                fixed = { -.5, -.5, -.5, .5, -.05, .5 },
            },
            -- _mcl_blast_resistance = 2,
            -- _mcl_hardness = 2,
            on_blast = on_blast,
            after_dig_node = function(pos, _, _, digger)
                drop_items(pos)
                campfire_drops(pos, digger, def.drops, name .. "_lit")
            end,
            -- _mcl_campfires_smothered_form = name,
        })
    end

    local function get_collisionbox(obj, smaller, storage)
        local cache = storage.collisionbox_cache
        if cache then
            local box = cache[smaller and 2 or 1]
            return box[1], box[2]
        else
            local box = obj:get_properties().collisionbox
            local minp, maxp = vector.new(box[1], box[2], box[3]), vector.new(box[4], box[5], box[6])
            local s_vec = vector.new(0.1, 0.1, 0.1)
            local s_minp = vector.add(minp, s_vec)
            local s_maxp = vector.subtract(maxp, s_vec)
            storage.collisionbox_cache = { { minp, maxp }, { s_minp, s_maxp } }
            return minp, maxp
        end
    end

    local cf_storage = {}

    local function update_hud(player)
        local hud_elem_type_field = "type"
        if not core.features.hud_def_type_field then
            hud_elem_type_field = "hud_elem_type"
        end

        local animation_frames = tonumber(core.settings:get("fire_animation_frames")) or 8
        local hud_flame_animated = "mcl_burning_hud_flame_animated.png^[opacity:180^[verticalframe:" ..
            animation_frames .. ":"

        local storage = player:is_player() and cf_storage[player] or player:get_luaentity()
        if not storage.fire_hud_id then
            storage.animation_frame = 1
            storage.fire_hud_id = player:hud_add({
                [hud_elem_type_field] = "image",
                position = { x = 0.5, y = 0.5 },
                scale = { x = -100, y = -100 },
                text = hud_flame_animated .. storage.animation_frame,
                z_index = 1000,
            })
        else
            storage.animation_frame = storage.animation_frame + 1
            if storage.animation_frame > animation_frames - 1 then
                storage.animation_frame = 0
            end
            player:hud_change(storage.fire_hud_id, "text", hud_flame_animated .. storage.animation_frame)
        end
    end

    local function set_on_fire(obj, burn_time)
        if obj:get_hp() < 0 then
            return
        end

        local luaentity = obj:get_luaentity()
        if luaentity and luaentity.fire_resistant then
            return
        end

        if obj:is_player() and not core.settings:get_bool("enable_damage") then
            return
        else
            local max_fire_prot_lvl = 0
            -- local inv = mcl_util.get_inventory(obj)
            -- local armor_list = inv and inv:get_list("armor")

            -- if armor_list then
            --     for _, stack in pairs(armor_list) do
            --         local fire_prot_lvl = mcl_enchanting.get_enchantment(stack, "fire_protection")
            --         if fire_prot_lvl > max_fire_prot_lvl then
            --             max_fire_prot_lvl = fire_prot_lvl
            --         end
            --     end
            -- end
            if max_fire_prot_lvl > 0 then
                burn_time = burn_time - math.floor(burn_time * max_fire_prot_lvl * 0.15)
            end
        end

        local storage = obj:is_player() and cf_storage[obj] or obj:get_luaentity()
        if storage.burn_time then
            if burn_time > storage.burn_time then
                storage.burn_time = burn_time
            end
            return
        end
        storage.burn_time = burn_time
        storage.fire_damage_timer = 0

        local minp, maxp = get_collisionbox(obj, false, storage)
        local size = vector.subtract(maxp, minp)
        size = vector.multiply(size, vector.new(1.1, 1.2, 1.1))
        size = vector.divide(size, obj:get_properties().visual_size)

        local fire_entity = core.add_entity(obj:get_pos(), "fire:basic_flame")
        if fire_entity and fire_entity:get_pos() then
            fire_entity:set_properties({ visual_size = size })
            fire_entity:set_attach(obj, "", vector.new(0, size.y * 5, 0), vector.new(0, 0, 0))
        end

        if obj:is_player() then
            update_hud(obj)
        end
    end

    local function burn_in_campfire(obj)
        local p = obj:get_pos()
        if p then
            local n = core.find_node_near(p, 0.4, { "group:lit_campfire" }, true)
            if n then
                set_on_fire(obj, 5)
            end
        end
    end

    local function valid_object_iterator(objects)
        local i = 0
        local function next_valid_object()
            i = i + 1
            local obj = objects[i]
            if obj == nil then
                return
            end
            if obj:get_pos() then
                return obj
            end
            return next_valid_object()
        end
        return next_valid_object
    end

    local function valid_object_iterator_in_radius(objects, center, radius)
        local i = 0
        local function next_valid_object()
            i = i + 1
            local obj = objects[i]
            if obj == nil then
                return
            end
            local p = obj:get_pos()
            if p and vector.distance(p, center) <= radius then
                return obj
            end
            return next_valid_object()
        end
        return next_valid_object
    end

    local function connected_players(center, radius)
        local pls = core.get_connected_players()
        if not center then return valid_object_iterator(pls) end
        return valid_object_iterator_in_radius(pls, center, radius or 1)
    end


    local etime = 0
    core.register_globalstep(function(dtime)
        etime = dtime + etime
        if etime < 0.5 then return end
        etime = 0
        for pl in connected_players() do
            -- local armor_feet = pl:get_inventory():get_stack("armor", 5)
            if pl and pl:get_player_control().sneak
            -- or mcl_enchanting.has_enchantment(armor_feet, "frost_walker")
            -- or mcl_potions.has_effect(pl, "fire_resistance")
            then
                return
            end
        end
        for _, ent in pairs(core.luaentities) do
            if ent.is_mob then
                burn_in_campfire(ent.object)
            end
        end
    end)

    local function generate_smoke(pos)
        local smoke_timer = 4.75

        -- if core.get_node(vector.offset(pos, 0, -1, 0)).name == "mcl_farming:hay_block" then
        --     smoke_timer = 8
        -- end

        local ph = core.hash_node_position(pos)
        for pl in connected_players() do
            if not player_particlespawners[pl] then player_particlespawners[pl] = {} end
            if not player_particlespawners[pl][ph] and vector.distance(pos, pl:get_pos()) < PARTICLE_DISTANCE then
                player_particlespawners[pl][ph] = core.add_particlespawner({
                    amount = 2,
                    time = 0,
                    minpos = vector.offset(pos, -0.25, 0.25, -0.25),
                    maxpos = vector.offset(pos, 0.25, 0.25, 0.25),
                    minvel = vector.new(-0.1, 0.5, -0.1),
                    maxvel = vector.new(0.1, 1.2, 0.1),
                    minacc = vector.new(-0.1, 0.2, -0.1),
                    maxacc = vector.new(0.1, 0.5, 0.1),
                    minexptime = smoke_timer - 2,
                    maxexptime = smoke_timer,
                    minsize = 3,
                    maxsize = 5,
                    collisiondetection = true,
                    vertical = true,
                    texture = "mcl_campfires_particle_9.png",
                    playername = pl:get_player_name(),
                    texpool = {
                        { name = "mcl_campfires_particle_1.png" },
                        { name = "mcl_campfires_particle_2.png" },
                        { name = "mcl_campfires_particle_3.png" },
                        { name = "mcl_campfires_particle_4.png" },
                        { name = "mcl_campfires_particle_5.png" },
                        { name = "mcl_campfires_particle_6.png" },
                        { name = "mcl_campfires_particle_7.png" },
                        { name = "mcl_campfires_particle_8.png" },
                        { name = "mcl_campfires_particle_9.png" },
                        { name = "mcl_campfires_particle_10.png" },
                        { name = "mcl_campfires_particle_11.png" },
                        { name = "mcl_campfires_particle_11.png" },
                        { name = "mcl_campfires_particle_12.png" },
                    }
                })
            end
        end

        for pl, pt in pairs(player_particlespawners) do
            for _, sp in pairs(pt) do
                if not pl or not pl:get_pos() then
                    core.delete_particlespawner(sp)
                elseif player_particlespawners[pl][ph] and vector.distance(pos, pl:get_pos()) > PARTICLE_DISTANCE then
                    core.delete_particlespawner(player_particlespawners[pl][ph])
                    player_particlespawners[pl][ph] = nil
                end
            end
            if not pl or not pl:get_pos() then
                player_particlespawners[pl] = nil
            end
        end
    end

    core.register_on_leaveplayer(function(player)
        if player_particlespawners[player] then
            for _, v in pairs(player_particlespawners[player]) do
                core.delete_particlespawner(v)
            end
            player_particlespawners[player] = nil
        end
    end)

    -- Register Visual Food Entity
    core.register_entity("campfires:food_entity", {
        initial_properties = {
            physical = false,
            visual = "wielditem",
            visual_size = { x = 0.25, y = 0.25 },
            collisionbox = { 0, 0, 0, 0, 0, 0 },
            pointable = false,
        },
        on_step = function(self, dtime)
            self._timer = (self._timer or 1) - dtime
            if self._timer > 0 then return end
            if not self._start_time or not self._campfire_poshash then
                --if self._poshash isn't set that essentially means this campfire entity was migrated. Remove it to let a new one spawn.
                self.object:remove()
            end
            if core.get_gametime() - self._start_time > (self._cook_time or 1) then
                if food_entities[self._campfire_poshash] then
                    food_entities[self._campfire_poshash][self._spot] = nil
                end
                if count_table(food_entities[self._campfire_poshash]) == 0 then
                    delete_entities(self._campfire_poshash or "")
                end
                core.add_item(self.object:get_pos() + campfire_spots[self._spot], self._drop)
                self.object:remove()
            end
        end,
        get_staticdata = function(self)
            local d = {}
            for k, v in pairs(self) do
                local t = type(v)
                if t ~= "function" and t ~= "nil" and t ~= "userdata" then
                    d[k] = self[k]
                end
            end
            return core.serialize(d)
        end,
        on_activate = function(self, staticdata)
            if type(staticdata) == "userdata" then return end
            local s = core.deserialize(staticdata)
            if type(s) == "table" then
                for k, v in pairs(s) do self[k] = v end
                self.object:set_properties({ wield_item = self._item })
                if self._campfire_poshash and (not food_entities[self._campfire_poshash] or not food_entities[self._campfire_poshash][self._spot]) then
                    local spot = self._spot or get_free_spot(self._campfire_poshash)
                    if spot and self._campfire_poshash then
                        food_entities[self._campfire_poshash] = food_entities[self._campfire_poshash] or {}
                        food_entities[self._campfire_poshash][spot] = self
                        self._spot = spot
                    else
                        self.object:remove()
                        return
                    end
                else
                    self.object:remove()
                    return
                end
            end
            self._start_time = self._start_time or core.get_gametime()
            self.object:set_rotation({ x = math.pi / -2, y = 0, z = 0 })
            self.object:set_armor_groups({ immortal = 1 })
        end,
    })

    core.register_abm({
        label = "Campfire Smoke",
        nodenames = { "group:lit_campfire" },
        interval = 2,
        chance = 2,
        action = generate_smoke,
    })

    register_campfire("campfires:campfire", {
        description = "Campfire",
        inv_texture = "mcl_campfires_campfire_inv.png",
        fire_texture = "mcl_campfires_campfire_fire.png",
        lit_logs_texture = "mcl_campfires_campfire_log_lit.png",
        drops = "charcoal:charcoal 2",
        lightlevel = core.LIGHT_MAX - 5,
        damage = 1,
    })

    core.register_craft({
        output = 'campfires:campfire',
        recipe = {
            { '',            'group:stick',     '' },
            { 'group:stick', 'group:flammable', 'group:stick' },
            { "group:tree",  "group:tree",      "group:tree" },
        }
    })

    if core.global_exists("fire_plus") then
        fire_plus.ignition_nodes['campfires:campfire_lit'] = { burns = 4, damage = 2 };
    end
else
    core.register_alias("campfires:campfire", "mcl_core:campfire")
    core.register_alias("campfires:campfire_lit", "mcl_core:campfire_lit")
end
