diff --git a/README.md b/README.md index 63008c1..2d32874 100644 --- a/README.md +++ b/README.md @@ -17,31 +17,26 @@ content["msg-argument-missing"] = "[Zeus] Failed to executed because Argument {1 content["msg-argument-invalid"] = "[Zeus] Failed to executed because Argument {1} is invalid!" content["msg-mod-success"] = "[Zeus] Module {1} was successfully executed!" content["msg-mod-disabled"] = "[Zeus] Module {1} was successfully disabled!" +content["msg-mod-failed"] = "[Zeus] Module {1} failed because {2}!" content["msg-banned"] = "You are banned: {1}" --- VEH SPAWN -- content["msg-veh-model-not-exist"] = "[Zeus] The Vehicle Model {1} does not exist!" -content["custom-chat"] = false -content["store-type"] = "LOCAL" -- coming soon: mysql -content["admins"] = { } -- Enter SteamID64 in here which will have all permissions -content["dev-mode"] = true -- Every User on the Server has Admin Permissions, when true +content["custom-chat"] = false -- coming soon: mysql +content["store-type"] = "LOCAL" -- or MYSQL +content["dev-mode"] = true -function IsLocalStorage() - return content["store-type"] == "LOCAL" -end - -function IsAdmin(steamID) - if content["dev-mode"] == true then - return true - end +content["db-host"] = "localhost" +content["db-user"] = "zeus-db" +content["db-password"] = "this-is-a-safe-pw" +content["db-name"] = "zeus-db" +content["db-charset"] = "utf8mb4" - for _, value in ipairs(content["admins"]) do - if tostring(value) == steamID then - return true - end - end +function GetDatabaseConnection() + return content["db-host"], content["db-user"], content["db-password"], content["db-name"] +end - return false +function IsLocalStorage() + return content["store-type"] == "LOCAL" end function FormatMsg(key, ...) @@ -61,8 +56,13 @@ return content | ---------- | -------------| ------------- | -----------| | **msg-** | *see above*| *every text* | This are the i18n messages printed by Zeus. You can change them like you want to. Some messages have placeholders (e.g. *{1}*) this placeholders will be replaced by Zeus with values dynamically. You can use them, too but you don't need to. | | **custom-chat** | false | false / true | *not implemented yet* | -| **store-type** | LOCAL | LOCAL / MYSQL (*not implemented yet*) | The way Zeus stores its data | +| **store-type** | LOCAL | LOCAL / MYSQL | The way Zeus stores its data | | **dev-mode** | true | false / true | When enabled, every player on the server has all permissions. Zeus will warn you on server start, if the dev mode is enabled | +| **db-host** | localhost | any ip | The hostname of th MySQL database | +| **db-user** | zeus-db | text | The username of the MySQL database | +| **db-password** | this-is-a-safe-pw | text | The password of the MySQL database | +| **db-name** | zeus-db | text | The database name of the MySQL database | +| **db-charset** | utf8mb4 | MariaDB Charsets | The charset of the MySQL database | ## Modules Modules are the main components of Zeus. Without them, Zeus is only a frame. In Zeus are some default modules which offers only the basics. But with a little bit of work and the knowledge to code in LUA you can create own ones easily. diff --git a/client/ui/index.html b/client/ui/index.html index 76f0090..b9d5493 100644 --- a/client/ui/index.html +++ b/client/ui/index.html @@ -393,7 +393,7 @@ const MOD_WRAPPER = $('#wrapper'); const PLAYER_SELECT = $('#player-select'); const PLAYER_SELECT_LIST = $('#player-select ul'); - const COMPONENT_PATTERN = /((?[P|T|N])(\((?[a-zA-Z0-9_-]*)\))?(?\?)?)/gi; + const COMPONENT_PATTERN = /((?[P|T|N])(\((?[a-zA-Z0-9_\- ]*)\))?(?\?)?)/gi; let currentPlayerInput; let currentModules; let currentModule; @@ -496,7 +496,7 @@ name = "Argument " + (id + 1); } if (key === "P") { - return ''; + return ''; } if (key === "T") { return ''; @@ -507,6 +507,11 @@ return ''; } + function onPlayerKeyDown(sender, event) { + if (event.which === 8 || event.which === 46) + $(sender).val(""); + } + function showPlayerSelect(input) { currentPlayerInput = input; getPlayers(function (players) { @@ -573,6 +578,12 @@ } function setup(json) { + ADMIN_MODS.html(""); + FUN_MODS.html(""); + OTHER_MODS.html(""); + PERM_MODS.html(""); + SPAWN_MODS.html(""); + UTILS_MODS.html(""); let modules = JSON.parse(json); currentModules = modules; for (let i = 0; i < modules.length; i++) { diff --git a/server/api.lua b/server/api.lua index 4094645..1c7dabb 100644 --- a/server/api.lua +++ b/server/api.lua @@ -1,4 +1,4 @@ -local zeus = { _VERSION = "1.0:3" } +local zeus = { _VERSION = "1.1:0" } local date = require('packages/' .. GetPackageName() .. '/server/libs/date') local config = require('packages/' .. GetPackageName() .. '/server/io/config') local storage @@ -35,7 +35,6 @@ function zeus.FormatDate(val) local num = "" for i = 1, #str do local c = str:sub(i, i) - print(c) if c == "y" then local v = tonumber(num) if v == nil then @@ -127,7 +126,9 @@ function zeus.MakeAdmin(player) steam = GetPlayerSteamId(player) end - return storage:MakeAdmin(player, true) + local result = storage:MakeAdmin(player, true) + _G.zeus.RefreshPlayer(player) + return result end AddFunctionExport("MakeAdmin", zeus.MakeAdmin); @@ -137,7 +138,9 @@ function zeus.RemoveAdmin(player) steam = GetPlayerSteamId(player) end - return storage:MakeAdmin(player, false) + local result = storage:MakeAdmin(player, false) + _G.zeus.RefreshPlayer(player) + return result end AddFunctionExport("RemoveAdmin", zeus.RemoveAdmin); @@ -179,7 +182,9 @@ end AddFunctionExport("HasGroup", zeus.HasGroup); function zeus.AddGroup(player, group) - return storage:AddGroup(player, group) + local result = storage:AddGroup(player, group) + _G.zeus.RefreshPlayer(player) + return result end AddFunctionExport("AddGroup", zeus.AddGroup); @@ -189,7 +194,9 @@ end AddFunctionExport("GetGroup", zeus.GetGroup); function zeus.RemoveGroup(player, group) - return storage:RemoveGroup(player, group) + local result = storage:RemoveGroup(player, group) + _G.zeus.RefreshPlayer(player) + return result end AddFunctionExport("RemoveGroup", zeus.RemoveGroup); @@ -204,27 +211,47 @@ end AddFunctionExport("CreateGroup", zeus.CreateGroup); function zeus.DeleteGroup(group) - return storage:DeleteGroup(group) + local result = storage:DeleteGroup(group) + for _, v in pairs(GetAllPlayers()) do + _G.zeus.RefreshPlayer(v) + end + return result end AddFunctionExport("DeleteGroup", zeus.DeleteGroup); function zeus.AddPermission(group, permission) - return storage:AddPermission(group, permission) + local result = storage:AddPermission(group, permission) + for _, v in pairs(GetAllPlayers()) do + if zeus.HasGroup(v, group) then + _G.zeus.RefreshPlayer(v) + end + end + return result end AddFunctionExport("AddPermission", zeus.AddPermission); function zeus.RemovePermission(group, permission) - return storage:RemovePermission(group, permission) + local result = storage:RemovePermission(group, permission) + for _, v in pairs(GetAllPlayers()) do + if zeus.HasGroup(v, group) then + _G.zeus.RefreshPlayer(v) + end + end + return result end AddFunctionExport("RemovePermission", zeus.RemovePermission); function zeus.AddPlayerPermission(player, permission) - return storage:AddPlayerPermission(player, permission) + local result = storage:AddPlayerPermission(player, permission) + _G.zeus.RefreshPlayer(player) + return result end AddFunctionExport("AddPlayerPermission", zeus.AddPlayerPermission); function zeus.RemovePlayerPermission(player, permission) - return storage:RemovePlayerPermission(player, permission) + local result = storage:RemovePlayerPermission(player, permission) + _G.zeus.RefreshPlayer(player) + return result end AddFunctionExport("RemovePlayerPermission", zeus.RemovePlayerPermission); diff --git a/server/io/config.lua b/server/io/config.lua index da81cd8..9902308 100644 --- a/server/io/config.lua +++ b/server/io/config.lua @@ -11,9 +11,19 @@ content["msg-banned"] = "You are banned: {1}" content["msg-veh-model-not-exist"] = "[Zeus] The Vehicle Model {1} does not exist!" content["custom-chat"] = false -- coming soon: mysql -content["store-type"] = "LOCAL" -- coming soon: mysql +content["store-type"] = "LOCAL" -- or MYSQL content["dev-mode"] = true +content["db-host"] = "localhost" +content["db-user"] = "zeus-db" +content["db-password"] = "this-is-a-safe-pw" +content["db-name"] = "zeus-db" +content["db-charset"] = "utf8mb4" + +function GetDatabaseConnection() + return content["db-host"], content["db-user"], content["db-password"], content["db-name"] +end + function IsLocalStorage() return content["store-type"] == "LOCAL" end diff --git a/server/io/database.lua b/server/io/database.lua new file mode 100644 index 0000000..7383380 --- /dev/null +++ b/server/io/database.lua @@ -0,0 +1,141 @@ +local config = require('packages/' .. GetPackageName() .. '/server/io/config') +local database = { _VERSION = "1.1:0", current = nil } + +local function IsOpen() + return database.current ~= false +end + +local function CreateTable(name) + if IsOpen() then + mariadb_await_query(database.current, name, false) + end +end + +function database.Insert(query) + if IsOpen() then + mariadb_await_query(database.current, query, false) + end +end + +function HasRows() + local flag = mariadb_get_row_count() + + if not flag == false then + flag = mariadb_get_row_count() > 0 + end + + return flag +end + +function database.Exists(query) + if IsOpen() then + local result = mariadb_await_query(database.current, query) + local flag = HasRows() + + mariadb_delete_result(result) + return flag + end + return true +end + +function database.Get(query, field, def) + local result = mariadb_await_query(database.current, query) + + local value = def + if HasRows() then + value = mariadb_get_value_name(1, field) + + if value == nil or value == "NULL" then + value = def + end + end + + mariadb_delete_result(result) + return value +end + +function database.GetTwo(query, field1, field2, def1, def2) + local result = mariadb_await_query(database.current, query) + + local value = def1 + local value2 = def2 + if HasRows() then + value = mariadb_get_value_name(1, field1) + value2 = mariadb_get_value_name(1, field2) + end + + mariadb_delete_result(result) + return value, value2 +end + +function database.Init() + mariadb_log("error") + database.current = mariadb_connect(GetDatabaseConnection()) + + if IsOpen() then + mariadb_set_charset(database.current, config["db-charset"]) + + -- TABLES + CreateTable([[CREATE TABLE IF NOT EXISTS `zeus_groups` ( + `GroupName` VARCHAR(45) NOT NULL, + `Format` VARCHAR(45) NULL, + PRIMARY KEY (`GroupName`)) + ENGINE = InnoDB;]]) + CreateTable([[CREATE TABLE IF NOT EXISTS `zeus_groupperms` ( + `GroupName` VARCHAR(45) NULL, + `Permission` VARCHAR(45) NULL, + CONSTRAINT `GroupPermission` + FOREIGN KEY (`GroupName`) + REFERENCES `zeus_groups` (`GroupName`) + ON DELETE CASCADE + ON UPDATE NO ACTION) + ENGINE = InnoDB;]]) + CreateTable([[CREATE TABLE IF NOT EXISTS `zeus_players` ( + `SteamID` VARCHAR(45) NOT NULL, + `GroupName` VARCHAR(45) NULL DEFAULT 'def_group', + `IsAdmin` TINYINT(1) DEFAULT '0', + PRIMARY KEY (`SteamID`), + CONSTRAINT `PlayerGroup` + FOREIGN KEY (`GroupName`) + REFERENCES `zeus_groups` (`GroupName`) + ON DELETE SET NULL + ON UPDATE NO ACTION) + ENGINE = InnoDB;]]) + CreateTable([[CREATE TABLE IF NOT EXISTS `zeus_playerperms` ( + `Player` VARCHAR(45) NULL, + `Permission` VARCHAR(45) NULL, + CONSTRAINT `PlayerPermission` + FOREIGN KEY (`Player`) + REFERENCES `zeus_players` (`SteamID`) + ON DELETE CASCADE + ON UPDATE NO ACTION) + ENGINE = InnoDB;]]) + CreateTable([[CREATE TABLE IF NOT EXISTS `zeus_playerbans` ( + `Player` VARCHAR(45) NULL, + `Duration` VARCHAR(45) NULL, + `Reason` VARCHAR(45) NULL, + CONSTRAINT `PlayerBan` + FOREIGN KEY (`Player`) + REFERENCES `zeus_players` (`SteamID`) + ON DELETE CASCADE + ON UPDATE NO ACTION) + ENGINE = InnoDB;]]) + + if not database.Exists(mariadb_prepare(database.current, "SELECT * FROM zeus_groups WHERE GroupName = '?'", tostring("def_group"))) then + database.Insert(mariadb_prepare(database.current, "INSERT INTO zeus_groups (GroupName, Format) VALUES ('?', '?')", tostring("def_group"), tostring("soon"))) + end + end +end + +function database.Handle() + return database.current +end + +function database.Close() + if IsOpen() then + mariadb_close(database.current) + database.current = false + end +end + +return database \ No newline at end of file diff --git a/server/io/storage/local.lua b/server/io/storage/local.lua index 33bbe80..6f3722f 100644 --- a/server/io/storage/local.lua +++ b/server/io/storage/local.lua @@ -116,11 +116,14 @@ function initPlayer(player) end function returnSteam(id) + local steamId = id + if IsValidPlayer(id) then - return GetPlayerSteamId(id) + steamId = tostring(GetPlayerSteamId(id)) end - return id + initPlayer(steamId) + return steamId end function store:IsAdmin(playerId) diff --git a/server/io/storage/mysql.lua b/server/io/storage/mysql.lua new file mode 100644 index 0000000..9c77371 --- /dev/null +++ b/server/io/storage/mysql.lua @@ -0,0 +1,288 @@ +local config = require('packages/' .. GetPackageName() .. '/server/io/config') +local database = require('packages/' .. GetPackageName() .. '/server/io/database') +database.Init() +local store = { _VERSION = "1.1:0" } + +function format(player) + if IsValidPlayer(player) then + return tostring(GetPlayerSteamId(player)) + end + + return nil +end + +function initPlayer(player) + if not database.Exists(mariadb_prepare(database.Handle(), "SELECT * FROM zeus_players WHERE SteamID = '?'", tostring(player))) then + database.Insert(mariadb_prepare(database.Handle(), "INSERT INTO zeus_players (SteamID) VALUES ('?')", tostring(player))) + end +end + +function returnSteam(id) + local steamId = id + + if IsValidPlayer(id) then + steamId = tostring(GetPlayerSteamId(id)) + end + + initPlayer(steamId) + return steamId +end + +function isAdminInDb(steamId) + return tostring(database.Get(mariadb_prepare(database.Handle(), "SELECT IsAdmin FROM zeus_players WHERE SteamID = '?'", tostring(steamId)), "IsAdmin", "0")) == "1" +end + +function store:IsAdmin(playerId) + if config["dev-mode"] == true then + return true + end + + local steamId = returnSteam(playerId) + return isAdminInDb(steamId) +end + +function store:MakeAdmin(playerId, enable) + local steamId = returnSteam(playerId) + if enable == true then + if not isAdminInDb(steamId) then + database.Insert(mariadb_prepare(database.Handle(), "UPDATE zeus_players SET IsAdmin = '?' WHERE SteamID = '?'", 1, tostring(steamId))) + return true + end + else + if isAdminInDb(steamId) then + database.Insert(mariadb_prepare(database.Handle(), "UPDATE zeus_players SET IsAdmin = '?' WHERE SteamID = '?'", 0, tostring(steamId))) + return true + end + end + + return false +end + +function hasPermission(player, playerId, permission) + if store:IsAdmin(player) then + return true + end + + if not database.Exists(mariadb_prepare(database.Handle(), "SELECT * FROM zeus_playerperms WHERE Player = '?' AND Permission = '?'", tostring(player), tostring(permission))) then + if not store:HasGroupPermission(store:GetGroup(playerId), permission) then + return false + end + end + return true +end + +function split(inputstr, sep) + if sep == nil then + sep = "%s" + end + local t = {} + for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do + t[#t + 1] = str + end + return t +end + +function formatPermissions(permission) + local permissions = {} + permissions[#permissions + 1] = "*" + permissions[#permissions + 1] = permission + + local parts = split(permission, ".") + local len = #parts - 1 + if len >= 1 then + for i = 1, len do + local newPermission = "" + for j = 1, i do + if newPermission == "" then + newPermission = parts[j] + else + newPermission = newPermission .. "." .. parts[j] + end + end + permissions[#permissions + 1] = newPermission .. ".*" + end + end + return permissions +end + +function store:Unban(playerId) + local player = format(playerId) + if player == nil then + return false + end + + initPlayer(player) + database.Insert(mariadb_prepare(database.Handle(), "DELETE FROM zeus_playerbans WHERE Player = '?'", tostring(player))) +end + +function store:TempBan(playerId, time, reasonText) + local player = format(playerId) + if player == nil then + return false + end + + initPlayer(player) + if not store:IsBanned(playerId) then + local date = _G.zeus.FormatDate(time) + database.Insert(mariadb_prepare(database.Handle(), "INSERT INTO zeus_playerbans (Player, Duration, Reason) VALUES ('?', '?', '?')", tostring(player), tostring(date), tostring(reasonText))) + return true + end + + return false +end + +function store:Ban(player, reason) + return store:TempBan(player, "P", reason) +end + +-- First: true or false (if player is banned), Second: the ban reason +function store:IsBanned(playerId) + local player = format(playerId) + if player == nil then + return false + end + + initPlayer(player) + if not database.Exists(mariadb_prepare(database.current, "SELECT * FROM zeus_playerbans WHERE Player = '?'", tostring(player))) then + return false, nil + end + + local exp, reason = database.GetTwo(mariadb_prepare(database.current, "SELECT * FROM zeus_playerbans WHERE Player = '?'", tostring(player)), "Duration", "Reason", nil, "") + if exp == nil then + return false + end + + return not _G.zeus.IsDateExpired(exp), reason +end + +function store:HasPermission(playerId, permission) + local player = format(playerId) + if player == nil then + return false + end + + initPlayer(player) + for _, perm in ipairs(formatPermissions(permission)) do + if hasPermission(player, playerId, perm) then + return true + end + end + return false +end + +function store:AddPlayerPermission(playerId, permission) + local player = format(playerId) + if player == nil then + return false + end + + initPlayer(player) + if not database.Exists(mariadb_prepare(database.Handle(), "SELECT * FROM zeus_playerperms WHERE Permission = '?'", tostring(permission))) then + database.Insert(mariadb_prepare(database.Handle(), "INSERT INTO zeus_playerperms (Player, Permission) VALUES ('?', '?')", tostring(player), tostring(permission))) + end + return true +end + +function store:RemovePlayerPermission(playerId, permission) + local player = format(playerId) + if player == nil then + return false + end + + initPlayer(player) + if database.Exists(mariadb_prepare(database.Handle(), "SELECT * FROM zeus_playerperms WHERE Player = '?' AND Permission = '?'", tostring(player), tostring(permission))) then + database.Insert(mariadb_prepare(database.Handle(), "DELETE FROM zeus_playerperms WHERE Player = '?' AND Permission = '?'", tostring(player), tostring(permission))) + end + return true +end + +function store:HasGroup(playerId, group) + local player = format(playerId) + if player == nil then + return false + end + + initPlayer(player) + return database.Exists(mariadb_prepare(database.Handle(), "SELECT * FROM zeus_players WHERE SteamID = '?' AND GroupName = '?'", tostring(player), tostring(group))) +end + +function store:AddGroup(playerId, group) + local player = format(playerId) + if player == nil then + return false + end + + initPlayer(player) + database.Insert(mariadb_prepare(database.Handle(), "UPDATE zeus_players SET GroupName = '?' WHERE SteamID = '?'", tostring(group), tostring(player))) + return true +end + +function store:GetGroup(playerId) + local player = format(playerId) + if player == nil then + return "def_group" + end + + initPlayer(player) + return database.Get(mariadb_prepare(database.Handle(), "SELECT GroupName FROM zeus_players WHERE SteamID = '?'", tostring(player)), "GroupName", "def_group") +end + +function store:RemoveGroup(playerId, group) + local player = format(playerId) + if player == nil then + return false + end + + initPlayer(player) + if store:HasGroup(playerId, group) then + database.Insert(mariadb_prepare(database.Handle(), "UPDATE zeus_players SET GroupName = '?' WHERE SteamID = '?'", "def_group", tostring(player))) + return true + end + return false +end + +function store:ExistsGroup(group) + return database.Exists(mariadb_prepare(database.Handle(), "SELECT * FROM zeus_groups WHERE GroupName = '?'", tostring(group))) +end + +function store:CreateGroup(group) + if not store:ExistsGroup(group) then + database.Insert(mariadb_prepare(database.Handle(), "INSERT INTO zeus_groups (GroupName, Format) VALUES ('?', '?')", tostring(group), tostring("soon"))) + return true + end + return false +end + +function store:DeleteGroup(group) + if store:ExistsGroup(group) then + database.Insert(mariadb_prepare(database.Handle(), "DELETE FROM zeus_groups WHERE GroupName = '?'", tostring(group))) + return true + end + return false +end + +function store:AddPermission(group, permission) + if store:ExistsGroup(group) and not store:HasGroupPermission(group, permission) then + database.Insert(mariadb_prepare(database.Handle(), "INSERT INTO zeus_groupperms (GroupName, Permission) VALUES ('?', '?')", tostring(group), tostring(permission))) + return true + end + return false +end + +function store:RemovePermission(group, permission) + if store:ExistsGroup(group) and store:HasGroupPermission(group, permission) then + database.Insert(mariadb_prepare(database.Handle(), "DELETE FROM zeus_groupperms WHERE GroupName = '?' AND Permission = '?'", tostring(group), tostring(permission))) + return true + end + return false +end + +function store:HasGroupPermission(group, permission) + return database.Exists(mariadb_prepare(database.Handle(), "SELECT * FROM zeus_groupperms WHERE GroupName = '?' AND Permission = '?'", tostring(group), tostring(permission))) +end + +AddEvent("OnPackageStop", function() + database.Close() +end) + +return store \ No newline at end of file diff --git a/server/modules/admin/remove_admin.lua b/server/modules/admin/remove_admin.lua index 5fa11ae..42c0c20 100644 --- a/server/modules/admin/remove_admin.lua +++ b/server/modules/admin/remove_admin.lua @@ -3,7 +3,7 @@ local config = require('packages/' .. GetPackageName() .. '/server/io/config') local mod = { name = "Remove Admin", description = "Removes a Players Admin Privileges", - ui_component = "P(Target)" + ui_component = "P(Target)?T(SteamID64)?" } function mod:GetName() @@ -11,17 +11,20 @@ function mod:GetName() end function mod:GetTarget(player, args) - if args[1] == nil then - AddPlayerChat(player, FormatMsg("msg-argument-missing", "Player")) - return nil + local target = nil + + if args[1] ~= nil and IsValidPlayer(args[1]) then + target = args[1] + elseif args[2] ~= nil then + target = args[2] end - if not IsValidPlayer(args[1]) then - AddPlayerChat(player, FormatMsg("msg-argument-invalid", "Player")) + if target == nil then + AddPlayerChat(player, FormatMsg("msg-argument-invalid", "Target")) return nil end - return args[1] + return target end function mod:GetUIComponent() diff --git a/server/modules/permission/add_permission.lua b/server/modules/permission/add_permission.lua index 03a081c..8efc036 100644 --- a/server/modules/permission/add_permission.lua +++ b/server/modules/permission/add_permission.lua @@ -37,7 +37,7 @@ function mod:Activate(executor, target, args) return nil end - if _G.zeus.RemovePermission(args[1], args[2]) == false then + if _G.zeus.AddPermission(args[1], args[2]) == false then AddPlayerChat(executor, FormatMsg("msg-mod-failed", mod.name, "the group is not existing")) return nil end diff --git a/server/modules/permission/set_group.lua b/server/modules/permission/set_group.lua index 4789b0c..cc04b75 100644 --- a/server/modules/permission/set_group.lua +++ b/server/modules/permission/set_group.lua @@ -2,7 +2,7 @@ local config = require('packages/' .. GetPackageName() .. '/server/io/config') local mod = { name = "Set Group", - description = "Sets a Player Group (Arg1: Player, Arg2: Group)", + description = "Sets a Player Group", ui_component = "P(Target)T(Group Name)" }