From 08fc8ccf6244661ce778ceb4efbfcddf3d614fe0 Mon Sep 17 00:00:00 2001 From: Sawayama Date: Thu, 16 May 2024 00:28:19 -0500 Subject: [PATCH 1/8] Command arguments overhaul start + simple type library --- gamemode/core/libs/sh_command.lua | 115 ++++++++++++++++++++++++++---- gamemode/core/libs/sh_type.lua | 101 ++++++++++++++++++++++++++ gamemode/core/sh_commands.lua | 37 +++++----- 3 files changed, 220 insertions(+), 33 deletions(-) create mode 100644 gamemode/core/libs/sh_type.lua diff --git a/gamemode/core/libs/sh_command.lua b/gamemode/core/libs/sh_command.lua index 54a5f802..b66c9251 100644 --- a/gamemode/core/libs/sh_command.lua +++ b/gamemode/core/libs/sh_command.lua @@ -1,19 +1,57 @@ + nut.command = nut.command or {} nut.command.list = nut.command.list or {} local COMMAND_PREFIX = "/" --- Adds a new command to the list of commands. -function nut.command.add(command, data) - -- For showing users the arguments of the command. - data.syntax = data.syntax or "[none]" +function nut.command.add(name, data) + if (!isstring(name)) then + return ErrorNoHaltWithStack("nut.command.add expected string for #1 argument but got: " .. nut.type(name)) + end + + if (!istable(data)) then + return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected table for #2 argument but got: " .. nut.type(data)) + end + + if (!isfunction(data.onRun)) then + return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected an onRun function in #2 argument but got: " .. nut.type(data.onRun)) + end + + -- new argument system + if (istable(data.arguments)) then + local missingArguments = {} + local syntaxes = {} + + local hadOptionalArgument + + for i, v in ipairs(data.arguments) do + local argumentName = debug.getlocal(data.onRun, i + 1) - -- Why bother adding a command if it doesn't do anything. - if (!data.onRun) then - return ErrorNoHalt("Command '"..command.."' does not have a callback, not adding!\n") + if (nut.type.isOptional(v)) then + hadOptionalArgument = true + elseif (hadOptionalArgument) then + return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") a required argument is after an optional argument, optional arguments must be last") + end + + if (argumentName) then + table.insert(syntaxes, nut.type.isOptional(v) and "[" .. nut.type.getName(v) .. " " .. argumentName .. "]" or "<" .. nut.type.getName(v) .. " " .. argumentName .. ">") + else + table.insert(missingArguments, nut.type.getName(v)) + end + end + + if (#missingArguments > 0) then + return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") is missing (" .. table.concat(missingArguments, ", ") .. ") argument declarations(s) in the onRun function") + end + + -- build syntax if we don't have a custom syntax + if (!data.syntax) then + data.syntax = table.concat(syntaxes, " ") + end end - -- Store the old onRun because we're able to change it. + data.syntax = data.syntax or "[none]" + if (!data.onCheckAccess) then -- Check if the command is for basic admins only. if (data.adminOnly) then @@ -56,7 +94,7 @@ function nut.command.add(command, data) data._onRun = data.onRun -- for refactoring purpose. data.onRun = function(client, arguments) - if (hook.Run("CanPlayerUseCommand", client, command) or onCheckAccess(client)) then + if (hook.Run("CanPlayerUseCommand", client, name) or onCheckAccess(client)) then return onRun(client, arguments) else return "@noPerm" @@ -77,12 +115,12 @@ function nut.command.add(command, data) end end - if (command == command:lower()) then - nut.command.list[command] = data + if (name == name:lower()) then + nut.command.list[name] = data else - data.realCommand = command + data.realCommand = name - nut.command.list[command:lower()] = data + nut.command.list[name:lower()] = data end end @@ -187,9 +225,56 @@ if (SERVER) then function nut.command.run(client, command, arguments) command = nut.command.list[command:lower()] + PrintTable(arguments) + if (command) then - -- Run the command's callback and get the return. - local results = {command.onRun(client, arguments or {})} + local results + + -- new argument system + if (command.arguments) then + if (#arguments > #command.arguments) then + client:notify("Too many arguments provided, expected \'" .. #command.arguments .. "\' got \'" .. #arguments .. "\'") + return + end + + for i, v in ipairs(command.arguments) do + local argument = arguments[i] + local nutType = command.arguments[i] + local bIsOptional = nut.type.isOptional(nutType) + nutType = bIsOptional and bit.bxor(nutType, nut.type.optional) or nutType + + if (!bIsOptional) then + if (argument == nil or argument == "") then + client:notify("Missing argument #" .. i .. " expected \'" .. nut.type.getName(nutType) .. "\'") + return + end + end + + if (argument) then + local assertion = nut.type.assert(nutType, argument) + + if (assertion) then + if (!isbool(assertion)) then + arguments[i] = assertion + end + else + if (nut.type.getName(nutType) == "player") then + client:notify("Could not find player \'" .. argument .. "\'") + else + client:notify("Wrong type to #" .. i .. " argument, expected \'" .. nut.type.getName(nutType) .. "\' got \'" .. nut.type(argument) .. "\'") + end + + return + end + end + end + + results = {command.onRun(client, unpack(arguments))} + else + -- Run the command's callback and get the return. + results = {command.onRun(client, arguments or {})} + end + local result = results[1] -- If a string is returned, it is a notification. diff --git a/gamemode/core/libs/sh_type.lua b/gamemode/core/libs/sh_type.lua new file mode 100644 index 00000000..82cc9a63 --- /dev/null +++ b/gamemode/core/libs/sh_type.lua @@ -0,0 +1,101 @@ + +nut.type = nut.type or {} +nut.type.map = nut.type.map or {} +nut.type.types = nut.type.types or {} +nut.type.bitsum = nut.type.bitsum or 0 + +-- _G.type but for nut.type +function nut.type.type(...) + local value = select(1, ...) + value = (istable(value) and value == nut.type) and select(2, ...) or value + + if (istable(value)) then + if (value.nutType and nut.type.map[value.nutType]) then + return nut.type.types[nut.type.map[value.nutType]].name + end + + for _, v in ipairs(nut.type.types) do + if (v.assertion and v.assertion(value)) then + return v.name + end + end + end + + return type(value) +end + +function nut.type.add(name, assertion) + if (nut.type[name]) then + nut.type.types[nut.type.map[name]].assertion = assertion + + return + end + + if (!isstring(name)) then + error("nut.type.add expected string for #1 input but got: " .. type(name)) + end + + if (assertion and !isfunction(assertion)) then + error("nut.type.add expected function for #2 input but got: " .. type(assertion)) + end + + local pow2 = 2 ^ (#nut.type.types + 1) + + nut.type.bitsum = bit.bor(nut.type.bitsum, pow2) + + nut.type[pow2] = name + nut.type[name] = pow2 + + nut.type.map[pow2] = table.insert(nut.type.types, {assertion = assertion, name = name}) + nut.type.map[name] = nut.type.map[pow2] +end + +function nut.type.assert(nutType, value) + if (nut.type.map[nutType]) then + nutType = nut.type.types[nut.type.map[nutType]] + + if (nutType.assertion) then + return nutType.assertion(value) + else + return true + end + end +end + +function nut.type.getName(nutType) + if (nut.type.map[nutType]) then + nutType = nut.type.types[nut.type.map[nutType]] + + if (nutType.name) then + return nutType.name + end + -- could be a 'bit.bor(x, nut.type.optional)', let's see if it is + elseif (nut.type.isOptional(nutType)) then + local xor = bit.bxor(nutType, nut.type.optional) + + if (xor) then + return nut.type.getName(xor) + end + end +end + +nut.type = setmetatable(nut.type, {__call = nut.type.type}) + +nut.type.add("optional") +function nut.type.isOptional(num) + return isnumber(num) and bit.band(num, nut.type.optional) == nut.type.optional +end + +nut.type.add("string", function(value) return isstring(value) end) +nut.type.add("number", function(value) return isnumber(tonumber(value)) end) +nut.type.add("bool", function(value) return isbool(value) end) +nut.type.add("steamid64", function(value) return isstring(value) and string.format("%017.17s", value) == value end) +nut.type.add("player", function(value) + if (isentity(value)) then + return value:IsPlayer() and value + end + + if (isstring(value)) then + return nut.util.findPlayer(value) + end +end) diff --git a/gamemode/core/sh_commands.lua b/gamemode/core/sh_commands.lua index b7528379..6b563bea 100644 --- a/gamemode/core/sh_commands.lua +++ b/gamemode/core/sh_commands.lua @@ -1,29 +1,30 @@ + nut.command.add("roll", { - syntax = "[number maximum]", - onRun = function(client, arguments) - nut.chat.send(client, "roll", math.random(0, math.min(tonumber(arguments[1]) or 100, 100))) + arguments = { + bit.bor(nut.type.number, nut.type.optional) + }, + onRun = function(client, maximum) + nut.chat.send(client, "roll", math.random(0, math.min(tonumber(maximum) or 100, 100))) end }) nut.command.add("pm", { - syntax = " ", - onRun = function(client, arguments) - local message = table.concat(arguments, " ", 2) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target)) then - local voiceMail = target:getNutData("vm") + arguments = { + nut.type.player, + nut.type.string + }, + onRun = function(client, target, message) + local voiceMail = target:getNutData("vm") - if (voiceMail and voiceMail:find("%S")) then - return target:Name()..": "..voiceMail - end + if (voiceMail and voiceMail:find("%S")) then + return target:Name()..": "..voiceMail + end - if ((client.nutNextPM or 0) < CurTime()) then - nut.chat.send(client, "pm", message, false, {client, target}) + if ((client.nutNextPM or 0) < CurTime()) then + nut.chat.send(client, "pm", message, false, {client, target}) - client.nutNextPM = CurTime() + 0.5 - target.nutLastPM = client - end + client.nutNextPM = CurTime() + 0.5 + target.nutLastPM = client end end }) From b0332d82b11728bc923622ed03332e0d52c6f5b6 Mon Sep 17 00:00:00 2001 From: Sawayama Date: Thu, 16 May 2024 17:34:36 -0500 Subject: [PATCH 2/8] Moved argument parsing from nut.command.run to nut.command.parse Final string arguments no longer require quotes surrounding them Added character type --- gamemode/core/libs/sh_command.lua | 112 ++++++++++++++++-------------- gamemode/core/libs/sh_type.lua | 10 +++ gamemode/core/sh_commands.lua | 86 +++++++++++------------ 3 files changed, 111 insertions(+), 97 deletions(-) diff --git a/gamemode/core/libs/sh_command.lua b/gamemode/core/libs/sh_command.lua index b66c9251..aaf2e166 100644 --- a/gamemode/core/libs/sh_command.lua +++ b/gamemode/core/libs/sh_command.lua @@ -93,9 +93,9 @@ function nut.command.add(name, data) local onRun = data.onRun data._onRun = data.onRun -- for refactoring purpose. - data.onRun = function(client, arguments) + data.onRun = function(client, ...) if (hook.Run("CanPlayerUseCommand", client, name) or onCheckAccess(client)) then - return onRun(client, arguments) + return onRun(client, ...) else return "@noPerm" end @@ -224,57 +224,10 @@ if (SERVER) then -- Forces a player to run a command. function nut.command.run(client, command, arguments) command = nut.command.list[command:lower()] - - PrintTable(arguments) + arguments = arguments or {} if (command) then - local results - - -- new argument system - if (command.arguments) then - if (#arguments > #command.arguments) then - client:notify("Too many arguments provided, expected \'" .. #command.arguments .. "\' got \'" .. #arguments .. "\'") - return - end - - for i, v in ipairs(command.arguments) do - local argument = arguments[i] - local nutType = command.arguments[i] - local bIsOptional = nut.type.isOptional(nutType) - nutType = bIsOptional and bit.bxor(nutType, nut.type.optional) or nutType - - if (!bIsOptional) then - if (argument == nil or argument == "") then - client:notify("Missing argument #" .. i .. " expected \'" .. nut.type.getName(nutType) .. "\'") - return - end - end - - if (argument) then - local assertion = nut.type.assert(nutType, argument) - - if (assertion) then - if (!isbool(assertion)) then - arguments[i] = assertion - end - else - if (nut.type.getName(nutType) == "player") then - client:notify("Could not find player \'" .. argument .. "\'") - else - client:notify("Wrong type to #" .. i .. " argument, expected \'" .. nut.type.getName(nutType) .. "\' got \'" .. nut.type(argument) .. "\'") - end - - return - end - end - end - - results = {command.onRun(client, unpack(arguments))} - else - -- Run the command's callback and get the return. - results = {command.onRun(client, arguments or {})} - end - + local results = {command.onRun(client, unpack(command.arguments and arguments or {arguments}))} local result = results[1] -- If a string is returned, it is a notification. @@ -318,7 +271,62 @@ if (SERVER) then if (command) then -- Get the arguments like a console command. if (!arguments) then - arguments = nut.command.extractArgs(text:sub(#match + 3)) + -- new argument system + if (command.arguments) then + local length = #match + 3 + arguments = nut.command.extractArgs(text:sub(length)) + + for i, v in ipairs(command.arguments) do + local nutType = command.arguments[i] + local bIsOptional = nut.type.isOptional(nutType) + nutType = bIsOptional and bit.bxor(nutType, nut.type.optional) or nutType + + local argument = arguments[i] + + if (argument and bit.band(nutType, nut.type.string) == nut.type.string and i == #command.arguments) then + argument = text:sub(length) + arguments[i] = argument + + for _ = i + 1, #arguments do + table.remove(arguments, i + 1) + end + end + + length = length + string.len(argument or "") + 1 + + if (!bIsOptional) then + if (argument == nil or argument == "") then + client:notify("Missing argument #" .. i .. " expected \'" .. nut.type.getName(nutType) .. "\'") + return true + end + end + + if (argument) then + local assertion = nut.type.assert(nutType, argument) + + if (assertion) then + if (!isbool(assertion)) then + arguments[i] = assertion + end + else + if (nut.type.getName(nutType) == "player" or nut.type.getName(nutType) == "character") then + client:notify("Could not find the target \'" .. argument .. "\'") + else + client:notify("Wrong type to #" .. i .. " argument, expected \'" .. nut.type.getName(nutType) .. "\' got \'" .. nut.type(argument) .. "\'") + end + + return true + end + end + end + + if (#arguments > #command.arguments) then + client:notify("Too many arguments provided, expected \'" .. #command.arguments .. "\' got \'" .. #arguments .. "\'") + return true + end + else + arguments = nut.command.extractArgs(text:sub(#match + 3)) + end end -- Runs the actual command. diff --git a/gamemode/core/libs/sh_type.lua b/gamemode/core/libs/sh_type.lua index 82cc9a63..cc6f69a2 100644 --- a/gamemode/core/libs/sh_type.lua +++ b/gamemode/core/libs/sh_type.lua @@ -99,3 +99,13 @@ nut.type.add("player", function(value) return nut.util.findPlayer(value) end end) +nut.type.add("character", function(value) + if (isentity(value)) then + return value.getChar and value:GetChar() + end + + if (isstring(value)) then + local client = nut.util.findPlayer(value) + return client and client:getChar() + end +end) diff --git a/gamemode/core/sh_commands.lua b/gamemode/core/sh_commands.lua index 6b563bea..42027cca 100644 --- a/gamemode/core/sh_commands.lua +++ b/gamemode/core/sh_commands.lua @@ -30,23 +30,25 @@ nut.command.add("pm", { }) nut.command.add("reply", { - syntax = "", - onRun = function(client, arguments) + arguments = { + nut.type.string + }, + onRun = function(client, message) local target = client.nutLastPM if (IsValid(target) and (client.nutNextPM or 0) < CurTime()) then - nut.chat.send(client, "pm", table.concat(arguments, " "), false, {client, target}) + nut.chat.send(client, "pm", message, false, {client, target}) client.nutNextPM = CurTime() + 0.5 end end }) nut.command.add("setvoicemail", { - syntax = "[string message]", - onRun = function(client, arguments) - local message = table.concat(arguments, " ") - - if (message:find("%S")) then + arguments = { + bit.bor(nut.type.string, nut.type.optional) + }, + onRun = function(client, message) + if (message and message:find("%S")) then client:setNutData("vm", message:sub(1, 240)) return "@vmSet" @@ -60,54 +62,48 @@ nut.command.add("setvoicemail", { nut.command.add("flaggive", { adminOnly = true, - syntax = " [string flags]", - onRun = function(client, arguments) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target) and target:getChar()) then - local flags = arguments[2] - - if (not flags) then - local available = "" - - -- Aesthetics~~ - for k in SortedPairs(nut.flag.list) do - if (not target:getChar():hasFlags(k)) then - available = available..k - end + arguments = { + nut.type.character, + bit.bor(nut.type.string, nut.type.optional) + }, + onRun = function(client, target, flags) + if (not flags) then + local available = "" + + -- Aesthetics~~ + for k in SortedPairs(nut.flag.list) do + if (not target:hasFlags(k)) then + available = available..k end - - return client:requestString("@flagGiveTitle", "@flagGiveDesc", function(text) - nut.command.run(client, "flaggive", {target:Name(), text}) - end, available) end - target:getChar():giveFlags(flags) - - nut.util.notifyLocalized("flagGive", nil, client:Name(), target:Name(), flags) + return client:requestString("@flagGiveTitle", "@flagGiveDesc", function(text) + nut.command.run(client, "flaggive", {target, text}) + end, available) end + + target:giveFlags(flags) + + nut.util.notifyLocalized("flagGive", nil, client:Name(), target:getName(), flags) end }) nut.command.add("flagtake", { adminOnly = true, - syntax = " [string flags]", - onRun = function(client, arguments) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target) and target:getChar()) then - local flags = arguments[2] - - if (not flags) then - return client:requestString("@flagTakeTitle", "@flagTakeDesc", function(text) - nut.command.run(client, "flagtake", {target:Name(), text}) - end, target:getChar():getFlags()) - end + arguments = { + nut.type.character, + bit.bor(nut.type.string, nut.type.optional) + }, + onRun = function(client, target, flags) + if (not flags) then + return client:requestString("@flagTakeTitle", "@flagTakeDesc", function(text) + nut.command.run(client, "flagtake", {target, text}) + end, target:getFlags()) + end - target:getChar():takeFlags(flags) + target:takeFlags(flags) - nut.util.notifyLocalized("flagTake", nil, client:Name(), flags, target:Name()) - end + nut.util.notifyLocalized("flagTake", nil, client:Name(), flags, target:getName()) end }) From 6e3c631465e30e894e94ccb433f0d7717a833913 Mon Sep 17 00:00:00 2001 From: Sawayama Date: Thu, 16 May 2024 23:22:06 -0500 Subject: [PATCH 3/8] Switch from pow2 calc to just iterating over the bits to find the next free bit position. (this may fail when we hit 32 types, possibly cause stack overflow or halt) Any final argument can be typed without quotes now, not just strings. Chatbox modification to make it work Fixed the help menu to actually display syntax. Changed all commands in sh_commands.lua to use it (not yet plugin commands) Now arguments can be a single value instead of table, and also arguments can have multiple types accepted, like in the case of /charunban (we could expand this to more commands later) --- gamemode/core/libs/sh_command.lua | 44 +++- gamemode/core/libs/sh_type.lua | 156 ++++++++++- gamemode/core/sh_commands.lua | 379 +++++++++++---------------- plugins/chatbox/derma/cl_chatbox.lua | 8 +- plugins/f1menu/derma/cl_helps.lua | 6 +- 5 files changed, 343 insertions(+), 250 deletions(-) diff --git a/gamemode/core/libs/sh_command.lua b/gamemode/core/libs/sh_command.lua index aaf2e166..7f1a0a58 100644 --- a/gamemode/core/libs/sh_command.lua +++ b/gamemode/core/libs/sh_command.lua @@ -18,6 +18,10 @@ function nut.command.add(name, data) end -- new argument system + if (isnumber(data.arguments)) then + data.arguments = {data.arguments} + end + if (istable(data.arguments)) then local missingArguments = {} local syntaxes = {} @@ -33,10 +37,12 @@ function nut.command.add(name, data) return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") a required argument is after an optional argument, optional arguments must be last") end + local typeName = nut.type.getName(v) + if (argumentName) then - table.insert(syntaxes, nut.type.isOptional(v) and "[" .. nut.type.getName(v) .. " " .. argumentName .. "]" or "<" .. nut.type.getName(v) .. " " .. argumentName .. ">") + table.insert(syntaxes, nut.type.isOptional(v) and "[" .. typeName .. ": " .. argumentName .. "]" or "<" .. typeName .. ": " .. argumentName .. ">") else - table.insert(missingArguments, nut.type.getName(v)) + table.insert(missingArguments, typeName) end end @@ -283,7 +289,7 @@ if (SERVER) then local argument = arguments[i] - if (argument and bit.band(nutType, nut.type.string) == nut.type.string and i == #command.arguments) then + if (argument and i == #command.arguments) then argument = text:sub(length) arguments[i] = argument @@ -302,19 +308,31 @@ if (SERVER) then end if (argument) then - local assertion = nut.type.assert(nutType, argument) + local multipleTypes = nut.type.getMultiple(nutType) + local failed - if (assertion) then - if (!isbool(assertion)) then - arguments[i] = assertion - end - else - if (nut.type.getName(nutType) == "player" or nut.type.getName(nutType) == "character") then - client:notify("Could not find the target \'" .. argument .. "\'") + for _, v in ipairs(multipleTypes) do + local assertion = nut.type.assert(v, argument) + + if (assertion) then + failed = nil + + if (!isbool(assertion)) then + arguments[i] = assertion + end + + break else - client:notify("Wrong type to #" .. i .. " argument, expected \'" .. nut.type.getName(nutType) .. "\' got \'" .. nut.type(argument) .. "\'") + if (nut.type.getName(v) == "player" or nut.type.getName(v) == "character") then + failed = "Could not find the target \'" .. argument .. "\'" + else + failed = "Wrong type to #" .. i .. " argument, expected \'" .. nut.type.getName(nutType) .. "\' got \'" .. nut.type(argument) .. "\'" + end end - + end + + if (failed) then + client:notify(failed) return true end end diff --git a/gamemode/core/libs/sh_type.lua b/gamemode/core/libs/sh_type.lua index cc6f69a2..7a0aec12 100644 --- a/gamemode/core/libs/sh_type.lua +++ b/gamemode/core/libs/sh_type.lua @@ -39,15 +39,20 @@ function nut.type.add(name, assertion) error("nut.type.add expected function for #2 input but got: " .. type(assertion)) end - local pow2 = 2 ^ (#nut.type.types + 1) + local bitPosition = 1 + while (bitPosition <= nut.type.bitsum) do + bitPosition = bit.lshift(bitPosition, 1) + end + + nut.type.bitsum = bit.bor(nut.type.bitsum, bitPosition) - nut.type.bitsum = bit.bor(nut.type.bitsum, pow2) + nut.type[bitPosition] = name + nut.type[name] = bitPosition - nut.type[pow2] = name - nut.type[name] = pow2 + nut.type.map[bitPosition] = #nut.type.types + 1 + nut.type.map[name] = nut.type.map[bitPosition] - nut.type.map[pow2] = table.insert(nut.type.types, {assertion = assertion, name = name}) - nut.type.map[name] = nut.type.map[pow2] + table.insert(nut.type.types, {assertion = assertion, name = name}) end function nut.type.assert(nutType, value) @@ -62,6 +67,18 @@ function nut.type.assert(nutType, value) end end +function nut.type.getMultiple(nutType) + local operands = {} + + for i = 0, 31 do + if bit.band(nutType, bit.lshift(1, i)) > 0 then + operands[#operands + 1] = bit.lshift(1, i) + end + end + + return operands +end + function nut.type.getName(nutType) if (nut.type.map[nutType]) then nutType = nut.type.types[nut.type.map[nutType]] @@ -76,6 +93,19 @@ function nut.type.getName(nutType) if (xor) then return nut.type.getName(xor) end + -- could be multiple types, lets see if it is + else + local types = nut.type.getMultiple(nutType) + + if (#types > 0) then + local typeNames = {} + + for i, v in ipairs(types) do + table.insert(typeNames, nut.type.getName(v)) + end + + return table.concat(typeNames, "|") + end end end @@ -100,12 +130,122 @@ nut.type.add("player", function(value) end end) nut.type.add("character", function(value) + if (istable(value)) then + return GetMetaTable(value) == nut.meta.character and true + end + if (isentity(value)) then - return value.getChar and value:GetChar() + return value.getChar and value:getChar() end if (isstring(value)) then local client = nut.util.findPlayer(value) - return client and client:getChar() + + if (client) then + return client:getChar() + end + + for _, v in pairs(nut.char.loaded) do + if (nut.util.stringMatches(v:getName(), value)) then + return v + end + end + end +end) +nut.type.add("item", function(value) + if (istable(value)) then + return value.isItem and value + end + + if (isentity(value)) then + if (value.getItemTable) then + return nut.item.instances[value:getItemID()] + end + end + + if (isstring(value)) then + if (nut.item.list[value]) then + return nut.item.list[value] + end + end + + if (isnumber(tonumber(value))) then + if (nut.item.instances[tonumber(value)]) then + return nut.faction.instances[tonumber(value)] + end + end +end) +nut.type.add("faction", function(value) + if (istable(value)) then + if (value.uniqueID and nut.faction.teams[value.uniqueID]) then + return nut.faction.teams[value.uniqueID] + end + + if (value.getFaction) then + return nut.faction.indices[value:getFaction()] + end + end + + if (isentity(value)) then + if (value.Team) then + return nut.faction.indices[value:Team()] + end + end + + if (isstring(value)) then + if (nut.faction.teams[value]) then + return nut.faction.teams[value] + end + + for _, v in pairs(nut.faction.indices) do + if (nut.util.stringMatches(v.name, value)) then + return v + end + end + + local client = nut.util.findPlayer(value) + + if (client) then + return nut.faction.indices[client:Team()] + end + end + + if (isnumber(tonumber(value))) then + if (nut.faction.indices[tonumber(value)]) then + return nut.faction.indices[tonumber(value)] + end + end +end) +nut.type.add("class", function(value) + if (istable(value)) then + if (value.getClass) then + return nut.class.list[value:getClass()] + end + end + + if (isentity(value)) then + if (value.getChar) then + return nut.class.list[value:getChar():getClass()] + end + end + + if (isstring(value)) then + for _, v in pairs(nut.class.list) do + if (nut.util.stringMatches(L(v.name, client), value)) then + return v + end + end + + local client = nut.util.findPlayer(value) + + if (client) then + return nut.class.list[client:getChar():getClass()] + end + end + + if (isnumber(tonumber(value))) then + if (nut.class.list[tonumber(value)]) then + return nut.class.list[tonumber(value)] + end end end) diff --git a/gamemode/core/sh_commands.lua b/gamemode/core/sh_commands.lua index 42027cca..92cd20be 100644 --- a/gamemode/core/sh_commands.lua +++ b/gamemode/core/sh_commands.lua @@ -1,8 +1,6 @@ nut.command.add("roll", { - arguments = { - bit.bor(nut.type.number, nut.type.optional) - }, + arguments = bit.bor(nut.type.number, nut.type.optional), onRun = function(client, maximum) nut.chat.send(client, "roll", math.random(0, math.min(tonumber(maximum) or 100, 100))) end @@ -44,9 +42,7 @@ nut.command.add("reply", { }) nut.command.add("setvoicemail", { - arguments = { - bit.bor(nut.type.string, nut.type.optional) - }, + arguments = bit.bor(nut.type.string, nut.type.optional), onRun = function(client, message) if (message and message:find("%S")) then client:setNutData("vm", message:sub(1, 240)) @@ -109,200 +105,149 @@ nut.command.add("flagtake", { nut.command.add("charsetmodel", { adminOnly = true, - syntax = " ", - onRun = function(client, arguments) - if (not arguments[2]) then - return L("invalidArg", client, 2) - end - - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target) and target:getChar()) then - target:getChar():setModel(arguments[2]) - target:SetupHands() + arguments = { + nut.type.character, + nut.type.string + }, + onRun = function(client, target, model) + target:setModel(model) + target:getPlayer():SetupHands() - nut.util.notifyLocalized("cChangeModel", nil, client:Name(), target:Name(), arguments[2]) - end + nut.util.notifyLocalized("cChangeModel", nil, client:Name(), target:getName(), model) end }) nut.command.add("charsetskin", { adminOnly = true, - syntax = " [number skin]", - onRun = function(client, arguments) - local skin = tonumber(arguments[2]) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target) and target:getChar()) then - target:getChar():setData("skin", skin) - target:SetSkin(skin or 0) + arguments = { + nut.type.character, + nut.type.number + }, + onRun = function(client, target, skin) + target:setData("skin", skin) + target:getPlayer():SetSkin(skin or 0) - nut.util.notifyLocalized("cChangeSkin", nil, client:Name(), target:Name(), skin or 0) - end + nut.util.notifyLocalized("cChangeSkin", nil, client:Name(), target:getName(), skin or 0) end }) nut.command.add("charsetbodygroup", { adminOnly = true, - syntax = " [number value]", - onRun = function(client, arguments) - local value = tonumber(arguments[3]) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target) and target:getChar()) then - local index = target:FindBodygroupByName(arguments[2]) + arguments = { + nut.type.character, + nut.type.string, + bit.bor(nut.type.number, nut.type.optional) + }, + onRun = function(client, target, bodygroup, value) + local index = target:getPlayer():FindBodygroupByName(bodygroup) - if (index > -1) then - if (value and value < 1) then - value = nil - end + if (index != -1) then + if (value and value < 1) then + value = nil + end - local groups = target:getChar():getData("groups", {}) - groups[index] = value - target:getChar():setData("groups", groups) - target:SetBodygroup(index, value or 0) + local groups = target:getData("groups", {}) + groups[index] = value + target:setData("groups", groups) + target:getPlayer():SetBodygroup(index, value or 0) - nut.util.notifyLocalized("cChangeGroups", nil, client:Name(), target:Name(), arguments[2], value or 0) - else - return "@invalidArg", 2 - end + nut.util.notifyLocalized("cChangeGroups", nil, client:Name(), target:getName(), bodygroup, value or 0) + else + client:notify("Bodygroup \'" .. bodygroup .. "\' was not found for \'" .. target:getName() .. "\'") end end }) nut.command.add("charsetname", { adminOnly = true, - syntax = " [string newName]", - onRun = function(client, arguments) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target) and not arguments[2]) then + arguments = { + nut.type.character, + bit.bor(nut.type.string, nut.type.optional) + }, + onRun = function(client, target, name) + if (not name) then return client:requestString("@chgName", "@chgNameDesc", function(text) - nut.command.run(client, "charsetname", {target:Name(), text}) - end, target:Name()) + nut.command.run(client, "charsetname", {target, text}) + end, target:getName()) end - table.remove(arguments, 1) - - local targetName = table.concat(arguments, " ") - - if (IsValid(target) and target:getChar()) then - nut.util.notifyLocalized("cChangeName", nil, client:Name(), target:Name(), targetName) - - target:getChar():setName(targetName) - end + nut.util.notifyLocalized("cChangeName", nil, client:Name(), target:getName(), name) + target:setName(name) end }) nut.command.add("chargiveitem", { adminOnly = true, - syntax = " ", - onRun = function(client, arguments) - if (not arguments[2]) then - return L("invalidArg", client, 2) - end - - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target) and target:getChar()) then - local uniqueID = arguments[2]:lower() - local amount = tonumber(arguments[3]) - - if (not nut.item.list[uniqueID]) then - for k, v in SortedPairs(nut.item.list) do - if (nut.util.stringMatches(v.name, uniqueID)) then - uniqueID = k + arguments = { + nut.type.character, + nut.type.item, + bit.bor(nut.type.number, nut.type.optional), + }, + onRun = function(client, target, name, amount) + local item = name.uniqueID - break - end + target:getInv():add(item, amount or 1) + :next(function(res) + if (IsValid(target:getPlayer())) then + target:getPlayer():notifyLocalized("itemCreated") end - end - - if (arguments[3] and arguments[3] ~= "") and (not amount) then - return L("invalidArg", client, 3) - end - - target:getChar():getInv():add(uniqueID, amount or 1) - :next(function(res) - if (IsValid(target)) then - target:notifyLocalized("itemCreated") - end - if (IsValid(client) and client ~= target) then - client:notifyLocalized("itemCreated") - end - hook.Run("CharGivenItem",target,res) - end) - :catch(function(err) - if (IsValid(client)) then - client:notifyLocalized(err) - end - end) - end + if (IsValid(client) and client ~= target:getPlayer()) then + client:notifyLocalized("itemCreated") + end + hook.Run("CharGivenItem", target:getPlayer(), res) + end) + :catch(function(err) + if (IsValid(client)) then + client:notifyLocalized(err) + end + end) end }) nut.command.add("charkick", { adminOnly = true, - syntax = "", - onRun = function(client, arguments) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target)) then - local char = target:getChar() - if (char) then - for k, v in ipairs(player.GetAll()) do - v:notifyLocalized("charKick", client:Name(), target:Name()) - end - - char:kick() - end + arguments = nut.type.character, + onRun = function(client, target) + for k, v in ipairs(player.GetAll()) do + v:notifyLocalized("charKick", client:Name(), target:getName()) end + + target:kick() end }) nut.command.add("charban", { - syntax = "", adminOnly = true, - onRun = function(client, arguments) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target)) then - local char = target:getChar() - - if (char) then - nut.util.notifyLocalized("charBan", client:Name(), target:Name()) - char:ban() - end - end + arguments = nut.type.character, + onRun = function(client, target) + nut.util.notifyLocalized("charBan", client:Name(), target:getName()) + target:ban() end }) nut.command.add("charunban", { - syntax = "", adminOnly = true, - onRun = function(client, arguments) + arguments = bit.bor(nut.type.character, nut.type.string), + onRun = function(client, target) if ((client.nutNextSearch or 0) >= CurTime()) then return L("charSearching", client) end - local name = table.concat(arguments, " ") - - for k, v in pairs(nut.char.loaded) do - if (nut.util.stringMatches(v:getName(), name)) then - if (v:getData("banned")) then - v:setData("banned") - v:setData("permakilled") - else - return "@charNotBanned" - end - - return nut.util.notifyLocalized("charUnBan", nil, client:Name(), v:getName()) + if (target.getPlayer) then + if (target:getData("banned")) then + target:setData("banned", nil) + target:setData("permakilled", nil) + nut.util.notifyLocalized("charUnBan", nil, client:Name(), target:getName()) + return + else + client:notifyLocalized("charNotBanned") + return end end client.nutNextSearch = CurTime() + 15 - nut.db.query("SELECT _id, _name, _data FROM nut_characters WHERE _name LIKE \"%"..nut.db.escape(name).."%\" LIMIT 1", function(data) + nut.db.query("SELECT _id, _name, _data FROM nut_characters WHERE _name LIKE \"%" .. nut.db.escape(target) .. "%\" LIMIT 1", function(data) if (data and data[1]) then local charID = tonumber(data[1]._id) local data = util.JSONToTable(data[1]._data or "[]") @@ -315,22 +260,23 @@ nut.command.add("charunban", { data.banned = nil - nut.db.updateTable({_data = data}, nil, nil, "_id = "..charID) + nut.db.updateTable({_data = data}, nil, "characters", "_id = " .. charID) nut.util.notifyLocalized("charUnBan", nil, client:Name(), nut.char.loaded[charID]:getName()) + else + client:notify("Could not find the character \'" .. target .. "\'") + return end end) end }) nut.command.add("givemoney", { - syntax = "", - onRun = function(client, arguments) - local number = tonumber(arguments[1]) - number = number or 0 + arguments = nut.type.number, + onRun = function(client, number) local amount = math.floor(number) - if (not amount or not isnumber(amount) or amount <= 0) then - return L("invalidArg", client, 1) + if (amount < 1) then + return client:notify("Amount must be greater than zero.") end local data = {} @@ -359,39 +305,31 @@ nut.command.add("givemoney", { nut.command.add("charsetmoney", { adminOnly = true, - syntax = " ", - onRun = function(client, arguments) - local amount = tonumber(arguments[2]) + arguments = { + nut.type.character, + nut.type.number + }, + onRun = function(client, target, money) + local amount = math.floor(money) - if (not amount or not isnumber(amount) or amount < 0) then - return "@invalidArg", 2 + if (amount < 0) then + return client:notify("Amount must be atleast zero.") end - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target)) then - local char = target:getChar() - - if (char and amount) then - amount = math.Round(amount) - char:setMoney(amount) - client:notifyLocalized("setMoney", target:Name(), nut.currency.get(amount)) - end - end + target:setMoney(amount) + client:notifyLocalized("setMoney", target:getName(), nut.currency.get(amount)) end }) nut.command.add("dropmoney", { - syntax = "", - onRun = function(client, arguments) - local amount = tonumber(arguments[1]) + arguments = nut.type.number, + onRun = function(client, money) + local amount = math.floor(number) - if (not amount or not isnumber(amount) or amount < 1) then - return "@invalidArg", 1 + if (amount < 1) then + return client:notify("Amount must be greater than zero.") end - amount = math.Round(amount) - if (not client:getChar():hasMoney(amount)) then return end @@ -407,26 +345,23 @@ nut.command.add("dropmoney", { nut.command.add("plywhitelist", { adminOnly = true, - syntax = " ", - onRun = function(client, arguments) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target)) then - local faction = nut.command.findFaction(client,table.concat(arguments, " ", 2)) + arguments = { + nut.type.player, + nut.type.faction + }, + onRun = function(client, target, name) + local faction = name - if (faction) then - if (target:setWhitelisted(faction.index, true)) then - for k, v in ipairs(player.GetAll()) do - v:notifyLocalized("whitelist", client:Name(), target:Name(), L(faction.name, v)) - end - end + if (target:setWhitelisted(faction.index, true)) then + for k, v in ipairs(player.GetAll()) do + v:notifyLocalized("whitelist", client:Name(), target:Name(), L(faction.name, v)) end end end }) nut.command.add("chargetup", { - onRun = function(client, arguments) + onRun = function(client) local entity = client.nutRagdoll if (IsValid(entity) and entity.nutGrace and entity.nutGrace < CurTime() and entity:GetVelocity():Length2D() < 8 and not entity.nutWakingUp) then @@ -445,29 +380,24 @@ nut.command.add("chargetup", { nut.command.add("plyunwhitelist", { adminOnly = true, - syntax = " ", - onRun = function(client, arguments) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target)) then - local faction = nut.command.findFaction(client,table.concat(arguments, " ", 2)) + arguments = { + nut.type.player, + nut.type.faction + }, + onRun = function(client, target, name) + local faction = name - if (faction) then - if (target:setWhitelisted(faction.index, false)) then - for k, v in ipairs(player.GetAll()) do - v:notifyLocalized("unwhitelist", client:Name(), target:Name(), L(faction.name, v)) - end - end + if (target:setWhitelisted(faction.index, false)) then + for k, v in ipairs(player.GetAll()) do + v:notifyLocalized("unwhitelist", client:Name(), target:Name(), L(faction.name, v)) end end end }) nut.command.add("fallover", { - syntax = "[number time]", - onRun = function(client, arguments) - local time = tonumber(arguments[1]) - + arguments = bit.bor(nut.type.number, nut.type.optional), + onRun = function(client, time) if (not isnumber(time)) then time = 5 end @@ -485,9 +415,8 @@ nut.command.add("fallover", { }) nut.command.add("beclass", { - syntax = "", - onRun = function(client, arguments) - local class = table.concat(arguments, " ") + arguments = nut.type.number, + onRun = function(client, class) local char = client:getChar() if (IsValid(client) and char) then @@ -529,24 +458,22 @@ nut.command.add("beclass", { }) nut.command.add("chardesc", { - syntax = "", - onRun = function(client, arguments) - arguments = table.concat(arguments, " ") - - if (not arguments:find("%S")) then + arguments = bit.bor(nut.type.string, nut.type.optional), + onRun = function(client, desc) + if (not desc or not desc:find("%S")) then return client:requestString("@chgDesc", "@chgDescDesc", function(text) nut.command.run(client, "chardesc", {text}) end, client:getChar():getDesc()) end local info = nut.char.vars.desc - local result, fault, count = info.onValidate(arguments) + local result, fault, count = info.onValidate(desc) if (result == false) then return "@"..fault, count end - client:getChar():setDesc(arguments) + client:getChar():setDesc(desc) return "@descChanged" end @@ -554,11 +481,13 @@ nut.command.add("chardesc", { nut.command.add("plytransfer", { adminOnly = true, - syntax = " ", - onRun = function(client, arguments) - local target = nut.command.findPlayer(client, arguments[1]) - local faction = nut.command.findFaction(client, table.concat(arguments, " ", 2)) + arguments = { + nut.type.player, + nut.type.faction + }, + onRun = function(client, target, name) local character = target:getChar() + local faction = name if (not IsValid(target) or not character) then return "@plyNotExist" @@ -588,17 +517,13 @@ nut.command.add("plytransfer", { -- Credit goes to SmithyStanley nut.command.add("clearinv", { adminOnly = true, - syntax = "", - onRun = function (client, arguments) - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target) and target:getChar()) then - for k, v in pairs(target:getChar():getInv():getItems()) do - v:remove() - end - - client:notifyLocalized("resetInv", target:getChar():getName()) + arguments = nut.type.character, + onRun = function (client, target) + for k, v in pairs(target:getInv():getItems()) do + v:remove() end + + client:notifyLocalized("resetInv", target:getName()) end }) diff --git a/plugins/chatbox/derma/cl_chatbox.lua b/plugins/chatbox/derma/cl_chatbox.lua index c9ba646e..d609b702 100644 --- a/plugins/chatbox/derma/cl_chatbox.lua +++ b/plugins/chatbox/derma/cl_chatbox.lua @@ -58,7 +58,7 @@ local PANEL = {} if (k == command and v.syntax) then local i2 = 0 - for argument in v.syntax:gmatch("([%[<][%w_]+[%s][%w_]+[%]>])") do + for argument in v.syntax:gmatch("([%[<][%g_]+[%s][%g_]+[%]>])") do i2 = i2 + 1 local color = COLOR_FADED @@ -66,6 +66,12 @@ local PANEL = {} color = COLOR_ACTIVE end + if (v.arguments and i2 == #v.arguments) then + if ((#arguments - 1) >= i2) then + color = COLOR_ACTIVE + end + end + x = x + nut.util.drawText(argument.." ", x, i * 20, color) end end diff --git a/plugins/f1menu/derma/cl_helps.lua b/plugins/f1menu/derma/cl_helps.lua index 2386e336..d224d224 100644 --- a/plugins/f1menu/derma/cl_helps.lua +++ b/plugins/f1menu/derma/cl_helps.lua @@ -114,7 +114,11 @@ hook.Add("BuildHelpMenu", "nutBasicHelp", function(tabs) end if (allowed) then - body = body.."

