--[[ © 2017 Thriving Ventures Limited do not share, re-distribute or modify without permission of its author (gustaf@thrivingventures.com). ]] --- ## Shared --- Extension of util library to provide convenience functions. -- @module util color_white = Color(255, 255, 255, 255) color_black = Color(0, 0, 0, 255) color_red = Color(255, 0, 0, 255) color_green = Color(0, 255, 0, 255) color_green_darker = Color(0, 200, 0, 255) color_blue = Color(0, 0, 255, 255) color_yellow = Color(255, 255, 0, 255) color_purple_light = Color(106, 90, 205, 255) color_orange = Color(255, 165, 0, 255) color_brown = Color(150, 75, 0, 255) local temptbl = {} local function MergeSortByName( tbl, iLow, iHigh, bReverse ) if ( iLow < iHigh ) then local iMiddle = math.floor( iLow + (iHigh - iLow) / 2 ) MergeSortByName( tbl, iLow, iMiddle, bReverse ) MergeSortByName( tbl, iMiddle + 1, iHigh, bReverse ) for i = iLow, iHigh do temptbl[i] = tbl[i] end local i = iLow local j = iMiddle + 1 local k = iLow while ( i <= iMiddle and j <= iHigh ) do if ( temptbl[i]:Nick() <= temptbl[j]:Nick() ) then if ( bReverse ) then tbl[k] = temptbl[j] j = j + 1 else tbl[k] = temptbl[i] i = i + 1 end else if ( bReverse ) then tbl[k] = temptbl[i] i = i + 1 else tbl[k] = temptbl[j] j = j + 1 end end k = k + 1 end while ( i <= iMiddle ) do tbl[k] = temptbl[i] k = k + 1 i = i + 1 end end end --- Gets a sorted list of players connected to the server. -- @treturn table Player list. function player.GetSortedPlayers(bReverse --[[= false]]) local tbl = player.GetAll() MergeSortByName(tbl, 1, #tbl, bReverse) return tbl; end; -- Taken from the gmod table library. Necessary to restore the original -- functionality broken due to the following commit: -- https://github.com/garrynewman/garrysmod/commit/5e9d4fa05ea9cc69c56e5c0857d5247dcabdc192 local function fnPairsSorted(tab, index) if (index == nil) then index = 1; else for k, v in pairs(tab.__SortedIndex) do if (v == index) then index = k + 1; break; end; end; end; local key = tab.__SortedIndex[index]; if (!key) then tab.__SortedIndex = nil; return; end; index = index + 1; return key, tab[key]; end -- See fnPairsSorted function util.SortedPairsByMemberValue(tab, valueName, descending) descending = descending or false; local sortedIndex = {}; local sortedTable = table.ClearKeys(tab, true); table.SortByMember(sortedTable, valueName, !descending); for k, v in ipairs(sortedTable) do table.insert(sortedIndex, v.__key); end; tab.__SortedIndex = sortedIndex; return fnPairsSorted, tab, nil; end; --- Returns whether or not the player can be compared **EXACTLY** with a given identifier. -- This is more strict, and will match exact names. -- @player player The player to extract the information from. -- @string identifier The string to compare with. -- @treturn bool Whether the player matches the given identifier. function util.PlayerMatchesIdentifier(player, identifier) local result = hook.Call("serverguard.PlayerMatchesIdentifier", nil, player, identifier); return (IsValid(player) and identifier and result or string.lower(identifier) == string.lower(player:Name()) or string.lower(identifier) == string.lower(player:Nick()) or player:SteamID() == identifier or player:UniqueID() == identifier or player:SteamID64() == identifier or (player:IPAddress():gsub(":%d+", "")) == identifier); end; --- Returns whether or not the player can be compared with a given identifier. -- This will be more lenient with finding substrings in a player's name, for example. -- @player player The player to extract the information from. -- @string identifier The string to compare with. -- @treturn bool Whether the player contains the given identifier. function util.PlayerContainsIdentifier(player, identifier) local result = hook.Call("serverguard.PlayerContainsIdentifier", nil, player, identifier); return (IsValid(player) and identifier and result or (string.find(utf8.lower(player:Name()), utf8.lower(identifier), 0, true) or string.find(utf8.lower(player:Nick()), utf8.lower(identifier), 0, true) or player:SteamID() == identifier or player:UniqueID() == identifier or player:SteamID64() == identifier or (player:IPAddress():gsub(":%d+", "")) == identifier)); end; --- Returns whether or not the target's immunity level fulfills the required immunity comparison with the player. -- @number requiredImmunity The required immunity comparison. -- @player player The player to be compared to. -- @player target The player to be compared with. -- @treturn bool Whether the immunity requirement has been fulfilled. function util.PlayerMatchesImmunity(requiredImmunity, pPlayer, target, bNoTargetSelf) local result = hook.Call("serverguard.PlayerMatchesImmunity", nil, requiredImmunity, pPlayer, target, bNoTargetSelf); return result or (requiredImmunity == SERVERGUARD.IMMUNITY.EQUAL and serverguard.player:GetImmunity(target) == serverguard.player:GetImmunity(pPlayer)) or (requiredImmunity == SERVERGUARD.IMMUNITY.LESS and serverguard.player:GetImmunity(target) < serverguard.player:GetImmunity(pPlayer)) or (requiredImmunity == SERVERGUARD.IMMUNITY.LESSOREQUAL and serverguard.player:GetImmunity(target) <= serverguard.player:GetImmunity(pPlayer)) or (requiredImmunity == SERVERGUARD.IMMUNITY.ANY) or (!bNoTargetSelf and pPlayer == target); end; local identifierTags = { -- Immunity is automatically checked beforehand so we don't have to do it here. ["^"] = { filter = function(callingPlayer, targetPlayer) return callingPlayer == targetPlayer end, negated = function(callingPlayer, targetPlayer) return callingPlayer != targetPlayer end }, ["\\*"] = { filter = function(callingPlayer, targetPlayer) return true end, negated = function(callingPlayer, targetPlayer) return false end }, ["#*"] = { filter = function(callingPlayer, targetPlayer, argument) return serverguard.player:GetRank(targetPlayer) == argument end, negated = function(callingPlayer, targetPlayer, argument) return serverguard.player:GetRank(targetPlayer) != argument end }, ["@"] = { filter = function(callingPlayer, targetPlayer, argument) return callingPlayer:GetEyeTraceNoCursor().Entity == targetPlayer end, negated = function(callingPlayer, targetPlayer, argument) return callingPlayer:GetEyeTraceNoCursor().Entity != targetPlayer end } }; --- Attempts to find all connected players matching the given query, and runs a function with that player as an argument. -- Valid identifiers include name, Steam ID, 64-bit Steam ID, IP address, a comma-separated list, or a special tag. -- Tags include ^ for self, * for all, and #name for all players with the given rank. You can use ! to negate the action. -- @string identifier String to try and match players with. -- @player callingPlayer Player who initiated the query. -- @number requiredImmunity The required immunity level between the calling player and any targets for the function to run. -- @func func Function to run with all applicable targets. -- @treturn table List of applicable players. function util.ExecuteOnPlayers(identifier, callingPlayer, requiredImmunity, func) if (!identifier or !callingPlayer or !requiredImmunity or !func) then return; end; local tAllPlayers = player.GetAll() local players = {}; for k, v in ipairs(tAllPlayers) do if (util.PlayerMatchesImmunity(requiredImmunity, callingPlayer, v)) then if (util.PlayerMatchesIdentifier(v, identifier)) then -- If we match exactly, we only execute on the one player. table.insert(players, v); break; elseif (util.PlayerContainsIdentifier(v, identifier)) then if (#players == 0) then table.insert(players, v); else serverguard.Notify(callingPlayer, SGPF("player_found_multiple", identifier)); return {}; end; end; end; end; -- Matching special tags. if (#players == 0) then local negate = (string.sub(identifier, 1, 1) == "!"); for k, v in pairs(identifierTags) do local tag = k; local text = identifier; local argument = ""; local func = "filter"; if (negate) then text = string.sub(identifier, 2, string.len(identifier)); func = "negated"; end; local wildcardPosition = string.find(tag, "*", 0, true); if (wildcardPosition) then if (string.sub(tag, wildcardPosition - 1, wildcardPosition - 1) == "\\") then tag = string.sub(tag, wildcardPosition, wildcardPosition); else tag = string.gsub(tag, "*", ""); argument = string.gsub(text, tag, ""); tag = tag .. argument; end; end; for k, pPlayer in pairs(tAllPlayers) do if (tag == text and util.PlayerMatchesImmunity(requiredImmunity, callingPlayer, pPlayer) and v[func](callingPlayer, pPlayer, argument)) then table.insert(players, pPlayer); end; end; end; -- Matching name-separated list. if (#players == 0) then if (string.find(identifier, ",", 0, true)) then local identifiers = string.Explode(",", identifier); for k, v in pairs(identifiers) do local found = false; for k2, v2 in ipairs(tAllPlayers) do if (util.PlayerMatchesImmunity(requiredImmunity, callingPlayer, v2) and util.PlayerContainsIdentifier(v2, v)) then if (!found) then found = true; table.insert(players, v2); else serverguard.Notify(callingPlayer, SGPF("player_found_multiple", v)); return {}; end; end; end; end; end; -- Give up if we haven't found anyone. if (#players == 0) then serverguard.Notify(callingPlayer, SGPF("player_cant_find_suitable")); return {}; end; end; end; local result = {}; for k, v in pairs(players) do local status = func(v); if (status) then table.insert(result, v); end; end; -- Oops, everyone was filtered out. if (#result == 0) then serverguard.Notify(callingPlayer, SGPF("player_cant_find_suitable")); return {}; end; return result; end; function util.GetListenServerHost() for k, v in ipairs(player.GetAll()) do if (v:IsListenServerHost()) then return v end end return NULL end local PLAYER = FindMetaTable("Player") function isplayer(v) return getmetatable(v) == PLAYER end --- Obtains a list of elements suitable for `serverguard.Notify` for the given player list. -- It is assumed that there is at least one target. -- @table targets Player targets to output. -- @bool bOwnership Whether or not to add an ownership suffix (such as "'s"). -- @number targetColor A SERVERGUARD.NOTIFY color to use. -- @treturn table Notify components. -- @see serverguard.Notify function util.GetNotifyListForTargets(targets, bOwnership, targetColor) targetColor = targetColor or SERVERGUARD.NOTIFY.RED; if (#targets < 1) then return {}; end; if (#targets == #player.GetAll() and #targets != 1) then if (bOwnership) then return {targetColor, "everyone's"}; end; return {targetColor, "everyone"}; end local result = {}; table.insert(result, targetColor); table.insert(result, targets[1]:Name()); for i = 2, #targets - 1 do table.insert(result, SERVERGUARD.NOTIFY.WHITE); table.insert(result, ", "); table.insert(result, targetColor); table.insert(result, targets[i]:Name()); end; if (#targets > 1) then table.insert(result, SERVERGUARD.NOTIFY.WHITE); table.insert(result, " and "); table.insert(result, targetColor); table.insert(result, targets[#targets]:Name()); end; if (bOwnership) then table.insert(result, string.Ownership(result[#result], true)); end; hook.Call("serverguard.GetNotifyListForTargets", nil, targets, bOwnership, targetColor, result); return result; end; --- Attempts to find a connected player by a given identifier. -- Valid identifiers include name, Steam ID, 64-bit Steam ID, and IP address. -- @string identifier Identifier to search with. -- @player user Player to provide notifies to. -- @bool bNoMessage Whether or not to provide feedback to the player. function util.FindPlayer(identifier, user, bNoMessage) if (!identifier) then return; end; local output = {}; for k, v in ipairs(player.GetAll()) do local playerNick = string.lower(v:Nick()); local playerName = string.lower(v:Name()); if (v:SteamID() == identifier or v:UniqueID() == identifier or v:SteamID64() == identifier or (v:IPAddress():gsub(":%d+", "")) == identifier or playerNick == string.lower(identifier) or playerName == string.lower(identifier)) then return v; end; if (string.find(playerNick, string.lower(identifier), 0, true) or string.find(playerName, string.lower(identifier), 0, true)) then table.insert(output, v); end; end; if (#output == 1) then return output[1]; elseif (#output > 1) then if (!bNoMessage) then if (IsValid(user)) then if (serverguard and serverguard.Notify) then serverguard.Notify(user, SERVERGUARD.NOTIFY.RED, "Found more than one player with that identifier."); else user:ChatPrint("Found more than one player with that identifier."); end; else if (SERVER) then Msg("Found more than one player with that identifier.\n"); end; end; end; else if (!bNoMessage) then if (IsValid(user)) then if (serverguard and serverguard.Notify) then serverguard.Notify(user, SERVERGUARD.NOTIFY.RED, "Can't find any player with that identifier."); else user:ChatPrint("Can't find any player with that identifier."); end; else if (SERVER) then Msg("Can't find any player with that identifier.\n"); end; end; end; end; end; --- Alternative tonumber function that always returns a number. Returns 0 if unable to convert to number. -- @param value Any variable to attempt to a number. -- @treturn number Converted number. function util.ToNumber(value) if (type(value) == "string") then if (tonumber(value) == nil) then return 0; end; return tonumber(value); end; if (type(value) == "boolean") then if (value) then return 1; end; return 0; end; if (type(value) == "number") then return value; end; if (value == nil) then return 0; end; return 0; end; --- Formats a number to include commas. -- @number number Number to format. -- @treturn string Formatted number. function util.FormatNumber(number) if (number >= 1e14) then return tostring(number) end number = tostring(number) local dp = string.find(number, "%.") or #number +1 for i = dp -4, 1, -3 do number = string.sub(number, 1, i) .. "," .. string.sub(number, i +1) end return number end --- Capitalizes the first letter of a string. -- @string text String to capitalize. -- @treturn string Capitalized string. function string.Capitalize(text) return string.upper(string.sub(text, 1, 1)) .. string.sub(text, 2) end --- Returns the ownership suffix of a string. -- @string text String to find ownership for. -- @bool bSuffixOnly Whether or not to return only the suffix. -- @treturn string String with ownership. function string.Ownership(text, bSuffixOnly) local suffix = "'s"; if (text[string.len(text)] == "'" or text[string.len(text)] == "'s") then suffix = ""; elseif (text[string.len(text)] == "s") then suffix = "'" end; if (bSuffixOnly) then return suffix; end; return text .. suffix; end; --- Returns a Steam ID from a string. -- @string text String to check for a Steam ID. -- @treturn text The matched Steam ID. function string.SteamID(text) return string.match(text, "STEAM_%d:%d:%d+"); end; --- Explodes a string by enclosed tabs. -- @string text String to explode. -- @string seperator String to separate tags by. -- @string open String to use as the beginning of a tag. -- @string close String to use as the end of a tag. -- @bool bRemoveTag Whether or not to remove the tag characters from the result. -- @treturn table Exploded string. -- @usage util.ExplodeByTags("!this 'is a test'", " ", "'", "'", true); function util.ExplodeByTags(text, seperator, open, close, bRemoveTag) local results = {}; local current = ""; local tag = nil; text = string.gsub(text, "%s+", " "); for i = 1, #text do local character = string.sub(text, i, i); if (!tag) then if (character == open) then if (!bRemoveTag) then current = current..character; end; tag = true; elseif (character == seperator) then results[#results + 1] = current; current = ""; else current = current..character; end; else if (character == close) then if (!bRemoveTag) then current = current..character; end; tag = nil; else current = current..character; end; end; end; if (current != "") then results[#results + 1] = current; end; return results; end; --- Checks to see whether or not a variable is the console. -- @param object Any variable. -- @treturn bool Whether or not the variable is the console. function util.IsConsole(object) if (type(object) == "Entity" and !IsValid(object) and (object.EntIndex and object:EntIndex() == 0)) then return true; end; return false; end; --- Converts an ISO 8601 duration timestamp used by YouTube to seconds. -- Note: this is not entirely accurate to the ISO standard - it's only tested for use with YouTube's duration format. -- @string iso The ISO duration. -- @treturn number The ISO duration in seconds. Returns -1 for an invalid ISO duration. function util.IsoDurationToSeconds(iso) local duration = 0; local number = ""; if (string.sub(iso, 1, 1) != "P") then return -1; end; for i = 1, string.len(iso), 1 do local character = string.sub(iso, i, i); if (character == "P" or character == "T") then continue; end; if (tonumber(character)) then number = number .. character; end; if (!tonumber(number)) then return -1; end; if (character == "D") then duration = duration + tonumber(number) * 86400; number = ""; end; if (character == "H") then duration = duration + tonumber(number) * 3600; number = ""; end; if (character == "M") then duration = duration + tonumber(number) * 60; number = ""; end; if (character == "S") then duration = duration + tonumber(number); number = ""; end; end; return duration; end; local durationUnits = { ["m"] = {1, "minute"}, ["h"] = {60, "hour"}, ["d"] = {1440, "day"}, ["w"] = {10080, "week"}, ["n"] = {43200, "month"}, ["y"] = {525600, "year"} }; local orderedDurationUnits = { {525600, "year"}, {43200, "month"}, {10080, "week"}, {1440, "day"}, {60, "hour"}, {1, "minute"}, }; --- Converts a duration (e.g 1d12h) to minutes. Clamps all numbers between 0 and 99. -- @string input The input duration. This can be a number, but it will return that number. -- @treturn number The duration in minutes. -- @treturn text The duration in text form (1 year, 3 days, etc). -- @treturn bool Whether or not any input values have been clamped. function util.ParseDuration(input) if (tonumber(input)) then local output = {}; local number = tonumber(input); if (number <= 0) then return 0, "Indefinitely", false; end; for k, v in ipairs(orderedDurationUnits) do if (number >= v[1]) then local count = math.floor(number / v[1]); if (count > 1) then output[#output + 1] = tostring(count).." "..v[2].."s"; else output[#output + 1] = tostring(count).." "..v[2]; end; number = number - (v[1] * count); end; end; return tonumber(input), table.concat(output, ", "), false; end; local bClamped = false; local duration = 0; local text = ""; local number = ""; for i = 1, string.len(input), 1 do local character = string.sub(input, i, i); if (tonumber(character)) then number = number .. character; continue; end if (!tonumber(number)) then number = ""; continue; end; if (!durationUnits[character]) then number = ""; continue; end; if (tonumber(number) < 0 || tonumber(number) > 99) then number = tostring(math.Clamp(tonumber(number), 0, 99)); bClamped = true; end; duration = duration + (tonumber(number) * durationUnits[character][1]); if (tonumber(number) > 0) then if (string.len(text) > 0) then text = text .. ", "; end; text = text .. number .. " " .. durationUnits[character][2]; if (tonumber(number) > 1) then text = text .. "s"; end; end; number = ""; end; if (text == "") then text = "Indefinitely"; end; return duration, text, bClamped; end; if (SERVER) then --- (SERVER) Prints text to the chatbox of all connected players. -- @string text Text to print. function util.PrintAll(text) for k, v in ipairs(player.GetAll()) do v:ChatPrint(text) end end --- (SERVER) Prints coloured text to the chatbox of all connected players. -- @param ... Color and text to use. -- @usage util.PrintAllColor(Color(255, 255, 255), "Hello! ", Color(200, 30, 30), "This is a test."); function util.PrintAllColor(...) serverguard.netstream.Start(nil, "sgPrintAllColor", {...}); end; --- (SERVER) Prints text to the console of connected admins. -- @string text Text to print. function util.PrintConsoleAdmins(text) for k, pPlayer in ipairs(player.GetAll()) do if (pPlayer:IsAdmin()) then serverguard.netstream.Start(pPlayer, "sgPrintConsole", text); end; end; end; elseif (CLIENT) then serverguard.bLogEnabled = CreateClientConVar("serverguard_log", "1", true, false); serverguard.netstream.Hook("sgPrintAllColor", function(data) chat.AddText(unpack(data)); end); serverguard.netstream.Hook("sgPrintConsole", function(data) if (serverguard.bLogEnabled:GetBool()) then Msg(data); end; end); -- Thanks capsadmin <3. --- (CLIENT) Draws a textured line. -- @number x1 Starting X coordinate. -- @number y1 Starting Y coordinate. -- @number x2 Ending X coordinate. -- @number y2 Ending Y coordinate. -- @number w With of the line. function surface.DrawLineEx(x1, y1, x2, y2, w) local dx, dy = x1 -x2, y1 -y2 local rotation = math.deg(math.atan2(dx, dy)) local distance = math.Distance(x1, y1, x2, y2) x1 = x1 -dx *0.5 y1 = y1 -dy *0.5 surface.DrawTexturedRectRotated(x1, y1, w, distance, rotation) end --- Makes a panel change the cursor to a hand when hovered. -- @panel panel Panel to install hover to. function util.InstallHandHover(panel) panel:SetMouseInputEnabled(true) function panel:OnCursorEntered() self:SetCursor("hand") end function panel:OnCursorExited() self:SetCursor("arrow") end end --- (CLIENT) Draws a box shadow. -- @number x X coordinate. -- @number y Y coordinate. -- @number w Width of box. -- @number h Height of box. -- @number passes The amount of passes when drawing the shadow. -- @number depth The depth of the shadow. function util.PaintShadow(x, y, w, h, passes, depth) passes = passes or 4 depth = depth or 0.2 for i = 1, passes do local color = Color(0, 0, 0, (255 /i) *depth) -- Top shadow. draw.SimpleRect(x, y +(-1 +i), w, 1, color) -- Left shadow. draw.SimpleRect(x +(-1 +i), y, 1, h, color) -- Bottom shadow. draw.SimpleRect(x, y +(h -i), w, 1, color) -- Right shadow. draw.SimpleRect(x +(w -i), y, 1, h, color) end end --- (CLIENT) Darkens a colour by a given amount. Does not include alpha. -- @color color Colour to dim. -- @number amount How much to dim. -- @treturn color Dimmed colour. function util.DimColor(color, amount) return Color( math.Clamp(color.r - amount, 0, 255), math.Clamp(color.g - amount, 0, 255), math.Clamp(color.b - amount, 0, 255), color.a ); end; --- (CLIENT) Limits how dark a colour can be. -- @color color Colour to limit. -- @number r Minimum red value. -- @number g Minimum green value. -- @number b Minimum blue value. -- @number a Minimum alpha value. -- @treturn color Limited colour. function util.ColorLimit(color, r, g, b, a) return Color( math.min(color.r, r), math.min(color.g, g), math.min(color.b, b), math.min(color.a, a) ) end --- (CLIENT) Gets the string version of a colour. -- @color color Colour to get string from. -- @bool bExcludeAlpha Whether or not to include the alpha in the string. -- @treturn string Colour string. function util.ColorString(color, bExcludeAlpha) return color.r .. "," .. color.g .. "," .. color.b .. (!bExcludeAlpha and "," .. color.a or "") end --- (CLIENT) Draws a coloured rectangle. -- @number x X coordinate. -- @number y Y coordinate. -- @number w Width of rectangle. -- @number h Height of rectangle. -- @color col Colour of rectangle. function draw.SimpleRect(x, y, w, h, col) surface.SetDrawColor(col) surface.DrawRect(x, y, w, h) end --- (CLIENT) Draws a coloured rectangle outline. -- @number x X coordinate. -- @number y Y coordinate. -- @number w Width of rectangle. -- @number h Height of rectangle. -- @color col Colour of rectangle. function draw.SimpleOutlined(x, y, w, h, col) surface.SetDrawColor(col) surface.DrawOutlinedRect(x, y, w, h) end --- (CLIENT) Draws two coloured rectangle outlines. -- @number x X coordinate. -- @number y Y coordinate. -- @number w Width of rectangle. -- @number h Height of rectangle. -- @color col Colour of rectangle. function draw.DoubleOutlined(x, y, w, h, col) surface.SetDrawColor(col) surface.DrawOutlinedRect(x, y, w, h) surface.DrawOutlinedRect(x +1, y +1, w -2, h -2) end --- (CLIENT) Draws a coloured rectangle with a material. -- @number x X coordinate. -- @number y Y coordinate. -- @number w Width of rectangle. -- @number h Height of rectangle. -- @color color Colour of rectangle. -- @material material Material to draw with. function draw.Material(x, y, w, h, color, material) surface.SetDrawColor(color) surface.SetMaterial(material) surface.DrawTexturedRect(x, y, w, h) end --- (CLIENT) Draws a coloured rotated rectangle with a material. -- @number x X coordinate. -- @number y Y coordinate. -- @number w Width of rectangle. -- @number h Height of rectangle. -- @color color Colour of rectangle. -- @material material Material to draw with. -- @number rotated Amount to rotate rectangle by. function draw.MaterialRotated(x, y, w, h, color, material, rotated) surface.SetDrawColor(color) surface.SetMaterial(material) surface.DrawTexturedRectRotated(x, y, w, h, rotated) end --- (CLIENT) Draws a coloured rectangle with a texture. -- @number x X coordinate. -- @number y Y coordinate. -- @number w Width of rectangle. -- @number h Height of rectangle. -- @color color Colour of rectangle. -- @texture texture Texture to draw with. function draw.Texture(x, y, w, h, color, texture) surface.SetDrawColor(color) surface.SetTexture(texture) surface.DrawTexturedRect(x, y, w, h) end --- (CLIENT) Draws a coloured rotated rectangle with a texture. -- @number x X coordinate. -- @number y Y coordinate. -- @number w Width of rectangle. -- @number h Height of rectangle. -- @color color Colour of rectangle. -- @texture texture Texture to draw with. -- @number rotated Amount to rotate rectangle by. function draw.TextureRotated(x, y, w, h, color, texture, rotated) surface.SetDrawColor(color) surface.SetTexture(texture) surface.DrawTexturedRectRotated(x, y, w, h, rotated) end --- (CLIENT) Draws text with an outline. -- @string text Text to draw. -- @string font Font to use. -- @number x X coordinate. -- @number y Y coordinate. -- @color col Colour of text. -- @color colOutline Colour of outline. -- @number xAlign Horizontal alignment. -- @number yAlign Vertical alignment. -- @number outline Outline width. function draw.SimpleTextOutline(text, font, x, y, col, colOutline, xAlign, yAlign, outline) draw.SimpleText(text, font, x +(outline or 1), y +(outline or 1), colOutline, xAlign, yAlign) draw.SimpleText(text, font, x, y, col, xAlign, yAlign) end --- (CLIENT) Gets the size of text with a given font. -- @string font Font to use. -- @string text Text to use. -- @treturn number Width of text. -- @treturn number Height of text. function util.GetTextSize(font, text) surface.SetFont(font) local w, h = surface.GetTextSize(text) return w, h end --- (CLIENT) Creates a parented label. -- @panel parent Panel to parent to. -- @string title Text for the label. -- @color col Colour of text. -- @string font Font to use. -- @number x X coordinate. -- @number y Y coordinate. -- @treturn panel Created label. function util.simpleLabel(parent, title, col, font, x, y) local DLabel = nil if (parent != nil) then DLabel = vgui.Create("DLabel", parent) else DLabel = vgui.Create("DLabel") end if (x and y) then DLabel:SetPos(x, y) end if (col) then DLabel:SetColor(col) end DLabel:SetText(title) DLabel:SetFont(font) DLabel:SizeToContents() return DLabel end; --- (CLIENT) Creates a parented button. -- @panel parent Panel to parent to. -- @number x X coordinate. -- @number y Y coordinate. -- @number w Width of button. -- @number h Height of button. -- @string text Text for the button. -- @bool bDisabled Whether or not the button is disabled. -- @func func Callback to use when button is clicked. -- @treturn panel Created button. function util.simpleButton(parent, x, y, w, h, text, bDisabled, func) local DButton = nil if (parent != nil) then DButton = vgui.Create("DButton", parent) else DButton = vgui.Create("DButton") end DButton:SetText(text) if (w and h) then DButton:SetSize(w, h) end if (x and y) then DButton:SetPos(x, y) end DButton:SetDisabled(bDisabled) DButton.DoClick = func return DButton end --- (CLIENT) Creates a visual request for an input string. -- @string title Title of request. -- @param ... Options to use. -- @usage util.CreateStringRequest("Please enter a string!", function(text) print(text) end, "Okay", function(text) end, "Cancel"); function util.CreateStringRequest(title, ...) local arguments = {...} if (#arguments == 0) then return; end; local buttons = {}; local dialog = vgui.Create("tiger.panel"); dialog:SetTitle(title, false); dialog:SetSize(350, 150); dialog:Center(); dialog:MakePopup(); dialog:DockPadding(24, 24, 24, 48); local textEntry = dialog:Add("DTextEntry"); textEntry:SetTall(20); textEntry:SetSkin("serverguard"); textEntry:Dock(TOP); for k, v in ipairs(arguments) do if (isfunction(v)) then local arg = arguments[k + 1] if (!arg or not isstring(arg)) then continue; end; local button = dialog:Add("tiger.button"); button:SetText(arg); button:SizeToContents(); function button:DoClick() v(textEntry:GetValue()); dialog:Remove(); end; table.insert(buttons, button); end; end; function dialog:PerformLayout(width, height) local button = buttons[1] if (button) then button:SetPos(width - (button:GetWide() + 24), height - (button:GetTall() + 14)); for i = 2, #buttons do buttons[i]:SetPos(0, height - (buttons[i]:GetTall() + 14)); buttons[i]:MoveLeftOf(button, 14); end; end; end; end; --- (CLIENT) Creates a dialog. Prefix option strings with "&" to make the button text bold. -- @string title Title of dialog. -- @string text Text for dialog. -- @param ... Options to use. -- @usage util.CreateDialog("Test", "This is simply a test dialog.", function() print("Clicked okay!") end, "&Okay", function() end, "Cancel"); function util.CreateDialog(title, text, ...) local arguments = {...} local dialog = vgui.Create("tiger.dialog") dialog:SetTitle(title) dialog:SetText(text) for k, v in ipairs(arguments) do if (isfunction(v)) then local arg = arguments[k + 1] if (!arg or not isstring(arg)) then continue; end; local buttonText = arguments[k + 1] dialog:AddButton(buttonText, function() v() dialog:FadeOut(0.5, function() dialog:Remove() end) end) end end dialog:SizeToContents() dialog:Center() dialog:MakePopup() dialog:FadeIn() return dialog end --- (CLIENT) Creates multiple derma controls. -- @param object[opt] parent The parent control. -- @param ... The controls to create. -- @usage local label, button = util.CreateControls(parent, "DLabel", "tiger.button"); -- @usage local panel, label, button = util.CreateControls("tiger.panel", "DLabel", "tiger.button"); function util.CreateControls(parent, ...) local controls = {}; local bCreatedParent = false; if (isstring(parent)) then parent = vgui.Create(parent); bCreatedParent = true; end; for k, v in ipairs({...}) do table.insert(controls, vgui.Create(v, parent)); end; if (bCreatedParent) then return parent, unpack(controls); else return unpack(controls); end; end; -- Author: Wizard of Ass -- http://www.facepunch.com/threads/1089200?p=32614030&viewfull=1#post32614030 local DrawText = surface.DrawText local SetTextPos = surface.SetTextPos local PopModelMatrix = cam.PopModelMatrix local PushModelMatrix = cam.PushModelMatrix local matrix = Matrix() local matrixAngle = Angle(0, 0, 0) local matrixScale = Vector(0, 0, 0) local matrixTranslation = Vector(0, 0, 0) --- (CLIENT) Draws rotated text. -- @string text Text to draw. -- @number x X coordinate. -- @number y Y coordinate. -- @number xScale Horizontal scale of text. -- @number yScale Vertical scale of text. -- @number angle Angle to rotate text by. function draw.TextRotated(text, x, y, xScale, yScale, angle) matrixAngle.y = angle matrix:SetAngle(matrixAngle) matrixTranslation.x = x matrixTranslation.y = y matrix:SetTranslation(matrixTranslation) matrixScale.x = xScale matrixScale.y = yScale matrix:Scale(matrixScale) SetTextPos(0, 0) PushModelMatrix(matrix) DrawText(text) PopModelMatrix() end --- (CLIENT) Draws a circle. -- @number x X coordinate. -- @number y Y coordinate. -- @number radius Radius of circle. -- @number segments Number of line segments to use. function draw.Circle(x, y, radius, segments) local points = {}; table.insert(points, { x = x, y = y, u = 0.5, v = 0.5 }); for i = 0, segments do local angle = math.rad((i / segments) * -360); table.insert(points, { x = x + math.sin(angle) * radius, y = y + math.cos(angle) * radius, u = math.sin(angle) / 2 + 0.5, v = math.cos(angle) / 2 + 0.5 }); end; local angle = math.rad(0); table.insert(points, { x = x + math.sin(angle) * radius, y = y + math.cos(angle) * radius, u = math.sin(angle) / 2 + 0.5, v = math.cos(angle) / 2 + 0.5 }); surface.DrawPoly(points); end; end;