math.randomseed(os.time())
bb_player = {}
local storage = minetest.get_mod_storage()



--settings
bb_player.spawn = {x=10,y=10,z=10}
bb_player.killdist = 60 -- no map may be bigger than this distance away from the origin or players will die
bb_player.wear_amt = 300 -- amount of wear to add to bad powerups each globalstep
bb_player.debug = false
bb_player.normal_speed = .5-- the speed players start at
bb_player.speed_boost = .2 -- the speed ech speed boost adds
bb_player.speed_snail = .25
bb_player.infohuds = {}


bb_player.textures = {} -- stores the textures that the player will have in-game (out-game, their textures are clear)
bb_player.saved_colors = minetest.deserialize(storage:get_string("saved_colors")) or {} -- stores the color assigned to the player
bb_player.powerup_huds = {} -- stores 


bb_player.die_messages = {
    " was burnt to a crisp.",
    " is flat-out done.",
    " was pancaked by a small explosion.",
    " turned into small specks.",
    " is toast. Literally.",
    " tried biting off more than they could chew, and burnt their mouth.",
    " popped.",
    " was killed by death.",
    " learned explosions aren't powerups.",
    " terminated by THE GAME.",
    " lost their appitite.",
    " didnt think good.",
    " was slightly too slow.",
    " flew on a blast of hot air.",
    " might have lacked intellignce, but compensated with plenty of explosive.",
    ' made a small "pop" and disappeared.',
    " became a star!",
    " had a new experience.",
    " was struck by an interesting thought.",
    " tried, at least.",
    " realized their mistake.",
    " had a bright idea!",
    " was fired.",
    " will rest in pieces.",
    " underestimated their invincibility.",
    " accurately simulated sublimation.",
    " was fully oxidized.",
    " learned the hard way.",
    " reached escape velocity",
    " had an explosion of intelligence",
    " wanted to spectate.",
    " witnessed the Big Bang.",
    " caused an eruption",
    " doesnt feel like it anymore.",
    " went for a coffee.",
    " croaked.",
    " got too excited.",
    " felt slightly discombobulated.",
    " died a little on the inside.",
    " spontaneously combusted.",
    " lived the fast life.",
    " loves particles, I guess.",
    " went out with a bang.",
    " is flashy.",
    "'s hypothesis was incorrect.",
    " theory was disproved by The Universe.",
    " played with fire.",
    " was too eager.",
    " ate something hot.",
    " is well done.",
    " is ready.",
    " looked too far into the oven.",
    "; rest in peices.",
    " checked if the oven was warm enough.",
    " is creating a toxic atmosphere.",
    " is letting their mind wander again.",
    " failed fire safety class.",
    " has some spare parts now.",
    " is no longer with us.",
    " is at large.",
    " is experiencing a chemical imbalence.",
    " didnt know the game was hot potato.",
    " went out with a whimper AND a bang.",
    " didn't really need those organs anyway.",
    " is just fine ... honest!",
    " is having regrets already.",
    " will be sure to return the favor.",
    " decided to keep the game short and to the point.",
    " earned a much-needed break.",
    " was doing really well there for a while.",
    " wants a do-over.",
    " doesn't remember this part from the training video.",
    " is trying to any% their bucket list.",
    " forgot to take their body.",
    " is visiting many exotic places, simultaneously.",
    " turned out for the party.",
    " needs to keep their insides where they belong.",
    " opened up shop, and business is booming!",
    " went out with a bang.",
    " is *not* invincible, after all.",
    " suffered a minor case of death.",
    " popped off.",
    " suddenly discovered the meaning of life, the universe, and everything.",
    " is one with the universe.",

}

bb_player.fall_messages = {

    " forgot to miss the ground.",
    " is off to new horizons.",
    " reached termnal velocity",
    " apparently had too much pride",
    " is flying. No wait, im looking at this backwards...",
    " believed they could fly.",
    " went down to the bottom.",
    " is still falling...",
    " is playing on a whole other level",
    " has embraced his deepest tendency",
    " saw a bird, or a plane, or something... anyhow they're gone now.",
    " went exploring.",
    " slipped.",
    " flopped.",
    " learned flying is for the birds.",
    " tried to find the surface.",
    ' says: "hey guys things look different from here! *zcchzz* "',
    ': "over and out"',
    " went down the rabbithole.",
    " overstepped his boundaries.",
    " zoomed waaay out.",
    " is reaching for the stars.",
    " went off the deep end.",
    " went skydiving.",
    " became smaller and smaller in the distance.",
    " is approching the speed of light.",
    ' says: "hey guys, what happened to the gravity? ... oh."',
    " sprouted wings.",
    " reached for the horizon and slipped.",
    " found the event horizon.",
    " entered the twilight zone.",
    " forgot their chute.",
    " has their head in the clouds.",
    " passed out.",
    " lacked a safety net.",
    " tried living on the edge.",
    " realized the meaning of gravity.",
    " didnt watch their step.",
    " forgot that there was no atmosphere in space.",
    " dident realize that total vacuum kills.",
    " was caught breaking the Law... Of Gravity.",
    " is boldly going where no one has gone before.",
    " is exploring the final frontier.",
    " is assuming a new perspective.",
    " failed to miss the ground.",
    " forgot to pack a parachute",
    " can't fly, and found out the hard way.",
    " is looking up ... with regret.",
    " fellout.",
    " has their feet planted firmly on the ... oh wait.",
    " was once mighty, but is fallen!",
    " thought the floor was invisible. FYI: its not.",
    " and the playing field have had a bit of a falling out.",
    " sailed through the air... AND DIED.",
    " wanted to see what's under the map.",
    " tried to noclip without fly privs.",
    " tried to noclip without fly privs.",
    " is testing the effect of gravity on a mass.",
    " is somewhere... over the rainbow.",
    " is measuring terminal velocity.",
    " likes the feeling of weightlessness.",
    " clearly flies more like a banana than an arrow.",
    " shouldn't have booked the cheap flight.",
    " wanted a window seat, but got more than they bargained for.",
    " confused personal and outer space.",
    " cures insomnia by lying down hard enough.",
    " is enjoying the breeze.",
    " finds it peaceful out here.",
    " de-orbited.",
    " contracted Kessler Syndrome.",
    " wonders if there eventually is a ground below.",
    " thought they were an admin.",
 
}