/"..k.."

Syntax: "..v.syntax.."

" + local syntax = v.syntax + syntax = string.gsub(syntax, "<", "<") + syntax = string.gsub(syntax, ">", ">") + + body = body.."

/"..k.."

Syntax: "..syntax.."

" end end From 73ced8f969501cd5c33bebbb30527b5040e50aa2 Mon Sep 17 00:00:00 2001 From: Sawayama Date: Fri, 17 May 2024 02:31:09 -0500 Subject: [PATCH 4/8] Go through assertion list backwards to prevent the string assert picking it all up. Change what nut.type returns Change target.getPlayer into a type check for /charunban --- gamemode/core/libs/sh_command.lua | 14 ++++++++------ gamemode/core/libs/sh_type.lua | 28 +++++++++++++++++++--------- gamemode/core/sh_commands.lua | 2 +- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/gamemode/core/libs/sh_command.lua b/gamemode/core/libs/sh_command.lua index 7f1a0a58..5fc3e880 100644 --- a/gamemode/core/libs/sh_command.lua +++ b/gamemode/core/libs/sh_command.lua @@ -6,15 +6,15 @@ local COMMAND_PREFIX = "/" function nut.command.add(name, data) if (!isstring(name)) then - return ErrorNoHaltWithStack("nut.command.add expected string for #1 argument but got: " .. nut.type(name)) + return ErrorNoHaltWithStack("nut.command.add expected string for #1 argument but got: " .. nut.type.getName(nut.type(name))) end if (!istable(data)) then - return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected table for #2 argument but got: " .. nut.type(data)) + return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected table for #2 argument but got: " .. nut.type.getName(nut.type(data))) end if (!isfunction(data.onRun)) then - return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected an onRun function in #2 argument but got: " .. nut.type(data.onRun)) + return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected an onRun function in #2 argument but got: " .. nut.type.getName(nut.type(data.onRun))) end -- new argument system @@ -308,10 +308,12 @@ if (SERVER) then end if (argument) then - local multipleTypes = nut.type.getMultiple(nutType) + local multipleTypes = table.Reverse(nut.type.getMultiple(nutType)) local failed - for _, v in ipairs(multipleTypes) do + -- string types are an early bit value in types, but we want to parse/assert them last, so go through the types we have backwards + for i = #multipleTypes, 1, -1 do + local v = multipleTypes[i] local assertion = nut.type.assert(v, argument) if (assertion) then @@ -326,7 +328,7 @@ if (SERVER) then if (nut.type.getName(v) == "player" or nut.type.getName(v) == "character") then failed = "Could not find the target \'" .. argument .. "\'" else - failed = "Wrong type to #" .. i .. " argument, expected \'" .. nut.type.getName(nutType) .. "\' got \'" .. nut.type(argument) .. "\'" + failed = "Wrong type to #" .. i .. " argument, expected \'" .. nut.type.getName(nutType) .. "\' got \'" .. nut.type.getName(nut.type(argument)) .. "\'" end end end diff --git a/gamemode/core/libs/sh_type.lua b/gamemode/core/libs/sh_type.lua index 7a0aec12..1dfad708 100644 --- a/gamemode/core/libs/sh_type.lua +++ b/gamemode/core/libs/sh_type.lua @@ -7,17 +7,23 @@ nut.type.bitsum = nut.type.bitsum or 0 -- _G.type but for nut.type function nut.type.type(...) local value = select(1, ...) - value = (istable(value) and value == nut.type) and select(2, ...) or value + + if (istable(value) and value == nut.type) then + value = select(2, ...) + end if (istable(value)) then - if (value.nutType and nut.type.map[value.nutType]) then - return nut.type.types[nut.type.map[value.nutType]].name + if (value.nutType) then + return value.nutType end + end - for _, v in ipairs(nut.type.types) do - if (v.assertion and v.assertion(value)) then - return v.name - end + -- basic types (strings, numbers, bools) are early in the bit values, go through the types list backwards to parse complex types first + -- as they could use isstring, isnumber, isbool calls to narrow the assertion and give useful returns + for i = #nut.type.types, 1, -1 do + local v = nut.type.types[i] + if (v.assertion and v.assertion(value)) then + return nut.type[v.name] end end @@ -94,7 +100,7 @@ function nut.type.getName(nutType) return nut.type.getName(xor) end -- could be multiple types, lets see if it is - else + elseif (isnumber(nutType)) then local types = nut.type.getMultiple(nutType) if (#types > 0) then @@ -106,6 +112,8 @@ function nut.type.getName(nutType) return table.concat(typeNames, "|") end + elseif (isstring(nutType)) then + return nutType end end @@ -116,6 +124,8 @@ function nut.type.isOptional(num) return isnumber(num) and bit.band(num, nut.type.optional) == nut.type.optional end +-- may move this kind of parsing/searching through values to the commands or util library instead, and reference it there + nut.type.add("string", function(value) return isstring(value) end) nut.type.add("number", function(value) return isnumber(tonumber(value)) end) nut.type.add("bool", function(value) return isbool(value) end) @@ -131,7 +141,7 @@ nut.type.add("player", function(value) end) nut.type.add("character", function(value) if (istable(value)) then - return GetMetaTable(value) == nut.meta.character and true + return getmetatable(value) == nut.meta.character and value end if (isentity(value)) then diff --git a/gamemode/core/sh_commands.lua b/gamemode/core/sh_commands.lua index 92cd20be..cd959459 100644 --- a/gamemode/core/sh_commands.lua +++ b/gamemode/core/sh_commands.lua @@ -233,7 +233,7 @@ nut.command.add("charunban", { return L("charSearching", client) end - if (target.getPlayer) then + if (nut.type(target) == nut.type.character) then if (target:getData("banned")) then target:setData("banned", nil) target:setData("permakilled", nil) From f60ddfebcf6600bf109000a7f1e76d664aec3053 Mon Sep 17 00:00:00 2001 From: Sawayama Date: Fri, 17 May 2024 16:05:10 -0500 Subject: [PATCH 5/8] Switch from bitwise operations to just a table, and then implement functions for and/or. Change where types "resolve" any value into their own type, instead of being in assert it will now be a different function. --- gamemode/core/libs/sh_command.lua | 105 +++++++---- gamemode/core/libs/sh_type.lua | 296 +++++++++++++++--------------- gamemode/core/sh_commands.lua | 60 ++---- 3 files changed, 233 insertions(+), 228 deletions(-) diff --git a/gamemode/core/libs/sh_command.lua b/gamemode/core/libs/sh_command.lua index 5fc3e880..dcfb63dd 100644 --- a/gamemode/core/libs/sh_command.lua +++ b/gamemode/core/libs/sh_command.lua @@ -4,21 +4,35 @@ nut.command.list = nut.command.list or {} local COMMAND_PREFIX = "/" +local function buildTypeName(types, bIsOr) + local name = "" + + for k, v in ipairs(types) do + if (isfunction(v)) then + name = name .. "(" .. buildTypeName(nut.type.getMultiple(v), nut.types.ors[v]) .. ")" + else + name = name .. v .. (k != #types and (bIsOr and "|" or "&") or "") + end + end + + return name +end + function nut.command.add(name, data) if (!isstring(name)) then - return ErrorNoHaltWithStack("nut.command.add expected string for #1 argument but got: " .. nut.type.getName(nut.type(name))) + return ErrorNoHaltWithStack("nut.command.add expected string for #1 argument but got: " .. type(name)) end if (!istable(data)) then - return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected table for #2 argument but got: " .. nut.type.getName(nut.type(data))) + return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected table for #2 argument but got: " .. type(data)) end if (!isfunction(data.onRun)) then - return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected an onRun function in #2 argument but got: " .. nut.type.getName(nut.type(data.onRun))) + return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") expected an onRun function in #2 argument but got: " .. type(data.onRun)) end -- new argument system - if (isnumber(data.arguments)) then + if (isstring(data.arguments) or isfunction(data.arguments)) then data.arguments = {data.arguments} end @@ -31,16 +45,27 @@ function nut.command.add(name, data) for i, v in ipairs(data.arguments) do local argumentName = debug.getlocal(data.onRun, i + 1) - if (nut.type.isOptional(v)) then + local types = nut.type.getMultiple(v) + local bIsOr = nut.type.ors[v] + local bIsOptional = bIsOr and table.HasValue(types, nut.type.optional) + + if (bIsOptional) then hadOptionalArgument = true elseif (hadOptionalArgument) then return ErrorNoHaltWithStack("nut.command.add(\"" .. name .. "\") a required argument is after an optional argument, optional arguments must be last") end - local typeName = nut.type.getName(v) + -- we don't want 'optional' text showing up in the argument syntax + for k, nutType in pairs(types) do + if (nutType == nut.type.optional) then + types[k] = nil + end + end + + local typeName = buildTypeName(types, bIsOr) if (argumentName) then - table.insert(syntaxes, nut.type.isOptional(v) and "[" .. typeName .. ": " .. argumentName .. "]" or "<" .. typeName .. ": " .. argumentName .. ">") + table.insert(syntaxes, bIsOptional and "[" .. typeName .. ": " .. argumentName .. "]" or "<" .. typeName .. ": " .. argumentName .. ">") else table.insert(missingArguments, typeName) end @@ -282,59 +307,59 @@ if (SERVER) then local length = #match + 3 arguments = nut.command.extractArgs(text:sub(length)) - for i, v in ipairs(command.arguments) do - local nutType = command.arguments[i] - local bIsOptional = nut.type.isOptional(nutType) - nutType = bIsOptional and bit.bxor(nutType, nut.type.optional) or nutType + for k, v in ipairs(command.arguments) do + local argument = arguments[k] - local argument = arguments[i] - - if (argument and i == #command.arguments) then + if (argument and k == #command.arguments) then argument = text:sub(length) - arguments[i] = argument + arguments[k] = argument - for _ = i + 1, #arguments do - table.remove(arguments, i + 1) + for _ = k + 1, #arguments do + table.remove(arguments, k + 1) end end length = length + string.len(argument or "") + 1 + local types = nut.type.getMultiple(v) + local bIsOr = nut.type.ors[v] + local bIsOptional = bIsOr and table.HasValue(types, nut.type.optional) + + -- we don't want 'optional' text showing up in the argument syntax + for k, nutType in pairs(types) do + if (nutType == nut.type.optional) then + types[k] = nil + end + end + + local typeName = buildTypeName(types, bIsOr) + if (!bIsOptional) then if (argument == nil or argument == "") then - client:notify("Missing argument #" .. i .. " expected \'" .. nut.type.getName(nutType) .. "\'") + client:notify("Missing argument #" .. k .. " expected \'" .. typeName .. "\'") return true end end if (argument) then - local multipleTypes = table.Reverse(nut.type.getMultiple(nutType)) - local failed - - -- string types are an early bit value in types, but we want to parse/assert them last, so go through the types we have backwards - for i = #multipleTypes, 1, -1 do - local v = multipleTypes[i] - local assertion = nut.type.assert(v, argument) - - if (assertion) then - failed = nil + local resolve, failString = nut.type.resolve(v, argument) + local success - if (!isbool(assertion)) then - arguments[i] = assertion - end + if (isfunction(v)) then + success = v(resolve or argument) + else + success = nut.type.assert(v, resolve) or nut.type.assert(v, argument) + end - break + if (success) then + arguments[k] = resolve or arguments[k] + else + if (failString) then + client:notify(failString) else - if (nut.type.getName(v) == "player" or nut.type.getName(v) == "character") then - failed = "Could not find the target \'" .. argument .. "\'" - else - failed = "Wrong type to #" .. i .. " argument, expected \'" .. nut.type.getName(nutType) .. "\' got \'" .. nut.type.getName(nut.type(argument)) .. "\'" - end + client:notify("Wrong type to #" .. k .. " argument, expected \'" .. typeName .. "\' got \'" .. nut.type(resolve or argument) .. "\'") end - end - if (failed) then - client:notify(failed) return true end end diff --git a/gamemode/core/libs/sh_type.lua b/gamemode/core/libs/sh_type.lua index 1dfad708..9634b5a1 100644 --- a/gamemode/core/libs/sh_type.lua +++ b/gamemode/core/libs/sh_type.lua @@ -1,10 +1,10 @@ nut.type = nut.type or {} -nut.type.map = nut.type.map or {} -nut.type.types = nut.type.types or {} -nut.type.bitsum = nut.type.bitsum or 0 +nut.type.list = nut.type.list or {} + +nut.type.ors = {} +nut.type.ands = {} --- _G.type but for nut.type function nut.type.type(...) local value = select(1, ...) @@ -12,18 +12,13 @@ function nut.type.type(...) value = select(2, ...) end - if (istable(value)) then - if (value.nutType) then - return value.nutType - end + if (istable(value) and value.nutType) then + return value.nutType end - -- basic types (strings, numbers, bools) are early in the bit values, go through the types list backwards to parse complex types first - -- as they could use isstring, isnumber, isbool calls to narrow the assertion and give useful returns - for i = #nut.type.types, 1, -1 do - local v = nut.type.types[i] - if (v.assertion and v.assertion(value)) then - return nut.type[v.name] + for k in pairs(nut.type.list) do + if (nut.type.assert(k, value)) then + return k end end @@ -31,146 +26,179 @@ function nut.type.type(...) end function nut.type.add(name, assertion) - if (nut.type[name]) then - nut.type.types[nut.type.map[name]].assertion = assertion - - return - end - if (!isstring(name)) then error("nut.type.add expected string for #1 input but got: " .. type(name)) end - if (assertion and !isfunction(assertion)) then - error("nut.type.add expected function for #2 input but got: " .. type(assertion)) + if (!isfunction(assertion)) then + error("nut.type.add(\"" .. name .. "\") expected function for #2 input but got: " .. type(assertion)) end - local bitPosition = 1 - while (bitPosition <= nut.type.bitsum) do - bitPosition = bit.lshift(bitPosition, 1) - end - - nut.type.bitsum = bit.bor(nut.type.bitsum, bitPosition) + nut.type.list[name] = {assertion = assertion} + nut.type[name] = name +end - nut.type[bitPosition] = name - nut.type[name] = bitPosition +function nut.type.addResolve(name, resolve) + if (!isstring(name)) then + error("nut.type.addResolve expected string for #1 input but got: " .. type(name)) + end - nut.type.map[bitPosition] = #nut.type.types + 1 - nut.type.map[name] = nut.type.map[bitPosition] + if (!isfunction(resolve)) then + error("nut.type.addResolve(\"" .. name .. "\") expected function for #2 input but got: " .. type(assertion)) + end - table.insert(nut.type.types, {assertion = assertion, name = name}) + nut.type.list[name].resolve = resolve end function nut.type.assert(nutType, value) - if (nut.type.map[nutType]) then - nutType = nut.type.types[nut.type.map[nutType]] - - if (nutType.assertion) then - return nutType.assertion(value) - else - return true - end + -- if it's a function then it's an or/and func, we run it now + if (isfunction(nutType)) then + return nutType(value) end + + return nut.type.list[nutType] and nut.type.list[nutType].assertion(value) end -function nut.type.getMultiple(nutType) - local operands = {} +function nut.type.resolve(nutType, value) + local resolve, failString - for i = 0, 31 do - if bit.band(nutType, bit.lshift(1, i)) > 0 then - operands[#operands + 1] = bit.lshift(1, i) + -- if it's a function then it's an or/and func, we'll have to resolve each type see what succeeds + if (isfunction(nutType)) then + local types = nut.type.ors[nutType] or nut.type.ands[nutType] + + for _, v in ipairs(types) do + if (nut.type.list[v].resolve) then + resolve, failString = nut.type.list[v].resolve(value) + + if (resolve) then + return resolve, failString + end + end end end - return operands -end + if (nut.type.list[nutType] and nut.type.list[nutType].resolve) then + resolve, failString = nut.type.list[nutType].resolve(value) + end -function nut.type.getName(nutType) - if (nut.type.map[nutType]) then - nutType = nut.type.types[nut.type.map[nutType]] + return resolve, failString +end - if (nutType.name) then - return nutType.name +function nut.type.tor(...) + local types = {...} + + local func = function(value) + for _, nutType in ipairs(types) do + if (isfunction(nutType)) then + if (nutType(value)) then + return true + end + else + if (nut.type.assert(nutType, value)) then + return true + end + end end - -- could be a 'bit.bor(x, nut.type.optional)', let's see if it is - elseif (nut.type.isOptional(nutType)) then - local xor = bit.bxor(nutType, nut.type.optional) - if (xor) then - return nut.type.getName(xor) - end - -- could be multiple types, lets see if it is - elseif (isnumber(nutType)) then - local types = nut.type.getMultiple(nutType) + return false + end - if (#types > 0) then - local typeNames = {} + nut.type.ors[func] = types - for i, v in ipairs(types) do - table.insert(typeNames, nut.type.getName(v)) - end + return func +end - return table.concat(typeNames, "|") +function nut.type.tand(...) + local types = {...} + + local func = function(value) + for _, nutType in ipairs(types) do + if (isfunction(nutType)) then + if (!nutType(value)) then + return false + end + else + if (!nut.type.assert(nutType, value)) then + return false + end + end end - elseif (isstring(nutType)) then - return nutType + + return true end + + nut.type.ands[func] = types + + return func end -nut.type = setmetatable(nut.type, {__call = nut.type.type}) +function nut.type.getMultiple(nutType) + if (isfunction(nutType)) then + return table.Copy(nut.type.ors[nutType]) or table.Copy(nut.type.ands[nutType]) + end -nut.type.add("optional") -function nut.type.isOptional(num) - return isnumber(num) and bit.band(num, nut.type.optional) == nut.type.optional + if (isstring(nutType)) then + return {nutType} + end end --- may move this kind of parsing/searching through values to the commands or util library instead, and reference it there +function nut.type.getName(nutType) + if (isstring(nutType) and nut.type.list[nutType]) then + return nutType + end +end + +nut.type = setmetatable(nut.type, {__call = nut.type.type}) -nut.type.add("string", function(value) return isstring(value) end) -nut.type.add("number", function(value) return isnumber(tonumber(value)) end) -nut.type.add("bool", function(value) return isbool(value) end) -nut.type.add("steamid64", function(value) return isstring(value) and string.format("%017.17s", value) == value end) -nut.type.add("player", function(value) - if (isentity(value)) then - return value:IsPlayer() and value +-- do type definitions +nut.type.add("optional", function(value) return (value == "") end) +nut.type.add("string", function(value) return (isstring(value)) end) +nut.type.add("number", function(value) return (isnumber(value)) end) +nut.type.add("bool", function(value) return (isbool(value)) end) +nut.type.add("steamid64", function(value) return (isstring(value) and string.format("%017.17s", value) == value) end) +nut.type.add("player", function(value) return (isentity(value) and value:IsPlayer()) end) +nut.type.add("character", function(value) return (istable(value) and getmetatable(value) == nut.meta.character) end) +nut.type.add("item", function(value) return (istable(value) and value.isItem != nil) or (isentity(value) and value.getItemTable != nil) end) +nut.type.add("faction", function(value) return (istable(value) and value.uniqueID and nut.faction.teams[value.uniqueID] != nil) end) +nut.type.add("class", function(value) return (istable(value) and value.index and nut.class.list[value.index] != nil) end) + +-- do type resolves +nut.type.addResolve("number", function(value) return (tonumber(value)) end) +nut.type.addResolve("bool", function(value) return (tobool(value)) end) +nut.type.addResolve("player", function(value) + if (nut.type.assert(nut.type.player, value)) then + return value end if (isstring(value)) then return nut.util.findPlayer(value) end + + return false, "Could not find the player \'" .. value .. "\'" end) -nut.type.add("character", function(value) - if (istable(value)) then - return getmetatable(value) == nut.meta.character and value +nut.type.addResolve("character", function(value) + if (nut.type.assert(nut.type.character, value)) then + return value end - if (isentity(value)) then - return value.getChar and value:getChar() + -- if the value can resolve to a player then we probably want the player's character + if (nut.type.resolve(nut.type.player, value)) then + return nut.type.resolve(nut.type.player, value):getChar() end if (isstring(value)) then - local client = nut.util.findPlayer(value) - - if (client) then - return client:getChar() - end - for _, v in pairs(nut.char.loaded) do if (nut.util.stringMatches(v:getName(), value)) then return v end end end -end) -nut.type.add("item", function(value) - if (istable(value)) then - return value.isItem and value - end - if (isentity(value)) then - if (value.getItemTable) then - return nut.item.instances[value:getItemID()] - end + return false, "Could not find the character \'" .. value .. "\'" +end) +nut.type.addResolve("item", function(value) + if (nut.type.assert(nut.type.item, value)) then + return (value.getItemTable and value:getItemTable()) or value end if (isstring(value)) then @@ -181,24 +209,20 @@ nut.type.add("item", function(value) if (isnumber(tonumber(value))) then if (nut.item.instances[tonumber(value)]) then - return nut.faction.instances[tonumber(value)] + return nut.item.instances[tonumber(value)] end end -end) -nut.type.add("faction", function(value) - if (istable(value)) then - if (value.uniqueID and nut.faction.teams[value.uniqueID]) then - return nut.faction.teams[value.uniqueID] - end - if (value.getFaction) then - return nut.faction.indices[value:getFaction()] - end + return false, "Could not find the item \'" .. value .. "\'" +end) +nut.type.addResolve("faction", function(value) + if (nut.type.assert(nut.type.faction, value)) then + return value end - if (isentity(value)) then - if (value.Team) then - return nut.faction.indices[value:Team()] + if (isnumber(tonumber(value))) then + if (nut.faction.indices[tonumber(value)]) then + return nut.faction.indices[tonumber(value)] end end @@ -208,54 +232,32 @@ nut.type.add("faction", function(value) end for _, v in pairs(nut.faction.indices) do - if (nut.util.stringMatches(v.name, value)) then + if (nut.util.stringMatches(v.uniqueID, value) or nut.util.stringMatches(v.name, value)) then return v end end - - local client = nut.util.findPlayer(value) - - if (client) then - return nut.faction.indices[client:Team()] - end end - if (isnumber(tonumber(value))) then - if (nut.faction.indices[tonumber(value)]) then - return nut.faction.indices[tonumber(value)] - end - end + return false, "Could not find the faction \'" .. value .. "\'" end) -nut.type.add("class", function(value) - if (istable(value)) then - if (value.getClass) then - return nut.class.list[value:getClass()] - end +nut.type.addResolve("class", function(value) + if (nut.type.assert(nut.type.class, value)) then + return value end - if (isentity(value)) then - if (value.getChar) then - return nut.class.list[value:getChar():getClass()] + if (isnumber(tonumber(value))) then + if (nut.class.list[tonumber(value)]) then + return nut.class.list[tonumber(value)] end end if (isstring(value)) then for _, v in pairs(nut.class.list) do - if (nut.util.stringMatches(L(v.name, client), value)) then + if (nut.util.stringMatches(v.uniqueID, value) or nut.util.stringMatches(v.name, value)) then return v end end - - local client = nut.util.findPlayer(value) - - if (client) then - return nut.class.list[client:getChar():getClass()] - end end - if (isnumber(tonumber(value))) then - if (nut.class.list[tonumber(value)]) then - return nut.class.list[tonumber(value)] - end - end + return false, "Could not find the class \'" .. value .. "\'" end) diff --git a/gamemode/core/sh_commands.lua b/gamemode/core/sh_commands.lua index cd959459..9f31b10e 100644 --- a/gamemode/core/sh_commands.lua +++ b/gamemode/core/sh_commands.lua @@ -1,6 +1,6 @@ nut.command.add("roll", { - arguments = bit.bor(nut.type.number, nut.type.optional), + arguments = nut.type.tor(nut.type.number, nut.type.optional), onRun = function(client, maximum) nut.chat.send(client, "roll", math.random(0, math.min(tonumber(maximum) or 100, 100))) end @@ -42,7 +42,7 @@ nut.command.add("reply", { }) nut.command.add("setvoicemail", { - arguments = bit.bor(nut.type.string, nut.type.optional), + arguments = nut.type.tor(nut.type.string, nut.type.optional), onRun = function(client, message) if (message and message:find("%S")) then client:setNutData("vm", message:sub(1, 240)) @@ -60,7 +60,7 @@ nut.command.add("flaggive", { adminOnly = true, arguments = { nut.type.character, - bit.bor(nut.type.string, nut.type.optional) + nut.type.tor(nut.type.string, nut.type.optional) }, onRun = function(client, target, flags) if (not flags) then @@ -88,7 +88,7 @@ nut.command.add("flagtake", { adminOnly = true, arguments = { nut.type.character, - bit.bor(nut.type.string, nut.type.optional) + nut.type.tor(nut.type.string, nut.type.optional) }, onRun = function(client, target, flags) if (not flags) then @@ -136,7 +136,7 @@ nut.command.add("charsetbodygroup", { arguments = { nut.type.character, nut.type.string, - bit.bor(nut.type.number, nut.type.optional) + nut.type.tor(nut.type.number, nut.type.optional) }, onRun = function(client, target, bodygroup, value) local index = target:getPlayer():FindBodygroupByName(bodygroup) @@ -162,7 +162,7 @@ nut.command.add("charsetname", { adminOnly = true, arguments = { nut.type.character, - bit.bor(nut.type.string, nut.type.optional) + nut.type.tor(nut.type.string, nut.type.optional) }, onRun = function(client, target, name) if (not name) then @@ -181,7 +181,7 @@ nut.command.add("chargiveitem", { arguments = { nut.type.character, nut.type.item, - bit.bor(nut.type.number, nut.type.optional), + nut.type.tor(nut.type.number, nut.type.optional), }, onRun = function(client, target, name, amount) local item = name.uniqueID @@ -227,7 +227,7 @@ nut.command.add("charban", { nut.command.add("charunban", { adminOnly = true, - arguments = bit.bor(nut.type.character, nut.type.string), + arguments = nut.type.tor(nut.type.character, nut.type.string), onRun = function(client, target) if ((client.nutNextSearch or 0) >= CurTime()) then return L("charSearching", client) @@ -396,7 +396,7 @@ nut.command.add("plyunwhitelist", { }) nut.command.add("fallover", { - arguments = bit.bor(nut.type.number, nut.type.optional), + arguments = nut.type.tor(nut.type.number, nut.type.optional), onRun = function(client, time) if (not isnumber(time)) then time = 5 @@ -415,42 +415,20 @@ nut.command.add("fallover", { }) nut.command.add("beclass", { - arguments = nut.type.number, - onRun = function(client, class) + arguments = nut.type.class, + onRun = function(client, name) + local class = name + local char = client:getChar() if (IsValid(client) and char) then - local num = isnumber(tonumber(class)) and tonumber(class) or -1 - - if (nut.class.list[num]) then - local v = nut.class.list[num] - - if (char:joinClass(num)) then - client:notifyLocalized("becomeClass", L(v.name, client)) - - return - else - client:notifyLocalized("becomeClassFail", L(v.name, client)) - - return - end + if (char:joinClass(class.index)) then + client:notifyLocalized("becomeClass", L(class.name, client)) + return else - for k, v in ipairs(nut.class.list) do - if (nut.util.stringMatches(v.uniqueID, class) or nut.util.stringMatches(L(v.name, client), class)) then - if (char:joinClass(k)) then - client:notifyLocalized("becomeClass", L(v.name, client)) - - return - else - client:notifyLocalized("becomeClassFail", L(v.name, client)) - - return - end - end - end + client:notifyLocalized("becomeClassFail", L(class.name, client)) + return end - - client:notifyLocalized("invalid", L("class", client)) else client:notifyLocalized("illegalAccess") end @@ -458,7 +436,7 @@ nut.command.add("beclass", { }) nut.command.add("chardesc", { - arguments = bit.bor(nut.type.string, nut.type.optional), + arguments = nut.type.tor(nut.type.string, nut.type.optional), onRun = function(client, desc) if (not desc or not desc:find("%S")) then return client:requestString("@chgDesc", "@chgDescDesc", function(text) From b7c8746bd0b994dd0937c8c3fbc463a7ee516f26 Mon Sep 17 00:00:00 2001 From: Sawayama Date: Sat, 18 May 2024 17:24:52 -0500 Subject: [PATCH 6/8] Only return findPlayer if we actually succeed to resolve to a player. Same for the getChar in character type. --- gamemode/core/libs/sh_type.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gamemode/core/libs/sh_type.lua b/gamemode/core/libs/sh_type.lua index 9634b5a1..31b32f63 100644 --- a/gamemode/core/libs/sh_type.lua +++ b/gamemode/core/libs/sh_type.lua @@ -171,7 +171,9 @@ nut.type.addResolve("player", function(value) end if (isstring(value)) then - return nut.util.findPlayer(value) + if (nut.util.findPlayer(value)) then + return nut.util.findPlayer(value) + end end return false, "Could not find the player \'" .. value .. "\'" @@ -181,9 +183,11 @@ nut.type.addResolve("character", function(value) return value end + local client = nut.type.resolve(nut.type.player, value) + -- if the value can resolve to a player then we probably want the player's character - if (nut.type.resolve(nut.type.player, value)) then - return nut.type.resolve(nut.type.player, value):getChar() + if (client and client:getChar()) then + return client:getChar() end if (isstring(value)) then From 75fa41c6f65068d43fca0903c066977abdceeb97 Mon Sep 17 00:00:00 2001 From: Sawayama Date: Sun, 19 May 2024 01:12:36 -0500 Subject: [PATCH 7/8] Change all remaining commands to use new argument system. Change nut.util.findPlayer to use the player type resolve instead. If an argument is set to nut.type.optional only it won't show in the syntax and will always count as a success to resolve. Resolves made a table on the type with unique identifiers per resolve, so you could add have more resolves per type now. --- gamemode/core/libs/sh_chatbox.lua | 4 +- gamemode/core/libs/sh_command.lua | 122 +++++++++++++++-------------- gamemode/core/libs/sh_type.lua | 111 ++++++++++++-------------- gamemode/core/sh_commands.lua | 15 ++-- gamemode/core/sh_util.lua | 15 +--- plugins/3dpanel.lua | 21 ++--- plugins/3dtext.lua | 15 ++-- plugins/act/sh_plugin.lua | 8 +- plugins/area/sh_plugin.lua | 18 ++--- plugins/attributes/sh_commands.lua | 76 ++++++------------ plugins/doors/sh_commands.lua | 115 +++++++++------------------ plugins/mapscene.lua | 12 +-- plugins/sam_commands.lua | 6 +- plugins/spawns.lua | 86 ++++++-------------- plugins/storage/sh_plugin.lua | 8 +- 15 files changed, 257 insertions(+), 375 deletions(-) diff --git a/gamemode/core/libs/sh_chatbox.lua b/gamemode/core/libs/sh_chatbox.lua index 73872d06..963a89e4 100644 --- a/gamemode/core/libs/sh_chatbox.lua +++ b/gamemode/core/libs/sh_chatbox.lua @@ -1,7 +1,7 @@ nut.chat = nut.chat or {} nut.chat.classes = nut.char.classes or {} -local DUMMY_COMMAND = {syntax = "", onRun = function() end} +local DUMMY_COMMAND = {arguments = "string", onRun = function(_, text) end} if (not nut.command) then include("sh_command.lua") @@ -393,7 +393,7 @@ end -- Private messages between players. nut.chat.register("pm", { - format = "[PM] %s: %s.", + format = "[PM] %s: %s", color = Color(249, 211, 89), filter = "pm", deadCanChat = true diff --git a/gamemode/core/libs/sh_command.lua b/gamemode/core/libs/sh_command.lua index dcfb63dd..b43e62f4 100644 --- a/gamemode/core/libs/sh_command.lua +++ b/gamemode/core/libs/sh_command.lua @@ -65,7 +65,9 @@ function nut.command.add(name, data) local typeName = buildTypeName(types, bIsOr) if (argumentName) then - table.insert(syntaxes, bIsOptional and "[" .. typeName .. ": " .. argumentName .. "]" or "<" .. typeName .. ": " .. argumentName .. ">") + if (v != nut.type.optional) then + table.insert(syntaxes, bIsOptional and "[" .. typeName .. ": " .. argumentName .. "]" or "<" .. typeName .. ": " .. argumentName .. ">") + end else table.insert(missingArguments, typeName) end @@ -76,7 +78,7 @@ function nut.command.add(name, data) end -- build syntax if we don't have a custom syntax - if (!data.syntax) then + if (!data.syntax and #syntaxes > 0) then data.syntax = table.concat(syntaxes, " ") end end @@ -87,7 +89,7 @@ function nut.command.add(name, data) -- Check if the command is for basic admins only. if (data.adminOnly) then data.onCheckAccess = function(client) - return client:IsAdmin() + return !IsValid(client) and true or client:IsAdmin() end -- Or if it is only for super administrators. elseif (data.superAdminOnly) then @@ -300,77 +302,81 @@ if (SERVER) then local command = nut.command.list[match] -- We have a valid, registered command. if (command) then - -- Get the arguments like a console command. - if (!arguments) then - -- new argument system - if (command.arguments) then - local length = #match + 3 - arguments = nut.command.extractArgs(text:sub(length)) - - for k, v in ipairs(command.arguments) do - local argument = arguments[k] - - if (argument and k == #command.arguments) then - argument = text:sub(length) - arguments[k] = argument - - for _ = k + 1, #arguments do - table.remove(arguments, k + 1) - end - end - - length = length + string.len(argument or "") + 1 + arguments = arguments or nut.command.extractArgs(text:sub(#match + 3)) - local types = nut.type.getMultiple(v) - local bIsOr = nut.type.ors[v] - local bIsOptional = bIsOr and table.HasValue(types, nut.type.optional) + if (command.arguments) then + for k, v in ipairs(command.arguments) do + local types = nut.type.getMultiple(v) + local bIsOptional = table.HasValue(types, nut.type.optional) - -- we don't want 'optional' text showing up in the argument syntax - for k, nutType in pairs(types) do - if (nutType == nut.type.optional) then - types[k] = nil - end + if (arguments[k] and k == #command.arguments and table.HasValue(types, nut.type.string)) then + for _ = k + 1, #arguments do + arguments[k] = arguments[k] .. " " .. arguments[k + 1] + table.remove(arguments, k + 1) end + end - local typeName = buildTypeName(types, bIsOr) + local argument = arguments[k] - if (!bIsOptional) then - if (argument == nil or argument == "") then - client:notify("Missing argument #" .. k .. " expected \'" .. typeName .. "\'") - return true - end + -- we don't want 'optional' text showing up in the argument syntax + for k, nutType in pairs(types) do + if (nutType == nut.type.optional) then + types[k] = nil end + end - if (argument) then - local resolve, failString = nut.type.resolve(v, argument) - local success + local typeName = buildTypeName(types, nut.type.ors[v]) - if (isfunction(v)) then - success = v(resolve or argument) + if (!bIsOptional) then + if (argument == nil or argument == "") then + if (IsValid(client)) then + client:notify("Missing argument #" .. k .. " expected \'" .. typeName .. "\'") else - success = nut.type.assert(v, resolve) or nut.type.assert(v, argument) + print("Missing argument #" .. k .. " expected \'" .. typeName .. "\'") end - if (success) then - arguments[k] = resolve or arguments[k] - else - if (failString) then - client:notify(failString) - else - client:notify("Wrong type to #" .. k .. " argument, expected \'" .. typeName .. "\' got \'" .. nut.type(resolve or argument) .. "\'") - end + return true + end + end - return true + if (arguments[k]) then + if (IsValid(client)) then + if (table.HasValue(types, nut.type.player) or table.HasValue(types, nut.type.character)) then + if (argument == "^") then + argument = client + elseif (argument == "@") then + local trace = client:GetEyeTrace().Entity + + if (IsValid(trace) and trace:IsPlayer()) then + argument = trace + else + client:notifyLocalized("lookToUseAt") + return true + end + end end end - end - if (#arguments > #command.arguments) then - client:notify("Too many arguments provided, expected \'" .. #command.arguments .. "\' got \'" .. #arguments .. "\'") - return true + local resolve = nut.type.resolve(v, argument) + + if (resolve == nil) then + resolve = argument + end + + local success = isfunction(v) and v(resolve) or nut.type.assert(v, resolve) + + if (success or v == nut.type.optional) then + arguments[k] = resolve + else + if (IsValid(client)) then + client:notify("Invalid \'" .. typeName .. "\' to argument #" .. k) + else + print("Invalid \'" .. typeName .. "\' to argument #" .. k) + end + + return true + end end - else - arguments = nut.command.extractArgs(text:sub(#match + 3)) end end diff --git a/gamemode/core/libs/sh_type.lua b/gamemode/core/libs/sh_type.lua index 31b32f63..8b96e365 100644 --- a/gamemode/core/libs/sh_type.lua +++ b/gamemode/core/libs/sh_type.lua @@ -34,20 +34,22 @@ function nut.type.add(name, assertion) error("nut.type.add(\"" .. name .. "\") expected function for #2 input but got: " .. type(assertion)) end - nut.type.list[name] = {assertion = assertion} + nut.type.list[name] = nut.type.list[name] or {} + nut.type.list[name].assertion = assertion nut.type[name] = name end -function nut.type.addResolve(name, resolve) - if (!isstring(name)) then - error("nut.type.addResolve expected string for #1 input but got: " .. type(name)) +function nut.type.addResolve(nutType, identifier, resolve) + if (!isstring(nutType)) then + error("nut.type.addResolve expected string for #1 input but got: " .. type(nutType)) end if (!isfunction(resolve)) then - error("nut.type.addResolve(\"" .. name .. "\") expected function for #2 input but got: " .. type(assertion)) + error("nut.type.addResolve(\"" .. nutType .. "\") expected function for #2 input but got: " .. type(assertion)) end - nut.type.list[name].resolve = resolve + nut.type.list[nutType].resolves = nut.type.list[nutType].resolves or {} + nut.type.list[nutType].resolves[identifier] = resolve end function nut.type.assert(nutType, value) @@ -60,28 +62,42 @@ function nut.type.assert(nutType, value) end function nut.type.resolve(nutType, value) - local resolve, failString + local resolve -- if it's a function then it's an or/and func, we'll have to resolve each type see what succeeds if (isfunction(nutType)) then local types = nut.type.ors[nutType] or nut.type.ands[nutType] for _, v in ipairs(types) do - if (nut.type.list[v].resolve) then - resolve, failString = nut.type.list[v].resolve(value) + -- if the value is the correct type we don't need to resolve, return the value + if (nut.type.assert(v, value)) then return value end + + if (nut.type.list[v].resolves) then + for _, resolveFunc in pairs(nut.type.list[v].resolves) do + resolve = resolveFunc(value) - if (resolve) then - return resolve, failString + if (resolve) then + return resolve + end end end end end - if (nut.type.list[nutType] and nut.type.list[nutType].resolve) then - resolve, failString = nut.type.list[nutType].resolve(value) + if (nut.type.list[nutType]) then + -- if the value is the correct type we don't need to resolve, return the value + if (nut.type.assert(nutType, value)) then return value end + + for _, resolveFunc in pairs(nut.type.list[nutType].resolves or {}) do + resolve = resolveFunc(value) + + if (resolve) then + return resolve + end + end end - return resolve, failString + return resolve end function nut.type.tor(...) @@ -156,38 +172,35 @@ nut.type.add("string", function(value) return (isstring(value)) end) nut.type.add("number", function(value) return (isnumber(value)) end) nut.type.add("bool", function(value) return (isbool(value)) end) nut.type.add("steamid64", function(value) return (isstring(value) and string.format("%017.17s", value) == value) end) +nut.type.add("steamid", function(value) return (isstring(value) and string.match(value, "STEAM_%d+:%d+:%d+") == value) end) nut.type.add("player", function(value) return (isentity(value) and value:IsPlayer()) end) nut.type.add("character", function(value) return (istable(value) and getmetatable(value) == nut.meta.character) end) -nut.type.add("item", function(value) return (istable(value) and value.isItem != nil) or (isentity(value) and value.getItemTable != nil) end) +nut.type.add("item", function(value) return (istable(value) and value.isItem != nil) end) nut.type.add("faction", function(value) return (istable(value) and value.uniqueID and nut.faction.teams[value.uniqueID] != nil) end) nut.type.add("class", function(value) return (istable(value) and value.index and nut.class.list[value.index] != nil) end) -- do type resolves -nut.type.addResolve("number", function(value) return (tonumber(value)) end) -nut.type.addResolve("bool", function(value) return (tobool(value)) end) -nut.type.addResolve("player", function(value) - if (nut.type.assert(nut.type.player, value)) then - return value - end - +nut.type.addResolve(nut.type.number, "tonumber", function(value) return (tonumber(value)) end) +nut.type.addResolve(nut.type.bool, "tobool", function(value) return (tobool(value)) end) +nut.type.addResolve(nut.type.player, "findPlayer", function(value) if (isstring(value)) then - if (nut.util.findPlayer(value)) then - return nut.util.findPlayer(value) + if (nut.type.assert(nut.type.steamid, value) and player.GetBySteamID(value)) then + return player.GetBySteamID(value) end - end - return false, "Could not find the player \'" .. value .. "\'" -end) -nut.type.addResolve("character", function(value) - if (nut.type.assert(nut.type.character, value)) then - return value + for _, v in ipairs(player.GetAll()) do + if (nut.util.stringMatches(v:Name(), value)) then + return v + end + end end - - local client = nut.type.resolve(nut.type.player, value) +end) +nut.type.addResolve(nut.type.character, "findCharacter", function(value) + local ply = nut.type.resolve(nut.type.player, value) -- if the value can resolve to a player then we probably want the player's character - if (client and client:getChar()) then - return client:getChar() + if (ply and ply:getChar()) then + return ply:getChar() end if (isstring(value)) then @@ -197,14 +210,8 @@ nut.type.addResolve("character", function(value) end end end - - return false, "Could not find the character \'" .. value .. "\'" end) -nut.type.addResolve("item", function(value) - if (nut.type.assert(nut.type.item, value)) then - return (value.getItemTable and value:getItemTable()) or value - end - +nut.type.addResolve(nut.type.item, "findItem", function(value) if (isstring(value)) then if (nut.item.list[value]) then return nut.item.list[value] @@ -216,14 +223,8 @@ nut.type.addResolve("item", function(value) return nut.item.instances[tonumber(value)] end end - - return false, "Could not find the item \'" .. value .. "\'" end) -nut.type.addResolve("faction", function(value) - if (nut.type.assert(nut.type.faction, value)) then - return value - end - +nut.type.addResolve(nut.type.faction, "findFaction", function(value) if (isnumber(tonumber(value))) then if (nut.faction.indices[tonumber(value)]) then return nut.faction.indices[tonumber(value)] @@ -236,19 +237,13 @@ nut.type.addResolve("faction", function(value) end for _, v in pairs(nut.faction.indices) do - if (nut.util.stringMatches(v.uniqueID, value) or nut.util.stringMatches(v.name, value)) then + if (nut.util.stringMatches(client and L(v.uniqueID, client) or v.uniqueID, value) or nut.util.stringMatches(client and L(v.name, client) or v.name, value)) then return v end end end - - return false, "Could not find the faction \'" .. value .. "\'" end) -nut.type.addResolve("class", function(value) - if (nut.type.assert(nut.type.class, value)) then - return value - end - +nut.type.addResolve(nut.type.class, "findClass", function(value) if (isnumber(tonumber(value))) then if (nut.class.list[tonumber(value)]) then return nut.class.list[tonumber(value)] @@ -257,11 +252,9 @@ nut.type.addResolve("class", function(value) if (isstring(value)) then for _, v in pairs(nut.class.list) do - if (nut.util.stringMatches(v.uniqueID, value) or nut.util.stringMatches(v.name, value)) then + if (nut.util.stringMatches(client and L(v.uniqueID, client) or v.uniqueID, value) or nut.util.stringMatches(client and L(v.name, client) or v.name, value)) then return v end end end - - return false, "Could not find the class \'" .. value .. "\'" end) diff --git a/gamemode/core/sh_commands.lua b/gamemode/core/sh_commands.lua index 9f31b10e..2aec0120 100644 --- a/gamemode/core/sh_commands.lua +++ b/gamemode/core/sh_commands.lua @@ -220,7 +220,7 @@ nut.command.add("charban", { adminOnly = true, arguments = nut.type.character, onRun = function(client, target) - nut.util.notifyLocalized("charBan", client:Name(), target:getName()) + nut.util.notifyLocalized("charBan", IsValid(client) and client:Name() or "System", target:getName()) target:ban() end }) @@ -237,10 +237,14 @@ nut.command.add("charunban", { if (target:getData("banned")) then target:setData("banned", nil) target:setData("permakilled", nil) - nut.util.notifyLocalized("charUnBan", nil, client:Name(), target:getName()) + nut.util.notifyLocalized("charUnBan", nil, IsValid(client) and client:Name() or "System", target:getName()) return else - client:notifyLocalized("charNotBanned") + if (IsValid(client)) then + client:notifyLocalized("charNotBanned") + else + print("This character is not banned.") + end return end end @@ -261,10 +265,7 @@ nut.command.add("charunban", { data.banned = nil nut.db.updateTable({_data = data}, nil, "characters", "_id = " .. charID) - nut.util.notifyLocalized("charUnBan", nil, client:Name(), nut.char.loaded[charID]:getName()) - else - client:notify("Could not find the character \'" .. target .. "\'") - return + nut.util.notifyLocalized("charUnBan", nil, IsValid(client) and client:Name() or "System", nut.char.loaded[charID]:getName()) end end) end diff --git a/gamemode/core/sh_util.lua b/gamemode/core/sh_util.lua index a8f95a60..94061756 100644 --- a/gamemode/core/sh_util.lua +++ b/gamemode/core/sh_util.lua @@ -97,27 +97,16 @@ end -- Returns true if a string is a 32-bit SteamID. function nut.util.isSteamID(value) - if (string.match(value, "STEAM_(%d+):(%d+):(%d+)")) then - return true - end - return false + return nut.type.assert(nut.type.steamid, value) end -- Finds a player by matching their name or steam id. function nut.util.findPlayer(identifier, allowPatterns) - if (nut.util.isSteamID(identifier)) then - return player.GetBySteamID(identifier) - end - if (!allowPatterns) then identifier = string.PatternSafe(identifier) end - for _, v in ipairs(player.GetAll()) do - if (nut.util.stringMatches(v:Name(), identifier)) then - return v - end - end + return nut.type.resolve(nut.type.player, identifier) end function nut.util.gridVector(vec, gridSize) diff --git a/plugins/3dpanel.lua b/plugins/3dpanel.lua index 2c29d0fd..9c8436f1 100644 --- a/plugins/3dpanel.lua +++ b/plugins/3dpanel.lua @@ -143,12 +143,13 @@ end nut.command.add("paneladd", { adminOnly = true, - syntax = " [number w] [number h] [number scale]", - onRun = function(client, arguments) - if (!arguments[1]) then - return L("invalidArg", client, 1) - end - + arguments = { + nut.type.string, + nut.type.tor(nut.type.number, nut.type.optional), + nut.type.tor(nut.type.number, nut.type.optional), + nut.type.tor(nut.type.number, nut.type.optional) + }, + onRun = function(client, url, w, h, scale) -- Get the position and angles of the panel. local trace = client:GetEyeTrace() local position = trace.HitPos @@ -157,7 +158,7 @@ nut.command.add("paneladd", { angles:RotateAroundAxis(angles:Forward(), 90) -- Add the panel. - PLUGIN:addPanel(position + angles:Up()*0.1, angles, arguments[1], tonumber(arguments[2]), tonumber(arguments[3]), tonumber(arguments[4])) + PLUGIN:addPanel(position + angles:Up()*0.1, angles, url, w, h, scale) -- Tell the player the panel was added. return L("panelAdded", client) @@ -166,13 +167,13 @@ nut.command.add("paneladd", { nut.command.add("panelremove", { adminOnly = true, - syntax = "[number radius]", - onRun = function(client, arguments) + arguments = nut.type.tor(nut.type.number, nut.type.optional), + onRun = function(client, radius) -- Get the origin to remove panel. local trace = client:GetEyeTrace() local position = trace.HitPos -- Remove the panel(s) and get the amount removed. - local amount = PLUGIN:removePanel(position, tonumber(arguments[1])) + local amount = PLUGIN:removePanel(position, radius) -- Tell the player how many panels got removed. return L("panelRemoved", client, amount) diff --git a/plugins/3dtext.lua b/plugins/3dtext.lua index 63ac5632..ea07c3b4 100644 --- a/plugins/3dtext.lua +++ b/plugins/3dtext.lua @@ -157,8 +157,11 @@ end nut.command.add("textadd", { adminOnly = true, - syntax = " [number scale]", - onRun = function(client, arguments) + arguments = { + nut.type.string, + nut.type.tor(nut.type.number, nut.type.optional) + }, + onRun = function(client, text, scale) -- Get the position and angles of the text. local trace = client:GetEyeTrace() local position = trace.HitPos @@ -167,7 +170,7 @@ nut.command.add("textadd", { angles:RotateAroundAxis(angles:Forward(), 90) -- Add the text. - PLUGIN:addText(position + angles:Up()*0.1, angles, arguments[1], tonumber(arguments[2])) + PLUGIN:addText(position + angles:Up()*0.1, angles, text, scale) -- Tell the player the text was added. return L("textAdded", client) @@ -176,13 +179,13 @@ nut.command.add("textadd", { nut.command.add("textremove", { adminOnly = true, - syntax = "[number radius]", - onRun = function(client, arguments) + arguments = nut.type.tor(nut.type.number, nut.type.optional), + onRun = function(client, radius) -- Get the origin to remove text. local trace = client:GetEyeTrace() local position = trace.HitPos + trace.HitNormal*2 -- Remove the text(s) and get the amount removed. - local amount = PLUGIN:removeText(position, tonumber(arguments[1])) + local amount = PLUGIN:removeText(position, radius) -- Tell the player how many texts got removed. return L("textRemoved", client, amount) diff --git a/plugins/act/sh_plugin.lua b/plugins/act/sh_plugin.lua index c99398a4..87961261 100644 --- a/plugins/act/sh_plugin.lua +++ b/plugins/act/sh_plugin.lua @@ -18,10 +18,12 @@ for k, v in pairs(PLUGIN.acts) do end if (multiple) then - data.syntax = "[number type]" + data.arguments = nut.type.tor(nut.type.number, nut.type.optional) + else + data.arguments = nut.type.optional end - data.onRun = function(client, arguments) + data.onRun = function(client, actType) if (client.nutSeqUntimed) then client:setNetVar("actAng") client:leaveSequence() @@ -51,7 +53,7 @@ for k, v in pairs(PLUGIN.acts) do local sequence if (istable(info.sequence)) then - local index = math.Clamp(math.floor(tonumber(arguments[1]) or 1), 1, #info.sequence) + local index = math.Clamp(math.floor(actType or 1), 1, #info.sequence) sequence = info.sequence[index] else diff --git a/plugins/area/sh_plugin.lua b/plugins/area/sh_plugin.lua index d4b5999d..5212e145 100644 --- a/plugins/area/sh_plugin.lua +++ b/plugins/area/sh_plugin.lua @@ -387,18 +387,11 @@ end nut.command.add("areaadd", { adminOnly = true, - syntax = "", - onRun = function(client, arguments) - local name = table.concat(arguments, " ") or "Area" - + arguments = nut.type.string, + onRun = function(client, name) local pos = client:GetEyeTraceNoCursor().HitPos if (!client:getNetVar("areaMin")) then - if (!name) then - nut.util.Notify(nut.lang.Get("missing_arg", 1), client) - - return - end netstream.Start(client, "displayPosition", pos) client:setNetVar("areaMin", pos, client) @@ -428,7 +421,7 @@ nut.command.add("areaadd", { nut.command.add("arearemove", { adminOnly = true, - onRun = function(client, arguments) + onRun = function(client) local areaID = client:getArea() if (!areaID) then @@ -452,9 +445,8 @@ nut.command.add("arearemove", { nut.command.add("areachange", { adminOnly = true, - syntax = "", - onRun = function(client, arguments) - local name = table.concat(arguments, " ") or "Area" + arguments = nut.type.string, + onRun = function(client, name) local areaID = client:getArea() if (!areaID) then diff --git a/plugins/attributes/sh_commands.lua b/plugins/attributes/sh_commands.lua index a21ab573..7cde50da 100644 --- a/plugins/attributes/sh_commands.lua +++ b/plugins/attributes/sh_commands.lua @@ -1,31 +1,17 @@ nut.command.add("charsetattrib", { adminOnly = true, - syntax = " ", - onRun = function(client, arguments) - local attribName = arguments[2] - if (!attribName) then - return L("invalidArg", client, 2) - end - - local attribNumber = arguments[3] - attribNumber = tonumber(attribNumber) - if (!attribNumber or !isnumber(attribNumber)) then - return L("invalidArg", client, 3) - end - - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target)) then - local char = target:getChar() - if (char) then - for k, v in pairs(nut.attribs.list) do - if (nut.util.stringMatches(L(v.name, client), attribName) or nut.util.stringMatches(k, attribName)) then - char:setAttrib(k, math.abs(attribNumber)) - client:notifyLocalized("attribSet", target:Name(), L(v.name, client), math.abs(attribNumber)) - - return - end - end + arguments = { + nut.type.character, + nut.type.string, + nut.type.number + }, + onRun = function(client, target, attribName, level) + for k, v in pairs(nut.attribs.list) do + if (nut.util.stringMatches(L(v.name, client), attribName) or nut.util.stringMatches(k, attribName)) then + target:setAttrib(k, math.abs(level)) + client:notifyLocalized("attribSet", target:getName(), L(v.name, client), math.abs(level)) + + return end end end @@ -33,32 +19,18 @@ nut.command.add("charsetattrib", { nut.command.add("charaddattrib", { adminOnly = true, - syntax = " ", - onRun = function(client, arguments) - local attribName = arguments[2] - if (!attribName) then - return L("invalidArg", client, 2) - end - - local attribNumber = arguments[3] - attribNumber = tonumber(attribNumber) - if (!attribNumber or !isnumber(attribNumber)) then - return L("invalidArg", client, 3) - end - - local target = nut.command.findPlayer(client, arguments[1]) - - if (IsValid(target)) then - local char = target:getChar() - if (char) then - for k, v in pairs(nut.attribs.list) do - if (nut.util.stringMatches(L(v.name, client), attribName) or nut.util.stringMatches(k, attribName)) then - char:updateAttrib(k, math.abs(attribNumber)) - client:notifyLocalized("attribUpdate", target:Name(), L(v.name, client), math.abs(attribNumber)) - - return - end - end + arguments = { + nut.type.character, + nut.type.string, + nut.type.number + }, + onRun = function(client, target, attribName, level) + for k, v in pairs(nut.attribs.list) do + if (nut.util.stringMatches(L(v.name, client), attribName) or nut.util.stringMatches(k, attribName)) then + target:updateAttrib(k, math.abs(level)) + client:notifyLocalized("attribUpdate", target:getName(), L(v.name, client), math.abs(level)) + + return end end end diff --git a/plugins/doors/sh_commands.lua b/plugins/doors/sh_commands.lua index 565e9a77..7c1da2fc 100644 --- a/plugins/doors/sh_commands.lua +++ b/plugins/doors/sh_commands.lua @@ -94,11 +94,10 @@ nut.command.add("doorbuy", { nut.command.add("doorsetunownable", { adminOnly = true, - syntax = "[string name]", - onRun = function(client, arguments) + arguments = nut.type.tor(nut.type.string, nut.type.optional), + onRun = function(client, name) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity - local name = table.concat(arguments, " ") -- Validate it is a door. if (IsValid(entity) and entity:isDoor() and !entity:getNetVar("disabled")) then @@ -106,14 +105,14 @@ nut.command.add("doorsetunownable", { entity:setNetVar("noSell", true) -- Change the name of the door if needed. - if (arguments[1] and name:find("%S")) then + if (name and name:find("%S")) then entity:setNetVar("name", name) end PLUGIN:callOnDoorChildren(entity, function(child) child:setNetVar("noSell", true) - if (arguments[1] and name:find("%S")) then + if (name and name:find("%S")) then child:setNetVar("name", name) end end) @@ -132,11 +131,10 @@ nut.command.add("doorsetunownable", { nut.command.add("doorsetownable", { adminOnly = true, - syntax = "[string name]", - onRun = function(client, arguments) + arguments = nut.type.tor(nut.type.string, nut.type.optional), + onRun = function(client, name) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity - local name = table.concat(arguments, " ") -- Validate it is a door. if (IsValid(entity) and entity:isDoor() and !entity:getNetVar("disabled")) then @@ -144,14 +142,14 @@ nut.command.add("doorsetownable", { entity:setNetVar("noSell", nil) -- Update the name. - if (arguments[1] and name:find("%S")) then + if (name and name:find("%S")) then entity:setNetVar("name", name) end PLUGIN:callOnDoorChildren(entity, function(child) child:setNetVar("noSell", nil) - if (arguments[1] and name:find("%S")) then + if (name and name:find("%S")) then child:setNetVar("name", name) end end) @@ -170,32 +168,15 @@ nut.command.add("doorsetownable", { nut.command.add("doorsetfaction", { adminOnly = true, - syntax = "[string faction]", - onRun = function(client, arguments) + arguments = nut.type.tor(nut.type.faction, nut.type.optional), + onRun = function(client, name) + local faction = name + -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:isDoor() and !entity:getNetVar("disabled")) then - local faction - - -- Check if the player supplied a faction name. - if (arguments[1]) then - -- Get all of the arguments as one string. - local name = table.concat(arguments, " ") - - -- Loop through each faction, checking the uniqueID and name. - for k, v in pairs(nut.faction.teams) do - if (nut.util.stringMatches(k, name) or nut.util.stringMatches(L(v.name, client), name)) then - -- This faction matches the provided string. - faction = v - - -- Escape the loop. - break - end - end - end - -- Check if a faction was found. if (faction) then entity.nutFactionID = faction.uniqueID @@ -207,14 +188,12 @@ nut.command.add("doorsetfaction", { end) client:notifyLocalized("dSetFaction", L(faction.name, client)) - -- The faction was not found. - elseif (arguments[1]) then - client:notifyLocalized("invalidFaction") - -- The player didn't provide a faction. else + entity.nutFactionID = nil entity:setNetVar("faction", nil) PLUGIN:callOnDoorChildren(entity, function() + entity.nutFactionID = nil entity:setNetVar("faction", nil) end) @@ -229,15 +208,13 @@ nut.command.add("doorsetfaction", { nut.command.add("doorsetdisabled", { adminOnly = true, - syntax = "", - onRun = function(client, arguments) + arguments = nut.type.bool, + onRun = function(client, disabled) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity -- Validate it is a door. if (IsValid(entity) and entity:isDoor()) then - local disabled = util.tobool(arguments[1] or true) - -- Set it so it is ownable. entity:setNetVar("disabled", disabled) @@ -258,8 +235,8 @@ nut.command.add("doorsetdisabled", { }) nut.command.add("doorsettitle", { - syntax = "", - onRun = function(client, arguments) + arguments = nut.type.tor(nut.type.string, nut.type.optional), + onRun = function(client, title) -- Get the door infront of the player. local data = {} data.start = client:GetShootPos() @@ -270,14 +247,6 @@ nut.command.add("doorsettitle", { -- Validate the door. if (IsValid(entity) and entity:isDoor() and !entity:getNetVar("disabled")) then - -- Get the supplied name. - local name = table.concat(arguments, " ") - - -- Make sure the name contains actual characters. - if (!name:find("%S")) then - return client:notifyLocalized("invalidArg", 1) - end - --[[ NOTE: Here, we are setting two different networked names. The title is a temporary name, while the other name is the @@ -288,17 +257,19 @@ nut.command.add("doorsettitle", { -- Check if they are allowed to change the door's name. if (entity:checkDoorAccess(client, DOOR_TENANT)) then - entity:setNetVar("title", name) + entity:setNetVar("title", title) elseif (client:IsAdmin()) then - entity:setNetVar("name", name) + entity:setNetVar("name", title) PLUGIN:callOnDoorChildren(entity, function(child) - child:setNetVar("name", name) + child:setNetVar("name", title) end) else -- Otherwise notify the player he/she can't. client:notifyLocalized("notOwner") end + + PLUGIN:SaveDoorData() else -- Notification of the door not being valid. client:notifyLocalized("dNotValid") @@ -308,7 +279,7 @@ nut.command.add("doorsettitle", { nut.command.add("doorsetparent", { adminOnly = true, - onRun = function(client, arguments) + onRun = function(client) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity @@ -325,7 +296,7 @@ nut.command.add("doorsetparent", { nut.command.add("doorsetchild", { adminOnly = true, - onRun = function(client, arguments) + onRun = function(client) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity @@ -362,7 +333,7 @@ nut.command.add("doorsetchild", { nut.command.add("doorremovechild", { adminOnly = true, - onRun = function(client, arguments) + onRun = function(client) -- Get the door the player is looking at. local entity = client:GetEyeTrace().Entity @@ -399,15 +370,13 @@ nut.command.add("doorremovechild", { nut.command.add("doorsethidden", { adminOnly = true, - syntax = "