fren = {} frenemies = fren fren.friends = {} fren.friend_color = "#00FF00" fren.enemies = {} fren.enemy_color = "#FF0000" fren.neutral_color = "#FFFFFF" fren.groups = {} --[[ storage: player is {level = n, color = ""} group is {name = "", level = n, color = "", members = {"", ""}} available: player is {level = n, color = "", groups = {"", ""}} group is {name = "", level = n, color = "", members = {"", ""}} qualify(player) name_of(qualified) is_enemy(player) is_friend(player) is_current_player(player) is_neutral(player) player_color(player) player_groups(player) player_level(player) get_online_players() get_online_friends() get_online_enemies() get_online_neutrals() get_online_group(group) if a player name contains @ it is a fully qualified name .friend and .enemy should support level .friend player -- make player friend or recolor them .fr player .unfriend player .unfr player .enemy player -- make player enemy or recolor them .en player .unenemy player .unen player .group name -- create or recolor group .rm_group name .gadd group player .grm group player .lfriends .lenemies .lgroup group .lfriends_all .lenemies_all .lgroup_all group maybe groups should be qualified per server? --]] local storage = minetest.get_mod_storage() -- should remove groups function fren.serialize() return minetest.write_json({ friends = fren.friends, friend_color = fren.friend_color, enemies = fren.enemies, enemy_color = fren.enemy_color, neutral_color = fren.neutral_color, groups = fren.groups }) end -- should relate groups function fren.deserialize(str) local des = minetest.parse_json(str) if des then fren.friends = des.friends or {} fren.friend_color = des.friend_color fren.enemies = des.enemies or {} fren.enemy_color = des.enemy_color fren.neutral_color = des.neutral_color fren.groups = des.groups or {} end end function fren.store() storage:set_string("data", fren.serialize()) end function fren.load() local d = storage:get("data") if d then fren.deserialize(d) end end fren.load() local server_info = minetest.get_server_info() function fren.qualify(player) local name = server_info.ip if server_info.address ~= "" then name = server_info.address end return player .. "@" .. name .. ":" .. server_info.port end function fren.name_of(qualified) return qualified:match("(.-)@") end function fren.on_server(name) local qname=fren.qualify(name) for k,v in pairs(fren.friends) do if k == qname then return true end end for k,v in pairs(fren.enemies) do if k == qname then return true end end return false end -- player required, color/level optional function fren.friend(player, color, level) local n = fren.qualify(player) fren.friends[n] = {placeholder = true} -- true because the way Minetest serializes Json replaces {} with null fren.friends[n].color = color fren.friends[n].level = level fren.store() end function fren.unfriend(player) fren.friends[fren.qualify(player)] = nil fren.store() end function fren.enemy(player, color, level) local n = fren.qualify(player) fren.enemies[n] = {placeholder = true} fren.enemies[n].color = color fren.enemies[n].level = level fren.store() end function fren.unenemy(player) fren.enemies[fren.qualify(player)] = nil fren.store() end function fren.group(name, color, level) fren.groups[name] = {placeholder = true} fren.groups[name].color = color fren.groups[name].level = level fren.store() end function fren.remove_group(name) fren.groups[name] = nil fren.store() end function fren.group_add_player(group, player, level) if fren.groups[group] then local q = fren.qualify(player) fren.groups[group].members[q] = {placeholder = true} fren.groups[group].members[q].level = level fren.store() end end function fren.group_remove_player(group, player) if fren.groups[group] then fren.groups[group].members[fren.qualify(player)] = nil fren.store() end end function fren.is_enemy(player) return fren.enemies[fren.qualify(player)] ~= nil end function fren.is_friend(player) return fren.friends[fren.qualify(player)] ~= nil end function fren.is_neutral(player) return not fren.is_enemy(player) and not fren.is_friend(player) end function fren.is_current_player(player) return player == minetest.locaplayer:get_name() end function fren.in_group(player, group) if fren.groups[group] then return fren.groups[group].members[fren.qualify(player)] ~= nil end end -- maybe groups should be involved? function fren.color(player) local q = fren.qualify(player) if fren.is_friend(player) then return fren.friends[q].color or fren.friend_color elseif fren.is_enemy(player) then return fren.enemies[q].color or fren.enemy_color else return friend.neutral_color end end -- should be a setting local check_interval = 1 local online_cached = {} local online_cached_last = 0 local friend_online_cached = {} local friend_online_cached_last = 0 local enemy_online_cached = {} local enemy_online_cached_last = 0 local neutral_online_cached = {} local neutral_online_cached_last = 0 local group_online_cached = {} local group_online_cached_last = {} local function is_time(epoch) if epoch == nil then return true end return os.clock() - epoch >= check_interval end local function uniq(l) local o = {} local oi = 1 local last for i, v in ipairs(l) do if last ~= v then o[oi] = v oi = oi + 1 end last = v end return o end function fren.get_online_players() if is_time(online_cached_last) then online_cached_last = os.clock() online_cached = minetest.get_player_names() table.sort(online_cached) online_cached = uniq(online_cached) end return online_cached end local function filter(filter, source) local o = {} for k, v in pairs(source) do if filter(v) then o[k] = v end end return o end function fren.get_online_friends() if is_time(friend_online_cached_last) then friend_online_cached_last = os.clock() friend_online_cached = filter(fren.is_friend, fren.get_online_players()) end return friend_online_cached end function fren.get_all_friends() if is_time(friend_online_cached_last) then friend_online_cached_last = os.clock() friend_online_cached = filter(fren.is_friend, fren.get_online_players()) end return friend_online_cached end function fren.get_online_enemies() if is_time(enemy_online_cached_last) then enemy_online_cached_last = os.clock() enemy_online_cached = filter(fren.is_enemy, fren.get_online_players()) end return enemy_online_cached end function fren.get_online_neutrals() if is_time(neutral_online_cached_last) then neutral_online_cached_last = os.clock() neutral_online_cached = filter(fren.is_neutral, fren.get_online_players()) end return neutral_online_cached end function fren.get_online_group(group) if is_time(group_online_cached_last[group]) then group_online_cached_last[group] = os.clock() group_online_cached[group] = filter( function(v) return fren.in_group(v, group) end, fren.get_online_players()) end return group_online_cached[group] end -- first second [opt_third] -- converts to -- { -- [1] = {name = "first", required = true}, -- [2] = {name = "second", required = true}, -- [3] = {name = "opt_third", required = false} -- } local function parse_opts(str) local o = {} local opts = string.split(str, " ") for i, v in ipairs(opts) do if v:match("%[(.-)%]") then o[i] = {name = v, required = false} else o[i] = {name = v, required = true} end end return o end -- first second [opt_third] -- returns {first = a, second = a, opt_third = a/nil} or nil if parsing failed local function parse_args(str, args) local opts = parse_opts(str) local splargs = string.split(args, " ") local parsed = {} for i, v in ipairs(opts) do if splargs[i] then parsed[v.name] = splargs[i] elseif v.required then minetest.display_chat_message("Error: argument '" .. v.name .. "' is required.") return nil else break end end return parsed end minetest.register_chatcommand("friend", { description = "Add a player as a friend.", params = " ", func = function(params) local args = parse_args("player [color]", params) if args then fren.friend(args.player, args.color) end end }) minetest.register_chatcommand("unfriend", { description = "Remove player from friend list.", params = "", func = function(params) local args = parse_args("player", params) if args then fren.unfriend(args.player) end end }) minetest.register_chatcommand("enemy", { description = "Add player as an enemy.", params = " ", func = function(params) local args = parse_args("player [color]", params) if args then fren.enemy(args.player, args.color) end end }) minetest.register_chatcommand("unenemy", { description = "Remove player from enemy list.", params = "", func = function(params) local args = parse_args("player", params) if args then fren.unenemy(args.player) end end }) local function lcat(l) return table.concat(l, ", ") end local function displist(l) minetest.display_chat_message(lcat(l)) end minetest.register_chatcommand("lfriends", { description = "List online friends.", func = function() displist(fren.get_online_friends()) end }) minetest.register_chatcommand("lenemies", { description = "List online enemies.", func = function() displist(fren.get_online_enemies()) end }) minetest.register_chatcommand("lgroup", { description = "List online members of a group.", params = "", func = function(params) local args = parse_args("group", params) if args then displist(fren.get_online_group(args.group)) end end })