487 lines
15 KiB
Lua
487 lines
15 KiB
Lua
--[[
|
||
<09> 2017 Thriving Ventures Ltd do not share, re-distribute or modify
|
||
without permission of its author (gustaf@thrivingventures.com).
|
||
]]
|
||
|
||
--- ## Shared
|
||
-- The registration and handling of chat commands.
|
||
-- @module serverguard.command
|
||
|
||
local string = string;
|
||
|
||
serverguard.command = serverguard.command or {};
|
||
serverguard.command.stored = serverguard.command.stored or {};
|
||
|
||
--- Gets a table of all the registered commands.
|
||
-- @treturn table Command data.
|
||
function serverguard.command:GetTable()
|
||
return self.stored;
|
||
end;
|
||
|
||
serverguard.command.GetStored = serverguard.command.GetTable;
|
||
|
||
--- Registers a new command.
|
||
-- @table data The command table to extract info from.
|
||
function serverguard.command:Add(data)
|
||
self.stored[data.command] = data;
|
||
|
||
if (data.permissions) then
|
||
serverguard.permission:Add(data.permissions);
|
||
end;
|
||
|
||
if (SERVER) then
|
||
self.stored[data.command].ContextMenu = nil;
|
||
end;
|
||
end;
|
||
|
||
--- Removes a command.
|
||
-- @string uniqueID The unique ID of the command.
|
||
function serverguard.command:Remove(uniqueID)
|
||
self.stored[uniqueID] = nil;
|
||
end;
|
||
|
||
--- Gets a command by its unique ID.
|
||
-- @string uniqueID The unique ID of the command.
|
||
-- @treturn table Command data.
|
||
function serverguard.command:FindByID(uniqueID)
|
||
local command = self.stored[uniqueID];
|
||
|
||
if (command) then
|
||
return command;
|
||
else
|
||
for k, v in pairs(self.stored) do
|
||
if (v.aliases and table.HasValue(v.aliases, uniqueID)) then
|
||
return v;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
--- Gets the formatted argument string from a command table.
|
||
-- @table commandTable The command table.
|
||
-- @treturn string Formatted string.
|
||
function serverguard.command:GetArgumentsString(commandTable)
|
||
local result = "";
|
||
local first = true;
|
||
|
||
if (commandTable.arguments) then
|
||
for k, v in pairs(commandTable.arguments) do
|
||
if (first) then
|
||
first = false;
|
||
else
|
||
result = result .. " ";
|
||
end;
|
||
|
||
result = result .. "<" .. v .. ">";
|
||
end;
|
||
end;
|
||
|
||
if (commandTable.optionalArguments) then
|
||
for k, v in pairs(commandTable.optionalArguments) do
|
||
if (first) then
|
||
first = false;
|
||
else
|
||
result = result .. " ";
|
||
end;
|
||
|
||
result = result .. "[" .. v .. "]";
|
||
end;
|
||
end;
|
||
|
||
return result;
|
||
end;
|
||
|
||
serverguard.command.Get = serverguard.command.FindByID;
|
||
|
||
local function GetPlayerSteamID32(player)
|
||
local stid_32 = string.format("%.0f", string.sub(player:SteamID64(), 3) - "561197960265728")
|
||
|
||
return stid_32
|
||
end
|
||
|
||
|
||
if (SERVER) then
|
||
local notifyCommands = octolib.array.toKeys{ 'ban', 'unban', 'kick', 'rank' }
|
||
|
||
--[[ Internal function run a command. --]]
|
||
local function RUN_COMMAND(commandTable, player, bIsSilent, arguments)
|
||
if (!commandTable) then
|
||
return;
|
||
end;
|
||
|
||
bIsSilent = bIsSilent or not notifyCommands[commandTable.command or '']
|
||
|
||
-- Legacy command style.
|
||
if (commandTable.Execute) then
|
||
local bStatus, value = pcall(commandTable.Execute, commandTable, player, bIsSilent, arguments);
|
||
|
||
if (!commandTable.bNoLog) then
|
||
serverguard.PrintConsole(string.format(
|
||
"%s ran command \"%s %s\"\n", serverguard.player:GetName(player), commandTable.command, table.concat(arguments, " ")
|
||
));
|
||
end;
|
||
|
||
if (bStatus) then
|
||
hook.Call("serverguard.RanCommand", nil, player, commandTable, bIsSilent, arguments);
|
||
else
|
||
ErrorNoHalt(string.format("The \"%s\" command has failed to run.\n%s\n", commandTable.command, value));
|
||
return;
|
||
end;
|
||
else
|
||
if (commandTable.OnExecute) then
|
||
commandTable:OnExecute(player, arguments);
|
||
end;
|
||
|
||
local targets = {};
|
||
|
||
if (commandTable.bSingleTarget) then
|
||
local target = util.FindPlayer(arguments[1], player);
|
||
|
||
if (target and IsValid(target)) then
|
||
if (!util.PlayerMatchesImmunity(commandTable.immunity, player, target)) then
|
||
serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "This player has a higher immunity than you.");
|
||
else
|
||
if (commandTable:OnPlayerExecute(player, target, arguments)) then
|
||
targets = {target};
|
||
end;
|
||
end;
|
||
end;
|
||
else
|
||
targets = util.ExecuteOnPlayers(arguments[1], player, commandTable.immunity or SERVERGUARD.IMMUNITY.LESSOREQUAL, function(target)
|
||
return commandTable:OnPlayerExecute(player, target, arguments);
|
||
end);
|
||
end;
|
||
|
||
if (!bIsSilent and commandTable.OnNotify and targets and #targets > 0 and !hook.Call("serverguard.CommandNotify", nil, player, targets, arguments)) then
|
||
local arguments = {commandTable:OnNotify(player, targets, arguments)};
|
||
|
||
serverguard.Notify(nil, unpack(arguments));
|
||
end;
|
||
|
||
if (commandTable.OnExecuted) then
|
||
commandTable:OnExecuted(player, targets, arguments);
|
||
end;
|
||
|
||
if (!commandTable.bNoLog) then
|
||
serverguard.PrintConsole(string.format(
|
||
"%s ran command \"%s %s\"\n", serverguard.player:GetName(player), commandTable.command, table.concat(arguments, " ")
|
||
));
|
||
end;
|
||
|
||
hook.Call("serverguard.RanCommand", nil, player, commandTable, bIsSilent, arguments);
|
||
end;
|
||
end;
|
||
|
||
--- Makes a player run a command.
|
||
-- @player player The player running the command. You should **not** pass this argument clientside.
|
||
-- @string command The unique ID of the command to run.
|
||
-- @bool bIsSilent Whether or not to display a notification.
|
||
-- @param ... Any arguments to pass to the command.
|
||
-- @treturn bool Whether or not the command exists.
|
||
function serverguard.command.Run(player, command, bIsSilent, ...)
|
||
local arguments = {...};
|
||
local commandTable = serverguard.command:FindByID(command);
|
||
|
||
if (commandTable) then
|
||
if (commandTable.bDisallowConsole and util.IsConsole(player)) then
|
||
serverguard.PrintConsole("You cannot run the \"" .. commandTable.command .. "\" command as the server.\n");
|
||
return true;
|
||
end;
|
||
|
||
if (hook.Call("serverguard.PlayerCanUseCommand", nil, player, commandTable, bIsSilent, arguments) != false) then
|
||
if (util.IsConsole(player)) then
|
||
RUN_COMMAND(commandTable, player, bIsSilent, arguments);
|
||
return true;
|
||
end;
|
||
|
||
if (commandTable.permissions and #commandTable.permissions > 0) then
|
||
if (!serverguard.player:HasPermission(player, commandTable.permissions)) then
|
||
serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, string.format("You do not have access to this command, %s.", player:Nick()));
|
||
return true;
|
||
end;
|
||
end;
|
||
|
||
if (!commandTable.arguments or #arguments >= #commandTable.arguments) then
|
||
RUN_COMMAND(commandTable, player, bIsSilent, arguments);
|
||
elseif (!util.IsConsole(player) and #arguments == 0 and commandTable.arguments and #commandTable.arguments == 1 and commandTable.arguments[1] == "player") then
|
||
RUN_COMMAND(commandTable, player, bIsSilent, {player:Name()});
|
||
else
|
||
serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "The syntax for this command is incorrect:");
|
||
serverguard.Notify(player, SERVERGUARD.NOTIFY.WHITE, string.format("!%s %s", commandTable.command, serverguard.command:GetArgumentsString(commandTable)));
|
||
end;
|
||
end;
|
||
|
||
return true;
|
||
end;
|
||
|
||
return false;
|
||
end;
|
||
|
||
hook.Add("PlayerSay", "serverguard.command.PlayerSay", function(pPlayer, text, m_bToAll, m_bDead)
|
||
if (text == "") then
|
||
return;
|
||
end;
|
||
|
||
local prefix = string.lower(string.sub(text, 1, 1));
|
||
local cmd = string.lower(string.Explode(' ', text)[1])
|
||
|
||
if (prefix == "!" or prefix == "~") then
|
||
local arguments = util.ExplodeByTags(text, " ", "\"", "\"", true);
|
||
local commandName = string.lower(string.sub(arguments[1], #prefix + 1));
|
||
|
||
table.remove(arguments, 1);
|
||
|
||
if (pPlayer:GetDBVar('sgMuted') and commandName != "unmute") then
|
||
return "";
|
||
end;
|
||
|
||
local commandExists = serverguard.command.Run(pPlayer, commandName, false, unpack(arguments));
|
||
|
||
if (commandExists) then
|
||
return "";
|
||
end;
|
||
elseif (pPlayer:GetDBVar('sgMuted')) then
|
||
return "";
|
||
end
|
||
end);
|
||
|
||
serverguard.netstream.Hook("sgRunCommand", function(pPlayer, data)
|
||
serverguard.command.Run(pPlayer, data.command, data.silent, unpack(data.arguments));
|
||
end);
|
||
|
||
concommand.Add("sg", function(pPlayer, command, arguments)
|
||
if (arguments and arguments[1]) then
|
||
local commandName = string.lower(arguments[1]);
|
||
local commandTable = serverguard.command:FindByID(commandName);
|
||
|
||
table.remove(arguments, 1);
|
||
|
||
if (commandTable) then
|
||
serverguard.command.Run(pPlayer, commandTable.command, false, unpack(arguments));
|
||
end;
|
||
end;
|
||
end);
|
||
elseif (CLIENT) then
|
||
local autoComplete = {}
|
||
local autoCompleteWidth = 0
|
||
local autoCompleteHeight = 0
|
||
local autoCompleteActive = 0
|
||
local autoCompletePrefix = {}
|
||
|
||
autoCompletePrefix["!"] = true
|
||
autoCompletePrefix["~"] = true
|
||
|
||
function serverguard.command.Run(command, bIsSilent, ...)
|
||
serverguard.netstream.Start("sgRunCommand", {
|
||
command = command, silent = bIsSilent, arguments = {...}
|
||
});
|
||
end;
|
||
|
||
concommand.Add("sg", function(_, command, arguments)
|
||
if (arguments and arguments[1]) then
|
||
local commandName = string.lower(arguments[1]);
|
||
local commandTable = serverguard.command:FindByID(commandName);
|
||
|
||
table.remove(arguments, 1);
|
||
|
||
if (commandTable) then
|
||
serverguard.command.Run(commandTable.command, false, unpack(arguments));
|
||
end;
|
||
end;
|
||
end, function(command, arguments)
|
||
local results = {};
|
||
local commands = serverguard.command:GetTable();
|
||
|
||
arguments = util.ExplodeByTags(string.Trim(arguments), " ", "\"", "\"", true);
|
||
|
||
if (arguments[1]) then
|
||
local command = serverguard.command:FindByID(arguments[1]);
|
||
|
||
if (command and serverguard.player:HasPermission(LocalPlayer(), command.permissions)) then
|
||
local sg_cmdabout = "sg " .. command.command .. " " .. serverguard.command:GetArgumentsString(command)
|
||
|
||
table.insert(results, sg_cmdabout);
|
||
|
||
if (command.arguments and #command.arguments > 0) then
|
||
for k, v in pairs(command.arguments) do
|
||
if (v == "player") then
|
||
for k, target in ipairs(player.GetAll()) do
|
||
if (arguments[2]) then
|
||
for _, result in pairs(results) do
|
||
if (result == sg_cmdabout) then
|
||
results[_] = nil
|
||
end
|
||
end
|
||
|
||
if !(target:IsBot()) then
|
||
if (target:Name():lower():find(arguments[2]) or target:SteamID():find(arguments[2]) or target:SteamID():lower():find(arguments[2]) or target:SteamID64():find(arguments[2]) or GetPlayerSteamID32(target):find(arguments[2])) then
|
||
table.insert(results, "sg " .. command.command .. " \"" .. target:Name() .. "\"")
|
||
end
|
||
else
|
||
if (target:Name():lower():find(arguments[2]) or target:SteamID():find(arguments[2])) then
|
||
table.insert(results, "sg " .. command.command .. " \"" .. target:Name() .. "\"");
|
||
end
|
||
end
|
||
else
|
||
table.insert(results, "sg " .. command.command .. " \"" .. target:Name() .. "\"")
|
||
end
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
elseif (!arguments[2]) then
|
||
for k, commandTable in util.SortedPairsByMemberValue(commands, "command") do
|
||
if (string.sub(commandTable.command, 1, string.len(arguments[1])) == arguments[1] and serverguard.player:HasPermission(LocalPlayer(), commandTable.permissions)) then
|
||
table.insert(results, "sg " .. commandTable.command .. " ");
|
||
end;
|
||
end;
|
||
end;
|
||
else
|
||
for k, commandTable in util.SortedPairsByMemberValue(commands, "command") do
|
||
if (serverguard.player:HasPermission(LocalPlayer(), commandTable.permissions)) then
|
||
table.insert(results, "sg " .. commandTable.command .. " ");
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
hook.Call("serverguard.ConsoleAutoComplete", nil, arguments, results);
|
||
|
||
return results;
|
||
end);
|
||
|
||
local function serverGuard_AddAutoCompletePrefixes()
|
||
hook.Call("serverguard.AddPrefixes", nil, autoCompletePrefix)
|
||
end
|
||
|
||
timer.Simple(4, serverGuard_AddAutoCompletePrefixes)
|
||
|
||
local function serverGuard_PaintAutoComplete()
|
||
if (#autoComplete > 0) then
|
||
local x, y = chat.GetChatBoxPos()
|
||
|
||
if (Clockwork) then
|
||
x, y = Clockwork.chatBox:GetPosition()
|
||
end
|
||
|
||
draw.RoundedBox(2, x, y -(6 +autoCompleteHeight), autoCompleteWidth +10, autoCompleteHeight, Color(0, 0, 0, 200))
|
||
|
||
for k, v in pairs(autoComplete) do
|
||
if (k == autoCompleteActive) then
|
||
draw.SimpleText(v, "serverGuard_ownerFont", x +4, y -16 *k, Color(20, 193, 20, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||
else
|
||
draw.SimpleText(v, "serverGuard_ownerFont", x +4, y -16 *k, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
hook.Add("HUDPaint", "serverguard_chatcommands.HUDPaint", serverGuard_PaintAutoComplete)
|
||
|
||
local table = table
|
||
local isTabbing = false
|
||
|
||
local function serverGuard_CheckChatText(text)
|
||
if (!isTabbing) then
|
||
autoComplete = {}
|
||
|
||
-- Check the prefix.
|
||
if (autoCompletePrefix[string.sub(text, 0, 1)]) then
|
||
autoCompleteWidth = 0
|
||
autoCompleteHeight = 0
|
||
|
||
-- IS THIS A GOOD IDEA? LOL
|
||
local commands = table.Copy(serverguard.command.stored)
|
||
|
||
hook.Call("serverguard.AddAutoComplete", nil, commands)
|
||
|
||
for command, data in pairs(commands) do
|
||
if (string.find(command, text)) then
|
||
local aText = command
|
||
local width, height = util.GetTextSize("serverGuard_ownerFont", aText)
|
||
|
||
if (data.arguments) then
|
||
local args = ""
|
||
|
||
for k, v in pairs(data.arguments) do
|
||
args = args .. v .. " "
|
||
end
|
||
|
||
aText = command .. " " .. args
|
||
|
||
width, height = util.GetTextSize("serverGuard_ownerFont", aText)
|
||
end
|
||
|
||
if (width > autoCompleteWidth) then
|
||
autoCompleteWidth = width
|
||
end
|
||
|
||
autoCompleteHeight = autoCompleteHeight +height
|
||
|
||
table.insert(autoComplete, aText)
|
||
end
|
||
end
|
||
|
||
if (#autoComplete == 1) then
|
||
autoCompleteActive = 1
|
||
end
|
||
|
||
autoCompleteHeight = autoCompleteHeight +3
|
||
end
|
||
end
|
||
end
|
||
|
||
hook.Add("ChatTextChanged", "serverguard_chatcommands.ChatTextChanged", serverGuard_CheckChatText)
|
||
|
||
local function serverGuard_ChatboxClosed()
|
||
autoComplete = {}
|
||
autoCompleteWidth = 0
|
||
autoCompleteHeight = 0
|
||
autoCompleteActive = 0
|
||
|
||
isTabbing = false
|
||
end
|
||
|
||
hook.Add("FinishChat", "serverguard_chatcommands.FinishChat", serverGuard_ChatboxClosed)
|
||
|
||
timer.Create("serverguard.autocomplete.timer", FrameTime() *8, 0, function() timer.Stop("serverguard.autocomplete.timer") end)
|
||
|
||
local function serverGuard_ChatboxTabbed(text)
|
||
isTabbing = true
|
||
|
||
autoCompleteActive = autoCompleteActive +1
|
||
|
||
if (autoCompleteActive > #autoComplete) then
|
||
autoCompleteActive = 1
|
||
end
|
||
|
||
if (autoComplete[autoCompleteActive]) then
|
||
local start = string.find(autoComplete[autoCompleteActive], "<")
|
||
|
||
if (start) then
|
||
timer.Adjust("serverguard.autocomplete.timer", FrameTime() *8, 1, function()
|
||
isTabbing = false
|
||
|
||
timer.Stop("serverguard.autocomplete.timer")
|
||
end)
|
||
|
||
timer.Start("serverguard.autocomplete.timer")
|
||
|
||
return string.sub(autoComplete[autoCompleteActive], 0, start -2)
|
||
else
|
||
timer.Adjust("serverguard.autocomplete.timer", FrameTime() *8, 1, function()
|
||
isTabbing = false
|
||
|
||
timer.Stop("serverguard.autocomplete.timer")
|
||
end)
|
||
|
||
timer.Start("serverguard.autocomplete.timer")
|
||
|
||
return autoComplete[autoCompleteActive]
|
||
end
|
||
end
|
||
|
||
isTabbing = false
|
||
end
|
||
|
||
hook.Add("OnChatTab", "serverguard_chatcommands.OnChatTab", serverGuard_ChatboxTabbed)
|
||
end
|