Add debug support via new "/td" chat command.

master
whats_his_face 2022-01-29 22:26:11 -06:00
parent a230914245
commit 91828b9d8c
2 changed files with 396 additions and 0 deletions

View File

@ -35,6 +35,12 @@ dofile(minetest.get_modpath('lobby')..'/functions.lua')
dofile(minetest.get_modpath('lobby')..'/player_callbacks.lua')
dofile(minetest.get_modpath('lobby')..'/shop.lua')
dofile(minetest.get_modpath('lobby')..'/tool.lua')
dofile(minetest.get_modpath('lobby')..'/tdc.lua')
minetest.register_privilege("traitor_dev", {
description = "Can use /td.",
give_to_singleplayer = false,
give_to_admin = true})
minetest.register_privilege("pro_player", {
description = "Completed the tutorial.",

390
mods/lobby/tdc.lua Normal file
View File

@ -0,0 +1,390 @@
-- tdc.lua: Traitor debugging utility
--
-- Implements the "/td" chat command.
-- Type "/td help" for info on how to use it.
local TAB = ' '
local function _keyw(msg)
return minetest.colorize('#a0a0ff', msg)
end
local function _info(msg)
return minetest.colorize('#ffff80', msg)
end
local function _lsitem(item)
return TAB .. '- ' .. item
end
function list_actions()
local hdr = 'Available commands: '
local msgtab = {}
for cmd in pairs(tdc.actions) do
table.insert(msgtab, _keyw(cmd))
end
return hdr .. table.concat(msgtab, '|')
end
-- return corpse count / info messages for a list <ctab> of node positions
local function list_corpses(ctab)
local node, meta
local count = 0
local corpses = {}
for _, pos in ipairs(ctab) do
node = minetest.get_node(pos)
meta = minetest.get_meta(pos)
if node and node.name and node.name ~= 'air' then
local cinfo = meta:get_string('infotext')
local cname = cinfo:match("[^']+") or node.name
table.insert(corpses, _lsitem(cname .. ' ' .. minetest.pos_to_string(pos)))
else
table.insert(corpses, _lsitem('<nil> ' .. minetest.pos_to_string(pos)))
end
count = count + 1
end
return count, corpses
end
tdc = {
fix_corpses = function(plr_name, 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
minetest.chat_send_player(plr_name,
_info('Corpses in ' .. _keyw(mapid) .. ' fixed.'))
break
end
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,
fix_map = function(plr_name, map)
local pmap = map or '*'
local count = 0
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
minetest.chat_send_player(plr_name,
_info('Map status of ' .. _keyw(mapid) .. ' fixed.'))
break
end
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
end,
}
-- tdc.actions: enumerates the possible /td commands
-- tdc.actions = { cmd_1 = def_1, cmd_2 = def_2, ... }
--
-- command definitions are maps:
-- def_n = {
-- info = (string, short description for "/td help")
-- help = (0-arg function, returns a longer description for "/td help <action>")
-- exec = (2-arg function, executes the action for player named <arg1> with the rest
-- of the cmdline string passed in as <arg2>)
-- }
tdc.actions = {
-- CMD: /td help
help = {
info = 'Show general help and list available commands',
help = function()
local msgtab = {
'Traitor debugging utility. Type "' .. _keyw('/td <cmd>') .. '" to execute a command.',
'Available commands:'
}
for cmd, action in pairs(tdc.actions) do
table.insert(msgtab, string.format(TAB .. '%-10s\t%s', cmd, action.info))
end
table.insert(msgtab, '\nCommands can be abbreviated (e.g., "/td h" shows this help).')
table.insert(msgtab, 'Type "' .. _keyw('/td help <cmd>') .. '" to get help for a specific command.')
return table.concat(msgtab, '\n')
end,
exec = function(plr_name, 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())
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)
else
minetest.chat_send_player(plr_name, tdc.actions['help'].help())
end
else
minetest.chat_send_player(plr_name, tdc.actions['help'].help())
end
end,
},
-- CMD: /td corpses
corpses = {
info = 'Show info on corpses',
help = function()
local msgtab = {
_info('Usage: /td corpses [<map>]'),
'Show corpses in map <map>. If <map> is omitted, list all corpses.',
'Params:',
TAB .. '<map> map id to search, or "*" to list all corpses'
}
return table.concat(msgtab, '\n')
end,
exec = function(plr_name, params)
local map = params:match('[%w_]+') or '*'
local hdr, msg, ccount, clist
if params == '' then
-- list all corpses instead
hdr = _info('List of corpses:\n')
for mid, ctab in pairs(lobby.corpses) do
local corpses
if ctab then ccount = list_corpses(ctab) end
if ccount > 0 then
msg = (msg or '') .. _lsitem(mid .. ': ' .. tostring(ccount)) .. '\n'
end
end
else
for mid, ctab in pairs(lobby.corpses) do
if ctab and (mid:find(map) or map == '*') then
hdr = _info('Corpses in ') .. _keyw(mid) .. ':\n'
ccount, clist = list_corpses(ctab)
if ccount > 0 then
msg = (msg or '') .. hdr .. table.concat(clist, '\n') .. '\n'
end
hdr = ''
if map ~= '*' then break end
end
end
end
if msg then
msg = hdr .. msg
else
msg = _info('No corpses yet.')
end
minetest.chat_send_player(plr_name, msg)
minetest.log('action', minetest.strip_colors(msg))
end,
},
-- CMD: /td maps
maps = {
info = 'Show maps currently visited by players',
help = function()
local msgtab = {
_info('Usage: /td maps'),
'Show map names of all currently active game sessions.'
}
return table.concat(msgtab, '\n')
end,
exec = function(plr_name, params)
local msgtab = {_info('Active maps:')}
local plr_count = 0
local msg
for mapid, plr_count in pairs(lobby.map) do
local gh_count = 0
local gh_map = mapid .. '_ghost'
for _, mid in pairs(lobby.game) do
if mid == gh_map then
gh_count = gh_count + 1
end
end
if plr_count > 0 or gh_count > 0 then
table.insert(msgtab,
_lsitem(mapid .. ': ' .. plr_count .. ' player(s)')
.. ' / ' .. _keyw(tostring(gh_count) .. ' ghost(s)'))
end
end
local clobby = 0
for _, mid in pairs(lobby.game) do
if mid == 'lobby' then
clobby = clobby + 1
end
end
if clobby > 0 then
table.insert(msgtab, _lsitem('lobby: ' .. tostring(clobby) .. ' player(s)'))
end
msg = table.concat(msgtab, '\n')
minetest.chat_send_player(plr_name, msg)
minetest.log('action', minetest.strip_colors(msg))
end,
},
-- CMD: /td traitors
traitors = {
info = 'Show the traitors in each active map',
help = function()
local msgtab = {
_info('Usage: /td traitors'),
'Show the names of all traitors in currently active game sessions'
}
return table.concat(msgtab, '\n')
end,
exec = function(plr_name, params)
local msg = ''
for mapid, traitor in pairs(lobby.traitors) do
-- table value could be nil if entry is to be GC'd
if traitor then
msg = msg .. _lsitem(mapid .. ': ' .. traitor) .. '\n'
end
end
if msg == '' then
msg = _info('No active traitors!')
else
msg = _info('Active traitors:\n') .. msg
end
minetest.chat_send_player(plr_name, msg)
minetest.log('action', minetest.strip_colors(msg))
end,
},
-- CMD: /td player <id>
player = {
info = 'Show player attributes',
help = function()
local msgtab = {
_info('Usage: /td player <id>'),
'List some player metadata.',
'Params:',
TAB .. '<id> player name (substring suffices).',
'\nNote: <id> is restricted to alphanumeric chars and "_", for the sake of security.'
}
return table.concat(msgtab, '\n')
end,
exec = function(plr_name, params)
if not params or not params:find("[%w_]+") then
minetest.chat_send_player(plr_name, 'Missing argument, type "/td help player" for help.')
else
local p1, p2, plid = params:find('([%w_]+)')
local count = 0
for _, player in pairs(minetest.get_connected_players()) do
local pname = player:get_player_name()
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 '<nil>'),
TAB .. 'voting: ' .. (attr:get_string('voting') or 'false')
}
minetest.chat_send_player(plr_name, table.concat(mtab, '\n'))
end
end
if count == 0 then
minetest.chat_send_player(plr_name, _info('No matching player'))
end
end
end,
},
-- CMD: /td fix (corpses|map) <map>
--[[
fix corpses: call lobby.corpse_removal(<map>), then reset its poslist
fix maps: set lobby.map[<map>] = 0, then call lobby.update_maps(<map>)
--]]
fix = {
info = 'Fix internal data',
help = function()
local msgtab = {
_info('Usage: /td fix <type> <map>'),
'Try to repair damages to game data.',
'Params:',
TAB .. '<type> One of the following:',
TAB .. ' ' .. _keyw('corpses') .. ' -- fix lobby.corpses',
TAB .. ' ' .. _keyw('maps') .. ' -- fix lobby.map and player status',
TAB .. '<map> map id to fix, or "*" to fix all active maps',
'\nNote that you can abbreviate both <type> and <map> parameters to a',
'prefix, e.g. \"' .. _keyw('/td fix c *') .. '\" tries to fix corpses in all maps.'
}
return table.concat(msgtab, '\n')
end,
exec = function(plr_name, params)
local helpcmd = 'type "/td help fix" for help.'
if not params then
minetest.chat_send_player(plr_name, _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))
else
if string.find('corpses', fix) then
tdc.fix_corpses(plr_name, map)
elseif string.find('map', fix) then
tdc.fix_map(plr_name, map)
else
minetest.chat_send_player(plr_name, _info('Unknown fix action, ' .. helpcmd))
end
end
end
end,
}
}
function tdc.exec(plr_name, params)
local cmd = nil
if params and params ~= '' then
local par1 = params:match('[%w_]+')
local parN = params:sub(par1:len() + 1) or ''
local cname
if tdc.actions[par1] then
cname = par1
cmd = tdc.actions[par1]
else
-- try cmd prefix match
for cmdname in pairs(tdc.actions) do
if cmdname:find(par1) == 1 then
cname = cmdname
cmd = tdc.actions[cmdname]
end
end
end
if cname then
minetest.log('action', plr_name .. ' runs "/td ' .. cname .. parN .. '"')
cmd.exec(plr_name, parN)
else
minetest.chat_send_player(plr_name,
_info('Unknown command "' .. par1 .. '", type "/td help" for possible commands.'))
end
else
minetest.chat_send_player(plr_name, list_actions())
end
return true
end
minetest.register_chatcommand('td', {
privs = {traitor_dev = true},
params = '<cmd> [<args>]',
description = 'Traitor debugging commands. Type "/td help" for a list of possible commands.',
func = tdc.exec
})