bb_player.player_colors_avail = {
    "#472d3c",
    "#5e3643",
    "#7a444a",
    "#a05b53",
    "#bf7958",
    "#eea160",
    "#b6d53c",
    "#71aa34",
    "#397b44",
    "#3c5956",
    "#5a5353",
    "#dff6f5",
    "#8aebf1",
    "#28ccdf",
    "#3978a8",
    "#394778",
    "#39314b",
    "#564064",
    "#8e478c",
    "#cd6093",
    "#ffaeb6",
    "#f4b41b",
    "#f47e1b",
    "#e6482e",
    "#a93b3b",
    "#827094",
    "#4f546b",
}


-- override the default hand item for shorter reach( so you have to approach a bomb inorder to punch it )



minetest.register_item(":", {
	type = "none",
    range = 1.0,
    groups = {not_in_creative_inventory=1},
})

-- player meta:
-- players can have the following metadata keys:

-- status: can be 
--      "inactive"- they just joined and are waiting for the round to be over. or they have finished a roud and are waiting for some reason
--      "admin" - they are in admin state and cannot play but are not limited as players are
--      "playing" - in-game
--      "dead" - in game but dead and waiting to possibly be respawned if that occurs



--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- ents

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



minetest.register_entity("bb_player:att",{
    initial_properties = {
        physical = false,
        use_texture_alpha = true,        
        collisionbox = {-0.01, -0.01, -0.01, 0.01, 0.01, 0.01},
        visual = "sprite",
        textures = {"blank.png"},
        visual_size = {x = 0.01, y = 0.01, z = 0.01},
        static_save = false,
    },
    on_step = function(self, dtime, moveresult)
        
        self._timer = self._timer + dtime
        if self._timer > 300 then
            self.object:remove()
        end
    
    end,

    _timer = 0,

})



minetest.register_entity("bb_player:falling_player",{
    initial_properties = {
        physical = false,
        use_texture_alpha = true,        
        collisionbox = {-0.01, -0.01, -0.01, 0.01, 0.01, 0.01},
        visual = "sprite",
        textures = {"blank.png"},
        visual_size = {x = 0.01, y = 0.01, z = 0.01},
        static_save = false,
    },
    on_step = function(self, dtime, moveresult)
        
        self._timer = self._timer + dtime
        if self._timer > 4 and self._falling then
            self.object:remove()
        end
        if bb_loop.gamestate == "loading" then
            self.object:remove()
        end
        
    end,

    _timer = 0,
    _falling = false,

})

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- functions declared

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



-- clears a player's inv
-- input: player objref
function bb_player.clear_inv(player)  return end



-- checks for air in node under player
-- input: player objref
function bb_player.check_for_air_under(player) return end
    


-- returns the number of a kind of item in inv
-- input player objref, itenname, output count (int)
function bb_player.get_count_in_inv(player,item) return end

-- resurrects all players and clears the board of deadplayer markers
function bb_player.resurrect_all_players() return end

-- kills a player who is alive
-- input ObjRef
function bb_player.kill_player(player) return end

function bb_player.loop_theme(song,len,p_name) return end






--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- allow admins to become admins and regular players again

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

minetest.register_privilege( "admin" , "allows becoming an admin user" )
minetest.register_privilege( "additem" , "allows adding items at yourpostion for testing purposes" )

minetest.register_chatcommand( "admin" , {
    privs = {
        admin = true,
    },
    description = " /admin args: up = become admin user, down = become regular player",
    func = function( name , param )
        local player = minetest.get_player_by_name( name ) 
        if not player then return end
        if param == "up" then
            local pmeta = player:get_meta()
            pmeta:set_string("status", "admin")  
            player:set_physics_override({speed = 1, jump = 1 ,gravity = 1})
        end
        if param == "down" then
            local pmeta = player:get_meta()
            pmeta:set_string("status", "inactive")  
            player:set_pos(bb_player.spawn)
            player:set_physics_override({speed = 0, jump = 0 ,gravity = 0})
        end

    end,
})

minetest.register_chatcommand( "additem" , {
    privs = {
        additem = true,
    },
    description = " additem <itemname> , adds the item to the world at your location (you will pick it up too)",
    func = function( name , param )
        local player = minetest.get_player_by_name( name ) 
        if not player then return end
        local pos = player:get_pos()

        minetest.add_item(pos, param)

    end,
})

minetest.register_chatcommand( "status" , {
    privs = {
        admin = true,
    },
    description = "tells you your player status",
    func = function( name , param )
        local player = minetest.get_player_by_name( name ) 
        if not player then return end
        local meta = player:get_meta()
        return meta:get_string("status")
        
    end,
})





--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- joinplayer

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


