--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] if SAM_LOADED then return end local sam = sam local SQL = sam.SQL local SUI = sam.SUI local netstream = sam.netstream sam.permissions.add("manage_players", nil, "superadmin") local get_pages_count = function(count) count = count / 35 local i2 = math.floor(count) return count ~= i2 and i2 + 1 or count end if SERVER then local check = function(ply) return ply:HasPermission("manage_players") and ply:sam_check_cooldown("MenuViewPlayers", 0.1) end local limit = 35 local get_page_count = function(callback, res, page, column, order_by, sort_by, keyword) local query = [[ SELECT COUNT(`steamid`) AS `count` FROM `sam_players`]] if keyword then if column == "steamid" and sam.is_steamid64(keyword) then keyword = util.SteamIDFrom64(keyword) end query = string.format("%s WHERE `%s` LIKE %s", query, column, SQL.Escape("%" .. keyword .. "%")) end SQL.Query(query, callback, true, {res, page, column, order_by, sort_by, keyword}) end local valid_columns = { steamid = true, name = true, rank = true } local valid_sorts = { id = true, name = true, rank = true, play_time = true, last_join = true } local resolve_promise = function(data, arguments) local res = arguments[1] arguments[1] = data res(arguments) end local get_players = function(count_data, arguments) local res, page, column, order_by, sort_by, keyword = unpack(arguments) local count = count_data.count local current_page if page < 1 then page, current_page = 1, 1 end local pages_count = get_pages_count(count) if page > pages_count then page, current_page = pages_count, pages_count end local query = [[ SELECT `steamid`, `name`, `rank`, `expiry_date`, `first_join`, `last_join`, `play_time` FROM `sam_players` ]] local args = {} if keyword then args[1] = column args[2] = "%" .. keyword .. "%" query = query .. [[ WHERE `{1f}` LIKE {2} ]] end args[3] = sort_by if order_by == "DESC" then query = query .. [[ ORDER BY `{3f}` DESC ]] else query = query .. [[ ORDER BY `{3f}` ASC ]] end args[4] = limit args[5] = math.abs(limit * (page - 1)) query = query .. [[ LIMIT {4} OFFSET {5} ]] SQL.FQuery(query, args, resolve_promise, false, {res, count, current_page}) end netstream.async.Hook("SAM.GetPlayers", function(res, ply, page, column, order_by, sort_by, keyword) if not isnumber(page) then return end if not valid_columns[column] then return end if order_by ~= "ASC" and order_by ~= "DESC" then return end if not valid_sorts[sort_by] then return end if keyword ~= nil and not sam.isstring(keyword) then return end get_page_count(get_players, res, page, column, order_by, sort_by, keyword) end, check) return end local GetColor = SUI.GetColor local Line = sui.TDLib.LibClasses.Line local COLUMN_FONT = SUI.CreateFont("Column", "Roboto", 18) local LINE_FONT = SUI.CreateFont("Line", "Roboto", 16) local NEXT_FONT = SUI.CreateFont("NextButton", "Roboto", 18) local button_click = function(s) local v = s.v local dmenu = vgui.Create("SAM.Menu") dmenu:SetInternal(s) if v.name and v.name ~= "" then dmenu:AddOption("Copy Name", function() SetClipboardText(v.name) end) end dmenu:AddOption("Copy SteamID", function() SetClipboardText(v.steamid) end) dmenu:AddOption("Copy Rank", function() SetClipboardText(v.rank) end) dmenu:AddOption("Copy Play Time", function() SetClipboardText(sam.reverse_parse_length(tonumber(v.play_time) / 60)) end) dmenu:AddSpacer() dmenu:AddOption("Change Rank", function() local querybox = vgui.Create("SAM.QueryBox") querybox:SetTitle(string.format("Change rank for '%s'", v.name or v.steamid)) querybox:SetWide(360) local ranks = querybox:Add("SAM.ComboBox") ranks:SetTall(28) for rank_name in SortedPairsByMemberValue(sam.ranks.get_ranks(), "immunity", true) do if v.rank ~= rank_name then ranks:AddChoice(rank_name, nil, true) end end querybox:Done() querybox.save:SetEnabled(true) querybox:SetCallback(function() RunConsoleCommand("sam", "setrankid", v.steamid, ranks:GetValue()) end) end) dmenu:Open() end sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/user.png", function(column_sheet) local refresh, pages local current_page, current_column, current_order, current_sort, keyword = nil, "steamid", "DESC", "id", nil local players_body = column_sheet:Add("Panel") players_body:Dock(FILL) players_body:DockMargin(0, 1, 0, 0) players_body:DockPadding(10, 10, 10, 10) local toggle_loading, is_loading = sam.menu.add_loading_panel(players_body) local title = players_body:Add("SAM.Label") title:Dock(TOP) title:SetFont(SAM_TAB_TITLE_FONT) title:SetText("Players") title:SetTextColor(GetColor("menu_tabs_title")) title:SizeToContents() local total = players_body:Add("SAM.Label") total:Dock(TOP) total:DockMargin(0, 6, 0, 0) total:SetFont(SAM_TAB_DESC_FONT) total:SetText("60 total players") total:SetTextColor(GetColor("menu_tabs_title")) total:SetPos(10, SUI.Scale(40)) total:SizeToContents() local search_entry do local container = players_body:Add("SAM.Panel") container:Dock(TOP) container:DockMargin(0, 6, 10, 0) container:SetTall(30) local sort_by = container:Add("SAM.ComboBox") sort_by:Dock(RIGHT) sort_by:DockMargin(4, 0, 0, 0) sort_by:SetWide(106) sort_by:SetValue("Sort By (ID)") sort_by:AddChoice("ID") sort_by:AddChoice("Name") sort_by:AddChoice("Rank") sort_by:AddChoice("Play Time") function sort_by:OnSelect(_, value) value = value:lower():gsub(" ", "_") if current_sort ~= value then current_sort = value refresh() end end local sort_order = container:Add("SAM.ComboBox") sort_order:Dock(RIGHT) sort_order:SetWide(96) sort_order:SetValue("Desc") sort_order:AddChoice("Desc") sort_order:AddChoice("Asc") function sort_order:OnSelect(_, value) value = value:upper() if current_order ~= value then current_order = value refresh() end end local column = container:Add("SAM.ComboBox") column:Dock(RIGHT) column:DockMargin(0, 0, 4, 0) column:SetWide(140) column:SetValue("Search (SteamID)") column:AddChoice("SteamID") column:AddChoice("Name") column:AddChoice("Rank") function column:OnSelect(_, value) value = value:lower() if current_column ~= value then current_column = value refresh() end end search_entry = container:Add("SAM.TextEntry") search_entry:Dock(LEFT) search_entry:SetNoBar(true) search_entry:SetPlaceholder("Search...") search_entry:SetRadius(4) search_entry:SetWide(220) function search_entry:OnEnter(no_refresh) local value = self:GetValue() if keyword ~= value then keyword = value ~= "" and value or nil if not no_refresh then refresh() end end end end Line(players_body, nil, -5, SUI.Scale(15), -5, 0) do local columns = players_body:Add("Panel") columns:Dock(TOP) columns:DockMargin(0, 10, 0, 0) local info = columns:Add("SAM.Label") info:Dock(LEFT) info:DockMargin(4, 0, 0, 0) info:SetFont(COLUMN_FONT) info:SetText("Player") info:SetTextColor(GetColor("player_list_titles")) info:SetWide(SUI.Scale(280) + SUI.Scale(34)) info:SizeToContentsY(3) local play_time = columns:Add("SAM.Label") play_time:Dock(LEFT) play_time:DockMargin(-4, 0, 0, 0) play_time:SetFont(COLUMN_FONT) play_time:SetText("Play Time") play_time:SetTextColor(GetColor("player_list_titles")) play_time:SetWide(SUI.Scale(180)) play_time:SizeToContentsY(3) local rank_expiry = columns:Add("SAM.Label") rank_expiry:Dock(LEFT) rank_expiry:DockMargin(-4, 0, 0, 0) rank_expiry:SetFont(COLUMN_FONT) rank_expiry:SetText("Rank Expiry") rank_expiry:SetTextColor(GetColor("player_list_titles")) rank_expiry:SetWide(SUI.Scale(280)) rank_expiry:SizeToContentsY(3) columns:SizeToChildren(false, true) end local body = players_body:Add("SAM.ScrollPanel") body:Dock(FILL) body:DockMargin(0, 10, 0, 0) body:SetVBarPadding(6) local set_data = function(data) body:GetCanvas():Clear() body.VBar.Scroll = 0 local players, players_count, current_page_2 = unpack(data) total:SetText(players_count .. " total players") pages = get_pages_count(players_count) current_page.i = pages == 0 and 0 or current_page_2 or current_page.i current_page:SetText(current_page.i .. "/" .. pages) body:Line() for k, v in ipairs(players) do local line = body:Add("SAM.PlayerLine") line:DockMargin(0, 0, 0, 10) local name = v.name ~= "" and v.name or nil line:SetInfo({ steamid = v.steamid, name = name, rank = v.rank }) local play_time = line:Add("SAM.Label") play_time:Dock(LEFT) play_time:DockMargin(4, 0, 0, 0) play_time:SetFont(LINE_FONT) play_time:SetText(sam.reverse_parse_length(tonumber(v.play_time) / 60)) play_time:SetTextColor(GetColor("player_list_data")) play_time:SetContentAlignment(4) play_time:SetWide(SUI.Scale(180)) local expiry_date = tonumber(v.expiry_date) local rank_expiry = line:Add("SAM.Label") rank_expiry:Dock(LEFT) rank_expiry:DockMargin(-3, 0, 0, 0) rank_expiry:SetFont(LINE_FONT) rank_expiry:SetText(expiry_date == 0 and "Never" or sam.reverse_parse_length((expiry_date - os.time()) / 60)) rank_expiry:SetTextColor(GetColor("player_list_data")) rank_expiry:SetContentAlignment(4) rank_expiry:SizeToContents() local but = line:Actions() but.v = v but:On("DoClick", button_click) body:Line() end end refresh = function() if not is_loading() and LocalPlayer():HasPermission("manage_players") then search_entry:OnEnter(true) local refresh_query = netstream.async.Start("SAM.GetPlayers", toggle_loading, current_page.i, current_column, current_order, current_sort, keyword) refresh_query:done(set_data) end end local bottom_panel = players_body:Add("SAM.Panel") bottom_panel:Dock(BOTTOM) bottom_panel:DockMargin(0, 6, 0, 0) bottom_panel:SetTall(30) bottom_panel:Background(GetColor("page_switch_bg")) local previous_page = bottom_panel:Add("SAM.Button") previous_page:Dock(LEFT) previous_page:SetWide(30) previous_page:SetText("<") previous_page:SetFont(NEXT_FONT) previous_page:On("DoClick", function() if current_page.i <= 1 then return end current_page.i = current_page.i - 1 refresh() end) current_page = bottom_panel:Add("SAM.Label") current_page:Dock(FILL) current_page:SetContentAlignment(5) current_page:SetFont(SAM_TAB_DESC_FONT) current_page:SetText("loading...") current_page.i = 1 local next_page = bottom_panel:Add("SAM.Button") next_page:Dock(RIGHT) next_page:SetWide(30) next_page:SetText(">") next_page:SetFont(NEXT_FONT) next_page:On("DoClick", function() if current_page.i == pages then return end current_page.i = current_page.i + 1 refresh() end) function bottom_panel:Think() next_page:SetEnabled(current_page.i ~= pages) previous_page:SetEnabled(current_page.i > 1) end do local refresh_2 = function() timer.Simple(1, refresh) end for k, v in ipairs({"SAM.AuthedPlayer", "SAM.ChangedPlayerRank", "SAM.ChangedSteamIDRank"}) do hook.Add(v, "SAM.MenuPlayers", refresh_2) end end refresh() return players_body end, function() return LocalPlayer():HasPermission("manage_players") end, 2)