├── LICENSE ├── README.md └── lua ├── autorun └── gfconsole_autorun.lua └── gfconsole ├── config.lua ├── core ├── cl_convars.lua ├── cl_core.lua ├── cl_fonts.lua ├── cl_hooks.lua ├── cl_net.lua ├── derma │ ├── cl_button.lua │ ├── cl_checkbox.lua │ ├── cl_console.lua │ ├── cl_container.lua │ ├── cl_header.lua │ └── cl_selector.lua ├── sv_core.lua └── sv_net.lua ├── extensions ├── bot.lua ├── errors.lua ├── execute.lua ├── relay.lua ├── retry.lua ├── subscriptions.lua └── utility.lua ├── init.lua └── libraries ├── cl_buttons.lua ├── cl_message.lua ├── sh_extension.lua ├── sh_filter.lua ├── sh_load.lua ├── sv_message.lua ├── sv_subscriptions.lua └── thirdparty └── sh_pon.lua /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Aleksandrs Filipovskis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gfconsole 2 | ![Test](https://img.shields.io/github/license/tochnonement/gfconsole) 3 | 4 | A **user-friendly** developer console for **Garry's Mod**. 5 | Makes possible to relay **messages** and **errors** from a **server's console** to developers in-game, so they could develop without need of interrupting to check a server's control panel. 6 | 7 | ### 💡 Features 8 | - All messages that come to the original game console and to the server console are relaying to this console 9 | - Errors are relaying to the console 10 | - Realm switching (client/server/both) 11 | - Subscription system 12 | - Filter system 13 | - Average ping and framerate indicators 14 | - Optimized message delivery 15 | - Frame customization (size, position, font and etc.) 16 | - Vectors and Angles being parsed in the console, so you could easily copy them 17 | - Simple to use 18 | 19 | ### 🔨 Setup 20 | 1. Download [the latest release](https://github.com/tochnonement/gfconsole/releases) from the releases section. 21 | 2. Extract the file. 22 | 3. Place `gfconsole` folder into your server's `garrysmod/addons` folder. 23 | 4. Restart your server. 24 | 5. You're done. 25 | 26 | #### (Optional) If you want error relaying 27 | 1. Download the [gm_luaerror](https://github.com/danielga/gm_luaerror) module for your server's OS from the releases section (`gmsv_luaerror_win32.dll` for example). 28 | 2. Place the downloaded dll into your server's `garrysmod/lua/bin` folder (if there is no folder just create one). 29 | 3. Be sure that `errors` extension is enabled in config. 30 | 4. You're done. 31 | 32 | ### 🤔 How To Use 33 | **NOTE:** 34 | You can configure console up to your preferences, just type `gfconsole_settings` in the game console. 35 | 36 | 1. Bind any button to `+gfconsole`. 37 | 2. Hold the button to interact with the console. 38 | 3. Press the "Subscribe" button at the console's top. 39 | 40 | ### 👀 Showcase 41 | ![Console](https://i.imgur.com/dQ3aYu3.png) 42 | ![Settings](https://i.imgur.com/fDgJ20H.png) 43 | 44 | ### 🔗 Credits 45 | - [thelastpenguin](https://github.com/thelastpenguin) - pON -------------------------------------------------------------------------------- /lua/autorun/gfconsole_autorun.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 11.03.2021 7 | 8 | --]] 9 | 10 | gfconsole = { 11 | __VERSION = "1.4.4" 12 | } 13 | 14 | AddCSLuaFile("gfconsole/libraries/sh_load.lua") 15 | AddCSLuaFile("gfconsole/init.lua") 16 | 17 | include("gfconsole/libraries/sh_load.lua") 18 | include("gfconsole/init.lua") -------------------------------------------------------------------------------- /lua/gfconsole/config.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 13.03.2021 7 | 8 | --]] 9 | 10 | gfconsole.config = {} 11 | 12 | local config = gfconsole.config 13 | 14 | -- The list of extensions, which will be enabled on start 15 | config.enabled = { 16 | ["errors"] = true, 17 | ["execute"] = true, 18 | ["relay"] = true, 19 | ["subscriptions"] = true, 20 | ["utility"] = true, 21 | ["bot"] = true, 22 | ["retry"] = false 23 | } 24 | 25 | -- The list of colors 26 | config.colors = { 27 | green = Color(39, 174, 96), 28 | blue = Color(52, 152, 219), 29 | watermelon = Color(255, 107, 129), 30 | coral = Color(255, 127, 80), 31 | sky = Color(83, 82, 237), 32 | amethyst = Color(155, 89, 182), 33 | turqoise = Color(26, 188, 156) 34 | } 35 | 36 | -- The list of available fonts 37 | config.fonts = { 38 | "Roboto", 39 | "Roboto Condensed", 40 | "Arial", 41 | "Tahoma", 42 | "Courier New" 43 | } 44 | 45 | -- The accent color 46 | config.color = Color(39, 174, 96) 47 | 48 | -- (DANGEROUS) The customcheck function for access to execute commands 49 | config.can_execute = function(ply) 50 | return ply:IsSuperAdmin() 51 | end 52 | 53 | -- The customcheck function for access to be a subscriber 54 | -- If "subscriptions" extension is disabled it will determine whether player can receive messages or not 55 | config.can_subscribe = function(ply) 56 | return ply:IsSuperAdmin() 57 | end -------------------------------------------------------------------------------- /lua/gfconsole/core/cl_convars.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | CreateClientConVar("cl_gfconsole_realm", "both", true, false) 11 | CreateClientConVar("cl_gfconsole_auto_subscribe", "0", true, false) 12 | CreateClientConVar("cl_gfconsole_auto_open", "0", true, false) 13 | CreateClientConVar("cl_gfconsole_timestamps", "0", true, false) 14 | CreateClientConVar("cl_gfconsole_hidetoolbar", "0", true, false) 15 | local cvColor = CreateClientConVar("cl_gfconsole_color", "green", true, false) 16 | 17 | cvars.AddChangeCallback("cl_gfconsole_hidetoolbar", function() 18 | local frame = gfconsole.frame 19 | if IsValid(frame) then 20 | frame.panel:UpdateToolBarVisibility() 21 | end 22 | end) 23 | 24 | do 25 | local function updateColor() 26 | local color = gfconsole.config.colors[cvColor:GetString()] 27 | if color then 28 | gfconsole.config.color = color 29 | 30 | local frame = gfconsole.frame 31 | if IsValid(frame) then 32 | frame.header.lblVersion:SetTextColor(color) 33 | frame.header.lblFps:SetTextColor(color) 34 | frame.header.lblPing:SetTextColor(color) 35 | end 36 | end 37 | end 38 | 39 | cvars.AddChangeCallback("cl_gfconsole_color", updateColor) 40 | updateColor() 41 | end -------------------------------------------------------------------------------- /lua/gfconsole/core/cl_core.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | function gfconsole.show() 11 | if IsValid(gfconsole.frame) then 12 | return 13 | end 14 | 15 | gfconsole.frame = vgui.Create("GFConsole") 16 | end 17 | 18 | function gfconsole.close() 19 | local frame = gfconsole.frame 20 | 21 | if IsValid(frame) then 22 | frame:Remove() 23 | end 24 | end 25 | concommand.Add("gfconsole_close", gfconsole.close) 26 | 27 | function gfconsole.reload_frame() 28 | local frame = gfconsole.frame 29 | 30 | if IsValid(frame) then 31 | frame:Remove() 32 | end 33 | 34 | gfconsole.show() 35 | end 36 | concommand.Add("gfconsole_reload", gfconsole.reload_frame) 37 | 38 | local function toggle(_, cmd) 39 | local enable = (cmd == "+gfconsole") 40 | 41 | gfconsole.holding = enable 42 | 43 | if enable then 44 | gfconsole.show() 45 | end 46 | 47 | gui.EnableScreenClicker(enable) 48 | end 49 | concommand.Add("+gfconsole", toggle) 50 | concommand.Add("-gfconsole", toggle) 51 | 52 | do 53 | local function title(list, text) 54 | local label = list:Add('DLabel') 55 | label:SetText(text:upper()) 56 | label:SetFont('gfconsole.Title') 57 | label:SetTextColor(color_white) 58 | label:SetExpensiveShadow(2, color_black) 59 | label:SetContentAlignment(5) 60 | label:Dock(TOP) 61 | label:DockMargin(0, 0, 0, ScreenScale(2)) 62 | 63 | return label 64 | end 65 | 66 | local function checkbox(list, text, cvar) 67 | local label = list:Add('DCheckBoxLabel') 68 | label:SetText(text) 69 | label:SetFont('gfconsole.Button') 70 | label:Dock(TOP) 71 | label:DockMargin(0, 0, 0, ScreenScale(2)) 72 | label:SetConVar(cvar:GetName()) 73 | 74 | return label 75 | end 76 | 77 | local function button(list, text, fn) 78 | local btn = list:Add('DButton') 79 | btn:SetText(text) 80 | btn:Dock(TOP) 81 | btn:DockMargin(0, 0, 0, ScreenScale(2)) 82 | btn.DoClick = function() 83 | fn() 84 | end 85 | 86 | return btn 87 | end 88 | 89 | local function credit(plist, data) 90 | local pnl = plist:Add('DButton') 91 | pnl:SetTall(ScreenScale(14.5)) 92 | pnl:SetText('') 93 | pnl:Dock(TOP) 94 | pnl:DockMargin(0, 0, 0, ScreenScale(2)) 95 | pnl.Paint = function(p, w, h) 96 | surface.SetDrawColor(0, 0, 0, p:IsHovered() and 200 or 100) 97 | surface.DrawRect(0, 0, w, h) 98 | end 99 | if data.url then 100 | pnl.DoClick = function(p) 101 | gui.OpenURL(data.url) 102 | end 103 | else 104 | pnl:SetMouseInputEnabled(false) 105 | end 106 | 107 | local avatar = pnl:Add('AvatarImage') 108 | avatar:SetSteamID(data.s64, 64) 109 | avatar:DockMargin(2, 2, 5, 2) 110 | avatar:SetWide(pnl:GetTall() - 4) 111 | avatar:Dock(LEFT) 112 | 113 | local lblName = pnl:Add('DLabel') 114 | lblName:SetText(data.name) 115 | lblName:SetFont('gfconsole.Title') 116 | lblName:SetExpensiveShadow(1, color_black) 117 | lblName:Dock(FILL) 118 | if data.rainbow then 119 | lblName.Think = function(p) 120 | p:SetTextColor(HSVToColor((CurTime() * 16) % 360, 1, 1)) 121 | end 122 | else 123 | lblName:SetTextColor(data.color or color_white) 124 | end 125 | 126 | local lblDesc = pnl:Add('DLabel') 127 | lblDesc:SetText(data.desc) 128 | lblDesc:SetFont('gfconsole.Tiny') 129 | lblDesc:SetExpensiveShadow(1, color_black) 130 | lblDesc:Dock(BOTTOM) 131 | end 132 | 133 | function gfconsole.show_settings() 134 | local frame = vgui.Create('DFrame') 135 | frame:SetTitle('GFConsole Settings') 136 | frame:SetSize(ScrW() * .25, ScrH() * .5) 137 | frame:SetIcon('icon16/cog.png') 138 | frame:Center() 139 | frame:MakePopup() 140 | 141 | local plist = frame:Add('DScrollPanel') 142 | plist:Dock(FILL) 143 | 144 | title(plist, 'Filters') 145 | 146 | for _, filter in ipairs(gfconsole.filter.get_all()) do 147 | checkbox(plist, 'Show: ' .. filter.id, filter.cv) 148 | end 149 | 150 | title(plist, 'Frame') 151 | checkbox(plist, 'Automatically create console on join', GetConVar('cl_gfconsole_auto_open')) 152 | checkbox(plist, 'Automatically subscribe on join', GetConVar('cl_gfconsole_auto_subscribe')) 153 | checkbox(plist, 'Display timestamps', GetConVar('cl_gfconsole_timestamps')) 154 | checkbox(plist, 'Hide toolbar when isn\'t active', GetConVar('cl_gfconsole_hidetoolbar')) 155 | do 156 | local cv = GetConVar('cl_gfconsole_color') 157 | local found = false 158 | 159 | local combo = plist:Add('DComboBox') 160 | combo:Dock(TOP) 161 | combo:DockMargin(0, 0, 0, ScreenScale(2)) 162 | 163 | for name in pairs(gfconsole.config.colors) do 164 | local index = combo:AddChoice(name) 165 | if cv:GetString() == name then 166 | combo:ChooseOptionID(index) 167 | found = true 168 | end 169 | end 170 | 171 | combo.OnSelect = function(p, index, value) 172 | cv:SetString(value) 173 | end 174 | 175 | if not found then 176 | combo:SetText(cv:GetString()) 177 | end 178 | end 179 | 180 | title(plist, 'Font') 181 | do 182 | local cv = GetConVar('cl_gfconsole_font_family') 183 | local found = false 184 | 185 | local combo = plist:Add('DComboBox') 186 | combo:Dock(TOP) 187 | combo:DockMargin(0, 0, 0, ScreenScale(2)) 188 | 189 | for _, family in ipairs(gfconsole.config.fonts) do 190 | local index = combo:AddChoice(family) 191 | if cv:GetString() == family then 192 | combo:ChooseOptionID(index) 193 | found = true 194 | end 195 | end 196 | 197 | combo.OnSelect = function(p, index, value) 198 | cv:SetString(value) 199 | end 200 | 201 | if not found then 202 | combo:SetText(cv:GetString()) 203 | end 204 | 205 | local slider = plist:Add('DNumSlider') 206 | slider:SetText('Size') 207 | slider:SetDecimals(0) 208 | slider:SetMin(14) 209 | slider:SetMax(64) 210 | slider:SetConVar('cl_gfconsole_font_size') 211 | slider:Dock(TOP) 212 | slider:DockMargin(0, 0, 0, ScreenScale(2)) 213 | 214 | checkbox(plist, 'Shadow', GetConVar('cl_gfconsole_font_shadow')) 215 | end 216 | 217 | title(plist, 'Actions') 218 | button(plist, 'Reload Console', function() 219 | gfconsole.reload_frame() 220 | end) 221 | button(plist, 'Close Console', function() 222 | gfconsole.close() 223 | end) 224 | button(plist, 'Check GitHub', function() 225 | gui.OpenURL('https://github.com/tochnonement/gfconsole') 226 | end) 227 | 228 | title(plist, 'Credits') 229 | credit(plist, { 230 | name = 'tochnonement', 231 | s64 = '76561198086200873', 232 | desc = 'Author (contact me if you\'d need any help)', 233 | url = 'https://steamcommunity.com/id/tochnonement/', 234 | rainbow = true 235 | }) 236 | credit(plist, { 237 | name = 'aStonedPenguin', 238 | s64 = '76561198042764635', 239 | desc = 'pON library' 240 | }) 241 | 242 | return frame 243 | end 244 | concommand.Add('gfconsole_settings', gfconsole.show_settings) 245 | end -------------------------------------------------------------------------------- /lua/gfconsole/core/cl_fonts.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | local function scale(int) 11 | return math.ceil(int / 900 * ScrH()) 12 | end 13 | 14 | local function initFonts() 15 | surface.CreateFont("gfconsole.Title", { 16 | font = "Roboto Bold", 17 | size = scale(18), 18 | extended = true 19 | }) 20 | 21 | surface.CreateFont("gfconsole.Button", { 22 | font = "Roboto", 23 | size = scale(16), 24 | extended = true 25 | }) 26 | 27 | surface.CreateFont("gfconsole.Tiny", { 28 | font = "Roboto", 29 | size = scale(14), 30 | extended = true 31 | }) 32 | end 33 | initFonts() 34 | 35 | local buildFont do 36 | local cvFontFamily = CreateClientConVar("cl_gfconsole_font_family", "Roboto") 37 | local cvFontSize = CreateClientConVar("cl_gfconsole_font_size", "16") 38 | local cvFontShadow = CreateClientConVar("cl_gfconsole_font_shadow", "1") 39 | 40 | function buildFont() 41 | local family = cvFontFamily:GetString() 42 | local size = scale(cvFontSize:GetInt()) 43 | local shadow = cvFontShadow:GetBool() 44 | 45 | surface.CreateFont("gfconsole.Text", { 46 | font = family, 47 | size = size, 48 | shadow = shadow, 49 | extended = true 50 | }) 51 | 52 | surface.CreateFont("gfconsole.Text.underline", { 53 | font = family, 54 | size = size, 55 | shadow = shadow, 56 | extended = true, 57 | italic = true 58 | }) 59 | end 60 | 61 | buildFont() 62 | cvars.AddChangeCallback("cl_gfconsole_font_family", buildFont) 63 | cvars.AddChangeCallback("cl_gfconsole_font_size", buildFont) 64 | cvars.AddChangeCallback("cl_gfconsole_font_shadow", buildFont) 65 | end 66 | 67 | hook.Add("OnScreenSizeChanged", "gfconsole.UpdateFonts", function() 68 | initFonts() 69 | buildFont() 70 | end) -------------------------------------------------------------------------------- /lua/gfconsole/core/cl_hooks.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochnonement 4 | Email: tochnonement@gmail.com 5 | 6 | 14/11/2021 7 | 8 | --]] 9 | 10 | hook.Add("InitPostEntity", "gfconsole.AutoSubscribe", function() 11 | if GetConVar("cl_gfconsole_auto_open"):GetBool() then 12 | gfconsole.show() 13 | end 14 | 15 | if GetConVar("cl_gfconsole_auto_subscribe"):GetBool() then 16 | net.Start("gfconsole:Subscribe") 17 | net.WriteBool(true) 18 | net.SendToServer() 19 | end 20 | end) 21 | 22 | hook.Add("OnScreenSizeChanged", "gfconsole.Adapt", function() 23 | local frame = gfconsole.frame 24 | if IsValid(frame) then 25 | frame:LoadCookies() 26 | end 27 | end) -------------------------------------------------------------------------------- /lua/gfconsole/core/cl_net.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochnonement 4 | Email: tochnonement@gmail.com 5 | 6 | 14/11/2021 7 | 8 | --]] 9 | 10 | -- Windows notifications 11 | do 12 | local sys_arch = string.sub(jit.arch, 2):gsub("86", "32") 13 | local dll_name = "gm" .. (CLIENT and "cl" or "sv") .. "_win_toast_" .. (system.IsWindows() and ("win" .. sys_arch) or system.IsLinux() and ("linux" .. sys_arch) or "osx") .. ".dll" 14 | local dll_exists = file.Exists("lua/bin/" .. dll_name, "MOD") 15 | 16 | if dll_exists then 17 | require("win_toast") 18 | 19 | net.Receive("gfconsole:SendWinNotify", function() 20 | WinToast.Show("GFConsole", net.ReadString()) 21 | end) 22 | else 23 | net.Receive("gfconsole:SendWinNotify", function() end) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lua/gfconsole/core/derma/cl_button.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | PANEL = {} 11 | 12 | function PANEL:Init() 13 | self:SetTextColor(color_white) 14 | self:SetFont("gfconsole.Button") 15 | self:SetExpensiveShadow(1, color_black) 16 | end 17 | 18 | function PANEL:Paint(w, h) 19 | if self:IsHovered() then 20 | surface.SetDrawColor(114, 123, 139) 21 | else 22 | surface.SetDrawColor(78, 84, 96) 23 | end 24 | surface.DrawRect(0, 0, w, h) 25 | 26 | surface.SetDrawColor(0, 0, 0, 230) 27 | surface.DrawOutlinedRect(0, 0, w, h, 1) 28 | end 29 | 30 | vgui.Register("gfconsole.Button", PANEL, "DButton") -------------------------------------------------------------------------------- /lua/gfconsole/core/derma/cl_checkbox.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | PANEL = {} 11 | 12 | AccessorFunc(PANEL, "value", "Value") 13 | 14 | function PANEL:Init() 15 | self:SetFont("gfconsole.Button") 16 | self:SetTextColor(color_white) 17 | self:SetValue(false) 18 | self:SetExpensiveShadow(1, color_black) 19 | end 20 | 21 | function PANEL:Paint(w, h) 22 | if self:IsHovered() then 23 | surface.SetDrawColor(114, 123, 139) 24 | else 25 | surface.SetDrawColor(78, 84, 96) 26 | end 27 | 28 | surface.DrawRect(0, 0, w, h) 29 | 30 | if self.value then 31 | surface.SetDrawColor(gfconsole.config.color) 32 | surface.DrawRect(0, h - 2, w, 2) 33 | end 34 | 35 | surface.SetDrawColor(0, 0, 0, 230) 36 | surface.DrawOutlinedRect(0, 0, w, h, 1) 37 | end 38 | 39 | function PANEL:DoClick() 40 | self:Toggle() 41 | end 42 | 43 | function PANEL:Toggle() 44 | local new = not self.value 45 | 46 | self.value = new 47 | 48 | if (self.OnChange) then 49 | self:OnChange(new) 50 | end 51 | end 52 | 53 | vgui.Register("GFConsole.Checkbox", PANEL, "DButton") -------------------------------------------------------------------------------- /lua/gfconsole/core/derma/cl_console.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 11.03.2021 7 | 8 | --]] 9 | 10 | PANEL = {} 11 | 12 | function PANEL:Init() 13 | self.header = self:Add("GFConsole.Header") 14 | 15 | self.panel = self:Add("GFConsole.Container") 16 | 17 | self:SetCookieName("gfconsole") 18 | end 19 | 20 | function PANEL:PerformLayout(w, h) 21 | self:DockPadding(5, 5, 5, 5) 22 | 23 | self.header:Dock(TOP) 24 | self.header:SetTall(30) 25 | 26 | self.panel:DockMargin(0, 5, 0, 0) 27 | self.panel:Dock(FILL) 28 | end 29 | 30 | function PANEL:Paint(w, h) 31 | surface.SetDrawColor(0, 0, 0, 200) 32 | surface.DrawRect(0, 0, w, h) 33 | end 34 | 35 | function PANEL:Think() 36 | if gfconsole.holding then 37 | self:ResizeController() 38 | end 39 | 40 | local hide = hook.Call("HUDShouldDraw", GAMEMODE, "gfconsole.Hide") 41 | if hide == false then 42 | self:SetAlpha(0) 43 | else 44 | if self:GetAlpha() ~= 255 then 45 | self:SetAlpha(255) 46 | end 47 | end 48 | end 49 | 50 | function PANEL:LoadCookies() 51 | local pos = self:GetCookie("position") 52 | local size = self:GetCookie("size") 53 | local scrw, scrh = ScrW(), ScrH() 54 | 55 | if pos then 56 | pos = util.JSONToTable(pos) 57 | 58 | self:SetPos(pos.x * scrw, pos.y * scrh) 59 | end 60 | 61 | if size then 62 | size = util.JSONToTable(size) 63 | 64 | self:SetSize(size.w * scrw, size.h * scrh) 65 | else 66 | self:SetSize(ScrW(), ScrH() * .25) 67 | end 68 | end 69 | 70 | -- Custom methods 71 | 72 | function PANEL:SaveSize(w, h) 73 | self:SetCookie("size", util.TableToJSON({ 74 | w = (w / ScrW()), 75 | h = (h / ScrH()) 76 | })) 77 | end 78 | 79 | function PANEL:SavePosition(x, y) 80 | self:SetCookie("position", util.TableToJSON({ 81 | x = (x / ScrW()), 82 | y = (y / ScrH()) 83 | })) 84 | end 85 | 86 | function PANEL:Move(x, y) 87 | local x0, y0 = self:GetPos() 88 | 89 | if x <= 0 or x >= (ScrW() - self:GetWide()) then 90 | x = x0 91 | end 92 | 93 | if y <= 0 or y >= (ScrH() - self:GetTall()) then 94 | y = y0 95 | end 96 | 97 | self:SetPos(x, y) 98 | self:SavePosition(x, y) 99 | end 100 | 101 | function PANEL:ResizeController() 102 | local x, y = self:GetRelativeToCursor() 103 | local x2, y2 = input.GetCursorPos() 104 | local w, h = self:GetSize() 105 | 106 | if x > (w - 8) and y > (h - 8) then 107 | self.resizable = input.IsMouseDown(MOUSE_LEFT) 108 | self:SetCursor("sizenwse") 109 | else 110 | self:SetCursor("cursor") 111 | end 112 | 113 | if self.resizable then 114 | local scrw, scrh = ScrW(), ScrH() 115 | 116 | if x < scrw * .1 or x2 > scrw then 117 | x = w 118 | end 119 | 120 | if y < scrh * .1 or y2 > scrh then 121 | y = h 122 | end 123 | 124 | self:SetSize(x, y) 125 | self:SaveSize(x, y) 126 | end 127 | end 128 | 129 | function PANEL:GetRelativeToCursor() 130 | local x, y = self:LocalToScreen(0, 0) 131 | local x2, y2 = input.GetCursorPos() 132 | 133 | return (x2 - x), (y2 - y) 134 | end 135 | 136 | vgui.Register("GFConsole", PANEL, "EditablePanel") -------------------------------------------------------------------------------- /lua/gfconsole/core/derma/cl_container.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 11.03.2021 7 | 8 | --]] 9 | 10 | PANEL = {} 11 | 12 | function PANEL:Init() 13 | self.control = self:Add("DHorizontalScroller") 14 | self.control:SetTall(30) 15 | self.control:SetOverlap(-2) 16 | 17 | self.selector = self:Add("GFConsole.Selector") 18 | self.selector:SetWide(200) 19 | self.selector:AddConvar("cl_gfconsole_realm", { 20 | {"client", "CLIENT"}, 21 | {"server", "SERVER"}, 22 | {"both", "BOTH"} 23 | }) 24 | 25 | self.richtext = self:Add("RichText") 26 | self.richtext.PerformLayout = function(panel) 27 | panel:SetFontInternal("gfconsole.Text") 28 | panel:SetUnderlineFont("gfconsole.Text.underline") 29 | end 30 | self.richtext.ActionSignal = function(panel, name, value) 31 | if name == "TextClicked" then 32 | local for_copy = string.match(value, "%b@@") 33 | if for_copy then 34 | for_copy = for_copy:Replace("@", "") 35 | 36 | SetClipboardText(for_copy) 37 | 38 | notification.AddLegacy("Copied to clipboard!", 0, 2) 39 | end 40 | end 41 | end 42 | self.richtext.Paint = function(panel, w, h) 43 | surface.SetDrawColor(0, 0, 0, 100) 44 | surface.DrawRect(0, 0, w, h) 45 | end 46 | 47 | self.control:AddPanel(self.selector) 48 | 49 | self:AddIconButton("icon16/cog.png", gfconsole.show_settings) 50 | self:AddIconButton("icon16/page_white.png", function() 51 | self.richtext:SetText("") 52 | end) 53 | 54 | self:LoadButtons() 55 | self:LoadFilters() 56 | end 57 | 58 | function PANEL:PerformLayout(w, h) 59 | self.control:Dock(TOP) 60 | self.control:SetTall(25) 61 | self.control:DockMargin(0, 0, 0, 5) 62 | 63 | self.richtext:Dock(FILL) 64 | end 65 | 66 | function PANEL:Paint(w, h) 67 | -- surface.SetDrawColor(0, 0, 0, 100) 68 | -- surface.DrawRect(0, 0, w, h) 69 | end 70 | 71 | function PANEL:UpdateToolBarVisibility() 72 | local bool = gfconsole.holding 73 | local old = self.control:IsVisible() 74 | 75 | if not GetConVar("cl_gfconsole_hidetoolbar"):GetBool() then 76 | bool = true 77 | end 78 | 79 | self.control:SetVisible(bool) 80 | 81 | if old ~= bool then 82 | self:InvalidateLayout() 83 | end 84 | end 85 | 86 | function PANEL:Think() 87 | if not GetConVar("cl_gfconsole_hidetoolbar"):GetBool() then return end 88 | 89 | self:UpdateToolBarVisibility() 90 | end 91 | 92 | function PANEL:AddCheckbox(text, bool, func) 93 | local checkbox = vgui.Create("GFConsole.Checkbox") 94 | checkbox:SetText("Show: " .. text) 95 | checkbox:SetValue(bool) 96 | checkbox.OnChange = function(panel, bool) 97 | func(bool) 98 | end 99 | checkbox:SizeToContentsX(20) 100 | 101 | self.control:AddPanel(checkbox) 102 | 103 | return checkbox 104 | end 105 | 106 | function PANEL:AddRecord(...) 107 | for _, object in ipairs({...}) do 108 | if isstring(object) or isnumber(object) then 109 | object = tostring(object) 110 | if object:match("Vector") then 111 | self.richtext:InsertClickableTextStart("@" .. object.. "@") 112 | self.richtext:AppendText(object) 113 | self.richtext:InsertClickableTextEnd() 114 | elseif object:match("Angle") then 115 | self.richtext:InsertClickableTextStart("@" .. object.. "@") 116 | self.richtext:AppendText(object) 117 | self.richtext:InsertClickableTextEnd() 118 | else 119 | self.richtext:AppendText(object) 120 | end 121 | else 122 | self.richtext:InsertColorChange(object.r, object.g, object.b, object.a) 123 | end 124 | end 125 | end 126 | 127 | function PANEL:AddButton(name, func) 128 | local button = vgui.Create("gfconsole.Button") 129 | button:SetText(name) 130 | button:SizeToContentsX(20) 131 | button.DoClick = function() 132 | func() 133 | end 134 | 135 | self.control:AddPanel(button) 136 | end 137 | 138 | function PANEL:AddIconButton(matPath, func) 139 | local mat = Material(matPath) 140 | local button = vgui.Create("gfconsole.Button") 141 | button:SetWide(self.control:GetTall()) 142 | button:SetText("") 143 | button.DoClick = function() 144 | func() 145 | end 146 | button.PaintOver = function(p, w, h) 147 | -- local iw, ih = h * .5, h * .5 148 | local iw, ih = 16, 16 149 | local ix, iy = w * .5 - iw * .5, h * .5 - ih * .5 150 | 151 | surface.SetMaterial(mat) 152 | surface.SetDrawColor(color_black) 153 | surface.DrawTexturedRect(ix, iy + 1, iw, ih) 154 | surface.SetDrawColor(color_white) 155 | surface.DrawTexturedRect(ix, iy, iw, ih) 156 | end 157 | 158 | self.control:AddPanel(button) 159 | end 160 | 161 | function PANEL:LoadButtons() 162 | for _, data in pairs(gfconsole.buttons.get()) do 163 | self:AddButton(data.name, data.func) 164 | end 165 | end 166 | 167 | function PANEL:LoadFilters() 168 | for _, data in ipairs(gfconsole.filter.get_all()) do 169 | local check = self:AddCheckbox(data.id, data.cv:GetBool(), function() 170 | gfconsole.filter.toggle(data.id) 171 | end) 172 | 173 | check.cv = data.cv 174 | end 175 | end 176 | 177 | vgui.Register("GFConsole.Container", PANEL) 178 | -------------------------------------------------------------------------------- /lua/gfconsole/core/derma/cl_header.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 11.03.2021 7 | 8 | --]] 9 | 10 | local function get_average(tbl) 11 | local count = #tbl 12 | local sum = 0 13 | for i = 1, count do 14 | sum = sum + tbl[i] 15 | end 16 | return sum / count 17 | end 18 | 19 | PANEL = {} 20 | 21 | function PANEL:Init() 22 | self.label = self:Add("DLabel") 23 | self.label:SetText("GFConsole") 24 | self.label:SetFont("gfconsole.Title") 25 | self.label:SetTextColor(color_white) 26 | self.label:SizeToContents() 27 | 28 | self.lblVersion = self:Add("DLabel") 29 | self.lblVersion:SetText(gfconsole.__VERSION) 30 | self.lblVersion:SetFont("gfconsole.Tiny") 31 | self.lblVersion:SetTextColor(gfconsole.config.color) 32 | self.lblVersion:SizeToContents() 33 | self.lblVersion:SetContentAlignment(7) 34 | 35 | self.fps = self:Add("DLabel") 36 | self.fps:SetFont("gfconsole.Tiny") 37 | 38 | self.lblFps = self:Add("DLabel") 39 | self.lblFps:SetFont("gfconsole.Tiny") 40 | self.lblFps:SetText("AVG Framerate: ") 41 | self.lblFps:SetTextColor(gfconsole.config.color) 42 | self.lblFps:SizeToContents() 43 | 44 | self.ping = self:Add("DLabel") 45 | self.ping:SetFont("gfconsole.Tiny") 46 | 47 | self.lblPing = self:Add("DLabel") 48 | self.lblPing:SetFont("gfconsole.Tiny") 49 | self.lblPing:SetText("AVG Ping: ") 50 | self.lblPing:SetTextColor(gfconsole.config.color) 51 | self.lblPing:SizeToContents() 52 | 53 | self:SetCursor("sizeall") 54 | 55 | self:ResetCollected() 56 | end 57 | 58 | function PANEL:PerformLayout(w, h) 59 | self.label:Dock(LEFT) 60 | self.label:DockMargin(5, 0, 0, 0) 61 | 62 | self.lblVersion:Dock(LEFT) 63 | self.lblVersion:DockMargin(5, 5, 0, 0) 64 | 65 | self.lblFps:Dock(RIGHT) 66 | self.fps:Dock(RIGHT) 67 | self.fps:DockMargin(0, 0, 5, 0) 68 | 69 | self.lblPing:Dock(RIGHT) 70 | self.ping:Dock(RIGHT) 71 | self.ping:DockMargin(0, 0, 5, 0) 72 | end 73 | 74 | function PANEL:Paint(w, h) 75 | surface.SetDrawColor(0, 0, 0, 200) 76 | surface.DrawRect(0, 0, w, h) 77 | end 78 | 79 | function PANEL:OnMousePressed(code) 80 | if code == MOUSE_LEFT then 81 | self:OnClick() 82 | end 83 | end 84 | 85 | function PANEL:OnMouseReleased(code) 86 | if code == MOUSE_LEFT then 87 | self:OnRelease() 88 | end 89 | end 90 | 91 | do 92 | local CurTime = CurTime 93 | local Round = math.Round 94 | 95 | function PANEL:Think() 96 | local curtime = CurTime() 97 | 98 | self:UpdateCollected() 99 | 100 | if (self.next_update or 0) <= curtime then 101 | self.fps:SetText(Round(get_average(self.collected.fps))) 102 | self.fps:SizeToContentsX() 103 | 104 | self.ping:SetText(Round(get_average(self.collected.ping)) .. "ms") 105 | self.ping:SizeToContentsX() 106 | 107 | self:ResetCollected() 108 | 109 | self.next_update = curtime + .8 110 | end 111 | 112 | if gfconsole.holding then 113 | self:MoveController() 114 | end 115 | end 116 | end 117 | 118 | -- Custom methods 119 | 120 | function PANEL:OnClick() 121 | self:CatchOffset() 122 | self:MouseCapture(true) 123 | self.moving = true 124 | end 125 | 126 | function PANEL:OnRelease() 127 | self:MouseCapture(false) 128 | self.moving = false 129 | end 130 | 131 | function PANEL:MoveController() 132 | if self.moving then 133 | local x, y = input.GetCursorPos() 134 | local cx, cy = self:GetOffset() 135 | 136 | self:GetParent():Move(x - cx, y - cy) 137 | end 138 | end 139 | 140 | function PANEL:CatchOffset() 141 | local x, y = self:GetRelativeToCursor() 142 | 143 | self.offset = { 144 | x = x, 145 | y = y 146 | } 147 | end 148 | 149 | function PANEL:GetOffset() 150 | local offset = self.offset 151 | 152 | if offset then 153 | return offset.x, offset.y 154 | else 155 | return 0, 0 156 | end 157 | end 158 | 159 | function PANEL:GetRelativeToCursor() 160 | local x, y = self:LocalToScreen(0, 0) 161 | local x2, y2 = input.GetCursorPos() 162 | 163 | return (x2 - x), (y2 - y) 164 | end 165 | 166 | function PANEL:UpdateCollected() 167 | table.insert(self.collected.fps, (1 / FrameTime())) 168 | table.insert(self.collected.ping, LocalPlayer():Ping()) 169 | end 170 | 171 | function PANEL:ResetCollected() 172 | self.collected = { 173 | fps = {}, 174 | ping = {} 175 | } 176 | end 177 | 178 | vgui.Register("GFConsole.Header", PANEL) -------------------------------------------------------------------------------- /lua/gfconsole/core/derma/cl_selector.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | PANEL = {} 11 | 12 | function PANEL:PerformLayout(w, h) 13 | local children = self:GetChildren() 14 | local wide = w / #children 15 | 16 | for _, panel in ipairs(children) do 17 | panel:SetWide(wide) 18 | end 19 | end 20 | 21 | function PANEL:PaintOver(w, h) 22 | surface.SetDrawColor(0, 0, 0, 230) 23 | surface.DrawOutlinedRect(0, 0, w-1, h, 1) 24 | end 25 | 26 | -- Custom methods 27 | 28 | function PANEL:AddConvar(name, options) 29 | local convar = GetConVar(name) 30 | local current = convar:GetString() 31 | 32 | for _, data in ipairs(options) do 33 | local id = data[1] 34 | local name = data[2] 35 | 36 | local option = self:AddOption(name, function() 37 | convar:SetString(id) 38 | end) 39 | 40 | if id == current then 41 | self:SelectOption(option) 42 | end 43 | end 44 | end 45 | 46 | function PANEL:AddOption(text, func) 47 | local button = self:Add("DButton") 48 | button:SetText(text) 49 | button:SetTextColor(color_white) 50 | button:SetFont("gfconsole.Button") 51 | button:Dock(LEFT) 52 | button.Paint = function(panel, w, h) 53 | if panel.Active then 54 | surface.SetDrawColor(gfconsole.config.color) 55 | else 56 | surface.SetDrawColor(0, 0, 0, 200) 57 | end 58 | surface.DrawRect(0, 0, w, h) 59 | end 60 | button.DoClick = function(panel) 61 | self:SelectOption(panel) 62 | func() 63 | end 64 | 65 | return button 66 | end 67 | 68 | function PANEL:SelectOption(panel) 69 | for _, child in ipairs(self:GetChildren()) do 70 | child:SetTextColor(color_white) 71 | child.Active = false 72 | end 73 | 74 | panel:SetTextColor(color_black) 75 | panel.Active = true 76 | end 77 | 78 | vgui.Register("GFConsole.Selector", PANEL) -------------------------------------------------------------------------------- /lua/gfconsole/core/sv_core.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 13.03.2021 7 | 8 | --]] 9 | 10 | function gfconsole.SendWinNotify(receivers, text) 11 | net.Start("gfconsole:SendWinNotify") 12 | net.WriteString(text) 13 | net.Send(receivers) 14 | end 15 | 16 | hook.Add("gfconsole.CanReceiveMessage", "gfconsole.Default", function(ply) 17 | if not gfconsole.extension:is_enabled("subscriptions") then 18 | if not gfconsole.config.can_subscribe(ply) then 19 | return false 20 | end 21 | end 22 | end) -------------------------------------------------------------------------------- /lua/gfconsole/core/sv_net.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochnonement 4 | Email: tochnonement@gmail.com 5 | 6 | 14/11/2021 7 | 8 | --]] 9 | 10 | util.AddNetworkString("gfconsole:SendWinNotify") -------------------------------------------------------------------------------- /lua/gfconsole/extensions/bot.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochnonement 4 | Email: tochnonement@gmail.com 5 | 6 | 28/04/2022 7 | 8 | --]] 9 | 10 | local GetBots = player.GetBots 11 | 12 | if CLIENT then 13 | gfconsole.buttons.add("Bot Management", function() 14 | local botCount = #GetBots() 15 | local dmenu = DermaMenu() 16 | dmenu:AddOption(botCount .. " Bots"):SetIcon("icon16/eye.png") 17 | 18 | local subCreate, optCreate = dmenu:AddSubMenu("Create") 19 | local subRemove, optRemove = dmenu:AddSubMenu("Remove") 20 | 21 | optCreate:SetIcon("icon16/add.png") 22 | optRemove:SetIcon("icon16/delete.png") 23 | 24 | for _, count in ipairs({1, 3, 5, 10}) do 25 | subCreate:AddOption(count, function() 26 | net.Start("gfconsole.bot:Create") 27 | net.WriteUInt(count, 8) 28 | net.SendToServer() 29 | end) 30 | end 31 | 32 | for _, count in ipairs({1, 3, 5, 10}) do 33 | if count < botCount then 34 | subRemove:AddOption(count, function() 35 | net.Start("gfconsole.bot:Remove") 36 | net.WriteUInt(count, 8) 37 | net.SendToServer() 38 | end) 39 | end 40 | end 41 | 42 | if botCount > 0 then 43 | subRemove:AddOption("Everyone", function() 44 | net.Start("gfconsole.bot:Remove") 45 | net.WriteUInt(botCount, 8) 46 | net.SendToServer() 47 | end) 48 | end 49 | 50 | dmenu:AddOption("Toggle Movement", function() 51 | net.Start("gfconsole.bot:ToggleMovement") 52 | net.SendToServer() 53 | end):SetIcon("icon16/controller.png") 54 | dmenu:Open() 55 | end) 56 | end 57 | 58 | if CLIENT then return end 59 | 60 | util.AddNetworkString("gfconsole.bot:Create") 61 | util.AddNetworkString("gfconsole.bot:Remove") 62 | util.AddNetworkString("gfconsole.bot:ToggleMovement") 63 | 64 | local required_to_create = 0 65 | local bots_cant_move = false 66 | 67 | local function log(ply, text) 68 | print(ply:Name() .. " (" .. ply:SteamID() .. ") " .. text) 69 | end 70 | 71 | net.Receive("gfconsole.bot:Create", function(len, ply) 72 | if not gfconsole.config.can_execute(ply) then 73 | return 74 | end 75 | 76 | local count = net.ReadUInt(8) 77 | 78 | required_to_create = required_to_create + count 79 | 80 | log(ply, "created " .. count .. " bots") 81 | end) 82 | 83 | net.Receive("gfconsole.bot:Remove", function(len, ply) 84 | if not gfconsole.config.can_execute(ply) then 85 | return 86 | end 87 | 88 | local count = net.ReadUInt(8) 89 | local diff = required_to_create - count 90 | local left = math.abs(diff) 91 | 92 | if diff < 0 then 93 | local bots = GetBots() 94 | for i = 1, left do 95 | if IsValid(bots[i]) then 96 | bots[i]:Kick("GFConsole") 97 | end 98 | end 99 | required_to_create = 0 100 | else 101 | required_to_create = diff 102 | end 103 | 104 | log(ply, "removed " .. count .. " bots") 105 | end) 106 | 107 | net.Receive("gfconsole.bot:ToggleMovement", function(len, ply) 108 | if not gfconsole.config.can_execute(ply) then 109 | return 110 | end 111 | 112 | bots_cant_move = not bots_cant_move 113 | 114 | log(ply, (bots_cant_move and "disabled" or "enabled") .. " movement for bots") 115 | end) 116 | 117 | hook.Add("StartCommand", "gfconsole.bot.Controller", function(ply, cmd) 118 | if ply:IsBot() and bots_cant_move then 119 | cmd:ClearMovement() 120 | end 121 | end) 122 | 123 | timer.Create("gfconsole.bot.CreationQueue", 1 / 2, 0, function() 124 | if required_to_create > 0 then 125 | required_to_create = required_to_create - 1 126 | RunConsoleCommand("bot") 127 | end 128 | end) -------------------------------------------------------------------------------- /lua/gfconsole/extensions/errors.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | gfconsole.FILTER_ERRORS = gfconsole.FILTER_ERRORS or gfconsole.filter.create("Errors") 11 | 12 | if CLIENT then return end 13 | 14 | local sys_arch = string.sub(jit.arch, 2):gsub("86", "32") 15 | local dll_name = "gm" .. (CLIENT and "cl" or "sv") .. "_luaerror_" .. (system.IsWindows() and ("win" .. sys_arch) or system.IsLinux() and ("linux" .. sys_arch) or "osx") .. ".dll" 16 | local dll_exists = file.Exists("lua/bin/" .. dll_name, "MOD") 17 | 18 | if not dll_exists then 19 | ErrorNoHalt("[GFConsole] \"Errors\" extension cannot start, because there's no valid \"luaerror\" module\n") 20 | return 21 | end 22 | 23 | require("luaerror") 24 | 25 | local color = Color(230, 25, 25) 26 | local client = Color(215, 241, 165) 27 | local server = Color(97, 214, 214) 28 | local stack_line = "%i. %s - %s:%i" 29 | 30 | local function parse_stack(stack) 31 | local spaces = "\n " 32 | local tbl = {} 33 | local counter = 1 34 | local parsed = "" 35 | 36 | for _, data in ipairs(stack) do 37 | local name = data.name 38 | local empty_name = data.name == "" 39 | local source = data.source 40 | 41 | source = string.Replace(source, "=", "") 42 | source = string.Replace(source, "@", "") 43 | 44 | if empty_name and source == "[C]" then 45 | goto skip 46 | end 47 | 48 | table.insert(tbl, stack_line:format(counter, (empty_name and "unknown" or name), source, data.lastlinedefined)) 49 | 50 | counter = counter + 1 51 | 52 | ::skip:: 53 | end 54 | 55 | for _, str in ipairs(tbl) do 56 | parsed = parsed .. spaces .. str 57 | spaces = spaces .. " " 58 | end 59 | 60 | return parsed 61 | end 62 | 63 | luaerror.EnableRuntimeDetour(true) 64 | luaerror.EnableCompiletimeDetour(true) 65 | luaerror.EnableClientDetour(true) 66 | 67 | hook.Add("LuaError", "gfconsole.Print", function(isruntime, fullerror, sourcefile, sourceline, errorstr, stack) 68 | gfconsole.send(gfconsole.FILTER_ERRORS, color, "[ERROR] ", server, fullerror, parse_stack(stack), "\n") 69 | end) 70 | 71 | hook.Add("ClientLuaError", "gfconsole.Print", function(ply, fullerror) 72 | gfconsole.send(gfconsole.FILTER_ERRORS, color, "[ERROR] (", ply:Name(), ") ", client, fullerror, "\n") 73 | end) -------------------------------------------------------------------------------- /lua/gfconsole/extensions/execute.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 13.03.2021 7 | 8 | --]] 9 | 10 | if CLIENT then 11 | gfconsole.buttons.add("Input", function() 12 | Derma_StringRequest("Send command to a server", "Input a command", "", function(text) 13 | net.Start("gfconsole.execute:Send") 14 | net.WriteString(text) 15 | net.SendToServer() 16 | end) 17 | end) 18 | end 19 | 20 | if SERVER then 21 | util.AddNetworkString("gfconsole.execute:Send") 22 | 23 | local can_access = gfconsole.config.can_execute 24 | 25 | net.Receive("gfconsole.execute:Send", function(len, ply) 26 | if can_access(ply) then 27 | local text = net.ReadString() 28 | local arguments = string.Explode(" ", text) 29 | local cmd = arguments[1] 30 | 31 | table.remove(arguments, 1) 32 | 33 | if cmd == "echo" then 34 | Msg(unpack(arguments)) 35 | Msg("\n") 36 | else 37 | RunConsoleCommand(cmd, unpack(arguments)) 38 | end 39 | end 40 | end) 41 | end -------------------------------------------------------------------------------- /lua/gfconsole/extensions/relay.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 22.07.2021 7 | 8 | --]] 9 | 10 | local send = gfconsole.send 11 | 12 | local color_client = Color(255, 221, 102) 13 | local color_server = Color(156, 221, 255, 255) 14 | 15 | gfconsole.FILTER_PRINT = gfconsole.FILTER_PRINT or gfconsole.filter.create("Print") 16 | gfconsole.FILTER_MSG = gfconsole.FILTER_MSG or gfconsole.filter.create("Etc") 17 | 18 | do 19 | local _G = _G 20 | OldPrint = OldPrint or _G.print 21 | OldMsg = OldMsg or _G.Msg 22 | OldMsgC = OldMsgC or _G.MsgC 23 | OldPrintTable = OldPrintTable or _G.PrintTable 24 | OldMsgN = OldMsgN or _G.MsgN 25 | end 26 | 27 | local function send_with_space(filter, ...) 28 | send(filter, ...) 29 | send(filter, "\n") 30 | end 31 | 32 | -- ANCHOR Utility 33 | 34 | local translate do 35 | local format = string.format 36 | local tostring = tostring 37 | local type = type 38 | local istable = istable 39 | local TypeID = TypeID 40 | 41 | local translators = { 42 | ["Vector"] = function(object) 43 | return format("Vector(%i, %i, %i)", object.x, object.y, object.z) 44 | end, 45 | ["Color"] = function(object) 46 | return format("Color(%i, %i, %i, %i)", object.r, object.g, object.b, object.a) 47 | end, 48 | ["Angle"] = function(object) 49 | return format("Angle(%i, %i, %i)", object.p, object.y, object.r) 50 | end 51 | } 52 | 53 | local function is_color(tbl) 54 | return istable(tbl) and TypeID(tbl) == TYPE_COLOR 55 | end 56 | 57 | function translate(any) 58 | local sType = type(any) 59 | local translator = translators[sType] 60 | 61 | if sType == "string" then 62 | return any 63 | elseif translator then 64 | return translator(any) 65 | else 66 | if is_color(any) then 67 | return translators["Color"](any) 68 | else 69 | return tostring(any) 70 | end 71 | end 72 | end 73 | end 74 | 75 | local parse_args do 76 | local select = select 77 | local unpack = unpack 78 | local nilStr = "nil" 79 | 80 | function parse_args(separator, ...) 81 | local result, count = {}, 0 82 | local arg_amount = select("#", ...) 83 | 84 | for i = 1, arg_amount do 85 | local value = select(i, ...) 86 | 87 | count = count + 1 88 | 89 | if value == nil then 90 | result[count] = nilStr 91 | else 92 | result[count] = translate(value) 93 | end 94 | 95 | if separator and i < arg_amount then 96 | count = count + 1 97 | result[count] = separator 98 | end 99 | end 100 | 101 | return unpack(result) 102 | end 103 | end 104 | 105 | -- ANCHOR Override 106 | 107 | local new_print do 108 | local getinfo = debug.getinfo 109 | local explode = string.Explode 110 | 111 | function new_print(...) 112 | OldPrint(...) 113 | 114 | local info = getinfo(2) 115 | 116 | if not info then 117 | send_with_space(gfconsole.FILTER_PRINT, color_white, parse_args(" ", ...)) 118 | return 119 | end 120 | 121 | local src = info.short_src 122 | local exploded = explode("/", src) 123 | local prefix = exploded[#exploded] .. ":" .. info.linedefined 124 | 125 | send_with_space(gfconsole.FILTER_PRINT, SERVER and color_server or color_client, prefix , color_white, " -- ", parse_args(" ", ...)) 126 | end 127 | end 128 | 129 | local function new_msg(...) 130 | OldMsg(...) 131 | send(gfconsole.FILTER_MSG, color_white, ...) 132 | end 133 | 134 | local function new_msgn(...) 135 | OldMsgN(...) 136 | send_with_space(gfconsole.FILTER_MSG, color_white, ...) 137 | end 138 | 139 | local function new_msgc(...) 140 | OldMsgC(...) 141 | send(gfconsole.FILTER_MSG, ...) 142 | end 143 | 144 | local new_print_table do 145 | local function msg(...) 146 | OldMsg(...) 147 | OldMsg("\n") 148 | 149 | send_with_space(gfconsole.FILTER_MSG, ...) 150 | end 151 | 152 | local function parse_key(key) 153 | if isnumber(key) then 154 | return "[" .. key .. "]" 155 | else 156 | return tostring(key) 157 | end 158 | end 159 | 160 | local function print_table(tbl, indent, key) 161 | indent = indent or 0 162 | 163 | local space = string.rep("\t", indent) 164 | local iterator = table.IsSequential(tbl) and ipairs or pairs 165 | 166 | if key then 167 | msg(space, parse_key(key), " = {") 168 | else 169 | msg(space, "{") 170 | end 171 | 172 | for k, v in iterator(tbl) do 173 | if istable(v) then 174 | print_table(v, indent + 1, k) 175 | else 176 | msg(space .. "\t", parse_key(k), " = ", translate(v)) 177 | end 178 | end 179 | 180 | msg(space, "}") 181 | end 182 | 183 | function new_print_table(tbl) 184 | print_table(tbl) 185 | end 186 | end 187 | 188 | local function override() 189 | print = new_print 190 | Msg = new_msg 191 | MsgN = new_msgn 192 | MsgC = new_msgc 193 | PrintTable = new_print_table 194 | end 195 | 196 | override() 197 | print('meow', 123, 321) 198 | hook.Add("PostGamemodeLoaded", "gfconsole.Relay", override) -------------------------------------------------------------------------------- /lua/gfconsole/extensions/retry.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochnonement 4 | Email: tochnonement@gmail.com 5 | 6 | 14/11/2021 7 | 8 | --]] 9 | 10 | if CLIENT then return end 11 | 12 | local success = pcall(require, "luaerror") 13 | 14 | if not success then 15 | return 16 | end 17 | 18 | local function retry(ply, file) 19 | if not ply:GetVar("gfconsole_RetryCalled", false) then 20 | gfconsole.SendWinNotify(ply, "Trying to load a new file: " .. file) 21 | 22 | ply:ConCommand("retry") 23 | ply:SetVar("gfconsole_RetryCalled", true) 24 | end 25 | end 26 | 27 | hook.Add("ClientLuaError", "gfconsole.ext.retry", function(ply, fullerror) 28 | if not fullerror:find("include file") then 29 | return 30 | end 31 | 32 | local f = string.match(fullerror, "%b''", 10):gsub("\\", "/") 33 | local path = string.match(fullerror, "@[%w./%[%]_]+") 34 | local where = string.match(path, "%w+") 35 | local fName = string.match(f, "[%w._]+.lua") 36 | local exists = false 37 | 38 | f = string.gsub(f, "'", "") 39 | path = string.sub(path, 2) 40 | 41 | if (where == "addons" or where == "gamemodes") then 42 | -- Check globally 43 | if file.Exists(f, "LUA") then 44 | exists = true 45 | goto process 46 | end 47 | 48 | -- Check relatively 49 | local rpath = string.Explode("/", path) 50 | rpath[#rpath] = f 51 | rpath = table.concat(rpath, "/") 52 | 53 | if file.Exists(rpath, "LUA") then 54 | exists = true 55 | end 56 | end 57 | 58 | ::process:: 59 | 60 | if exists then 61 | retry(ply, fName) 62 | end 63 | end) -------------------------------------------------------------------------------- /lua/gfconsole/extensions/subscriptions.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | gfconsole.FILTER_SUBSCRIPTIONS = gfconsole.filter.create("Subscriptions") 11 | 12 | if SERVER then 13 | util.AddNetworkString("gfconsole:Subscribe") 14 | 15 | local can_subscribe = gfconsole.config.can_subscribe 16 | 17 | hook.Add("PlayerDisconnected", "gfconsole.subscriptions.Remove", function(ply) 18 | gfconsole.subscriptions.remove(ply) 19 | end) 20 | 21 | hook.Add("gfconsole.CanReceiveMessage", "Subscriptions", function(ply) 22 | local is_subscriber = gfconsole.subscriptions.check(ply) 23 | if not is_subscriber then 24 | return false 25 | end 26 | end) 27 | 28 | hook.Add("gfconsole.SubscriberAdded", "Notify", function(ply) 29 | gfconsole.send(gfconsole.FILTER_SUBSCRIPTIONS, Color(59, 179, 95), "[Subscriptions] ", color_white, "User added: " .. ply:Name(), "\n") 30 | end) 31 | 32 | hook.Add("gfconsole.SubscriberRemoved", "Notify", function(ply) 33 | gfconsole.send(gfconsole.FILTER_SUBSCRIPTIONS, Color(59, 179, 95), "[Subscriptions] ", color_white, "User removed: " .. ply:Name(), "\n") 34 | end) 35 | 36 | net.Receive("gfconsole:Subscribe", function(len, ply) 37 | local bool = net.ReadBool() 38 | 39 | if can_subscribe(ply) then 40 | if bool then 41 | gfconsole.subscriptions.add(ply) 42 | else 43 | ply:ChatPrint("You unsubscribed.") 44 | gfconsole.subscriptions.remove(ply) 45 | end 46 | end 47 | end) 48 | else 49 | local function toggle(bool) 50 | net.Start("gfconsole:Subscribe") 51 | net.WriteBool(bool) 52 | net.SendToServer() 53 | end 54 | 55 | gfconsole.buttons.add("Subscribe", function() 56 | toggle(true) 57 | end) 58 | 59 | gfconsole.buttons.add("Unsubscribe", function() 60 | toggle(false) 61 | end) 62 | end -------------------------------------------------------------------------------- /lua/gfconsole/extensions/utility.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 27.03.2021 7 | 8 | --]] 9 | 10 | if SERVER then return end 11 | 12 | local color1 = Color(52, 152, 219) 13 | local color2 = Color(142, 68, 173) 14 | 15 | gfconsole.buttons.add("Get Pos/Ang", function() 16 | local pos = LocalPlayer():GetPos() 17 | local ang = LocalPlayer():GetAngles() 18 | 19 | for _, key in ipairs({"p", "y", "r"}) do 20 | ang[key] = math.Round(ang[key]) 21 | end 22 | 23 | for _, key in ipairs({"x", "y", "z"}) do 24 | pos[key] = math.Round(pos[key]) 25 | end 26 | 27 | local vecStr = ("Vector(%i, %i, %i)"):format(pos.x, pos.y, pos.z) 28 | local angStr = ("Angle(%i, %i, %i)"):format(ang.p, ang.y, ang.r) 29 | 30 | gfconsole.send(nil, color_white, color1, vecStr, ' ', color2, angStr, "\n") 31 | end) -------------------------------------------------------------------------------- /lua/gfconsole/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 11.03.2021 7 | 8 | --]] 9 | 10 | gfconsole.load.Shared("config.lua") 11 | gfconsole.load.Folder("gfconsole/libraries/thirdparty") 12 | gfconsole.load.Folder("gfconsole/libraries") 13 | gfconsole.load.Folder("gfconsole/core/derma") 14 | gfconsole.load.Folder("gfconsole/core") 15 | 16 | gfconsole.extension:init() -------------------------------------------------------------------------------- /lua/gfconsole/libraries/cl_buttons.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | gfconsole.buttons = {} 11 | 12 | local buttons = gfconsole.buttons 13 | local storage = {} 14 | 15 | function buttons.add(name, func) 16 | for _, data in ipairs(storage) do 17 | if data.name == name then 18 | data.func = func 19 | return 20 | end 21 | end 22 | 23 | table.insert(storage, { 24 | name = name, 25 | func = func 26 | }) 27 | end 28 | 29 | function buttons.exist(name) 30 | for _, data in ipairs(storage) do 31 | if data.name == name then 32 | return true 33 | end 34 | end 35 | 36 | return false 37 | end 38 | 39 | function buttons.get() 40 | return storage 41 | end -------------------------------------------------------------------------------- /lua/gfconsole/libraries/cl_message.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochnonement 4 | Email: tochnonement@gmail.com 5 | 6 | 27/04/2022 7 | 8 | --]] 9 | 10 | local add_message do 11 | local Run = hook.Run 12 | local Trim = string.Trim 13 | local GetConVar = GetConVar 14 | local select = select 15 | local isstring = isstring 16 | local SysTime = SysTime 17 | 18 | local color_gray = Color(200, 200, 200) 19 | local last_ts_print = 0 20 | 21 | function add_message(from_server, filter, ...) 22 | local frame = gfconsole.frame 23 | 24 | if not IsValid(frame) then return end 25 | 26 | local container = frame.panel 27 | local should_receive = Run("gfconsole.CanPass", filter) 28 | local realm = GetConVar("cl_gfconsole_realm"):GetString() 29 | 30 | if should_receive == false then 31 | return 32 | end 33 | 34 | if from_server then 35 | if realm == "client" then 36 | return 37 | end 38 | else 39 | if realm == "server" then 40 | return 41 | end 42 | end 43 | 44 | if last_ts_print ~= SysTime() then 45 | local first = select(1, ...) 46 | local second = select(2, ...) 47 | local is_ts_allowed = GetConVar("cl_gfconsole_timestamps"):GetBool() 48 | 49 | if isstring(first) and Trim(first) == "" then 50 | is_ts_allowed = false 51 | elseif isstring(second) and Trim(second) == "" then 52 | is_ts_allowed = false 53 | end 54 | 55 | if is_ts_allowed then 56 | container:AddRecord(color_gray, os.date("[%H:%M:%S] ")) 57 | end 58 | 59 | last_ts_print = SysTime() 60 | end 61 | 62 | container:AddRecord(...) 63 | end 64 | end 65 | 66 | function gfconsole.send(filter, ...) 67 | add_message(false, filter, ...) 68 | end 69 | 70 | do 71 | local net_ReadUInt = net.ReadUInt 72 | local net_ReadData = net.ReadData 73 | local pon_decode = pon.decode 74 | local unpack = unpack 75 | 76 | net.Receive("gfconsole:Send", function() 77 | local filter = net_ReadUInt(gfconsole.filter.bits) 78 | local length = net_ReadUInt(16) 79 | local data = net_ReadData(length) 80 | local decoded = pon_decode(data) 81 | 82 | add_message(true, filter, unpack(decoded)) 83 | end) 84 | end -------------------------------------------------------------------------------- /lua/gfconsole/libraries/sh_extension.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochnonement 4 | Email: tochnonement@gmail.com 5 | 6 | 14/11/2021 7 | 8 | --]] 9 | 10 | gfconsole.extension = {} 11 | gfconsole.extension.list = {} 12 | 13 | local base_path = "gfconsole/extensions" 14 | 15 | function gfconsole.extension:enable(id) 16 | local path = base_path .. "/" .. id .. ".lua" 17 | local success = pcall(gfconsole.load.Shared, path) 18 | 19 | if success then 20 | table.insert(self.list, id) 21 | end 22 | end 23 | 24 | function gfconsole.extension:is_enabled(id) 25 | for _, id2 in ipairs(self.list) do 26 | if id == id2 then 27 | return true 28 | end 29 | end 30 | 31 | return false 32 | end 33 | 34 | function gfconsole.extension:find_all() 35 | local files = file.Find(base_path .. "/*", "LUA") 36 | local result = {} 37 | 38 | for _, f in ipairs(files) do 39 | local name = string.Explode(".", f)[1] 40 | 41 | if name then 42 | table.insert(result, name) 43 | end 44 | end 45 | 46 | return result 47 | end 48 | 49 | function gfconsole.extension:init() 50 | local available = self:find_all() 51 | 52 | for _, id in ipairs(available) do 53 | if gfconsole.config.enabled[id] then 54 | self:enable(id) 55 | end 56 | end 57 | end -------------------------------------------------------------------------------- /lua/gfconsole/libraries/sh_filter.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochnonement 4 | Email: tochnonement@gmail.com 5 | 6 | 27/04/2022 7 | 8 | --]] 9 | 10 | gfconsole.filter = {} 11 | gfconsole.filter.bits = 0 12 | 13 | local storage, count = {}, 0 14 | 15 | local function count_bits(int) 16 | return math.floor(math.log(int, 2)) + 1 17 | end 18 | 19 | function gfconsole.filter.create(identifier) 20 | count = count + 1 21 | local index = count 22 | local cvName = "cl_gfconsole_show_" .. identifier 23 | 24 | storage[index] = SERVER and identifier or { 25 | index = index, 26 | id = identifier, 27 | cv = CreateClientConVar(cvName, "1", true, false) 28 | } 29 | gfconsole.filter.bits = count_bits(count) 30 | 31 | if CLIENT then 32 | cvars.AddChangeCallback(cvName, function(convar_name, value_old, value_new) 33 | local frame = gfconsole.frame 34 | if IsValid(frame) then 35 | for _, panel in ipairs(frame.panel.control.Panels) do 36 | if panel.ClassName == "GFConsole.Checkbox" and panel.cv:GetName() == convar_name then 37 | panel:SetValue(tobool(value_new)) 38 | break 39 | end 40 | end 41 | end 42 | end) 43 | end 44 | 45 | return index 46 | end 47 | gfconsole.filter.add = gfconsole.filter.create 48 | 49 | function gfconsole.filter.get(index) 50 | return storage[index] 51 | end 52 | 53 | function gfconsole.filter.get_all() 54 | return storage 55 | end 56 | 57 | function gfconsole.filter.find(identifier) 58 | for i = 1, count do 59 | local data = storage[i] 60 | if SERVER and data == identifier or data.id == identifier then 61 | return data, i 62 | end 63 | end 64 | end 65 | 66 | if CLIENT then 67 | function gfconsole.filter.is_enabled(index) 68 | return storage[index].cv:GetBool() 69 | end 70 | 71 | function gfconsole.filter.enable(identifier, bool) 72 | local filter = gfconsole.filter.find(identifier) 73 | if filter then 74 | filter.cv:SetBool(bool) 75 | end 76 | end 77 | 78 | function gfconsole.filter.toggle(identifier) 79 | local filter = gfconsole.filter.find(identifier) 80 | if filter then 81 | filter.cv:SetBool(not filter.cv:GetBool()) 82 | end 83 | end 84 | 85 | hook.Add("gfconsole.CanPass", "gfconsole.Filters", function(index) 86 | if index then 87 | return gfconsole.filter.is_enabled(index) 88 | end 89 | end) 90 | end -------------------------------------------------------------------------------- /lua/gfconsole/libraries/sh_load.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 13.05.2021 7 | 8 | --]] 9 | 10 | local load = {} 11 | 12 | function load.Server(path) 13 | if SERVER then 14 | return include(path) 15 | end 16 | end 17 | 18 | function load.Client(path) 19 | if SERVER then 20 | AddCSLuaFile(path) 21 | else 22 | return include(path) 23 | end 24 | end 25 | 26 | function load.Shared(path) 27 | if SERVER then 28 | AddCSLuaFile(path) 29 | return load.Server(path) 30 | else 31 | return load.Client(path) 32 | end 33 | end 34 | 35 | function load.Auto(path, ignore) 36 | local prefix = string.match(path, "/(%l+)_") 37 | 38 | if prefix then 39 | if prefix == "sv" then 40 | return load.Server(path) 41 | elseif prefix == "cl" then 42 | return load.Client(path) 43 | elseif prefix == "sh" then 44 | return load.Shared(path) 45 | end 46 | else 47 | if (not ignore) then 48 | error("No prefix found") 49 | end 50 | end 51 | end 52 | 53 | function load.Folder(path, recursive, ignore) 54 | local parsedPath = path .. "/" 55 | local files, folders = file.Find(parsedPath .. "*", "LUA") 56 | 57 | for _, f in ipairs(files) do 58 | if string.Right(f, 4) == ".lua" and f ~= "sh_load.lua" then 59 | load.Auto(parsedPath .. f, ignore) 60 | end 61 | end 62 | 63 | if recursive then 64 | for _, f in ipairs(folders) do 65 | load.Folder(path .. "/" .. f, true, ignore) 66 | end 67 | end 68 | end 69 | 70 | gfconsole.load = load -------------------------------------------------------------------------------- /lua/gfconsole/libraries/sv_message.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochnonement 4 | Email: tochnonement@gmail.com 5 | 6 | 27/04/2022 7 | 8 | --]] 9 | 10 | util.AddNetworkString("gfconsole:Send") 11 | 12 | local RECOVERY_TIME = 1 13 | local MAXIMUM_MESSAGES = 1024 14 | local MESSAGES_PER_TICK = 3 15 | local queue = {} 16 | 17 | local get_recipients do 18 | local GetHumans = player.GetHumans 19 | local Run = hook.Run 20 | 21 | function get_recipients() 22 | local recipient_list = {} 23 | local recipient_count = 0 24 | local players = GetHumans() 25 | local iterations = 0 26 | 27 | while (true) do 28 | iterations = iterations + 1 29 | local ply = players[iterations] 30 | if not ply then 31 | break 32 | end 33 | 34 | if Run("gfconsole.CanReceiveMessage", ply) ~= false then 35 | recipient_count = recipient_count + 1 36 | recipient_list[recipient_count] = ply 37 | end 38 | end 39 | 40 | return recipient_list, recipient_count 41 | end 42 | end 43 | 44 | local process_queue do 45 | local net_Start = net.Start 46 | local net_Send = net.Send 47 | local net_WriteUInt = net.WriteUInt 48 | local net_WriteData = net.WriteData 49 | local table_remove = table.remove 50 | local pon_encode = pon.encode 51 | 52 | function process_queue() 53 | local recipients, amount = get_recipients() 54 | 55 | if amount < 1 then 56 | queue = {} 57 | return 58 | end 59 | 60 | for i = 1, MESSAGES_PER_TICK do 61 | local message = queue[1] 62 | if message then 63 | local filter = message.filter 64 | local data = message.data 65 | local encoded = pon_encode(data) 66 | local length = #encoded 67 | 68 | net_Start("gfconsole:Send") 69 | net_WriteUInt(filter, gfconsole.filter.bits) 70 | net_WriteUInt(length, 16) 71 | net_WriteData(encoded) 72 | net_Send(recipients) 73 | 74 | table_remove(queue, 1) 75 | end 76 | end 77 | end 78 | end 79 | 80 | local function start_process() 81 | gfconsole.process_enabled = true 82 | 83 | hook.Add("Think", "gfconsole.QueueProcess", process_queue) 84 | end 85 | 86 | local function stop_process() 87 | gfconsole.process_enabled = false 88 | 89 | hook.Remove("Think", "gfconsole.QueueProcess") 90 | end 91 | 92 | local function is_queue_filled() 93 | return #queue >= MAXIMUM_MESSAGES 94 | end 95 | 96 | local function check_queue_overload() 97 | if is_queue_filled() then 98 | stop_process() 99 | 100 | queue = {} 101 | 102 | timer.Simple(RECOVERY_TIME, start_process) 103 | end 104 | end 105 | 106 | do 107 | local insert = table.insert 108 | 109 | function gfconsole.send(filter, ...) 110 | if gfconsole.process_enabled then 111 | filter = filter or 0 112 | 113 | insert(queue, { 114 | filter = filter, 115 | data = {...} 116 | }) 117 | 118 | check_queue_overload() 119 | end 120 | end 121 | end 122 | 123 | start_process() -------------------------------------------------------------------------------- /lua/gfconsole/libraries/sv_subscriptions.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Author: tochonement 4 | Email: tochonement@gmail.com 5 | 6 | 12.03.2021 7 | 8 | --]] 9 | 10 | gfconsole.subscriptions = {} 11 | 12 | local subscriptions = gfconsole.subscriptions 13 | local members = {} 14 | 15 | function subscriptions.add(ply) 16 | if subscriptions.check(ply) then 17 | return 18 | end 19 | 20 | table.insert(members, ply) 21 | 22 | hook.Run("gfconsole.SubscriberAdded", ply) 23 | end 24 | 25 | function subscriptions.check(ply) 26 | for index, ply2 in ipairs(members) do 27 | if ply == ply2 then 28 | return true, index 29 | end 30 | end 31 | 32 | return false 33 | end 34 | 35 | function subscriptions.remove(ply) 36 | local bool, index = subscriptions.check(ply) 37 | if bool then 38 | table.remove(members, index) 39 | 40 | hook.Run("gfconsole.SubscriberRemoved", ply) 41 | end 42 | end 43 | 44 | function subscriptions.get() 45 | return members 46 | end -------------------------------------------------------------------------------- /lua/gfconsole/libraries/thirdparty/sh_pon.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: undefined-global 2 | --[[ 3 | 4 | DEVELOPMENTAL VERSION; 5 | 6 | VERSION 1.2.2 7 | Copyright thelastpenguin™ 8 | 9 | You may use this for any purpose as long as: 10 | - You don't remove this copyright notice. 11 | - You don't claim this to be your own. 12 | - You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this. 13 | 14 | If you modify the code for any purpose, the above still applies to the modified code. 15 | 16 | The author is not held responsible for any damages incured from the use of pon, you use it at your own risk. 17 | 18 | DATA TYPES SUPPORTED: 19 | - tables - k,v - pointers 20 | - strings - k,v - pointers 21 | - numbers - k,v 22 | - booleans- k,v 23 | - Vectors - k,v 24 | - Angles - k,v 25 | - Entities- k,v 26 | - Players - k,v 27 | 28 | CHANGE LOG 29 | V 1.1.0 30 | - Added Vehicle, NPC, NextBot, Player, Weapon 31 | V 1.2.0 32 | - Added custom handling for k,v tables without any array component. 33 | V 1.2.1 34 | - fixed deserialization bug. 35 | 36 | THANKS TO... 37 | - VERCAS for the inspiration. 38 | ]] 39 | 40 | 41 | local pon = {}; 42 | _G.pon = pon; 43 | 44 | local type, count = type, table.Count ; 45 | local tonumber = tonumber ; 46 | local format = string.format; 47 | do 48 | local type, count = type, table.Count ; 49 | local tonumber = tonumber ; 50 | local format = string.format; 51 | 52 | local encode = {}; 53 | 54 | local tryCache ; 55 | 56 | local cacheSize = 0; 57 | 58 | encode['table'] = function( self, tbl, output, cache ) 59 | 60 | if( cache[ tbl ] )then 61 | output[ #output + 1 ] = format('(%x)', cache[tbl] ); 62 | return ; 63 | else 64 | cacheSize = cacheSize + 1; 65 | cache[ tbl ] = cacheSize; 66 | end 67 | 68 | 69 | local first = next(tbl, nil) 70 | local predictedNumeric = 1 71 | local lastKey = nil 72 | -- starts with a numeric dealio 73 | if first == 1 then 74 | output[#output + 1] = '{' 75 | 76 | for k,v in next, tbl do 77 | if k == predictedNumeric then 78 | predictedNumeric = predictedNumeric + 1 79 | 80 | local tv = type(v) 81 | if tv == 'string' then 82 | local pid = cache[v] 83 | if pid then 84 | output[#output + 1] = format('(%x)', pid) 85 | else 86 | cacheSize = cacheSize + 1 87 | cache[v] = cacheSize 88 | self.string(self, v, output, cache) 89 | end 90 | else 91 | self[tv](self, v, output, cache) 92 | end 93 | 94 | else 95 | break 96 | end 97 | end 98 | 99 | predictedNumeric = predictedNumeric - 1 100 | else 101 | predictedNumeric = nil 102 | end 103 | 104 | if predictedNumeric == nil then 105 | output[#output + 1] = '[' -- no array component 106 | else 107 | output[#output + 1] = '~' -- array component came first so shit needs to happen 108 | end 109 | 110 | for k, v in next, tbl, predictedNumeric do 111 | local tk, tv = type(k), type(v) 112 | 113 | -- WRITE KEY 114 | if tk == 'string' then 115 | local pid = cache[ k ]; 116 | if( pid )then 117 | output[ #output + 1 ] = format('(%x)', pid ); 118 | else 119 | cacheSize = cacheSize + 1; 120 | cache[ k ] = cacheSize; 121 | 122 | self.string( self, k, output, cache ); 123 | end 124 | else 125 | self[tk](self, k, output, cache) 126 | end 127 | 128 | -- WRITE VALUE 129 | if( tv == 'string' )then 130 | local pid = cache[ v ]; 131 | if( pid )then 132 | output[ #output + 1 ] = format('(%x)', pid ); 133 | else 134 | cacheSize = cacheSize + 1; 135 | cache[ v ] = cacheSize; 136 | 137 | self.string( self, v, output, cache ); 138 | end 139 | else 140 | self[ tv ]( self, v, output, cache ); 141 | end 142 | end 143 | 144 | output[#output + 1] = '}' 145 | end 146 | -- ENCODE STRING 147 | local gsub = string.gsub ; 148 | encode['string'] = function( self, str, output ) 149 | --if tryCache( str, output ) then return end 150 | local estr, count = gsub( str, ";", "\\;"); 151 | if( count == 0 )then 152 | output[ #output + 1 ] = '\''..str..';'; 153 | else 154 | output[ #output + 1 ] = '"'..estr..'";'; 155 | end 156 | end 157 | -- ENCODE NUMBER 158 | encode['number'] = function( self, num, output ) 159 | if num%1 == 0 then 160 | if num < 0 then 161 | output[ #output + 1 ] = format( 'x%x;', -num ); 162 | else 163 | output[ #output + 1 ] = format('X%x;', num ); 164 | end 165 | else 166 | output[ #output + 1 ] = tonumber( num )..';'; 167 | end 168 | end 169 | -- ENCODE BOOLEAN 170 | encode['boolean'] = function( self, val, output ) 171 | output[ #output + 1 ] = val and 't' or 'f' 172 | end 173 | -- ENCODE VECTOR 174 | encode['Vector'] = function( self, val, output ) 175 | output[ #output + 1 ] = ('v'..val.x..','..val.y)..(','..val.z..';'); 176 | end 177 | -- ENCODE ANGLE 178 | encode['Angle'] = function( self, val, output ) 179 | output[ #output + 1 ] = ('a'..val.p..','..val.y)..(','..val.r..';'); 180 | end 181 | encode['Entity'] = function( self, val, output ) 182 | output[ #output + 1] = 'E'..(IsValid( val ) and (val:EntIndex( )..';') or '#'); 183 | end 184 | encode['Player'] = encode['Entity']; 185 | encode['Vehicle'] = encode['Entity']; 186 | encode['Weapon'] = encode['Entity']; 187 | encode['NPC'] = encode['Entity']; 188 | encode['NextBot'] = encode['Entity']; 189 | encode['PhysObj'] = encode['Entity']; 190 | 191 | encode['nil'] = function() 192 | output[ #output + 1 ] = '?'; 193 | end 194 | encode.__index = function( key ) 195 | ErrorNoHalt('Type: '..key..' can not be encoded. Encoded as as pass-over value.'); 196 | return encode['nil']; 197 | end 198 | 199 | do 200 | local empty, concat = table.Empty, table.concat ; 201 | function pon.encode( tbl ) 202 | local output = {}; 203 | cacheSize = 0; 204 | encode[ 'table' ]( encode, tbl, output, {} ); 205 | local res = concat( output ); 206 | 207 | return res; 208 | end 209 | end 210 | end 211 | 212 | do 213 | local tonumber = tonumber ; 214 | local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode ; 215 | local Vector, Angle, Entity = Vector, Angle, Entity ; 216 | 217 | local decode = {}; 218 | decode['{'] = function( self, index, str, cache ) 219 | 220 | local cur = {}; 221 | cache[ #cache + 1 ] = cur; 222 | 223 | local k, v, tk, tv = 1, nil, nil, nil; 224 | while( true )do 225 | tv = sub( str, index, index ); 226 | if( not tv or tv == '~' )then 227 | index = index + 1; 228 | break ; 229 | end 230 | if( tv == '}' )then 231 | return index + 1, cur; 232 | end 233 | 234 | -- READ THE VALUE 235 | index = index + 1; 236 | index, v = self[ tv ]( self, index, str, cache ); 237 | cur[ k ] = v; 238 | 239 | k = k + 1; 240 | end 241 | 242 | while( true )do 243 | tk = sub( str, index, index ); 244 | if( not tk or tk == '}' )then 245 | index = index + 1; 246 | break ; 247 | end 248 | 249 | -- READ THE KEY 250 | 251 | index = index + 1; 252 | index, k = self[ tk ]( self, index, str, cache ); 253 | 254 | -- READ THE VALUE 255 | tv = sub( str, index, index ); 256 | index = index + 1; 257 | index, v = self[ tv ]( self, index, str, cache ); 258 | 259 | cur[ k ] = v; 260 | end 261 | 262 | return index, cur; 263 | end 264 | decode['['] = function( self, index, str, cache ) 265 | 266 | local cur = {}; 267 | cache[ #cache + 1 ] = cur; 268 | 269 | local k, v, tk, tv = 1, nil, nil, nil; 270 | while( true )do 271 | tk = sub( str, index, index ); 272 | if( not tk or tk == '}' )then 273 | index = index + 1; 274 | break ; 275 | end 276 | 277 | -- READ THE KEY 278 | index = index + 1; 279 | index, k = self[ tk ]( self, index, str, cache ); 280 | if not k then continue end 281 | 282 | -- READ THE VALUE 283 | tv = sub( str, index, index ); 284 | index = index + 1; 285 | if not self[tv] then 286 | print('did not find type: '..tv) 287 | end 288 | index, v = self[ tv ]( self, index, str, cache ); 289 | 290 | cur[ k ] = v; 291 | end 292 | 293 | return index, cur; 294 | end 295 | 296 | -- STRING 297 | decode['"'] = function( self, index, str, cache ) 298 | local finish = find( str, '";', index, true ); 299 | local res = gsub( sub( str, index, finish - 1 ), '\\;', ';' ); 300 | index = finish + 2; 301 | 302 | cache[ #cache + 1 ] = res; 303 | return index, res; 304 | end 305 | -- STRING NO ESCAPING NEEDED 306 | decode['\''] = function( self, index, str, cache ) 307 | local finish = find( str, ';', index, true ); 308 | local res = sub( str, index, finish - 1 ) 309 | index = finish + 1; 310 | 311 | cache[ #cache + 1 ] = res; 312 | return index, res; 313 | end 314 | 315 | -- NUMBER 316 | decode['n'] = function( self, index, str, cache ) 317 | index = index - 1; 318 | local finish = find( str, ';', index, true ); 319 | local num = tonumber( sub( str, index, finish - 1 ) ); 320 | index = finish + 1; 321 | return index, num; 322 | end 323 | decode['0'] = decode['n']; 324 | decode['1'] = decode['n']; 325 | decode['2'] = decode['n']; 326 | decode['3'] = decode['n']; 327 | decode['4'] = decode['n']; 328 | decode['5'] = decode['n']; 329 | decode['6'] = decode['n']; 330 | decode['7'] = decode['n']; 331 | decode['8'] = decode['n']; 332 | decode['9'] = decode['n']; 333 | decode['-'] = decode['n']; 334 | -- positive hex 335 | decode['X'] = function( self, index, str, cache ) 336 | local finish = find( str, ';', index, true ); 337 | local num = tonumber( sub( str, index, finish - 1), 16 ); 338 | index = finish + 1; 339 | return index, num; 340 | end 341 | -- negative hex 342 | decode['x'] = function( self, index, str, cache ) 343 | local finish = find( str, ';', index, true ); 344 | local num = -tonumber( sub( str, index, finish - 1), 16 ); 345 | index = finish + 1; 346 | return index, num; 347 | end 348 | 349 | -- POINTER 350 | decode['('] = function( self, index, str, cache ) 351 | local finish = find( str, ')', index, true ); 352 | local num = tonumber( sub( str, index, finish - 1), 16 ); 353 | index = finish + 1; 354 | return index, cache[ num ]; 355 | end 356 | 357 | -- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO. 358 | decode[ 't' ] = function( self, index ) 359 | return index, true; 360 | end 361 | decode[ 'f' ] = function( self, index ) 362 | return index, false; 363 | end 364 | 365 | -- VECTOR 366 | decode[ 'v' ] = function( self, index, str, cache ) 367 | local finish = find( str, ';', index, true ); 368 | local vecStr = sub( str, index, finish - 1 ); 369 | index = finish + 1; -- update the index. 370 | local segs = Explode( ',', vecStr, false ); 371 | return index, Vector( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) ); 372 | end 373 | -- ANGLE 374 | decode[ 'a' ] = function( self, index, str, cache ) 375 | local finish = find( str, ';', index, true ); 376 | local angStr = sub( str, index, finish - 1 ); 377 | index = finish + 1; -- update the index. 378 | local segs = Explode( ',', angStr, false ); 379 | return index, Angle( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) ); 380 | end 381 | -- ENTITY 382 | decode[ 'E' ] = function( self, index, str, cache ) 383 | if( str[index] == '#' )then 384 | index = index + 1; 385 | return index, NULL ; 386 | else 387 | local finish = find( str, ';', index, true ); 388 | local num = tonumber( sub( str, index, finish - 1 ) ); 389 | index = finish + 1; 390 | return index, Entity( num ); 391 | end 392 | end 393 | -- PLAYER 394 | decode[ 'P' ] = function( self, index, str, cache ) 395 | local finish = find( str, ';', index, true ); 396 | local num = tonumber( sub( str, index, finish - 1 ) ); 397 | index = finish + 1; 398 | return index, Entity( num ) or NULL; 399 | end 400 | -- NIL 401 | decode['?'] = function( self, index, str, cache ) 402 | return index + 1, nil; 403 | end 404 | 405 | 406 | function pon.decode( data ) 407 | local _, res = decode[sub(data,1,1)]( decode, 2, data, {}); 408 | return res; 409 | end 410 | end 411 | --------------------------------------------------------------------------------