minetest.register_on_joinplayer(function(player)
    -- set position to the spawn pos
    local p_name = player:get_player_name()
    player:set_pos(bb_player.spawn)
    local obj = minetest.add_entity(bb_player.spawn, "bb_player:att")
    player:set_attach(obj)
    -- remove the wielditem and healthbar
    player:hud_set_flags({wielditem=false,healthbar=false,crosshair=false,breathbar=false,minimap=true})
    -- dont allow them to move
    player:set_physics_override({speed = 0, jump = 0 ,gravity = 0})
    -- set their status to inactive
    local pmeta = player:get_meta()
    pmeta:set_string("status", "inactive") 
    -- clear their inv
    bb_player.clear_inv(player)

    --start theme
    bb_player.loop_theme("theme",41.1,p_name)



    -- set player privs
    local p_privs = minetest.get_player_privs(p_name)
    if not (p_privs.admin or p_privs.server) then
        p_privs = {
            shout = true,
            interact = true,
        }
    end
    minetest.set_player_privs(p_name, p_privs)

    -- set player skybox
    player:set_sky({
        base_color = "#000000",
        type = "skybox",
        textures = {"Up.jpg", "Down.jpg", "Front.jpg", "Back.jpg", "Right.jpg", "Left.jpg"},
        clouds = false
    })

    player:set_moon({visible = false})
    player:set_sun({visible = false, sunrise_visible = false})

    -- set player hotbar
    player:hud_set_hotbar_selected_image("blank.png")
    player:hud_set_hotbar_image("bb_player_gui_hotbar_2.png")
    player:hud_set_hotbar_itemcount(2)
    -- set player model
    local player_colors = bb_player.player_colors_avail

    local randcolor = player_colors[math.random(1,#player_colors)]

    -- dont change players who have a saved texture


    bb_player.saved_colors[p_name] = bb_player.saved_colors[p_name] or randcolor
    -- save for future use
    storage:set_string("saved_colors",minetest.serialize(bb_player.saved_colors))

    bb_player.textures[p_name] = {"white.png^[colorize:"..bb_player.saved_colors[p_name]..":255","player_parts.png"}

    player:set_properties({
        visual = "mesh",
        mesh = "bb_character.b3d",
        textures = {"blank.png","blank.png"},
        visual_size = {x = 4, y = 4},
        collisionbox = {-0.4, 0.0, -0.4, 0.4, 0.9,0.4},
        stepheight = 0.3,
        eye_height = 1.3,
        physical = false,
        collides_with_objects = false,
    })

    
    -- set their inv formspec --show the menu when they join
    player:set_inventory_formspec(bb_loop.get_inv_formspec(p_name))
    minetest.show_formspec(p_name, "bb_player:firstjoin", bb_loop.get_inv_formspec(p_name))

    local status = bb_loop.get_hud_status()

    -- set up the info hud (status and timer/suddendeath)
    bb_player.infohuds[p_name] = player:hud_add({
        hud_elem_type   = "text",
        number          = 0xE6482E,
        position        = { x = .97, y = .03},
        offset          = {x = 0,   y = 0},
        text            = status,
        alignment       = {x = -1, y = 1},  -- change y later!
        scale           = {x = 100, y = 100}, -- covered later
        size            = {x = 2 },
    })

end)



--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- player inv sorting

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

minetest.register_globalstep(function(dtime)
    for _,player in pairs(minetest.get_connected_players()) do

        if bb_loop.gamestate == "running" then
        
            local inv = player:get_inventory()
            local list = inv:get_list("main")
            
            local bombs = nil
            local power = nil
            local speed = nil
            local life = nil
            local throw = nil
            local kick = nil
            local multidir = nil
            local bad = nil
            local new_list = {}
            for _,stack in pairs(list) do
                if stack:get_name() == "bb_powerup:extra_bomb" then
                    bombs = stack                
                elseif stack:get_name() == "bb_powerup:extra_power" then
                    power = stack                
                elseif stack:get_name() == "bb_powerup:extra_speed" then
                   speed = stack                
                elseif stack:get_name() == "bb_powerup:extra_life" then
                    life = stack                
                elseif stack:get_name() == "bb_powerup:throw" then
                    throw = stack                
                elseif stack:get_name() == "bb_powerup:kick" then
                    kick = stack                 
                elseif stack:get_name() == "bb_powerup:multidir" then
                    multidir = stack                 
                elseif stack:get_name() ~= "" then
                    bad = stack 
                end
            end

            local counter = 1

            bb_player.clear_inv(player)
            if bombs ~= nil then
                inv:set_stack("main", counter, bombs)
                counter = counter + 1
            end
            if power ~= nil then
                inv:set_stack("main", counter, power)
                counter = counter + 1
            end
            if speed ~= nil then 
                inv:set_stack("main", counter, speed)
                counter = counter + 1
            end
            if life ~= nil then
                inv:set_stack("main", counter, life)
                counter = counter + 1
            end
            if throw ~= nil then
                inv:set_stack("main", counter, throw)
                counter = counter + 1
            end
            if kick ~= nil then 
                inv:set_stack("main", counter, kick)
                counter = counter + 1
            end
            if multidir ~= nil then 
                inv:set_stack("main", counter, multidir)
                counter = counter + 1
            end
            if bad ~= nil then
                inv:set_stack("main", counter, bad)
                counter = counter + 1
            end
            local hotbar = counter - 1

            player:hud_set_hotbar_itemcount(hotbar)
            player:hud_set_hotbar_image("bb_player_gui_hotbar_"..hotbar..".png")
        else
            player:hud_set_hotbar_itemcount(2)
            player:hud_set_hotbar_image("bb_player_gui_hotbar_2.png")
        end
    end

end)


--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- player animation

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


minetest.register_globalstep(function(dtime)
    for _, player in pairs(minetest.get_connected_players()) do

        local meta = player:get_meta()
        local p_name = player:get_player_name()

        if bb_loop.gamestate ~= "running" and bb_loop.gamestate ~= "celebrate" then
            -- set player textures to blank if the game is not running
            player:set_properties({
                textures = {"blank.png","blank.png"},
            })

        else
            if meta:get_string("diestatus") ~= "dead" then
                player:set_properties({
                    textures = bb_player.textures[p_name],
                })
            end

        end

        if meta:get_string("status") == "playing" then
            meta:set_string("diestatus", "notdead")
            local controls = player:get_player_control()
            -- if player is pressing movement keys
            if controls.up or controls.down or controls.left or controls.right then
                --walk anim
                player:set_animation({x=60,y=100}, 50, 0, true)
            else
                -- stand anim
                player:set_animation({x=0,y=40}, 20, 0, true)
            end
        elseif meta:get_string("status") == "dead" then
            


            if meta:get_string("diestatus") ~= "dead" then
                
                meta:set_string("diestatus", "dead")
                player:set_properties({
                    textures = {"blank.png","blank.png"},
                })

                

            end

        end

    end
end)


        

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- controls

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function bb_player.try_to_place_bomb(player)

    --get our vars
    if bb_loop.gamestate ~= "running" then return end
	if not player then return end
    local p_name = player:get_player_name()
    local inv = player:get_inventory()
    local pmeta = player:get_meta()
    local p_status = pmeta:get_string("status")
    if p_status ~= "playing" then return end
    local pos = vector.add(player:get_pos(),vector.new(0,.5,0))
    
    
    -- booleans
    -- note: bomb throwing is handled in bomb code
    local can_placebomb = false
    if inv:contains_item("main", "bb_powerup:extra_bomb") and not inv:contains_item("main", "bb_powerup:no_placebomb") then
        can_placebomb = true
    end

    -- if there is a bomb that the player has just placed (they are standing on it) and they have throw ability, then throw it

    local near_bomb = false
    local pos1 = vector.add(pos, vector.new(-.5,0,-.5))
    local pos2 = vector.add(pos, vector.new(.5,0,.5))

    local objs = minetest.get_objects_in_area(pos1, pos2)
    for k,v in ipairs(objs) do
        if v and not ( v:is_player() ) and v:get_luaentity() and v:get_luaentity().name == "bb_bomb:bomb" then
            near_bomb = true
        end
    end


    if can_placebomb and near_bomb ~= true then

        -- place bomb, remove bomb item from inv
        local power = bb_player.get_count_in_inv(player,"bb_powerup:extra_power") 


        if bb_player.debug == true then
            minetest.log("error","power: "..power)
        end


        local multidir = false
        if inv:contains_item("main", ItemStack('bb_powerup:multidir')) then
            multidir = true
            for k,v in pairs(inv:get_list("main")) do 
                if v:get_name() == 'bb_powerup:multidir' then
                    
                    v:add_wear((65535/3)+3)
                    inv:set_stack("main",k,v)
                end
            end
        end


        

        local staticdata = minetest.write_json({
            _power = power,
            _owner = p_name,
            _multidir = multidir,
        })

        minetest.sound_play({
            name = "sfx_sounds_impact4",
            gain = 0.6,
        }, {
            pos = player:get_pos(),
            max_hear_distance = 10,  
        }, true)

        minetest.add_entity(vector.round(pos), "bb_bomb:bomb", staticdata)

        inv:remove_item("main", "bb_powerup:extra_bomb")

    end

end


-- place bomb if no bomb very near and when pressed jump

controls.register_on_press(function(player, control_name)
    if control_name ~= "jump" then return end
    bb_player.try_to_place_bomb(player)
end)



-- implement party powerup (players autoplace bombs whenever they can)

local partytimer = 0
minetest.register_globalstep(function(dtime)
    if bb_loop.gamestate ~= "running" then return end 
    partytimer = partytimer + dtime
    if partytimer > .5 then
        partytimer = 0
        for _, player in pairs( minetest.get_connected_players() ) do
            local inv = player:get_inventory()
            if inv:contains_item("main", "bb_powerup:party") then
                bb_player.try_to_place_bomb(player)
            end
        end
    end
end)


-- special other controls (weird_control)

controls.register_on_press(function(player, control_name)
    if not ( control_name == "left" or control_name == "right" or control_name == "up" or control_name == "down") then
        return
    end
    local inv = player:get_inventory()
    if inv:contains_item("main", "bb_powerup:weird_control") then
        local look = player:get_look_horizontal()
        player:set_look_horizontal(look - 3.14)
    end

end)






--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- globalstep (player handler)

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- set player speed based on inventory contents
-- check for near explosions and kill player


minetest.register_globalstep(function(dtime)
    -- we are only concerned with the running gamestate
    -- iter through all players, but only affect alive and playing players
    local status = bb_loop.get_hud_status()
    for _, player in pairs(minetest.get_connected_players()) do


        --update the status HUD
        local p_name = player:get_player_name()
        player:hud_change(bb_player.infohuds[p_name],"text",status)
        -- since hitpoints does not factor in this game, we will set hp to max
        player:set_hp(300)
        local p_meta = player:get_meta()
        if p_meta:get_string("status") == "playing" and bb_loop.gamestate == "running" then

            --%%%%%%%%%%%%%%%%%%
            --set player speed
            --%%%%%%%%%%%%%%%%%%

            local snail = false
            local superfast = false
            local inv = player:get_inventory()
            local num_speed_boosts = bb_player.get_count_in_inv(player,"bb_powerup:extra_speed")
            -- determine the value of the above variables


            if inv:contains_item("main", ItemStack("bb_powerup:slow")) then
                snail = true
            end
            if inv:contains_item("main", ItemStack("bb_powerup:superfast")) then
                superfast = true
            end

            -- if they have a slow modifier then set their speed to slow
            if snail then
                player:set_physics_override({speed = bb_player.speed_snail, gravity = 1})
            -- if they have a super fast modifier then set their speed to very fast
            elseif superfast then
                player:set_physics_override({speed = 30, gravity = 1})
            -- else just set their speed based on the number of speed boosts they have collected
            else
                local normal_speed = bb_player.normal_speed --.75
                local speed_boost = bb_player.speed_boost -- .5
                local newspeed = normal_speed + num_speed_boosts * speed_boost
                player:set_physics_override({speed = newspeed, gravity = 1})
            end

            --%%%%%%%%%%%%%%%%%%
            --check for nearby explosions, kill players too close
            --%%%%%%%%%%%%%%%%%%

            local pos = player:get_pos()
            local near_exp = false
            local pos1 = vector.add(pos, vector.new(-.5,-.2,-.5))
            local pos2 = vector.add(pos, vector.new(.5,1,.5))
        
            local objs = minetest.get_objects_in_area(pos1, pos2)
            local o = nil -- we will store the explosion objectref in case we need to remove it
            for k,v in ipairs(objs) do
                if v and not ( v:is_player() ) and v:get_luaentity() and v:get_luaentity().name == "bb_bomb:explosion" then
                    near_exp = true
                    o = v
                end
            end
            if near_exp then
                
                -- if there was a nearby explosion, kill them if they dont have extra life. remove it if they do.
                local inv = player:get_inventory()
                if inv:contains_item("main", "bb_powerup:extra_life") == true then
                    
                    if o then -- remove the killing explosion
                        o:remove()
                    end
                    inv:remove_item("main", "bb_powerup:extra_life")
                else
                    --update the players respawn point
                    
                    local pmeta = player:get_meta()
                    pmeta:set_string("spawn", minetest.serialize( player:get_pos() )) 

                    minetest.sound_play({
                        name = "death",
                        gain = 2.0,
                    }, 
                    {
                        pos = player:get_pos(),
                        max_hear_distance = 32,  -- default, uses an euclidean metric
                    }, true)

                    bb_player.kill_player(player)

                end
            end

            --%%%%%%%%%%%%%%%%%%
            --check for air under player, move them to center of block and kill them
            --%%%%%%%%%%%%%%%%%%

            local pos = player:get_pos()
            
            if bb_player.check_for_air_under(player) then
                --player:set_pos(vector.add(vector.round(pos),vector.new(0,2,0)))
                local p_props = player:get_properties()

                -- local obj = minetest.add_entity(vector.round(pos),"bb_player:falling_player")
                -- obj:set_properties(p_props)
                -- obj:set_acceleration(vector.new(0,-1,0))

                if bb_player.debug == true then
                    minetest.log("error","player killed for being over air")
                end

                minetest.sound_play({
                    name = "GameOver",
                    gain = 1.0,
                }, {
                    pos = player:get_pos(),
                    gain = 1,  -- default
                    max_hear_distance = 10,  
                }, true)

                -- TODO: play falling sound
                bb_player.kill_player(player)
            end

            --%%%%%%%%%%%%%%%%%%
            --add wear to time limited powerups
            --%%%%%%%%%%%%%%%%%%

            local inv = player:get_inventory()
            local list = inv:get_list("main")
            --for k, v in pairs(list) do
            for  v = 1 , inv:get_size("main") do
                local stack = inv:get_stack("main", v)
                local i_name = stack:get_name()
                if i_name == "bb_powerup:superfast" or
                    i_name == "bb_powerup:slow" or
                    i_name == "bb_powerup:weird_control" or
                    i_name == "bb_powerup:party" or
                    i_name == "bb_powerup:no_placebomb" then

                    stack:add_wear(bb_player.wear_amt)
                    inv:set_stack("main", v, stack)
                end

            end

            --%%%%%%%%%%%%%%%%%%
            --collect nearby items
            --%%%%%%%%%%%%%%%%%%

            local objs = minetest.get_objects_inside_radius(vector.add(pos,vector.new(0,.5,0)), 0.5)
            for k,v in ipairs(objs) do
                if v and not ( v:is_player() ) and v:get_luaentity() and v:get_luaentity().name == '__builtin:item' then
                    local stack = ItemStack(v:get_luaentity().itemstring)
                    local stack_name = stack:get_name()
                    local goodgroup = minetest.get_item_group(stack_name, "good")
                    local badgroup = minetest.get_item_group(stack_name, "bad")

                    


                    v:remove()


                    -- play sounds
                    if goodgroup == 1 then 
                        minetest.sound_play({
                            name = "yippee",
                            gain = 1.0,
                        }, {
                            pos = player:get_pos(),
                            max_hear_distance = 32,  -- default, uses an euclidean metric
                        }, true)
                    end
                    if badgroup == 1 then 
                        minetest.sound_play({
                            name = "eew",
                            gain = .1,
                        }, {
                            pos = player:get_pos(),
                            max_hear_distance = 32,  -- default, uses an euclidean metric
                        }, true)
                    end

                    -- if there is a bad powerup in the inv, remove it, whenever you get any powerup
                    local list = inv:get_list("main")
                    for l, m in ipairs(list) do
                        local stack = m
                        local i_name = stack:get_name()
                        if i_name == "bb_powerup:superfast" or
                            i_name == "bb_powerup:slow" or
                            i_name == "bb_powerup:weird_control" or
                            i_name == "bb_powerup:party" or
                            i_name == "bb_powerup:no_placebomb" then
        
                            stack:take_item()
                            inv:set_stack("main", l, stack)
                        end
                    end

                    if stack_name == "bb_powerup:resurect" then
                        -- resurrect all players!!!
                        bb_player.resurrect_all_players()                   
                    
                    elseif stack_name == "bb_powerup:throw" or
                        stack_name == "bb_powerup:kick" or
                        stack_name == "bb_powerup:extra_life" or
                        stack_name == "bb_powerup:multidir" then

                            -- if the item is one of the pernament powerups then only add the item if the player doesnt already have one
                            
                        if not(inv:contains_item("main", stack_name)) then
                            inv:add_item("main", stack)
                        end

                    else
                        -- for everything else, we can just add the item
                        inv:add_item("main", stack)
                    end
                end
            end

        -- need to set players' physics override for outside running gamestate (eg, dead or not in game)
        elseif p_meta:get_string("status") ~= "admin" then
            player:set_physics_override({speed = 0, jump = 0 ,gravity = 0})
        end

    end


        

end)





--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- functions defined

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%








-- clears a player's inv
-- input: player objref
function bb_player.clear_inv(player)

    local inv = player:get_inventory()
    local list = inv:get_list("main")
    for k, v in pairs(list) do
        inv:remove_item("main", v)
    end
    
end












-- checks for air in node under player
-- input: player objref
function bb_player.check_for_air_under(player)
    
    local pos = player:get_pos()

    -- checkdistance to check for solid nodes under player in
    local cdist = .2

    --player pos is at the floor of the player's hitbox, where they are standing, in the center of the x,z
    -- u_pos is the pos under that
    local u_pos = vector.add(pos, vector.new(0,-.7,0))
    --iff all checkpoints have air, then the player has stepped off

    if minetest.get_node(vector.add(u_pos,vector.new(-cdist,0,-cdist))).name == "air" and
        minetest.get_node(vector.add(u_pos,vector.new(-cdist,0,0))).name == "air" and
        minetest.get_node(vector.add(u_pos,vector.new(-cdist,0,cdist))).name == "air" and
        minetest.get_node(vector.add(u_pos,vector.new(0,0,-cdist))).name == "air" and
        minetest.get_node(vector.add(u_pos,vector.new(0,0,0))).name == "air" and
        minetest.get_node(vector.add(u_pos,vector.new(0,0,cdist))).name == "air" and
        minetest.get_node(vector.add(u_pos,vector.new(cdist,0,-cdist))).name == "air" and
        minetest.get_node(vector.add(u_pos,vector.new(cdist,0,0))).name == "air" and
        minetest.get_node(vector.add(u_pos,vector.new(cdist,0,cdist))).name == "air" then
        return true
    else
        return false
    end


end















-- returns the number of a kind of item in inv
-- input player objref, itenname, output count (int)
function bb_player.get_count_in_inv(player,item)

    local count = 0
    local inv = player:get_inventory()
    local lists = inv:get_lists()
    for listname,itemstacklist in pairs(lists) do
        for _,stack in pairs( itemstacklist ) do
            if stack:get_name() == item then
                count = count + stack:get_count()
            end
        end
    end
    return count

end



    -- resurrect all players!!!
    -- first remove any deadplayer markers

function bb_player.resurrect_all_players()

    --remove all deadmarkers
    for _,obj in pairs(minetest.get_objects_inside_radius(vector.new(0,0,0), 200)) do

        local ent = obj:get_luaentity()
        if ent then
            local name = ent.name
            if name == "bb_player:falling_player" then
                obj:remove()
            end
        end
    end
    -- resurrect all players
    for _, i_player in pairs( minetest.get_connected_players()) do

        local pmeta = i_player:get_meta()
        local status = pmeta:get_string("status")
        local spawn = minetest.deserialize(pmeta:get_string("spawn"))
        local p_name = i_player:get_player_name()

        if status == "dead" then
            -- they dont get to keep their stuffz, if they had any
            bb_player.clear_inv(i_player)
            -- if they died from falling, move them to their spawn location
            -- if bb_player.check_for_air_under(i_player) then
                i_player:set_detach()
                i_player:set_pos(spawn)
            -- end
            -- give them the basic starting stuff

            local inv = i_player:get_inventory()
            local list = inv:get_list("main")
           
            inv:add_item("main", ItemStack("bb_powerup:extra_bomb")) 
            inv:add_item("main", ItemStack("bb_powerup:extra_power"))

            -- spawn some particles and give play a sound.
            minetest.add_particlespawner({
                amount = 40,
                time = 1,
                minpos = vector.add(spawn,vector.new(-.5,-.4,-.5)),
                maxpos = vector.add(spawn,vector.new(.5,1,.5)),
                minvel = vector.new(0,1,0),
                maxvel = vector.new(.01,4,.01),
                minacc = vector.new(0,0,0),
                maxacc = vector.new(.01,4,.01),
                minexptime = 1,
                maxexptime = 1,
                minsize = 3,
                maxsize = 4,
                collisiondetection = false,            
                collision_removal = false,
                object_collision = false,
                --attached = ObjectRef,
                vertical = false,
                texture = "respawn_particle.png",
                glow = 2,
            })

            minetest.sound_play({
                name = "respawn2",
                gain = 0.6,
            }, {
                pos = spawn,
                max_hear_distance = 10,  
            }, true)


            minetest.after(.2,function(p_name)
                local player = minetest.get_player_by_name(p_name)
                if not player then return end
                if bb_loop.gamestate ~= "running" then return end

                pmeta:set_string("status", "playing")
            end,p_name)
        end
    end
end






-- kills a player who is alive
-- input ObjRef
function bb_player.kill_player(player)
    
    -- clear the inv
    bb_player.clear_inv(player)
    -- set meta
    local p_name = player:get_player_name()
    local pmeta = player:get_meta()
    

    local air_under = bb_player.check_for_air_under(player)

    local ded_pos = player:get_pos()

    local look_dir = player:get_look_horizontal()

    player:set_physics_override({speed = 0, jump = 0, gravity = 0})

    -- make the deadplayer indicator, falling is a misnomer, send deathmessages
    local player_color = bb_player.saved_colors[p_name]
    local msg
    local p_props = player:get_properties()

    local obj = minetest.add_entity( vector.add(vector.round(ded_pos),vector.new(0,-.5,0)) ,"bb_player:falling_player")
    local ent = obj:get_luaentity()

    obj:set_properties(p_props)
    obj:set_properties({
        textures=bb_player.textures[p_name],
        physical = false,
    })
    obj:set_yaw(look_dir)

    pmeta:set_string("status", "dead") 

    if air_under then

        --obj:set_acceleration(vector.new(0,-1,0))
        obj:set_animation({x=110,y=150}, 20, 2, false)
        ent._falling = true
        math.randomseed(os.time())
        msg = bb_player.fall_messages[math.random(1,#bb_player.fall_messages)]

    else

        obj:set_animation({x=170,y=210}, 90, 2, false)
        ent._falling = false
        math.randomseed(os.time())
        msg = bb_player.die_messages[math.random(1,#bb_player.die_messages)]
    end
    --send the death message
    minetest.chat_send_all(minetest.colorize(player_color, p_name .. msg))

    --set the player's looking direction to down, so they can watch themselves die >:D

    player:set_look_vertical(0.5 * 3.14)
    
    -- move the player to the spectator pos
    if bb_loop.current_arena then


        local watch_pos = vector.add(ded_pos,vector.new(0,5,0))
        local att = player:get_attach()
        if att then 

            att:set_pos(watch_pos)

        else

            local att = minetest.add_entity(watch_pos, "bb_player:att")
            player:set_attach(att)
        end

        
    end

    -- after death animation, move them to the spectator position, if the arena is still the same
    minetest.after(3,function(loopcounter,p_name)
        
        if bb_loop.loopcounter == loopcounter then
            --check player exists, they could have ragequit lol
            local player = minetest.get_player_by_name(p_name)

            if player then

                local arena_spectator_pos = bb_loop.current_arena.spectator_pos
                local att = player:get_attach()

                -- we prob. can assume that  att exists, but check, just in case and create it if not
                if att then 
                    att:set_pos(arena_spectator_pos)
                else
                    local att = minetest.add_entity(arena_spectator_pos, "bb_player:att")
                    player:set_attach(att)
                end

            end

        end

    end, bb_loop.loopcounter, p_name )

end



function bb_player.loop_theme(song,len,p_name)

    minetest.sound_play({
        name = song,
        gain = 0.1,
    }, {
        to_player = p_name,        
        fade = 0.0,   -- default, change to a value > 0 to fade the sound in
        pitch = 1.0,  -- default
    }, true)
    minetest.after(len,function(song,len,p_name)
        local player = minetest.get_player_by_name(p_name)
        if player then
            bb_player.loop_theme(song,len,p_name)
        end
    end,song,len,p_name)

end






function bb_player.get_leaderboard_order()

    local leaderboard = bb_loop.leaderboard
    local players_by_score = {}

    for p_name,wins in pairs(leaderboard) do
        if players_by_score[wins] then
            table.insert(players_by_score[wins],p_name)
        else
            players_by_score[wins] = {p_name}
        end
    end

    local scores_array = {}

    for wins,p_names in pairs(players_by_score) do --wins is a num, p_names is a tbl
        table.insert(scores_array, wins)
    end
    --sorts the scores from greatest to least so I know what order to get the keys from blayers_by_score in
    table.sort(scores_array, function(a,b) return a > b end)
    return scores_array,players_by_score

end

















-- override the bb_loop function
-- get the inventory formspec, which contians the leaderboard, and color choice, and a glossary


function bb_loop.get_inv_formspec(p_name)




    local player = minetest.get_player_by_name(p_name)
    if not player then return "" end

    local status = ""

    if bb_loop.gamestate == "waiting" then
        status = "WAITING FOR OTHER PLAYERS"
    end
    if bb_loop.gamestate == "loading" then
        status = "LOADING ARENA"
    end
    if bb_loop.gamestate == "running" then
        local meta = player:get_meta()
        local p_status = meta:get_string("status")
         
        status = "A GAME IS ONGOING"

        if p_status == "inactive" then
            status = status.." WAITING TO JOIN NEXT ROUND"
        end
        if p_status == "playing" then
            status = "PLAYING"
        end
        if p_status == "dead" then
            status = status .. " YOU ARE DEAD"
        end
    end

    -- get the player's current textures for player model selection
    local textures = {"white.png^[colorize:"..bb_player.saved_colors[p_name]..":255","player_parts.png"}
    local player_textures = textures[1]..","..textures[2]

  
    -- -- build the leaderboard 

    -- local leaderfs = ""
    -- local leadercount = 1 -- counter for building fs
    -- local scores_array,players_by_score = bb_player.get_leaderboard_order()
    -- if scores_array and players_by_score then
    --     for _ , score in ipairs(scores_array) do

    --         local pls_str = ""
    --         local pls_list = players_by_score[score]
    --         for _,p_name in ipairs(pls_list) do
    --             if pls_string ~= "" then
    --                 pls_str = pls_str .. ", "
    --             end
    --             pls_str = pls_str .. p_name
    --         end
    --         leaderfs = leaderfs ..
    --         "label[1,"..leadercount..";"..score.."]"..
    --         "textlist[2,"..tostring(leadercount - 0.5) ..";17,1.5;score_"..score..";"..pls_str.."]"
    --         leadercount = leadercount + 2
    --     end
    -- end


        -- build the leaderboard 

        local leaderfs = ""
        local leadercount = 1 -- counter for building fs
        local scores_array,players_by_score = bb_player.get_leaderboard_order()
        if scores_array and players_by_score then
            local listcolor = "#dff6f566"
            for _ , score in ipairs(scores_array) do
                
                --local pls_str = ""
                local pls_list = players_by_score[score]
                for _,p_name in ipairs(pls_list) do
                    if listcolor == "#dff6f566" then listcolor = "#cfc6b866" else listcolor = "#dff6f566" end
                    local p_color = bb_player.saved_colors[p_name] or "#dff6f5"
                    leaderfs = leaderfs ..
                    "box[.8,"..tostring(leadercount-.5)..";1.9,.9;"..listcolor.."]"..
                    "box[2.8,"..tostring(leadercount-.5)..";16,.9;"..listcolor.."]"..
                    "label[1,"..leadercount..";"..minetest.colorize(p_color,score).."]"..
                    "label[3.2,"..leadercount..";"..minetest.colorize(p_color,p_name).."]"
                    --"textlist[2,"..tostring(leadercount - 0.5) ..";17,1.5;score_"..score..";"..pls_str.."]"
                    leadercount = leadercount + 1
                end
                
                
            end
        end
                
            





    local fs = "formspec_version[4]"..
    "size[25,20]"..
    "position[0.5,0.5]"..
    "bgcolor[#0000]"..
    "background9[0,0;25,20;bb_player_gui_bg.png^[opacity:50;false;50]"..
    "scrollbaroptions[max=1070;thumbsize=200;arrows=hide]"..
    "scrollbar[24,.5;.3,19;vertical;mainscroll;0]"..
    "scroll_container[.5,.5;23,19;mainscroll;vertical;]"..
    
        "image[1.2,1;21,4;bb_logo.png]"..
        "box[1.2,5.5;21,1;#302c2eCC]"..
        "image_button[1.2,5.5;21,1;blank.png;status_button;"..status..";false;false;blank.png]"..
    
    
        
        -- was 16
        "box[1.2,7;21,8;#302c2eCC]"..
        "box[1.2,7;21,1;#5a5353CC]"..
        "image_button[1.2,7;21,1;blank.png;color_button;CHOOSE YOUR BLOCKER;false;false;blank.png]"..
    
        "image_button[2.2,10;1,1;bb_player_arrow.png;color_left;;false;false;]"..
        "image_button[20.2,10;1,1;bb_player_arrow.png^[transformFX;color_right;;false;false;]"..
        "model[8.2,9;6,5;player_model;bb_character.b3d;"..player_textures..";0,-180;true;true;]"..
    
        
        --was 7
        "box[1.2,16;21,8;#302c2eCC]"..
        "box[1.2,16;21,1;#5a5353CC]"..
        "image_button[1.2,16;21,1;blank.png;leaderboard_button;LEADER BOARD;false;false;blank.png]"..
        
        "scrollbaroptions[max="..tostring(leadercount*8)..";thumbsize="..tostring(leadercount)..";arrows=hide]"..
        "scrollbar[21.5,17.5;.3,6;vertical;leaderscroll;0]    "..
        "scroll_container[2,17.5;19.5,6;leaderscroll;vertical]"..
    
            leaderfs ..

        "scroll_container_end[]"..
    
        "box[1.2,25;21,100;#302c2eCC]"..
        "box[1.2,25;21,1;#5a5353CC]"..
        "image_button[1.2,25;21,1;blank.png;bombs_button;BOMBS;false;false;blank.png]"..
        "model[2.25,27;2.5,2.5;bomb_model;bb_bomb.b3d;bb_bomb_texture.png;nil;true;true;]"..
        "box[5.5,27;16,3;#5a5353CC]"..
        "textarea[5.75,27.25;15.5,2.5;;;Place bombs with SPACE.  Bombs explode in orthagonal directions.  Once placed, it blocks your path, so be careful where you place it.]"..
    
        "box[1.2,31;21,1;#5a5353CC]"..
        "image_button[1.2,31;21,1;blank.png;powerups_button;POWERUPS;false;false;blank.png]"..
    
        "image[2,33;2.5,2.5;bb_powerup_bomb.png]"..
        "box[5.5,33;16,3;#5a5353CC]"..
        "textarea[5.75,33.25;15.5,2.5;;;Bomb items indicate your ability to place a bomb. Having more means you can place more bombs at once (bombs are removed until they explode). Collect them all!]"..
    
        "image[2,37;2.5,2.5;bb_powerup_power.png]"..
        "box[5.5,37;16,3;#5a5353CC]"..
        "textarea[5.75,37.25;15.5,2.5;;;TNT indicates the size of the explosion your bombs will make. Collect more for a bigger bang!]"..
    
        "image[2,41;2.5,2.5;bb_powerup_fast.png]"..
        "box[5.5,41;16,3;#5a5353CC]"..
        "textarea[5.75,41.25;15.5,2.5;;;Shoes indcate how fast you can go. Collect more to speed around the map!]"..
    
        "image[2,45;2.5,2.5;bb_powerup_extra_life.png]"..
        "box[5.5,45;16,3;#5a5353CC]"..
        "textarea[5.75,45.25;15.5,2.5;;;Hearts give protection against bomb blasts. One at a time, please!]"..
    
        "image[2,49;2.5,2.5;bb_powerup_kick.png]"..
        "box[5.5,49;16,3;#5a5353CC]"..
        "textarea[5.75,49.25;15.5,2.5;;;Fist gives you the ability to push bombs. Without being inside the bomb, stand next to it and use PUNCH or LMB to give it a shove!]"..
    
        "image[2,53;2.5,2.5;bb_powerup_throw.png]"..
        "box[5.5,53;16,3;#5a5353CC]"..
        "textarea[5.75,53.25;15.5,2.5;;;A Spring allows you to throw bombs that you are in 2 blocks, even if they land inside a block. Use SPACE, and instead of placing a new bomb, you will throw the bomb that you are in.]"..
    
        "image[2,57;2.5,2.5;bb_powerup_resurect.png]"..
        "box[5.5,57;16,3;#5a5353CC]"..
        "textarea[5.75,57.25;15.5,2.5;;;If a player gets a Pheonix, any players who have died are resurrected, with starting items. Give your friends a hand!]"..
    
        "image[2,61;2.5,2.5;bb_powerup_multidir.png]"..
        "box[5.5,61;16,3;#5a5353CC]"..
        "textarea[5.75,61.25;15.5,2.5;;;Compass makes your explosions diagonal as well as orthagonal. Use with caution, but this is a nice way to surprise people who think they're safe. Only 3 uses.]"..
    
        "box[1.2,65;21,1;#5a5353CC]"..
        "image_button[1.2,65;21,1;blank.png;handicaps_button;HANDICAPS;false;false;blank.png]"..
        "image_button[1.2,66;21,1;blank.png;handicapsinfo_button;All handicaps are time-limited;false;false;blank.png]"..
        "image_button[1.2,67;21,1;blank.png;handicapsinfo_button;and can be removed by getting any powerup;false;false;blank.png]"..
    
        "image[2,69;2.5,2.5;bb_powerup_no_placebomb.png]"..
        "box[5.5,69;16,3;#5a5353CC]"..
        "textarea[5.75,69.25;15.5,2.5;;;You can't place it. Sorry.]"..
    
        "image[2,73;2.5,2.5;bb_powerup_slow.png]"..
        "box[5.5,73;16,3;#5a5353CC]"..
        "textarea[5.75,73.25;15.5,2.5;;;You'll win the race, but slowly.]"..
    
        "image[2,77;2.5,2.5;bb_powerup_superfast.png]"..
        "box[5.5,77;16,3;#5a5353CC]"..
        "textarea[5.75,77.25;15.5,2.5;;;Too much energy, don't you think?]"..
    
        "image[2,81;2.5,2.5;bb_powerup_party.png]"..
        "box[5.5,81;16,3;#5a5353CC]"..
        "textarea[5.75,81.25;15.5,2.5;;;Time for a fireworks show!]"..
    
        "image[2,85;2.5,2.5;bb_powerup_weird_control.png]"..
        "box[5.5,85;16,3;#5a5353CC]"..
        "textarea[5.75,85.25;15.5,2.5;;;Ugh, can't move right. Was it something I ate?]"..
    
    
        "box[1.2,89;21,1;#5a5353CC]"..
        "image_button[1.2,89;21,1;blank.png;map_button;MAP;false;false;blank.png]"..
    
        "image[2,91;2.5,2.5;bb_nodes_floor.png]"..
        "box[5.5,91;16,3;#5a5353CC]"..
        "textarea[5.75,91.25;15.5,2.5;;;The Floor. It's solid. That means you can't fall thru it.]"..
    
        "image[2,95;2.5,2.5;blank.png]"..
        "box[5.5,95;16,3;#5a5353CC]"..
        "textarea[5.75,95.25;15.5,2.5;;;This is Air. Its not solid. If you think it is, then, please, try walking on it.]"..
    
        "image[2,99;2.5,2.5;bb_nodes_floor_with_arrow.png]"..
        "box[5.5,99;16,3;#5a5353CC]"..
        "textarea[5.75,99.25;15.5,2.5;;;The arrow pushes bombs that touch it.]"..
    
        "image[2,103;2.5,2.5;bb_nodes_floor_with_mortar.png]"..
        "box[5.5,103;16,3;#5a5353CC]"..
        "textarea[5.75,103.25;15.5,2.5;;;The Mortar. It throws bombs to a random location on the map.]"..
    
        "image[2,107;2.5,2.5;bb_nodes_ice.png]"..
        "box[5.5,107;16,3;#5a5353CC]"..
        "textarea[5.75,107.25;15.5,2.5;;;Ohh, Slippery! Watch out!]"..
    
        "image[2,111;2.5,2.5;bb_nodes_brick.png]"..
        "box[5.5,111;16,3;#5a5353CC]"..
        "textarea[5.75,111.25;15.5,2.5;;;This block can be removed with explosions. It might drop an item!]"..
    
        "image[2,115;2.5,2.5;bb_nodes_crate.png]"..
        "box[5.5,115;16,3;#5a5353CC]"..
        "textarea[5.75,115.25;15.5,2.5;;;It contains an item, if you explode it.]"..
    
        "image[2,119;2.5,2.5;bb_nodes_wall.png]"..
        "box[5.5,119;16,3;#5a5353CC]"..
        "textarea[5.75,119.25;15.5,2.5;;;Tough stuff. You can't break this wall.]"..
    

    "scroll_container_end[]"

    return fs


end


-- dont repeat yourself (used below)
local function save_new_color(p_name,player,new_idx)
    
    bb_player.saved_colors[p_name] = bb_player.player_colors_avail[new_idx]
    --save for future use
    storage:set_string("saved_colors",minetest.serialize(bb_player.saved_colors))
    -- save the new texture
    bb_player.textures[p_name] = {"white.png^[colorize:"..bb_player.saved_colors[p_name]..":255","player_parts.png"}
    -- dont do a live update if they have clear textures anyways
    local is_playing = true 
    if player:get_properties().textures[1] == "blank.png" then
        is_playing = false
    end
    if is_playing then
        --live update
        player:set_properties({textures=bb_player.textures[p_name]})
    end

end



minetest.register_on_player_receive_fields(function(player, formname, fields)
    if formname ~= "" then
        if formname ~= "bb_player:firstjoin" then return end
    end

    local p_name = player:get_player_name()
    -- color change right button
    if fields.color_right then
        -- change the player's color, incrementing up the index

        --determine the new texture color idx
        local old_color = bb_player.saved_colors[p_name]
        local old_idx = 1
        for idx , color in pairs(bb_player.player_colors_avail) do
            if color == old_color then 
                old_idx = idx 
            end
        end
        local max_idx = #bb_player.player_colors_avail
        local new_idx
        if old_idx == max_idx then
            new_idx = 1 
        else
            new_idx = old_idx + 1
        end

        -- save the new color
        save_new_color(p_name,player,new_idx)
        player:set_inventory_formspec(bb_loop.get_inv_formspec(p_name))
        if formname == "bb_player:firstjoin" then
            minetest.show_formspec(p_name, "bb_player:firstjoin", bb_loop.get_inv_formspec(p_name))
        end

    end

    --color change left button
    if fields.color_left then
        -- change the player's color, going down the index

        --determine the new texture color idx
        local old_color = bb_player.saved_colors[p_name]
        local old_idx = 1
        for idx , color in pairs(bb_player.player_colors_avail) do
            if color == old_color then 
                old_idx = idx 
            end
        end
        local min_idx = 1
        local new_idx
        if old_idx == min_idx then
            new_idx = #bb_player.player_colors_avail 
        else
            new_idx = old_idx - 1
        end

        -- save the new color
        save_new_color(p_name,player,new_idx)
        
        player:set_inventory_formspec(bb_loop.get_inv_formspec(p_name))
        if formname == "bb_player:firstjoin" then
            minetest.show_formspec(p_name, "bb_player:firstjoin", bb_loop.get_inv_formspec(p_name))
        end
    end
end)