diff --git a/mods/lobby/tdc.lua b/mods/lobby/tdc.lua index a13c4fc..baff33f 100644 --- a/mods/lobby/tdc.lua +++ b/mods/lobby/tdc.lua @@ -13,6 +13,10 @@ local function _info(msg) return minetest.colorize('#ffff80', msg) end +local function _stdout(msg) + return minetest.colorize('#80ddee', msg) +end + local function _lsitem(item) return TAB .. '- ' .. item end @@ -47,63 +51,157 @@ local function list_corpses(ctab) end -tdc = { - fix_corpses = function(plr_name, map) - local pmap = map or '*' - local count = 0 +--[[ -------------------- tdc namespace -------------------- ]]-- +tdc = {} - for mapid, cplist in pairs(lobby.corpses) do - if pmap == '*' then +function tdc.fix_corpses(td_user, map) + local pmap = map or '*' + local count = 0 + + for mapid, cplist in pairs(lobby.corpses) do + if pmap == '*' then + lobby.corpse_removal(mapid) + lobby.corpses[mapid] = {} + count = count + 1 + else + if mapid:find(pmap) then lobby.corpse_removal(mapid) lobby.corpses[mapid] = {} count = count + 1 - else - if mapid:find(pmap) then - lobby.corpse_removal(mapid) - lobby.corpses[mapid] = {} - count = count + 1 - minetest.chat_send_player(plr_name, - _info('Corpses in ' .. _keyw(mapid) .. ' fixed.')) - break - end + minetest.chat_send_player(td_user, + _info('Corpses in ' .. _keyw(mapid) .. ' fixed.')) + break end end - if count == 0 then - minetest.chat_send_player(plr_name, _info('No maps found')) - elseif pmap == '*' then - minetest.chat_send_player(plr_name, - _info('Corpses in ' .. tostring(count) .. ' map(s) fixed.')) - end - end, + end + if count == 0 then + minetest.chat_send_player(td_user, _info('No maps found')) + elseif pmap == '*' then + minetest.chat_send_player(td_user, + _info('Corpses in ' .. tostring(count) .. ' map(s) fixed.')) + end +end - fix_map = function(plr_name, map) - local pmap = map or '*' - local count = 0 +function tdc.fix_map(td_user, map) + local pmap = map or '*' + local count = 0 - for mapid, count in pairs(lobby.map) do - if pmap == '*' then + for mapid, count in pairs(lobby.map) do + if pmap == '*' then + lobby.map[mapid] = 0 + lobby.update_maps(mapid) + count = count + 1 + else + if mapid:find(pmap) then lobby.map[mapid] = 0 lobby.update_maps(mapid) count = count + 1 - else - if mapid:find(pmap) then - lobby.map[mapid] = 0 - lobby.update_maps(mapid) - count = count + 1 - minetest.chat_send_player(plr_name, - _info('Map status of ' .. _keyw(mapid) .. ' fixed.')) - break - end + minetest.chat_send_player(td_user, + _info('Map status of ' .. _keyw(mapid) .. ' fixed.')) + break end end - if count == 0 then - minetest.chat_send_player(plr_name, _info('No maps found')) - elseif pmap == '*' then - minetest.chat_send_player(plr_name, - _info('Status data for ' .. tostring(count) .. ' map(s) fixed.')) + end + if count == 0 then + minetest.chat_send_player(td_user, _info('No maps found')) + elseif pmap == '*' then + minetest.chat_send_player(td_user, + _info('Status data for ' .. tostring(count) .. ' map(s) fixed.')) + end +end + +-- Return active players whose player name matches `expr` +function tdc.active_players_matching(expr) + local players = {} + + for _, player in pairs(minetest.get_connected_players()) do + local pl_name = player:get_player_name() + + if pl_name:find(expr) then + table.insert(players, player) end - end, -} + end + return players +end + +-- Return active map ids matching `expr` +function tdc.active_mapids_matching(expr) + local mapids = {} + + for mapid, count in pairs(lobby.map) do + if mapid:find(expr) and count ~= nil and count > 0 then + mapids[mapid] = true + end + end + + -- The lobby itself never gets into lobby.map, so we insert it manually, + -- since we want to treat it just like a map. + local mapid_lobby = 'lobby' + if mapid_lobby:find(expr) then mapids[mapid_lobby] = true end + + return mapids +end + +-- Return players currently visiting a map whose id is in the list `mapids` +function tdc.players_visiting(mapids) + local players = {} + + for pl_name, mapid in pairs(lobby.game) do + local mid = mapid + + if mapid:find("_solo$") then + mid = mapid:sub(1, -6) + elseif mapid:find("_ghost$") then + mid = mapid:sub(1, -7) + end + if mapids[mid] then + table.insert(players, minetest.get_player_by_name(pl_name)) + end + end + return players +end + +-- For each player in the `players` list, create an entry in the `index` table, +-- having `player:get_player_name()` as keyword. +function tdc.index_by_name(players, index) + if players == nil or index == nil then return end + + for _, player in ipairs(players) do + local pl_name = player:get_player_name() + index[pl_name] = player + end +end + +-- Return a list of currently active players where either the player name +-- or the map id they're currently visiting matches `expr`. +function tdc.list_players_matching(expr) + local players = tdc.active_players_matching(expr) + local mapids = tdc.active_mapids_matching(expr) + local visitors = tdc.players_visiting(mapids) + local matches = {} + + -- index all matching players by their name + tdc.index_by_name(players, matches) + tdc.index_by_name(visitors, matches) + + return matches +end + +-- Return a "footprint" representation of the player privileges `privs` +function tdc.privs_footprint(privs) + local s, t, b, c, w, p, m + + if privs.server then s = 's' else s = '-' end + if privs.traitor_dev then t = 't' else t = '-' end + if privs.builder then b = 'b' else b = '-' end + if privs.creative then c = 'c' else c = '-' end + if privs.worldeditor then w = 'w' else w = '-' end + if privs.pro_player then p = 'p' else p = '-' end + if privs.multihome then m = 'm' else m = '-' end + + return table.concat({s, t, b, c, w, p, m}) +end + -- tdc.actions: enumerates the possible /td commands -- tdc.actions = { cmd_1 = def_1, cmd_2 = def_2, ... } @@ -131,20 +229,20 @@ tdc.actions = { table.insert(msgtab, 'Type "' .. _keyw('/td help ') .. '" to get help for a specific command.') return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) if params then local par1 = params:match('[%w_]+') if par1 and tdc.actions[par1] then - minetest.chat_send_player(plr_name, tdc.actions[par1].help()) + minetest.chat_send_player(td_user, tdc.actions[par1].help()) elseif par1 then -- TODO: find par1 as cmd prefix in tdc.actions local msg = _info('Unknown command "' .. par1 .. '"\n') .. list_actions() - minetest.chat_send_player(plr_name, msg) + minetest.chat_send_player(td_user, msg) else - minetest.chat_send_player(plr_name, tdc.actions['help'].help()) + minetest.chat_send_player(td_user, tdc.actions['help'].help()) end else - minetest.chat_send_player(plr_name, tdc.actions['help'].help()) + minetest.chat_send_player(td_user, tdc.actions['help'].help()) end end, }, @@ -160,7 +258,7 @@ tdc.actions = { } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) local map = params:match('[%w_]+') or '*' local hdr, msg, ccount, clist if params == '' then @@ -191,7 +289,7 @@ tdc.actions = { else msg = _info('No corpses yet.') end - minetest.chat_send_player(plr_name, msg) + minetest.chat_send_player(td_user, msg) minetest.log('action', minetest.strip_colors(msg)) end, }, @@ -205,7 +303,7 @@ tdc.actions = { } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) local msgtab = {_info('Active maps:')} local plr_count = 0 local msg @@ -233,7 +331,7 @@ tdc.actions = { table.insert(msgtab, _lsitem('lobby: ' .. tostring(clobby) .. ' player(s)')) end msg = table.concat(msgtab, '\n') - minetest.chat_send_player(plr_name, msg) + minetest.chat_send_player(td_user, msg) minetest.log('action', minetest.strip_colors(msg)) end, }, @@ -247,7 +345,7 @@ tdc.actions = { } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) local msg = '' for mapid, traitor in pairs(lobby.traitors) do -- table value could be nil if entry is to be GC'd @@ -260,47 +358,81 @@ tdc.actions = { else msg = _info('Active traitors:\n') .. msg end - minetest.chat_send_player(plr_name, msg) + minetest.chat_send_player(td_user, msg) minetest.log('action', minetest.strip_colors(msg)) end, }, - -- CMD: /td player - player = { + -- CMD: /td players + players = { info = 'Show player attributes', help = function() local msgtab = { - _info('Usage: /td player '), - 'List some player metadata.', + _info('Usage: /td players '), + 'Show metadata of one or more players.', 'Params:', - TAB .. ' player name (substring suffices).', - '\nNote: is restricted to alphanumeric chars and "_", for the sake of security.' + TAB .. ' Part of a player name or map ID used as search expression.', + TAB .. ' Any matching player name will be included in the result list.', + TAB .. ' If is part of a map ID, lists all players currently', + TAB .. ' visiting that map.', + '\nNote: is restricted to alphanumeric chars and "_", for the sake of security.', + '\nThe metadata of matching players is listed in a table with the following columns:', + TAB .. 'Player displays the player\'s login name', + TAB .. 'Map shows the map id the player is currently visiting', + TAB .. 'Privs shows the state of some more widely used traitor privileges, which is', + TAB .. ' a short string where each letter symbolizes a certain privilege', + TAB .. ' (' .. + _stdout('s') .. 'erver, ' .. + _stdout('t') .. 'raitor_dev, ' .. + _stdout('b') .. 'uilder, ' .. + _stdout('c') .. 'reative, ' .. + _stdout('w') .. 'orldeditor, ' .. + _stdout('p') .. 'ro_player, ' .. + _stdout('m') .. 'ultihome)', + TAB .. ' If a player does not have a certain privilege, the privilege\'s letter', + TAB .. ' is replaced by a \'-\' instead.', + TAB .. 'Mode displays the player mode', + TAB .. 'Tone shows the player\'s current tone color', + TAB .. 'Spawn prints the player\'s current spawn position (if any)', } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) if not params or not params:find("[%w_]+") then - minetest.chat_send_player(plr_name, 'Missing argument, type "/td help player" for help.') + minetest.chat_send_player(td_user, 'Missing argument, type "/td help players" for help.') else - local p1, p2, plid = params:find('([%w_]+)') + local p1, p2, expr = params:find('([%w_]+)') + local matches = tdc.list_players_matching(expr) local count = 0 + local mtab = { + _info('Player Map Privs Mode Tone Spawn'), + '--------------------------------------------------------------------------------------', + } + -- sort list by player name + local sorted = {} + for name in pairs(matches) do table.insert(sorted, name) end + table.sort(sorted) - for _, player in pairs(minetest.get_connected_players()) do - local pname = player:get_player_name() + for _, name in pairs(sorted) do + count = count + 1 + local player = matches[name] + local attr = player:get_meta() + local pl_name = _keyw(name) + local padding = 20 - name:len() + local pl_padding = string.rep(' ', padding) + local pl_map = lobby.game[name] or '' + local pl_privs = tdc.privs_footprint(minetest.get_player_privs(name)) + local pl_mode = attr:get_string('mode') or '' + local pl_tone = attr:get_int('tone') + local pl_spawn = attr:get_string('spawn_pos') or '' - if pname:find(plid) then - count = count + 1 - local attr = player:get_meta() - local mtab = { - _info('Attributes of ') .. _keyw(pname) .. ':', - TAB .. 'ghost: ' .. (attr:get_string('ghost') or 'false'), - TAB .. 'spawn: ' .. (attr:get_string('spawn_pos') or ''), - TAB .. 'voting: ' .. (attr:get_string('voting') or 'false') - } - minetest.chat_send_player(plr_name, table.concat(mtab, '\n')) - end + table.insert(mtab, string.format('%s%s %-20s %-7s %-7s %5d %-20s', + pl_name, pl_padding, pl_map, pl_privs, pl_mode, pl_tone, pl_spawn) + ) end if count == 0 then - minetest.chat_send_player(plr_name, _info('No matching player')) + minetest.chat_send_player(td_user, _info('No match')) + else + minetest.chat_send_player(td_user, table.concat(mtab, '\n')) end end end, @@ -326,23 +458,23 @@ tdc.actions = { } return table.concat(msgtab, '\n') end, - exec = function(plr_name, params) + exec = function(td_user, params) local helpcmd = 'type "/td help fix" for help.' if not params then - minetest.chat_send_player(plr_name, _info('Missing arguments, ' .. helpcmd)) + minetest.chat_send_player(td_user, _info('Missing arguments, ' .. helpcmd)) else local p1, p2, fix, map p1, p2, fix, map = params:find('(%w+)%s+([%w_*]+)') if not fix or not map then - minetest.chat_send_player(plr_name, _info('Missing arguments, ' .. helpcmd)) + minetest.chat_send_player(td_user, _info('Missing arguments, ' .. helpcmd)) else if string.find('corpses', fix) then - tdc.fix_corpses(plr_name, map) + tdc.fix_corpses(td_user, map) elseif string.find('map', fix) then - tdc.fix_map(plr_name, map) + tdc.fix_map(td_user, map) else - minetest.chat_send_player(plr_name, _info('Unknown fix action, ' .. helpcmd)) + minetest.chat_send_player(td_user, _info('Unknown fix action, ' .. helpcmd)) end end end @@ -350,7 +482,7 @@ tdc.actions = { } } -function tdc.exec(plr_name, params) +function tdc.exec(td_user, params) local cmd = nil if params and params ~= '' then local par1 = params:match('[%w_]+') @@ -369,14 +501,14 @@ function tdc.exec(plr_name, params) end end if cname then - minetest.log('action', plr_name .. ' runs "/td ' .. cname .. parN .. '"') - cmd.exec(plr_name, parN) + minetest.log('action', td_user .. ' runs "/td ' .. cname .. parN .. '"') + cmd.exec(td_user, parN) else - minetest.chat_send_player(plr_name, + minetest.chat_send_player(td_user, _info('Unknown command "' .. par1 .. '", type "/td help" for possible commands.')) end else - minetest.chat_send_player(plr_name, list_actions()) + minetest.chat_send_player(td_user, list_actions()) end return true end