From eabc71f73ebe7dc37e17ab1b7f18c12affb4c621 Mon Sep 17 00:00:00 2001 From: Grocel Date: Wed, 19 Jul 2023 22:58:41 +0200 Subject: [PATCH] Anti-Abuse / security changes, bugfixes, better UX and better performance. UX: - Moved error buttons to the side to make more space for the error message. - Added entity options (C-Menu > Right click) for the radio. - Added clientside only options to the entity options and entity property editor for volume and muting. (Not affected by Prop Protection) - Added a clientside only way to look up the error state and the URL on GUI less radios (e.g. speakers). (Not affected by Prop Protection) - Added a better explanation for "error 20". - Added more error codes for future updates. - Added mute option (not clientside!) to the toolgun and added seperator lines to its UI. Performance / Networking: - Networking is now delayed to ease on the server performance. - The GUI is loaded with a delay to avoid freezes on radio spawns. - The GUI is actually deleted when disabled instead of just being hidden. - Fixed freezes when entering/leaving a room full of radios. - The radio being randomly broken/desynced (empty or white screen) on spawn is a way less likely to happen. If it does disabling/enabling the GUI can fix it. Anti-Abuse / Security: - Radios from other players can be muted via the addon settings. Does not support buddies. - Individual radios can be muted on the clientside, even if Prop Protection would block access otherwise. - Individual radios can have their volume changed the clientside, even if Prop Protection would block access otherwise. - Added support to the cfc_cl_http_whitelist addon. It is a security addon that whitelist and monitors HTTP and streaming requests. The Radio has a decicated error state for blocked URLs and content now. - Clientside GM_BASS3 loading can be disabled by the server. (sv_streamradio_bass3_allow_client 0) ConVars: - Changed the CVar default of sv_streamradio_rebuildplaylists_community_auto to 2. Pre-existing installs are not affected. - Added sv_streamradio_bass3_enable: When set to 1, it uses GM_BASS3 on the server if installed. Default: 1 - Added sv_streamradio_bass3_allow_client: Allows connected clients to use GM_BASS3 when set to 1. Overrides cl_streamradio_bass3_enable. Default: 1 - Added cl_streamradio_bass3_enable: When set to 1, it uses GM_BASS3 if installed and allowed on the server. Default: 1 GM_BASS3: - If installed GM_BASS3 is lazy loaded now. It only loads if it is actually needed by an radio. GM_BASS3 is not supported on x86-64 branches. --- lua/autorun/streamradio_loader.lua | 464 ++--------- lua/entities/base_streamradio.lua | 8 +- lua/entities/base_streamradio_gui.lua | 198 +++-- lua/entities/sent_streamradio/cl_init.lua | 21 +- lua/entities/sent_streamradio/init.lua | 75 +- lua/entities/sent_streamradio/shared.lua | 131 ++- lua/streamradio_core/api.lua | 16 +- lua/streamradio_core/bass3.lua | 269 ++++++ lua/streamradio_core/cache.lua | 14 +- .../classes/base_listener.lua | 146 ++-- lua/streamradio_core/classes/clientconvar.lua | 3 - .../classes/gui_controller.lua | 66 +- .../classes/skin_controller.lua | 2 +- lua/streamradio_core/classes/stream.lua | 152 ++-- lua/streamradio_core/classes/ui/button.lua | 117 ++- lua/streamradio_core/classes/ui/debug.lua | 4 +- .../classes/ui/highlighter.lua | 36 +- lua/streamradio_core/classes/ui/image.lua | 4 +- lua/streamradio_core/classes/ui/label.lua | 4 +- .../classes/ui/label_fade.lua | 14 +- lua/streamradio_core/classes/ui/list.lua | 2 +- lua/streamradio_core/classes/ui/panel.lua | 47 +- .../classes/ui/progressbar.lua | 98 ++- .../classes/ui/radio/gui_browser.lua | 82 +- .../classes/ui/radio/gui_errorbox.lua | 130 +-- .../classes/ui/radio/gui_player.lua | 62 +- .../classes/ui/radio/gui_player_controls.lua | 32 +- .../classes/ui/radio/gui_player_spectrum.lua | 48 +- .../classes/ui/shadow_panel.lua | 36 +- lua/streamradio_core/classes/ui/text.lua | 6 +- lua/streamradio_core/classes/ui/textview.lua | 2 + lua/streamradio_core/classes/ui/tooltip.lua | 9 +- lua/streamradio_core/client/cl_help.lua | 479 +++-------- lua/streamradio_core/client/cl_lib.lua | 8 +- lua/streamradio_core/client/cl_menu.lua | 15 + .../client/cl_playlist_edit.lua | 4 +- lua/streamradio_core/client/cl_surface.lua | 2 +- lua/streamradio_core/client/cl_vgui.lua | 14 +- .../client/settings/general.lua | 19 +- lua/streamradio_core/enum.lua | 100 +-- lua/streamradio_core/error.lua | 771 ++++++++++++++++++ lua/streamradio_core/interface.lua | 82 -- lua/streamradio_core/interfaces/dropbox.lua | 13 +- lua/streamradio_core/interfaces/shoutcast.lua | 29 +- lua/streamradio_core/interfaces/youtube.lua | 68 +- lua/streamradio_core/json.lua | 44 +- lua/streamradio_core/language.lua | 72 ++ lua/streamradio_core/lib.lua | 161 ++-- lua/streamradio_core/load.lua | 4 + .../models/111as_h500_radio.lua | 4 +- .../models/cs_office_radio.lua | 4 +- lua/streamradio_core/models/cs_office_tv.lua | 4 +- .../models/csgo_italy_radio.lua | 4 +- .../models/fallout3_jukebox.lua | 4 +- .../models/hl2_crtscreen_big.lua | 4 +- .../models/hl2_crtscreen_small.lua | 4 +- lua/streamradio_core/models/hl2_radio.lua | 4 +- lua/streamradio_core/models/kankan_radio.lua | 4 +- .../models/kresopolski_radio.lua | 28 +- .../models/minecraft_jukebox.lua | 4 +- lua/streamradio_core/models/nm_screen.lua | 4 +- lua/streamradio_core/models/plasma_tv.lua | 4 +- lua/streamradio_core/models/portal_radio.lua | 4 +- .../models/sw_ghettoblaster.lua | 4 +- lua/streamradio_core/models/sw_gramophone.lua | 4 +- lua/streamradio_core/models/sw_jukebox.lua | 4 +- lua/streamradio_core/models/sw_radio.lua | 4 +- .../models/wire_monitor_big.lua | 4 +- .../models/wire_monitor_small.lua | 4 +- lua/streamradio_core/network.lua | 101 ++- lua/streamradio_core/print.lua | 194 +++++ lua/streamradio_core/properties.lua | 625 ++++++++++++++ lua/streamradio_core/server/sv_lib.lua | 4 +- .../server/sv_playlist_edit.lua | 2 +- lua/streamradio_core/server/sv_res.lua | 20 +- lua/streamradio_core/timer.lua | 8 +- lua/streamradio_core/tool.lua | 19 +- lua/weapons/gmod_tool/stools/streamradio.lua | 19 +- materials/3dstreamradio/_data/version.vmt | 4 +- 79 files changed, 3614 insertions(+), 1669 deletions(-) create mode 100644 lua/streamradio_core/bass3.lua create mode 100644 lua/streamradio_core/error.lua create mode 100644 lua/streamradio_core/language.lua create mode 100644 lua/streamradio_core/print.lua create mode 100644 lua/streamradio_core/properties.lua diff --git a/lua/autorun/streamradio_loader.lua b/lua/autorun/streamradio_loader.lua index e581a35..c6ac326 100644 --- a/lua/autorun/streamradio_loader.lua +++ b/lua/autorun/streamradio_loader.lua @@ -1,164 +1,77 @@ -- Loader of the 3D Stream Radio. Made By Grocel. -local IsValid = IsValid -local error = error -local ErrorNoHalt = ErrorNoHalt -local pcall = pcall -local require = require -local tonumber = tonumber -local tostring = tostring -local collectgarbage = collectgarbage - -local CL = CLIENT -local SV = SERVER -local string = string -local concommand = concommand -local system = system -local file = file -local net = net -local hook = hook - -local Gmodversion = VERSION - -local versiondata = file.Read("materials/3dstreamradio/_data/version.vmt", "GAME") or "" -versiondata = string.Explode("[\r\n|\r|\n]", versiondata, true) or {} - -local Version = string.Trim(tostring(versiondata[1] or "")) -local VersionTime = tonumber(string.Trim(versiondata[2] or "")) or -1 - -if Version == "" then - Version = "UNKNOWN" +AddCSLuaFile() + +local function getVersion() + local versiondata = file.Read("materials/3dstreamradio/_data/version.vmt", "GAME") or "" + versiondata = string.Explode("[\r\n|\r|\n]", versiondata, true) or {} + + local Version = string.Trim(tostring(versiondata[1] or "")) + local VersionTime = tonumber(string.Trim(versiondata[2] or "")) or -1 + + if Version == "" then + Version = "UNKNOWN" + end + + return Version, VersionTime end -local AddonTitle = ( "3D Stream Radio (ver. " .. Version .. ")" ) -local AddonPrefix = ( AddonTitle .. ":\n" ) +local g_version, g_versionTime = getVersion() -local thisfile = "autorun/streamradio_loader.lua" +local AddonTitle = ( "3D Stream Radio (ver. " .. g_version .. ")" ) +local AddonPrefix = ( AddonTitle .. ":\n" ) StreamRadioLib = StreamRadioLib or {} +table.Empty(StreamRadioLib) + StreamRadioLib.AddonTitle = AddonTitle StreamRadioLib.AddonPrefix = AddonPrefix StreamRadioLib.Loaded = nil -StreamRadioLib.HasBass = false StreamRadioLib.ErrorString = nil function StreamRadioLib.GetVersion() - return Version + return g_version end function StreamRadioLib.GetVersionTime() - return VersionTime + return g_versionTime end -function StreamRadioLib.IsDebug() - local devconvar = GetConVar("developer") - if not devconvar then return end - - return devconvar:GetInt() > 0 -end +local g_loader_ok = true -local loader_ok = true - -local g_loaded_dll = {} -local g_dllSupportedBranches = { - ["dev"] = true, - ["prerelease"] = true, - ["unknown"] = true, - ["none"] = true, - ["live"] = true, - ["main"] = true, - [""] = true, -} - -local function saveRequireDLL(dll, optional) - if not StreamRadioLib then - return false, nil - end - - if not StreamRadioLib.IsDebug then - return false, nil - end +local g_loaded_cs = {} +local g_loaded_lua = {} - dll = tostring(dll or "") - dll = string.lower(dll) +local function appendError(err) + local lib = StreamRadioLib or {} - if dll == "" then - return false, nil - end - - if g_loaded_dll[dll] then - return g_loaded_dll[dll] or false, nil + err = tostring(err or "") + if err == "" then + return end - local realm = SERVER and "sv" or "cl" - local osname = system.IsWindows() and "win32" or "linux" - local dllfile = "lua/bin/gm" .. realm .. "_" .. dll .. "_" .. osname .. ".dll" - local branch = BRANCH or "" - - local status, err = pcall(function() - if not file.Exists(dllfile, "GAME") then - if optional then - return - end - - error("Couldn't require file '" .. dllfile .. "' (File not found)", 0) - end - - if g_dllSupportedBranches[branch] then - if optional then - return - end - - error(dllfile .. " is not supported on branch '" .. branch .. "'!\n") - end - - require(dll) - end) - - if not status then - err = tostring(err or "") - - if err == "" then - err = "Unknown error" - end - - if optional then - if StreamRadioLib.IsDebug() then - ErrorNoHalt((StreamRadioLib.AddonPrefix or "") .. err .. "\n") - end + lib.ErrorString = lib.ErrorString or "" + lib.ErrorString = string.Trim(lib.ErrorString .. "\n\n" .. err) +end - return false, err - end +local function throwError(err) + local lib = StreamRadioLib or {} - StreamRadioLib.ErrorString = StreamRadioLib.ErrorString or "" + err = tostring(err or "") - if StreamRadioLib.ErrorString == "" then - StreamRadioLib.ErrorString = err - else - StreamRadioLib.ErrorString = StreamRadioLib.ErrorString .. "\n" .. err - end + if err == "" then + err = "Unknown error" + end - StreamRadioLib.Loaded = nil - g_loaded_dll[dll] = nil - loader_ok = false + appendError(err) - ErrorNoHalt((StreamRadioLib.AddonPrefix or "") .. err .. "\n") - return false, err - end + g_loader_ok = false + lib.Loaded = nil - g_loaded_dll[dll] = true - return true, nil + ErrorNoHaltWithStack((lib.AddonPrefix or "") .. err .. "\n") + return false, err end -local g_loaded_cs = {} local function saveCSLuaFile(lua, force) - if not StreamRadioLib then - return false - end - - if not StreamRadioLib.IsDebug then - return false - end - lua = tostring(lua or "") lua = string.lower(lua or "") @@ -170,10 +83,12 @@ local function saveCSLuaFile(lua, force) g_loaded_cs[lua] = nil end - if g_loaded_cs[lua] then + if g_loaded_cs[lua] ~= nil then return g_loaded_cs[lua] or false end + g_loaded_cs[lua] = false + local status, err = pcall(function() if CLIENT then return @@ -187,25 +102,7 @@ local function saveCSLuaFile(lua, force) end) if not status then - err = tostring(err or "") - - if err == "" then - err = "Unknown error" - end - - StreamRadioLib.ErrorString = StreamRadioLib.ErrorString or "" - - if StreamRadioLib.ErrorString == "" then - StreamRadioLib.ErrorString = err - else - StreamRadioLib.ErrorString = StreamRadioLib.ErrorString .. "\n" .. err - end - - StreamRadioLib.Loaded = nil - g_loaded_cs[lua] = nil - loader_ok = false - - ErrorNoHalt((StreamRadioLib.AddonPrefix or "") .. err .. "\n") + throwError(err) return false end @@ -213,17 +110,7 @@ local function saveCSLuaFile(lua, force) return true end -local g_loaded = {} - -local function saveinclude(lua, force) - if not StreamRadioLib then - return nil - end - - if not StreamRadioLib.IsDebug then - return nil - end - +local function saveInclude(lua, force) lua = tostring(lua or "") lua = string.lower(lua or "") @@ -231,22 +118,13 @@ local function saveinclude(lua, force) return nil end - if StreamRadioLib.IsDebug() then - -- For easier reloading during development - local result = include(lua) - return true, result - end - - -- Anything below is advanced error handling. - -- It is to ensure that the addon has loaded correctly and completely without errors. - if force then - g_loaded[lua] = nil + g_loaded_lua[lua] = nil end - if g_loaded[lua] then + if g_loaded_lua[lua] then -- Prevent loading twice - return true, g_loaded[lua] + return true, g_loaded_lua[lua] end local status, errOrResult = pcall(function() @@ -258,256 +136,69 @@ local function saveinclude(lua, force) end) if not status then - local err = tostring(errOrResult or "") - - if err == "" then - err = "Unknown error" - end + throwError(errOrResult) - StreamRadioLib.ErrorString = StreamRadioLib.ErrorString or "" + g_loaded_lua[lua] = nil - if StreamRadioLib.ErrorString == "" then - StreamRadioLib.ErrorString = err - else - StreamRadioLib.ErrorString = StreamRadioLib.ErrorString .. "\n" .. err - end - - StreamRadioLib.Loaded = nil - g_loaded[lua] = nil - loader_ok = false - - ErrorNoHalt((StreamRadioLib.AddonPrefix or "") .. err .. "\n") return nil end - g_loaded[lua] = errOrResult + g_loaded_lua[lua] = errOrResult return status, errOrResult end -local function loadBASS3() - if BASS3 and BASS3.Version and BASS3.ModuleVersion then - return true - end - - local dll = "bass3" - local dll_name = string.upper("gm_" .. dll) - - local status = saveRequireDLL(dll, true) - - if not status then - return false - end - - if not BASS3 then - return false - end - - if not BASS3.Version then - return false - end - - if not BASS3.ModuleVersion then - return false - end - - local BassModuleVersion = tonumber(BASS3.ModuleVersion) or 0 - - if BassModuleVersion < 14 then - local ErrorString = dll_name .. " is outdated!\n" - ErrorNoHalt(AddonPrefix .. ErrorString .. "\n") - - return false - end - - return true -end - function StreamRadioLib.SaveCSLuaFile(lua, force) return saveCSLuaFile(lua, force) end function StreamRadioLib.LoadSH(lua, force) if not saveCSLuaFile(lua) then return end - return saveinclude(lua, force) + return saveInclude(lua, force) end function StreamRadioLib.LoadCL(lua, force) - if SV then + if SERVER then return saveCSLuaFile(lua) end - return saveinclude(lua, force) + return saveInclude(lua, force) end function StreamRadioLib.LoadSV(lua, force) - if CL then return true end - return saveinclude(lua, force) -end - -local function getTextWithoutColor(text) - text = tostring(text or "") - text = string.gsub(text, "%[color%:[ ]?%d+[ %,][ ]?%d+[ %,][ ]?%d+%]", "") - - return text -end - -local function printColored(text) - text = tostring(text or "") - - local default = "[color:255,255,255]" - local lastcolor = default - local curcolor = Color(255, 255, 255, 255) - - text = default .. text .. default - - for data, color in string.gmatch(text, "(.-)(%[color%:[ ]?%d+[ %,][ ]?%d+[ %,][ ]?%d+%])") do - data = data or "" - color = color or "" - - if color ~= "" then - local r, g, b = string.match(lastcolor, "%[color%:[ ]?(%d+)[ %,][ ]?(%d+)[ %,][ ]?(%d+)%]") - - if r and g and b then - r = math.Clamp(tonumber(r) or 0, 0, 255) - g = math.Clamp(tonumber(g) or 0, 0, 255) - b = math.Clamp(tonumber(b) or 0, 0, 255) - - curcolor.r = r - curcolor.g = g - curcolor.b = b - end - end - - if data ~= "" then - MsgC(curcolor, data) - end - - lastcolor = color - end -end - -local function indentText(text, spaces) - text = tostring(text or "") - spaces = tonumber(spaces or 2) or 2 - - spaces = string.rep(" ", spaces) - text = string.gsub(spaces .. text, "\n", "\n" .. spaces) - - return text + if CLIENT then return true end + return saveInclude(lua, force) end -local function printWrapped(texts, ...) - if not istable(texts) then - texts = {texts} - end - - texts = table.Add(texts, {...}) - - local textlines = {} - - local longestline = 0 - - for k, v in pairs(texts) do - v = tostring(v or "") - - local lines = string.Explode("\n", v, false) - textlines[#textlines + 1] = lines - - for i, u in ipairs(lines) do - local collessu = getTextWithoutColor(u) - local len = #collessu - - if len <= longestline then - continue - end +do + local printLoaded = StreamRadioLib.LoadSH("streamradio_core/print.lua") - longestline = len - end - end - - local border_color = SERVER and "[color:137,222,255]" or "[color:255,222,102]" - local text_color = "[color:255,255,255]" - - local borderside_l = "=== " - local borderside_r = " ===" - - local border = border_color .. string.rep("=", longestline + #borderside_l + #borderside_r) - local border_inner = border_color .. string.rep("-", longestline) - - borderside_l = border_color .. "=== " - borderside_r = border_color .. " ===" - - local function group(lines, addborder) - if addborder then - printColored(borderside_l .. border_inner .. borderside_r .. "\n") - end - - for i, v in ipairs(lines) do - local collessv = getTextWithoutColor(v) - local len = #collessv - local slen = math.Clamp(longestline - len, 0, longestline) - local spaces = string.rep(" ", slen) - - local line = v .. spaces - printColored(borderside_l .. text_color .. line .. borderside_r .. "\n") - end - end - - printColored(border .. "\n") - - for i, v in ipairs(textlines) do - group(v, i > 1) - end - - printColored(border .. "\n") -end - -saveCSLuaFile(thisfile) -StreamRadioLib.HasBass = loadBASS3() - -local bassload_msg - -if SV then - bassload_msg = "[color:255,150,50]Serverside streaming API for advanced wire outputs could not be loaded!" - - if StreamRadioLib.HasBass then - bassload_msg = "[color:100,200,100]Serverside streaming API for advanced wire outputs loaded!" - bassload_col = "[color:100,200,100]" - end -else - bassload_msg = "[color:255,150,50]No clientside streaming API using GMod's one!" - - if StreamRadioLib.HasBass then - bassload_msg = "[color:100,200,100]Clientside streaming API loaded!" - bassload_col = "[color:100,200,100]" + if not printLoaded or not StreamRadioLib.Print then + throwError(AddonTitle .. "Fatal error: Print and reporting system not loaded!") + return end end local outdated = false if CLIENT then - if Gmodversion < 220713 and Gmodversion > 5 then - StreamRadioLib.ErrorString = "Your GMod-Client (Version: " .. Gmodversion .. ") is too old!\nPlease update the GMod-Client!" + if VERSION < 230714 and VERSION > 5 then + throwError("Your GMod-Client (Version: " .. VERSION .. ") is too old!\nPlease update the GMod-Client!") outdated = true - - ErrorNoHalt(AddonPrefix .. StreamRadioLib.ErrorString .. "\n") end else - if Gmodversion < 220713 and Gmodversion > 5 then - StreamRadioLib.ErrorString = "The GMod-Server (Version: " .. Gmodversion .. ") is too old!\nPlease update the GMod-Server!" + if VERSION < 230714 and VERSION > 5 then + throwError("The GMod-Server (Version: " .. VERSION .. ") is too old!\nPlease update the GMod-Server. Tell an Admin!") outdated = true - - ErrorNoHalt(AddonPrefix .. StreamRadioLib.ErrorString .. "\n") end end if not outdated then local status, loaded = StreamRadioLib.LoadSH("streamradio_core/load.lua") - StreamRadioLib.Loaded = status and loaded and loader_ok + StreamRadioLib.Loaded = status and loaded and g_loader_ok end local realmname = "clientside" -if SV then +if SERVER then realmname = "serverside" end @@ -518,15 +209,15 @@ if not StreamRadioLib.Loaded then end local errcol = "[color:255,128,128]" - local err = errcol .. indentText(StreamRadioLib.ErrorString) + local err = errcol .. StreamRadioLib.Print.IndentText(StreamRadioLib.ErrorString) err = string.Replace(err, "\n", "\n" .. errcol) - printWrapped(AddonTitle .. "[color:255,128,128] could not be loaded " .. realmname .. ".", "Error:\n" .. err) + StreamRadioLib.Print.Wrapped(AddonTitle .. "[color:255,128,128] could not be loaded " .. realmname .. ".", "Error:\n" .. err) else - printWrapped(AddonTitle .. "[color:100,200,100] is loaded " .. realmname .. ".", "Optional GM_BASS3:\n" .. indentText(bassload_msg)) + StreamRadioLib.Print.Wrapped(AddonTitle .. "[color:100,200,100] is loaded " .. realmname .. ".") end -if SV then +if SERVER then util.AddNetworkString("3DStreamRadio/LoadError") hook.Add("PlayerInitialSpawn", "3DStreamRadio/LoadError", function(ply) @@ -550,13 +241,8 @@ else net.Receive("3DStreamRadio/LoadError", function() local err = net.ReadString() if err == "" then return end - if not StreamRadioLib then return end - - StreamRadioLib.ErrorString = StreamRadioLib.ErrorString or "" - StreamRadioLib.ErrorString = string.Trim(StreamRadioLib.ErrorString .. "\n\n" .. err) - ErrorNoHalt((StreamRadioLib.AddonPrefix or "") .. StreamRadioLib.ErrorString .. "\n") - StreamRadioLib.Loaded = nil + throwError(err) end) end diff --git a/lua/entities/base_streamradio.lua b/lua/entities/base_streamradio.lua index 2144c93..c9f276a 100644 --- a/lua/entities/base_streamradio.lua +++ b/lua/entities/base_streamradio.lua @@ -355,7 +355,7 @@ function ENT:IsMutedForPlayer(ply) if not ply:IsPlayer() then return true end if ply:IsBot() then return true end - if StreamRadioLib.IsMuted(ply) then + if StreamRadioLib.IsMuted(ply, self:GetRealRadioOwner()) then return true end @@ -378,6 +378,10 @@ function ENT:IsMutedForAll() return true end + if self:GetSVMute() then + return true + end + local allplayers = player.GetHumans() for k, v in pairs(allplayers) do @@ -457,8 +461,6 @@ function ENT:FastThink() return end - LIBNetwork.Pull(self) - if SERVER then if self.__IsWiremodLoaded then self:WiremodThink() diff --git a/lua/entities/base_streamradio_gui.lua b/lua/entities/base_streamradio_gui.lua index 308cec6..0420b6f 100644 --- a/lua/entities/base_streamradio_gui.lua +++ b/lua/entities/base_streamradio_gui.lua @@ -14,6 +14,8 @@ ENT.AdminOnly = false local ang_zero = Angle( ) local vec_zero = Vector( ) +local g_displayBuildTimer = 0 + function ENT:SetScale(scale) self.Scale = scale or 0 end @@ -28,7 +30,7 @@ function ENT:SetDisplayPosAng(pos, ang) end function ENT:GetDisplayPos( ) - if not self:HasGUI() then return end + if self.NoDisplay then return end if self:GetDisableDisplay() then return end local pos = self:GetPos( ) @@ -242,31 +244,46 @@ function ENT:SetUpModel() self:SetSoundPosAng(MD.SoundPosOffset, MD.SoundAngOffset) - if self.NoDisplay then - if IsValid(self.GUI_Main) then - self.GUI_Main:Remove() - self.GUI_Main = nil - end + if self.OnModelSetup then + self:OnModelSetup() + end +end - if IsValid(self.GUI) then - self.GUI:Remove() - self.GUI = nil - end +function ENT:RemoveGui() + local tmpGui = self.GUI + local tmpGuiMain = self.GUI_Main - if self.OnSetupModelSetup then - self:OnSetupModelSetup() - end + local hasGui = IsValid(tmpGui) + local hasGuiMain = IsValid(tmpGuiMain) - return + if (hasGui or hasGuiMain) and self.OnGUIRemove then + self:OnGUIRemove(tmpGui, tmpGuiMain) end - self:SetupGui() + self.GUI = nil + self.GUI_Main = nil + + if hasGui then + tmpGui:Remove() + tmpGui = nil + end - if self.OnSetupModelSetup then - self:OnSetupModelSetup() + if hasGuiMain then + tmpGuiMain:Remove() + tmpGuiMain = nil end end +function ENT:ResetGui() + self:RemoveGui() + + if self.NoDisplay or self:GetDisableDisplay() then + return + end + + self:SetupGui() +end + function ENT:SetupGui() if not IsValid(self.GUI) then self.GUI = StreamRadioLib.CreateOBJ("gui_controller") @@ -319,6 +336,10 @@ function ENT:SetupGui() self.GUI:SetSkin(LIBSkin.GetDefaultSkin()) self.GUI:PerformRerender(true) + + if self.OnGUISetup then + self:OnGUISetup(self.GUI, self.GUI_Main) + end end function ENT:StreamOnConnect(stream, channel, metadata) @@ -359,8 +380,34 @@ function ENT:GetGUIMain() return self.GUI_Main end +function ENT:PollGuiSetup() + if self.NoDisplay then + return + end + + if self:GetDisableDisplay() then + return + end + + if self.GUI then + return + end + + if SysTime() > g_displayBuildTimer then + self:SetupGui() + + -- delay the GUI rebuild in case many newly spawned radios are seen at once + g_displayBuildTimer = SysTime() + 0.2 + end +end + function ENT:FastThink() BaseClass.FastThink(self) + + if SERVER then + self:PollGuiSetup() + end + self:ControlThink(self:GetLastUser(), self:GetLastUsingEntity()) end @@ -447,13 +494,23 @@ function ENT:SetupDataTables() } }) + self:AddDTNetworkVar( "Bool", "DisableSpectrum", { + KeyName = "DisableSpectrum", + Edit = { + category = "GUI", + title = "Disable spectrum", + type = "Boolean", + order = 12 + } + }) + self:AddDTNetworkVar( "Bool", "EnableDebug", { KeyName = "EnableDebug", Edit = { category = "GUI", title = "Show debug panel", type = "Boolean", - order = 12 + order = 13 } }) @@ -461,6 +518,12 @@ function ENT:SetupDataTables() if not IsValid(self.GUI) then return end self.GUI:SetDebug(newv) end) + + if CLIENT then + LIBNetwork.SetDTVarCallback(self, "DisableDisplay", function(this, name, oldv, newv) + self:RemoveGui() + end) + end end function ENT:IsPlaylistEnabled() @@ -484,27 +547,8 @@ function ENT:Initialize() end function ENT:OnRemove() - local GUI_Main = self.GUI_Main - local GUI = self.GUI - self:CallModelFunction("OnRemove", model) - - -- We run it in a timer to ensure the entity is actually gone - timer.Simple(0.05, function() - if IsValid(self) then - return - end - - if IsValid(GUI_Main) then - GUI_Main:Remove() - GUI_Main = nil - end - - if IsValid(GUI) then - GUI:Remove() - GUI = nil - end - end) + self:RemoveGui() BaseClass.OnRemove(self) end @@ -525,7 +569,17 @@ function ENT:OnPlayerShown() -- Override me end -function ENT:OnSetupModelSetup() +function ENT:OnModelSetup() + -- Override me +end + +function ENT:OnGUISetup() + if self._postClasssystemPasteLoadDupeOnGUISetup then + self:PostClasssystemPaste() + end +end + +function ENT:OnGUIRemove() -- Override me end @@ -543,13 +597,12 @@ if CLIENT then function ENT:CanSeeDisplay() if not self.__IsLibLoaded then return false end - if not self:HasGUI() then return false end + if self.NoDisplay then return false end if self:GetDisableDisplay() then return false end local ply = LocalPlayer() if StreamRadioLib.IsGUIHidden(ply) then return false end if not self:OnGUIShowCheck(ply) then return false end - local scale = self:GetScale() if scale <= 0 then return false end @@ -569,7 +622,6 @@ if CLIENT then function ENT:GetCurserFromLastUser() if not self.__IsLibLoaded then return false end - if not IsValid(self.GUI) then return false end local lastUser = self:GetLastUser() local userEntity = self:GetLastUsingEntity() @@ -585,14 +637,48 @@ if CLIENT then return self:GetCursor(lastUser, nil, userEntity) end + function ENT:ShouldRemoveGUI() + local ply = LocalPlayer() + + if StreamRadioLib.IsGUIHidden(ply) then + return true + end + + return false + end + function ENT:DrawGUI() if not self.__IsLibLoaded then return end - if not IsValid(self.GUI) then return end - if not self:CanSeeDisplay() then return end + if self.NoDisplay then + return + end - local ply = LocalPlayer() - if not self:CheckDistanceToEntity(ply, StreamRadioLib.GetDrawDistance(), nil, StreamRadioLib.GetCameraViewPos(ply)) then return end + if self:GetDisableDisplay() then + return + end + + if self:ShouldRemoveGUI() then + if self.GUI then + self:RemoveGui() + end + + return + end + + if not self:CheckDistanceToEntity(ply, StreamRadioLib.GetDrawDistance(), nil, StreamRadioLib.GetCameraViewPos(ply)) then + return true + end + + self:PollGuiSetup() + + if not IsValid(self.GUI) then + return + end + + if not self:CanSeeDisplay() then + return false + end local lastUser = self:GetLastUsingEntity() local scale = self:GetScale() @@ -622,7 +708,9 @@ if CLIENT then function ENT:CanDrawSpectrum() if not self.__IsLibLoaded then return false end if not IsValid(self.GUI) then return false end + if StreamRadioLib.IsSpectrumHidden() then return false end + if self:GetDisableSpectrum() then return false end if not self:CanSeeDisplay() then return false end @@ -667,8 +755,20 @@ else end function ENT:PostClasssystemPaste() - BaseClass.PostClasssystemPaste( self ) - if not IsValid(self.GUI) then return end - self.GUI:LoadFromDupe() + BaseClass.PostClasssystemPaste(self) + + if not IsValid(self.GUI) then + self._postClasssystemPasteLoadDupeOnGUISetup = true + return + end + + self._postClasssystemPasteLoadDupeOnGUISetup = nil + + timer.Simple(0.25, function() + if not IsValid(self) then return end + if not IsValid(self.GUI) then return end + + self.GUI:LoadFromDupe() + end) end end diff --git a/lua/entities/sent_streamradio/cl_init.lua b/lua/entities/sent_streamradio/cl_init.lua index 231d41c..73e743b 100644 --- a/lua/entities/sent_streamradio/cl_init.lua +++ b/lua/entities/sent_streamradio/cl_init.lua @@ -78,7 +78,7 @@ function ENT:ApplyTuneSound() return end - if self.StreamObj:GetError() ~= 0 then + if self.StreamObj:HasError() then self.streamswitchsound = true self:StartTuneSound() return @@ -136,7 +136,7 @@ function ENT:Initialize() self:MarkForUpdatePlaybackLoopMode() end -function ENT:OnSetupModelSetup() +function ENT:OnModelSetup() self:StreamStopAnimModel() end @@ -302,7 +302,15 @@ function ENT:IsMuted() if not ply:IsPlayer() then return true end if ply:IsBot() then return true end - if StreamRadioLib.IsMuted(ply) then + if StreamRadioLib.IsMuted(ply, self:GetRealRadioOwner()) then + return true + end + + if self:GetSVMute() then + return true + end + + if self:GetCLMute() then return true end @@ -346,6 +354,7 @@ function ENT:UpdateStream() self.StreamObj:Set3DFadeDistance(self.Radius / 3) local muted = self:IsMuted() + local clVolume = self:GetCLVolume() local wallvol = 0 local distVolume = 0 @@ -360,7 +369,7 @@ function ENT:UpdateStream() self.PlayerDistance = playerDistance - local StreamVol = distVolume * wallvol + local StreamVol = distVolume * clVolume * wallvol self.StreamObj:SetMuted(muted) self.StreamObj:SetClientVolume(StreamVol) @@ -371,7 +380,7 @@ function ENT:UpdateStream() local global_vol = StreamRadioLib.GetGlobalVolume() global_vol = math.Clamp(global_vol, 0, 1) - self.NoiseSound:ChangeVolume(self.StreamObj:GetVolume() * global_vol * wallvol * self.NoiseSound_vol, 0.5) + self.NoiseSound:ChangeVolume(self.StreamObj:GetVolume() * global_vol * clVolume * wallvol * self.NoiseSound_vol, 0.5) end self:StreamAnimModel() @@ -410,7 +419,7 @@ function ENT:StreamAnimModel() return end - if stream:GetError() ~= 0 then + if stream:HasError() then self:CallModelFunction("WhileError") return end diff --git a/lua/entities/sent_streamradio/init.lua b/lua/entities/sent_streamradio/init.lua index 284087c..db742e3 100644 --- a/lua/entities/sent_streamradio/init.lua +++ b/lua/entities/sent_streamradio/init.lua @@ -4,6 +4,7 @@ include( "shared.lua" ) DEFINE_BASECLASS( "base_streamradio_gui" ) local StreamRadioLib = StreamRadioLib +local LIBError = StreamRadioLib.Error function ENT:InitializeModel() local model = self:GetModel() @@ -38,6 +39,9 @@ function ENT:Initialize( ) self.ActivateExtraName = "" + self:SetCLMute(false) + self:SetCLVolume(1) + BaseClass.Initialize( self ) if self.__IsLibLoaded then @@ -46,6 +50,7 @@ function ENT:Initialize( ) self:AddWireInput("Play", "NORMAL") self:AddWireInput("Pause", "NORMAL") + self:AddWireInput("Mute", "NORMAL") self:AddWireInput("Volume", "NORMAL") self:AddWireInput("Radius", "NORMAL") self:AddWireInput("LoopMode", "NORMAL") @@ -58,6 +63,7 @@ function ENT:Initialize( ) self:AddWireOutput("Play", "NORMAL") self:AddWireOutput("Paused", "NORMAL") self:AddWireOutput("Stopped", "NORMAL") + self:AddWireOutput("Muted", "NORMAL") self:AddWireOutput("Volume", "NORMAL") self:AddWireOutput("Radius", "NORMAL") @@ -116,6 +122,7 @@ function ENT:SetSettings(settings) self:SetStreamName(settings.StreamName or "") + self:SetSVMute(settings.StreamMute or false) self:SetVolume(settings.StreamVolume or 1) self:SetRadius(settings.Radius or 1200) @@ -123,6 +130,7 @@ function ENT:SetSettings(settings) self:SetDisableDisplay(settings.DisableDisplay or false) self:SetDisableInput(settings.DisableInput or false) + self:SetDisableSpectrum(settings.DisableSpectrum or false) self:SetDisableAdvancedOutputs(noadvoutputs) if settings.PlaybackLoopMode then @@ -149,12 +157,14 @@ function ENT:GetSettings() settings.StreamUrl = self:GetStreamURL() settings.StreamName = self:GetStreamName() + settings.StreamMute = self:GetSVMute() settings.StreamVolume = self:GetVolume() settings.Radius = self:GetRadius() settings.Sound3D = self:GetSound3D() settings.DisableDisplay = self:GetDisableDisplay() settings.DisableInput = self:GetDisableInput() + settings.DisableSpectrum = self:GetDisableSpectrum() settings.DisableAdvancedOutputs = self:GetDisableAdvancedOutputs() settings.PlaybackLoopMode = self:GetPlaybackLoopMode() @@ -175,7 +185,7 @@ function ENT:OnReloaded( ) if not self.__IsLibLoaded then return end local ply, model, pos, ang = self.pl, self:GetModel( ), self:GetPos( ), self:GetAngles( ) - StreamRadioLib.Msg( ply, "Reloaded " .. tostring( self ) ) + StreamRadioLib.Print.Msg( ply, "Reloaded " .. tostring( self ) ) self:Remove( ) StreamRadioLib.Timedcall( function( ply, model, pos, ang ) @@ -272,6 +282,7 @@ function ENT:WiremodThink( ) self:TriggerWireOutput("Paused", self.StreamObj:IsPauseMode()) self:TriggerWireOutput("Stopped", self.StreamObj:IsStopMode()) self:TriggerWireOutput("Volume", self:GetVolume()) + self:TriggerWireOutput("Muted", self:GetSVMute()) self:TriggerWireOutput("Radius", self:GetRadius()) self:TriggerWireOutput("LoopMode", self:GetPlaybackLoopMode()) @@ -297,14 +308,14 @@ function ENT:WiremodThink( ) self:TriggerWireOutput("This Radio", self) + local err = LIBError.STREAM_ERROR_WIRE_ADVOUT_DISABLED + if hasadvoutputs then - local err = self.StreamObj:GetError() - self:TriggerWireOutput("Error", err) - self:TriggerWireOutput("Error Text", StreamRadioLib.DecodeErrorCode(err)) - else - self:TriggerWireOutput("Error", 500) - self:TriggerWireOutput("Error Text", "Advanced outputs are disabled") + err = self.StreamObj:GetError() end + + self:TriggerWireOutput("Error", err) + self:TriggerWireOutput("Error Text", LIBError.GetStreamErrorDescription(err)) end function ENT:Think( ) @@ -325,19 +336,8 @@ function ENT:Think( ) self:SetToolMode(self.ExtraURLs.Tool ~= "") self:SetWireMode(self.ExtraURLs.Wire ~= "") - if oldActivateURL ~= self.ActivateExtraURL or oldActivateName ~= self.ActivateExtraName then - local name = self.ActivateExtraName - local urlForDisplay = self.ActivateExtraURL - - if StreamRadioLib.IsBlockedURLCode(urlForDisplay) then - urlForDisplay = "(Blocked URL)" - end - - if urlForDisplay ~= "" then - name = name .. ": " .. urlForDisplay - end - - self:OnExtraURL(name, self.ActivateExtraURL) + if oldActivateURL ~= self.ActivateExtraURL or oldActivateName ~= self.ActivateExtraName or self._ForceOnExtraURLUpdate then + self:OnExtraURLUpdate() end end @@ -355,6 +355,30 @@ function ENT:Think( ) return true end +function ENT:OnExtraURLUpdate() + local name = self.ActivateExtraName + local urlForDisplay = self.ActivateExtraURL + + if StreamRadioLib.IsBlockedURLCode(urlForDisplay) then + urlForDisplay = "(Blocked URL)" + end + + if urlForDisplay ~= "" then + name = name .. ": " .. urlForDisplay + end + + self:OnExtraURL(name, self.ActivateExtraURL) + self._ForceOnExtraURLUpdate = nil +end + +function ENT:OnGUISetupServer() + if self:GetMasterRadioRecursive() then + return + end + + self._ForceOnExtraURLUpdate = true +end + function ENT:SetToolURL(url, setmode) if not self.__IsLibLoaded then return end @@ -527,6 +551,17 @@ function ENT:OnWireInputTrigger(name, value, wired) return end + if name == "Mute" then + value = tobool(value) + + if not wired then + value = false + end + + self:SetSVMute(value) + return + end + if name == "Volume" then value = tonumber(value) or 0 diff --git a/lua/entities/sent_streamradio/shared.lua b/lua/entities/sent_streamradio/shared.lua index a1ffdf6..911f5d6 100644 --- a/lua/entities/sent_streamradio/shared.lua +++ b/lua/entities/sent_streamradio/shared.lua @@ -23,6 +23,7 @@ function ENT:SetupDataTables( ) self:AddDTNetworkVar("Bool", "WireMode") self:AddDTNetworkVar("Bool", "ToolMode") self:AddDTNetworkVar("Entity", "LastUser") + self:AddDTNetworkVar("Entity", "RadioOwner") self:AddDTNetworkVar("Entity", "LastUsingEntity") self:AddDTNetworkVar("Entity", "MasterRadio") @@ -35,20 +36,30 @@ function ENT:SetupDataTables( ) category = "Wiremod", title = "Disable advanced outputs", type = "Boolean", - order = 99, + order = 70, } } end self:AddDTNetworkVar("Bool", "DisableAdvancedOutputs", adv_wire) + self:AddDTNetworkVar("Bool", "SVMute", { + KeyName = "SVMute", + Edit = { + category = "Volume", + title = "Entity Mute", + type = "Boolean", + order = 20 + } + }) + self:AddDTNetworkVar("Float", "Volume", { KeyName = "Volume", Edit = { - category = "Stream", - title = "Volume", + category = "Volume", + title = "Entity Volume", type = "Float", - order = 20, + order = 21, min = 0, max = 1, } @@ -57,10 +68,10 @@ function ENT:SetupDataTables( ) self:AddDTNetworkVar("Int", "Radius", { KeyName = "Radius", Edit = { - category = "Stream", + category = "World Sound", title = "Radius", type = "Int", - order = 21, + order = 30, min = 0, max = 5000, } @@ -69,10 +80,10 @@ function ENT:SetupDataTables( ) self:AddDTNetworkVar("Bool", "Sound3D", { KeyName = "Sound3D", Edit = { - category = "Stream", + category = "World Sound", title = "Enable 3D sound", type = "Boolean", - order = 22 + order = 31 } }) @@ -82,7 +93,7 @@ function ENT:SetupDataTables( ) category = "Loop", title = "Enable song loop", type = "Boolean", - order = 30 + order = 40 } }) @@ -92,14 +103,65 @@ function ENT:SetupDataTables( ) category = "Loop", title = "Enable playlist loop", type = "Boolean", - order = 31 + order = 41 + } + }) + + self:AddDTNetworkVar("Bool", "CLMute", { + KeyName = "CLMute", + Edit = { + category = "Volume", + title = "Clientside Mute", + type = "Boolean", + order = 22, } }) + self:AddDTNetworkVar("Float", "CLVolume", { + KeyName = "CLVolume", + Edit = { + category = "Volume", + title = "Clientside Volume", + type = "Float", + order = 23, + min = 0, + max = 1, + } + }) + + self._radio_EditValue = self._radio_EditValue or self.EditValue + self.EditValue = function(this, variable, value) + // This workaround allows for clientonly traffic on those data table vars. + + if variable == "CLMute" then + if SERVER then + return + end + + local mute = tobool(value) + this:SetCLMute(mute) + + return + end + + if variable == "CLVolume" then + if SERVER then + return + end + + local volume = tonumber(value or 0) or 0 + this:SetCLVolume(volume) + + return + end + + return this:_radio_EditValue(variable, value) + end + LIBNetwork.SetDTVarCallback(self, "Loop", function(this, name, oldv, newv) if not IsValid(self) then return end - if newv then + if newv and SERVER then self:SetPlaylistLoop(false) end @@ -109,7 +171,7 @@ function ENT:SetupDataTables( ) LIBNetwork.SetDTVarCallback(self, "PlaylistLoop", function(this, name, oldv, newv) if not IsValid(self) then return end - if newv then + if newv and SERVER then self:SetLoop(false) end @@ -117,6 +179,23 @@ function ENT:SetupDataTables( ) end) end +function ENT:GetRealRadioOwner() + if isfunction(self.CPPIGetOwner) then + local owner = self:CPPIGetOwner() + + if isentity(owner) and IsValid(owner) then + return owner + end + end + + local owner = self:GetRadioOwner() + if IsValid(owner) then + return owner + end + + return nil +end + function ENT:GetPlaybackLoopMode() local loop = self:GetLoop() local playlistLoop = self:GetPlaylistLoop() @@ -266,7 +345,7 @@ function ENT:OnGUIShowCheck(ply) local master_st = masterradio.StreamObj - if master_st:GetError() ~= 0 then return true end + if master_st:HasError() then return true end if not master_st:IsStopMode() then return true end if master_st:GetURL() ~= "" then return true end @@ -281,7 +360,7 @@ function ENT:OnGUIInteractionCheck(ply, trace, userEntity) local master_st = masterradio.StreamObj - if master_st:GetError() ~= 0 then return true end + if master_st:HasError() then return true end if not master_st:IsStopMode() then return true end if master_st:GetURL() ~= "" then return true end @@ -453,14 +532,26 @@ function ENT:StreamStopAnimModel() self.AnimStopped = true end -function ENT:OnSetupModelSetup() - if IsValid(self.GUI_Main) then - self.GUI_Main.OnPlaybackLoopModeChange = function(this, newLoopMode) - if not IsValid(self) then return end - self:SetPlaybackLoopMode(newLoopMode) - end +function ENT:OnGUISetup() + BaseClass.OnGUISetup(self) + + if not IsValid(self.GUI_Main) then + return + end + + self.GUI_Main.OnPlaybackLoopModeChange = function(this, newLoopMode) + if not IsValid(self) then return end + self:SetPlaybackLoopMode(newLoopMode) end + self:MarkForUpdatePlaybackLoopMode() + + if SERVER then + self:OnGUISetupServer() + end +end + +function ENT:OnModelSetup() self.AnimStopped = nil self:StreamStopAnimModel() end diff --git a/lua/streamradio_core/api.lua b/lua/streamradio_core/api.lua index 0d05cca..71d27a2 100644 --- a/lua/streamradio_core/api.lua +++ b/lua/streamradio_core/api.lua @@ -22,11 +22,13 @@ StreamUrl String "" The streaming source URL. StreamName String "" Name of the stream. - StreamVolume number 1 0 is muted and 1 is 100% volume + StreamMute boolean false True mutes the stream + StreamVolume number 1 0 is 0% and 1 is 100% volume Radius number 1200 Number in units of the sound range Sound3D boolean true True enables the 3D world sound DisableInput boolean false True disables the radio controlling. Does not affect Wiremod controlling. DisableDisplay boolean false True disables the radio display. + DisableSpectrum boolean false True disables the spectrum visualization on the radio display. DisableAdvancedOutputs boolean true True disables the Advanced Wire Outputs. PlaybackLoopMode number 1 Loop mode: @@ -47,11 +49,13 @@ local ValidTypes = { StreamName = "string", StreamUrl = "string", + StreamMute = "boolean", StreamVolume = "number", Radius = "number", Sound3D = "boolean", DisableInput = "boolean", DisableDisplay = "boolean", + DisableSpectrum = "boolean", DisableAdvancedOutputs = "boolean", PlaybackLoopMode = "number", @@ -135,10 +139,8 @@ function StreamRadioLib.SpawnRadio( ply, model, pos, ang, settings ) local err = Prefix .. "The Entity 'sent_streamradio' could not be spawned." - if StreamRadioLib.Msg then - StreamRadioLib.Msg( ply, err ) - else - StreamRadioLib.ErrorNoHaltWithStack( err, 2 ) + if StreamRadioLib.ErrorNoHaltWithStack then + StreamRadioLib.ErrorNoHaltWithStack(err) end return @@ -182,6 +184,10 @@ function StreamRadioLib.SpawnRadio( ply, model, pos, ang, settings ) ent:SetLastUser(ply) end + if isfunction(ent.SetRadioOwner) then + ent:SetRadioOwner(ply) + end + for k, v in pairs(data) do ent[k] = v end diff --git a/lua/streamradio_core/bass3.lua b/lua/streamradio_core/bass3.lua new file mode 100644 index 0000000..6489f0d --- /dev/null +++ b/lua/streamradio_core/bass3.lua @@ -0,0 +1,269 @@ +StreamRadioLib.Bass = StreamRadioLib.Bass or {} +local LIB = StreamRadioLib.Bass + +local g_dll = "bass3" +local g_dllName = string.upper("gm_" .. g_dll) +local g_dllMinVersion = 14 + +local g_dllSupportedBranches = { + ["dev"] = true, + ["prerelease"] = true, + ["unknown"] = true, + ["none"] = true, + ["live"] = true, + ["main"] = true, + [""] = true, +} + +local g_bass_loaded = nil +local g_bass_dll_required = nil +local g_bass_can_loaded = nil +local g_bass_info_shown = nil + +local g_cvar_cl_bass3_enable = nil +local g_cvar_sv_bass3_enable = nil +local g_cvar_sv_bass3_allow_client = nil + +local function resetCache(...) + g_bass_loaded = nil + g_bass_can_loaded = nil +end + +if SERVER then + CreateConVar( + "sv_streamradio_bass3_enable", + "1", + bit.bor( FCVAR_NOTIFY, FCVAR_ARCHIVE ), + "When set to 1, it uses GM_BASS3 on the server if installed. Default: 1", + 0, + 1 + ) + + CreateConVar( + "sv_streamradio_bass3_allow_client", + "1", + bit.bor( FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_REPLICATED ), + "Allows connected clients to use GM_BASS3 when set to 1. Overrides cl_streamradio_bass3_enable. Default: 1", + 0, + 1 + ) + + cvars.AddChangeCallback("sv_streamradio_bass3_enable", resetCache, "streamradio_bass3_callback") +end + +if CLIENT then + cvars.AddChangeCallback("cl_streamradio_bass3_enable", resetCache, "streamradio_bass3_callback") + cvars.AddChangeCallback("sv_streamradio_bass3_allow_client", resetCache, "streamradio_bass3_callback") +end + + +local function printBass3Info() + if g_bass_info_shown then + return + end + + g_bass_info_shown = true + + local baseString = string.format( + "%s [color:100,200,100]loaded %s (ver. %s, %s)", + StreamRadioLib.AddonTitle, + g_dllName, + BASS3.ModuleVersion or 0, + BASS3.Version or 0 + ) + + local message = nil + + if SERVER then + message = "[color:100,200,100]Serverside streaming API for advanced wire outputs active!" + else + message = "[color:100,200,100]Clientside streaming API active!" + end + + message = string.format( + "%s:\n %s", + baseString, + message + ) + + StreamRadioLib.Print.Wrapped(message) +end + +local function onLoadBASS3() + StreamRadioLib.Error.AddStreamErrorCode({ + id = 102, + name = "STREAM_ERROR_BASS3_FILESYSTEM", + description = "Valve Filesystem is missing in " .. g_dllName, + }) + + printBass3Info() +end + +local function loadBASS3() + if g_bass_dll_required ~= nil then + -- only attempt to load gm_bass3 once + return + end + + g_bass_dll_required = false + require(g_dll) + + if not BASS3 then + error("Couldn't load '" .. g_dllName .. "'! BASS3 is missing!") + return false + end + + if not BASS3.Version then + error("Couldn't load '" .. g_dllName .. "'! BASS3.Version is missing!") + return false + end + + if not BASS3.ModuleVersion then + error("Couldn't load '" .. g_dllName .. "'! BASS3.ModuleVersion is missing!") + return false + end + + if not BASS3.ENUM then + error("Couldn't load '" .. g_dllName .. "'! BASS3.ENUM is missing!") + return false + end + + local BassModuleVersion = tonumber(BASS3.ModuleVersion) or 0 + + if BassModuleVersion < g_dllMinVersion then + error("Couldn't load '" .. g_dllName .. "'! Version is outdated!") + return false + end + + g_bass_dll_required = true + return true +end + +function LIB.HasLoadedDLL() + if not g_bass_dll_required then + return false + end + + if not BASS3 then + return false + end + + if not BASS3.Version then + return false + end + + if not BASS3.ModuleVersion then + return false + end + + if not BASS3.ENUM then + return false + end + + local BassModuleVersion = tonumber(BASS3.ModuleVersion) or 0 + + if BassModuleVersion < g_dllMinVersion then + return false + end + + return true +end + +function LIB.IsInstalled() + if g_bass_dll_required == false then + -- already attempted to load, but failed + return false + end + + local branch = tostring(BRANCH or "") + + if not g_dllSupportedBranches[branch] then + -- GM_BASS3 is broken on some branches + return false + end + + if not util.IsBinaryModuleInstalled(g_dll) then + return false + end + + return true +end + +function LIB.CanLoadDLL() + if g_bass_loaded ~= nil then + return g_bass_loaded + end + + if g_bass_can_loaded ~= nil then + return g_bass_can_loaded + end + + g_bass_can_loaded = false + + if not LIB.IsInstalled() then + return false + end + + if SERVER then + if not g_cvar_sv_bass3_enable then + g_cvar_sv_bass3_enable = GetConVar("sv_streamradio_bass3_enable") + end + + if g_cvar_sv_bass3_enable and g_cvar_sv_bass3_enable:GetInt() <= 0 then + return false + end + end + + if CLIENT then + if not g_cvar_cl_bass3_enable then + g_cvar_cl_bass3_enable = GetConVar("cl_streamradio_bass3_enable") + end + + if g_cvar_cl_bass3_enable and g_cvar_cl_bass3_enable:GetInt() <= 0 then + return false + end + + if not g_cvar_sv_bass3_allow_client then + g_cvar_sv_bass3_allow_client = GetConVar("sv_streamradio_bass3_allow_client") + end + + if g_cvar_sv_bass3_allow_client and g_cvar_sv_bass3_allow_client:GetInt() <= 0 then + return false + end + end + + g_bass_can_loaded = true + return true +end + +function LIB.ClearCache() + resetCache() +end + +function LIB.LoadDLL() + if g_bass_loaded ~= nil then + return g_bass_loaded + end + + if not LIB.CanLoadDLL() then + g_bass_loaded = false + return g_bass_loaded + end + + if LIB.HasLoadedDLL() then + onLoadBASS3() + + g_bass_loaded = true + return g_bass_loaded + end + + StreamRadioLib.CatchAndErrorNoHaltWithStack(loadBASS3) + + g_bass_loaded = LIB.HasLoadedDLL() + + if g_bass_loaded then + onLoadBASS3() + end + + return g_bass_loaded +end diff --git a/lua/streamradio_core/cache.lua b/lua/streamradio_core/cache.lua index cda69fa..e1c4b9c 100644 --- a/lua/streamradio_core/cache.lua +++ b/lua/streamradio_core/cache.lua @@ -21,12 +21,12 @@ end do local function Cache_Clear( ply, cmd, args ) if game.SinglePlayer() then - StreamRadioLib.Msg( ply, "A server stream cache does not exist in single player!" ) + StreamRadioLib.Print.Msg( ply, "A server stream cache does not exist in single player!" ) return end if not g_isDedicatedServer then - StreamRadioLib.Msg( ply, "A server stream cache does not exist on listen servers!" ) + StreamRadioLib.Print.Msg( ply, "A server stream cache does not exist on listen servers!" ) return end @@ -36,16 +36,16 @@ do end if not StreamRadioLib.DeleteFolder( MainDir ) then - StreamRadioLib.Msg( ply, "Server stream cache could not be cleared!" ) + StreamRadioLib.Print.Msg( ply, "Server stream cache could not be cleared!" ) return end LIB.lastloaded = {} LIB.forbidden = {} - StreamRadioLib.Msg( ply, "Server stream cache cleared!" ) + StreamRadioLib.Print.Msg( ply, "Server stream cache cleared!" ) else - StreamRadioLib.Msg( ply, "You need to be an admin clear the server stream cache." ) + StreamRadioLib.Print.Msg( ply, "You need to be an admin clear the server stream cache." ) end end @@ -58,14 +58,14 @@ do end if not StreamRadioLib.DeleteFolder( MainDir ) then - StreamRadioLib.Msg( ply, "Client stream cache could not be cleared!" ) + StreamRadioLib.Print.Msg( ply, "Client stream cache could not be cleared!" ) return end LIB.lastloaded = {} LIB.forbidden = {} - StreamRadioLib.Msg( ply, "Client stream cache cleared!" ) + StreamRadioLib.Print.Msg( ply, "Client stream cache cleared!" ) end concommand.Add( "cl_streamradio_cacheclear", Cache_Clear ) diff --git a/lua/streamradio_core/classes/base_listener.lua b/lua/streamradio_core/classes/base_listener.lua index 8c7328c..bef48c4 100644 --- a/lua/streamradio_core/classes/base_listener.lua +++ b/lua/streamradio_core/classes/base_listener.lua @@ -15,7 +15,6 @@ local g_super_listeners = CLASS:GetGlobalVar("base_listener_super_listeners", {} CLASS:SetGlobalVar("base_listener_super_listeners", g_super_listeners) local g_hookname = "3dstreamradio_classsystem_listen" -local g_super_hookname = g_hookname .. "_fast" local g_networkhookname = "classsystem_listen" local g_listengroups = SERVER and 6 or 4 local g_lastgroup = 1 @@ -26,6 +25,40 @@ for i = 1, g_listengroups do g_listeners[i] = g_listeners[i] or {} end +local function listentogroup_loop(id, listener, thisgroup) + if not IsValid(listener) then + g_listeners[thisgroup][id] = nil + return + end + + if listener._markedforremove then + g_listeners[thisgroup][id] = nil + return + end + + if not listener.ThinkInternal then + g_listeners[thisgroup][id] = nil + return + end + + if not listener.Created then + return + end + + local listentimeout = listener._listentimeout + if listentimeout then + if listentimeout <= 0 then + g_listeners[thisgroup][id] = nil + return + end + + listener._listentimeout = listentimeout - 1 + end + + listener:ThinkInternal() + return true +end + local function listentogroup() for i = 0, g_listengroups do local found = nil @@ -35,37 +68,11 @@ local function listentogroup() g_listeners[thisgroup] = group for id, listener in pairs(group) do - if not IsValid(listener) then - g_listeners[thisgroup][id] = nil - continue - end - - if listener._markedforremove then - g_listeners[thisgroup][id] = nil - continue - end - - if not listener.ThinkInternal then - g_listeners[thisgroup][id] = nil - continue - end - - if not listener.Created then - continue - end - - local listentimeout = listener._listentimeout - if listentimeout then - if listentimeout <= 0 then - g_listeners[thisgroup][id] = nil - continue - end + local hasfound = listentogroup_loop(id, listener, thisgroup) - listener._listentimeout = listentimeout - 1 + if hasfound then + found = listener end - - listener:ThinkInternal() - found = listener end g_lastgroup = thisgroup @@ -81,9 +88,6 @@ local function listentogroup() end local function g_listenfunc() - if not StreamRadioLib then return end - if not StreamRadioLib.Loaded then return end - local starttime = SysTime() local found = listentogroup() @@ -92,42 +96,72 @@ local function g_listenfunc() end end -local function g_superlistenfunc() - if not StreamRadioLib then return end - if not StreamRadioLib.Loaded then return end +local function superlistenfunc_loop(id, listener) + if not IsValid(listener) then + g_super_listeners[id] = nil + return + end + + if listener._markedforremove then + g_super_listeners[id] = nil + return + end + + if not isfunction(listener.SuperThink) then + g_super_listeners[id] = nil + return + end + + if not listener.Created then + return + end + listener:SuperThink() + return true +end + +local function g_superlistenfunc() local starttime = SysTime() local found = nil + for id, listener in pairs(g_super_listeners) do - if not IsValid(listener) then - g_super_listeners[id] = nil - continue - end + local hasfound = superlistenfunc_loop(id, listener) - if listener._markedforremove then - g_super_listeners[id] = nil - continue + if hasfound then + found = listener end + end - if not isfunction(listener.SuperThink) then - g_super_listeners[id] = nil - continue - end + if found then + found:SetGlobalVar("base_listener_superthinktime", SysTime() - starttime) + end +end - if not listener.Created then - continue - end +local function g_thinkfunc() + if not StreamRadioLib then return end + if not StreamRadioLib.Loaded then return end - listener:SuperThink() - found = listener + if g_hookruns then + g_listenfunc() end - if found then - found:SetGlobalVar("base_listener_superthinktime", SysTime() - starttime) + if g_superhooksruns then + g_superlistenfunc() end end +local function g_register_thinkfunc() + if not StreamRadioLib then return end + if not StreamRadioLib.Loaded then return end + + if g_hookruns then return end + if g_superhooksruns then return end + + hook.Remove("Think", g_hookname) + hook.Add("Think", g_hookname, g_thinkfunc) +end + LIBNetwork.AddNetworkString(g_networkhookname) LIBNet.Receive(g_networkhookname, function(len, ply) @@ -522,7 +556,7 @@ function CLASS:StartListen() if g_hookruns then return end - hook.Add("Think", g_hookname, g_listenfunc) + g_register_thinkfunc() g_hookruns = true end @@ -545,7 +579,7 @@ function CLASS:_StartSuperThink() g_super_listeners[id] = self if not g_superhooksruns then - hook.Add("Think", g_super_hookname, g_superlistenfunc) + g_register_thinkfunc() g_superhooksruns = true end end diff --git a/lua/streamradio_core/classes/clientconvar.lua b/lua/streamradio_core/classes/clientconvar.lua index 4d8bae9..3b42d59 100644 --- a/lua/streamradio_core/classes/clientconvar.lua +++ b/lua/streamradio_core/classes/clientconvar.lua @@ -198,7 +198,6 @@ function CLASS:GetOptions() end function CLASS:SetHidden(var) - if self._convar then return end self.hidden = var or false end @@ -207,7 +206,6 @@ function CLASS:GetHidden() end function CLASS:SetDisabled(var) - if self._convar then return end self.disabled = var or false end @@ -216,7 +214,6 @@ function CLASS:GetDisabled() end function CLASS:SetPanellabel(var) - if self._convar then return end self.panellabel = tostring(var or "") end diff --git a/lua/streamradio_core/classes/gui_controller.lua b/lua/streamradio_core/classes/gui_controller.lua index 518afda..6768710 100644 --- a/lua/streamradio_core/classes/gui_controller.lua +++ b/lua/streamradio_core/classes/gui_controller.lua @@ -73,7 +73,6 @@ function CLASS:Create() if SERVER then return end - self.Colors.DrawAlpha = 1 self.Colors.Cursor = Color(255, 255, 255) self.Layout.CornerSize = 16 @@ -303,13 +302,23 @@ function CLASS:RenderSystem() render.PushFilterMin(TEXFILTER.NONE) render.PushFilterMag(TEXFILTER.NONE) - local alpha = self:GetDrawAlpha() + local currentRenderAlpha = surface.GetAlphaMultiplier() + local drawAlpha = self:GetDrawAlpha() + local alpha = drawAlpha * currentRenderAlpha + local isTransparent = drawAlpha < 1 + local oldtune = render.GetToneMappingScaleLinear( ) render.SetToneMappingScaleLinear(tune_nohdr) -- Turns off hdr - surface.SetAlphaMultiplier(alpha) + if isTransparent then + surface.SetAlphaMultiplier(alpha) + end + catchAndErrorNoHaltWithStack(self.DrawBorder, self) - surface.SetAlphaMultiplier(1) + + if isTransparent then + surface.SetAlphaMultiplier(currentRenderAlpha) + end if self:HasRendertarget() then surface.SetDrawColor(255, 255, 255, alpha * 255) @@ -320,17 +329,29 @@ function CLASS:RenderSystem() self.FrameTime = self._RT:ProfilerTime("Render") else self:ProfilerStart("Render_rtfallback") - surface.SetAlphaMultiplier(alpha) + + if isTransparent then + surface.SetAlphaMultiplier(alpha) + end catchAndErrorNoHaltWithStack(self._RenderInternal, self) - surface.SetAlphaMultiplier(1) + if isTransparent then + surface.SetAlphaMultiplier(currentRenderAlpha) + end + self.FrameTime = self:ProfilerEnd("Render_rtfallback") end - surface.SetAlphaMultiplier(alpha) + if isTransparent then + surface.SetAlphaMultiplier(alpha) + end + catchAndErrorNoHaltWithStack(self.DrawCursor, self) - surface.SetAlphaMultiplier(1) + + if isTransparent then + surface.SetAlphaMultiplier(currentRenderAlpha) + end render.SetToneMappingScaleLinear(oldtune) -- Resets hdr @@ -357,6 +378,7 @@ function CLASS:DrawCursor() local cx, cy = self:GetCursor() local cw, ch = self:GetCursorSize() + local colCursor = self.Colors.Cursor or color_white local cu = ((cx + cw) - ax2) / cw local cv = ((cy + ch) - ay2) / ch @@ -365,7 +387,7 @@ function CLASS:DrawCursor() cv = math.Clamp(1 - cv, 0, 1) surface.SetMaterial(CursorMat) - surface.SetDrawColor(self.Colors.Cursor) + surface.SetDrawColor(colCursor:Unpack()) surface.DrawTexturedRectUV(cx, cy, cw * cu, ch * cv, 0, 0, cu, cv) end @@ -571,28 +593,22 @@ end function CLASS:SetCursorColor(color) if SERVER then return end + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Cursor = color end function CLASS:GetCursorColor() if SERVER then return end - local col = self.Colors.Cursor - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) -end - -function CLASS:SetDrawAlpha(alpha) - if SERVER then return end - - alpha = math.Clamp(alpha, 0, 1) - self.Colors.DrawAlpha = alpha -end - -function CLASS:GetDrawAlpha() - if SERVER then return end - local alpha = self.Colors.DrawAlpha - - return alpha or 0 + local col = self.Colors.Cursor + return col end function CLASS:SetName(...) diff --git a/lua/streamradio_core/classes/skin_controller.lua b/lua/streamradio_core/classes/skin_controller.lua index 07be857..34691f9 100644 --- a/lua/streamradio_core/classes/skin_controller.lua +++ b/lua/streamradio_core/classes/skin_controller.lua @@ -190,7 +190,7 @@ function CLASS:CalcHash() if CLIENT then return end if not self.Network.Active then return end - local hash = StreamRadioLib.Hash(self:GetSkinEncoded()) + local hash = LIBNetwork.Hash(self:GetSkinEncoded()) self.Hash.value = hash or "" end diff --git a/lua/streamradio_core/classes/stream.lua b/lua/streamradio_core/classes/stream.lua index 871464a..0e01cf8 100644 --- a/lua/streamradio_core/classes/stream.lua +++ b/lua/streamradio_core/classes/stream.lua @@ -22,22 +22,34 @@ local CLIENT = CLIENT local EmptyVector = Vector() local catchAndErrorNoHaltWithStack = StreamRadioLib.CatchAndErrorNoHaltWithStack -local BASS3 = BASS3 or {} +local BASS3 = nil local LIBNetwork = StreamRadioLib.Network +local LIBBass = StreamRadioLib.Bass +local LIBError = StreamRadioLib.Error local BASE = CLASS:GetBaseClass() +local function LoadBass() + local hasBass = LIBBass.LoadDLL() + + if hasBass and not BASS3 then + BASS3 = _G.BASS3 + end + + return hasBass +end + local function ChannelIsCacheAble( channel ) - if ( not IsValid( channel ) ) then return false end + if not IsValid( channel ) then return false end local len = channel:GetLength( ) return len > 0 end local function ChannelStop( channel ) - if ( not channel ) then - return nil + if not channel then + return end channel:Stop() @@ -46,7 +58,8 @@ local function ChannelStop( channel ) channel:Remove( ) end - return nil + LIBBass.ClearCache() + return end local retry_errors_non3d = { @@ -181,6 +194,8 @@ function CLASS:Create() self.StateTable_r[v] = i end + LIBBass.ClearCache() + self.State = self:CreateListener({ Error = 0, PlayMode = StreamRadioLib.STREAM_PLAYMODE_STOP, @@ -192,7 +207,7 @@ function CLASS:Create() Name = "", Seeking = false, ValidChannel = false, - HasBass = CLIENT and StreamRadioLib.HasBass, + HasBass = CLIENT and LoadBass(), }, function(this, k, v) if k == "PlayMode" then self:UpdateChannelPlayMode() @@ -207,11 +222,19 @@ function CLASS:Create() if k == "Stopped" and v then self:CallHook("OnClose") + + if CLIENT then + self.State.HasBass = LoadBass() + end end if k == "Muted" then self:UpdateChannelMuted() self:CallHook("OnMute", v) + + if CLIENT then + self.State.HasBass = LoadBass() + end end if k == "Name" then @@ -762,14 +785,14 @@ function CLASS:UpdateChannelPlayMode() end if playmode == StreamRadioLib.STREAM_PLAYMODE_PLAY_RESTART then - self:SetTime(0) - self.Channel:Play(true) + self:SetTime(0, true) + self.Channel:Play() self.State.PlayMode = StreamRadioLib.STREAM_PLAYMODE_PLAY return end if playmode == StreamRadioLib.STREAM_PLAYMODE_PLAY then - self.Channel:Play(false) + self.Channel:Play() return end end @@ -836,7 +859,7 @@ function CLASS:IsLoading() if not self.Valid then return false end if not self:StillSearching() then return false end if IsValid(self.Channel) then return false end - if self.State.Error ~= 0 then return false end + if self:HasError() then return false end return true end @@ -864,15 +887,17 @@ function CLASS:IsDownloadingToCache() end function CLASS:SetBASSEngineEnabled(bool) - if not StreamRadioLib.HasBass then - bool = false + bool = bool or false + + if bool then + bool = LIBBass.LoadDLL() end - self.State.HasBass = bool or false + self.State.HasBass = bool end function CLASS:IsBASSEngineEnabled() - if not StreamRadioLib.HasBass then return false end + if not LIBBass.HasLoadedDLL() then return false end return self.State.HasBass or false end @@ -887,7 +912,7 @@ function CLASS:StillSearching() if self.State.Stopped then return false end if IsValid(self.Channel) then return false end - if self.State.Error ~= 0 then return false end + if self:HasError() then return false end return true end @@ -1388,6 +1413,11 @@ function CLASS:GetError() return self.State.Error or 0 end +function CLASS:HasError() + if not self.Valid then return false end + return self:GetError() ~= LIBError.STREAM_OK +end + function CLASS:GetMetadata() if not self.Valid then return {} end return self.Metadata or {} @@ -2115,50 +2145,62 @@ end local tempArray_fft = {} local tempArray_fftc = {} -local powres_bass = {} - -if StreamRadioLib.HasBass then - powres_bass[0] = BASS3.ENUM.FFT_16 - powres_bass[1] = BASS3.ENUM.FFT_16 - powres_bass[2] = BASS3.ENUM.FFT_16 - powres_bass[3] = BASS3.ENUM.FFT_16 - powres_bass[4] = BASS3.ENUM.FFT_32 - powres_bass[5] = BASS3.ENUM.FFT_64 - powres_bass[6] = BASS3.ENUM.FFT_128 - powres_bass[7] = BASS3.ENUM.FFT_256 - powres_bass[8] = BASS3.ENUM.FFT_512 - powres_bass[9] = BASS3.ENUM.FFT_1024 - powres_bass[10] = BASS3.ENUM.FFT_2048 - powres_bass[11] = BASS3.ENUM.FFT_4096 - powres_bass[12] = BASS3.ENUM.FFT_8192 - powres_bass[13] = BASS3.ENUM.FFT_16384 - powres_bass[14] = BASS3.ENUM.FFT_32768 -end - -local powres_nobass = { - [0] = FFT_256, - [1] = FFT_256, - [2] = FFT_256, - [3] = FFT_256, - [4] = FFT_256, - [5] = FFT_256, - [6] = FFT_256, - [7] = FFT_256, - [8] = FFT_512, - [9] = FFT_1024, - [10] = FFT_2048, - [11] = FFT_2048, - [12] = FFT_8192, - [13] = FFT_16384, - [14] = FFT_32768, -} + +local g_powres_nobass = nil +local g_powres_bass = nil + +local function buildpowres() + if not g_powres_nobass then + g_powres_nobass = {} + + g_powres_nobass[0] = FFT_256 + g_powres_nobass[1] = FFT_256 + g_powres_nobass[2] = FFT_256 + g_powres_nobass[3] = FFT_256 + g_powres_nobass[4] = FFT_256 + g_powres_nobass[5] = FFT_256 + g_powres_nobass[6] = FFT_256 + g_powres_nobass[7] = FFT_256 + g_powres_nobass[8] = FFT_512 + g_powres_nobass[9] = FFT_1024 + g_powres_nobass[10] = FFT_2048 + g_powres_nobass[11] = FFT_2048 + g_powres_nobass[12] = FFT_8192 + g_powres_nobass[13] = FFT_16384 + g_powres_nobass[14] = FFT_32768 + end + + if not g_powres_bass then + if BASS3 and BASS3.ENUM and BASS3.ENUM.FFT_16 then + g_powres_bass = {} + + g_powres_bass[0] = BASS3.ENUM.FFT_16 + g_powres_bass[1] = BASS3.ENUM.FFT_16 + g_powres_bass[2] = BASS3.ENUM.FFT_16 + g_powres_bass[3] = BASS3.ENUM.FFT_16 + g_powres_bass[4] = BASS3.ENUM.FFT_32 + g_powres_bass[5] = BASS3.ENUM.FFT_64 + g_powres_bass[6] = BASS3.ENUM.FFT_128 + g_powres_bass[7] = BASS3.ENUM.FFT_256 + g_powres_bass[8] = BASS3.ENUM.FFT_512 + g_powres_bass[9] = BASS3.ENUM.FFT_1024 + g_powres_bass[10] = BASS3.ENUM.FFT_2048 + g_powres_bass[11] = BASS3.ENUM.FFT_4096 + g_powres_bass[12] = BASS3.ENUM.FFT_8192 + g_powres_bass[13] = BASS3.ENUM.FFT_16384 + g_powres_bass[14] = BASS3.ENUM.FFT_32768 + end + end +end function CLASS:GetSpectrum( resolution, func, minfrq, maxfrq ) if not self.Valid then return false end if not IsValid(self.Channel) then return false end if self:IsSeeking() then return false end - local powres = self.State.HasBass and powres_bass or powres_nobass + buildpowres() + + local powres = self.State.HasBass and g_powres_bass or g_powres_nobass resolution = resolution or 0 resolution = powres[resolution] @@ -2223,7 +2265,9 @@ function CLASS:GetSpectrumComplex( resolution, func, minfrq, maxfrq ) if not IsValid(self.Channel) then return false end if self:IsSeeking() then return false end - local powres = self.State.HasBass and powres_bass or powres_nobass + buildpowres() + + local powres = self.State.HasBass and g_powres_bass or g_powres_nobass resolution = resolution or 0 resolution = powres[resolution] diff --git a/lua/streamradio_core/classes/ui/button.lua b/lua/streamradio_core/classes/ui/button.lua index 6ab2b5e..ddc6a6b 100644 --- a/lua/streamradio_core/classes/ui/button.lua +++ b/lua/streamradio_core/classes/ui/button.lua @@ -320,110 +320,191 @@ end function CLASS:SetColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.NoHover = color end function CLASS:GetColor() if SERVER then return end - local col = self.Colors.NoHover - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.NoHover + return col end function CLASS:SetHoverColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Hover = color end function CLASS:GetHoverColor() if SERVER then return end - local col = self.Colors.Hover - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.Hover + return col end function CLASS:SetDisabledColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Disabled = color end function CLASS:GetDisabledColor() if SERVER then return end - local col = self.Colors.Disabled - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.Disabled + return col end function CLASS:SetTextColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.NoHoverText = color end function CLASS:GetTextColor() if SERVER then return end - local col = self.Colors.NoHoverText - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.NoHoverText + return col end function CLASS:SetTextHoverColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.HoverText = color end function CLASS:GetTextHoverColor() if SERVER then return end - local col = self.Colors.HoverText - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.HoverText + return col end function CLASS:SetTextDisabledColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.DisabledText = color end function CLASS:GetTextDisabledColor() if SERVER then return end - local col = self.Colors.DisabledText - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.DisabledText + return col end function CLASS:SetIconColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.NoHoverIcon = color end function CLASS:GetIconColor() if SERVER then return end - local col = self.Colors.NoHoverIcon - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.NoHoverIcon + return col end function CLASS:SetIconHoverColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.HoverIcon = color end function CLASS:GetIconHoverColor() if SERVER then return end - local col = self.Colors.HoverIcon - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.HoverIcon + return col end function CLASS:SetIconDisabledColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.DisabledIcon = color end function CLASS:GetIconDisabledColor() if SERVER then return end - local col = self.Colors.DisabledIcon - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.DisabledIcon + return col end function CLASS:DoClick() diff --git a/lua/streamradio_core/classes/ui/debug.lua b/lua/streamradio_core/classes/ui/debug.lua index f11960a..af9f8ce 100644 --- a/lua/streamradio_core/classes/ui/debug.lua +++ b/lua/streamradio_core/classes/ui/debug.lua @@ -89,7 +89,7 @@ function CLASS:Render() local bg_h = 0 surface.SetFont( "DebugFixed" ) - surface.SetTextColor( g_textcol ) + surface.SetTextColor( g_textcol:Unpack() ) for i, v in ipairs(self.debugtexttab) do if not v then break end @@ -101,7 +101,7 @@ function CLASS:Render() end end - surface.SetDrawColor( g_textcolbg ) + surface.SetDrawColor( g_textcolbg:Unpack() ) surface.DrawRect( x, y, self.bgwidth + 6, bg_h + 5 ) bg_h = 0 diff --git a/lua/streamradio_core/classes/ui/highlighter.lua b/lua/streamradio_core/classes/ui/highlighter.lua index 1ded21c..4ec5313 100644 --- a/lua/streamradio_core/classes/ui/highlighter.lua +++ b/lua/streamradio_core/classes/ui/highlighter.lua @@ -29,26 +29,44 @@ end function CLASS:SetBorderColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Border1 = color end function CLASS:SetBorderColor2(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Border2 = color end function CLASS:GetBorderColor() if SERVER then return end - local col = self.Colors.Border1 - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.Border1 + return col end function CLASS:GetBorderColor2() if SERVER then return end - local col = self.Colors.Border2 - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.Border2 + return col end function CLASS:HighlightClear() @@ -97,15 +115,17 @@ function CLASS:RenderHighlight(panel) pw = math.min(pw, spw - padding * 2) ph = math.min(ph, sph - padding * 2) - surface.SetDrawColor(self.Colors.Main) + local colMain = self.Colors.Main or color_white + + surface.SetDrawColor(colMain:Unpack()) surface.DrawRect(px, py, pw, ph) - local col1 = self.Colors.Border1 - local col2 = self.Colors.Border2 + local col1 = self.Colors.Border1 or color_white + local col2 = self.Colors.Border2 or color_black for i = 1, lines do local col = ((i % 2) == 0) and col2 or col1 - surface.SetDrawColor(col) + surface.SetDrawColor(col:Unpack()) for j = 0, thickness - 1 do local t = (i - 1) * thickness + j diff --git a/lua/streamradio_core/classes/ui/image.lua b/lua/streamradio_core/classes/ui/image.lua index 0b6ead2..c5f823c 100644 --- a/lua/streamradio_core/classes/ui/image.lua +++ b/lua/streamradio_core/classes/ui/image.lua @@ -47,7 +47,7 @@ function CLASS:Render() local tx, ty = x, y local tsw, tsh = self:GetTextureSize() local xalign, yalign = self.ImageData.AlignX, self.ImageData.AlignY - local col = self.Colors.Main + local colMain = self.Colors.Main or color_white if ( xalign == TEXT_ALIGN_CENTER ) then tx = x + w / 2 - tsw / 2 @@ -61,7 +61,7 @@ function CLASS:Render() ty = y + h - tsh end - surface.SetDrawColor( col ) + surface.SetDrawColor( colMain:Unpack() ) surface.SetMaterial( mat ) surface.DrawTexturedRectUV( tx, ty, tsw, tsh, 0, 0, 1, 1 ) end diff --git a/lua/streamradio_core/classes/ui/label.lua b/lua/streamradio_core/classes/ui/label.lua index 71ca932..dee0a55 100644 --- a/lua/streamradio_core/classes/ui/label.lua +++ b/lua/streamradio_core/classes/ui/label.lua @@ -194,11 +194,11 @@ function CLASS:Render() local x, y = self:GetRenderPos() local w, h = self:GetSize() - local col = self.Colors.Main + local col = self.Colors.Main or color_black local font = self.TextData.Font surface.SetFont( font ) - surface.SetTextColor( col ) + surface.SetTextColor( col:Unpack() ) self:DrawText(self.InternalText, x, y, w, h) end diff --git a/lua/streamradio_core/classes/ui/label_fade.lua b/lua/streamradio_core/classes/ui/label_fade.lua index 69b4a80..c28cb09 100644 --- a/lua/streamradio_core/classes/ui/label_fade.lua +++ b/lua/streamradio_core/classes/ui/label_fade.lua @@ -180,14 +180,16 @@ function CLASS:Render() local x, y = self:GetRenderPos() local w, h = self:GetSize() - local col = self:GetTextColor() - local font = self.TextData.Font + local colText = self:GetTextColor() + local cR, cG, cB, cA = colText:Unpack() - surface.SetFont( font ) local phase = self:GetPhase() - local alpha = col.a * phase + cA = cA * phase + + local font = self.TextData.Font + + surface.SetFont(font) + surface.SetTextColor(cR, cG, cB, cA) - col.a = alpha - surface.SetTextColor( col ) self:DrawText(self.InternalText, x, y, w, h) end diff --git a/lua/streamradio_core/classes/ui/list.lua b/lua/streamradio_core/classes/ui/list.lua index 2242846..6983be6 100644 --- a/lua/streamradio_core/classes/ui/list.lua +++ b/lua/streamradio_core/classes/ui/list.lua @@ -410,7 +410,7 @@ function CLASS:GetHashFromData(data) table.insert(datastring, string.format("[%d]", #data)) datastring = table.concat(datastring, "\n") - local hash = StreamRadioLib.Hash(datastring) + local hash = LIBNetwork.Hash(datastring) return hash end diff --git a/lua/streamradio_core/classes/ui/panel.lua b/lua/streamradio_core/classes/ui/panel.lua index 25d7a81..73c6863 100644 --- a/lua/streamradio_core/classes/ui/panel.lua +++ b/lua/streamradio_core/classes/ui/panel.lua @@ -105,7 +105,8 @@ function CLASS:Create() if CLIENT then self.Colors = self:CreateListener({ - Main = Color(255,255,255) + Main = Color(255,255,255), + DrawAlpha = 1, }, function(...) self:QueueCall("PerformRerender") end) @@ -229,13 +230,26 @@ function CLASS:_RenderInternal() return end + local currentRenderAlpha = surface.GetAlphaMultiplier() + local drawAlpha = self:GetDrawAlpha() + local alpha = drawAlpha * currentRenderAlpha + local isTransparent = drawAlpha < 1 + + if isTransparent then + surface.SetAlphaMultiplier(alpha) + end + self:Render() self:ForEachChild(RenderInternalPanel, true) + if isTransparent then + surface.SetAlphaMultiplier(currentRenderAlpha) + end + self._rendered = true end -local coldebug = Color(0,0,0,200) +local g_colDebug = Color(0,0,0,200) function CLASS:Render() if not self.debugborders then return end @@ -243,7 +257,7 @@ function CLASS:Render() local x, y = self:GetRenderPos() local w, h = self:GetSize() - surface.SetDrawColor( coldebug ) + surface.SetDrawColor( g_colDebug:Unpack() ) surface.DrawOutlinedRect(x, y, w, h) surface.DrawOutlinedRect(x + 1, y + 1, w - 2, h - 2) end @@ -890,14 +904,37 @@ end function CLASS:SetColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Main = color end function CLASS:GetColor() if SERVER then return end + local col = self.Colors.Main + return col +end + +function CLASS:SetDrawAlpha(alpha) + if SERVER then return end + + alpha = math.Clamp(alpha, 0, 1) + self.Colors.DrawAlpha = alpha +end + +function CLASS:GetDrawAlpha() + if SERVER then return end + local alpha = self.Colors.DrawAlpha - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + return alpha or 0 end function CLASS:SetParent(panel) @@ -1099,7 +1136,7 @@ function CLASS:IsVisible() return self:SetCacheValue("IsVisible", parent:IsVisible()) end - return true + return self:SetCacheValue("IsVisible", true) end function CLASS:SetVisible(bool) diff --git a/lua/streamradio_core/classes/ui/progressbar.lua b/lua/streamradio_core/classes/ui/progressbar.lua index 44ab4ad..a559d79 100644 --- a/lua/streamradio_core/classes/ui/progressbar.lua +++ b/lua/streamradio_core/classes/ui/progressbar.lua @@ -143,31 +143,33 @@ end function CLASS:Render() local x, y = self:GetRenderPos() local w, h = self:GetSize() - local ShadowWidth = self:GetShadowWidth() + local shadowWidth = self:GetShadowWidth() local col1 = self.Colors.Main or color_white local col2 = self.Colors.Secondary or color_white local fraction1 = self.Progress.Fraction - if ShadowWidth <= 0 then - surface.SetDrawColor(col1) + if shadowWidth <= 0 then + surface.SetDrawColor(col1:Unpack()) surface.DrawRect(x, y, w, h) - surface.SetDrawColor(col2) + surface.SetDrawColor(col2:Unpack()) surface.DrawRect(x, y, w * fraction1, h) return end - local sx, sy = x + ShadowWidth, y + ShadowWidth - local sw, sh = w - ShadowWidth, h - ShadowWidth + local colShadow = self.Colors.Shadow or color_black - surface.SetDrawColor(self.Colors.Shadow or color_black) + local sx, sy = x + shadowWidth, y + shadowWidth + local sw, sh = w - shadowWidth, h - shadowWidth + + surface.SetDrawColor(colShadow:Unpack()) surface.DrawRect(sx, sy, sw, sh) - surface.SetDrawColor(col1) + surface.SetDrawColor(col1:Unpack()) surface.DrawRect(x, y, sw, sh) - surface.SetDrawColor(col2) + surface.SetDrawColor(col2:Unpack()) surface.DrawRect(x, y, sw * fraction1, sh) end @@ -242,74 +244,128 @@ end function CLASS:SetColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.NoHover = color end function CLASS:GetColor() if SERVER then return end - local col = self.Colors.NoHover - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.NoHover + return col end function CLASS:SetHoverColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Hover = color end function CLASS:GetHoverColor() if SERVER then return end - local col = self.Colors.Hover - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.Hover + return col end function CLASS:SetDisabledColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Disabled = color end function CLASS:GetDisabledColor() if SERVER then return end - local col = self.Colors.Disabled - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.Disabled + return col end function CLASS:SetTextColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.NoHoverText = color end function CLASS:GetTextColor() if SERVER then return end - local col = self.Colors.NoHoverText - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.NoHoverText + return col end function CLASS:SetTextHoverColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.HoverText = color end function CLASS:GetTextHoverColor() if SERVER then return end - local col = self.Colors.HoverText - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.HoverText + return col end function CLASS:SetTextDisabledColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.DisabledText = color end function CLASS:GetTextDisabledColor() if SERVER then return end - local col = self.Colors.DisabledText - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.DisabledText + return col end diff --git a/lua/streamradio_core/classes/ui/radio/gui_browser.lua b/lua/streamradio_core/classes/ui/radio/gui_browser.lua index 1983836..05921f3 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_browser.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_browser.lua @@ -104,6 +104,11 @@ function CLASS:Create() self.Errorbox:SetNWName("err") self.Errorbox:SetSkinIdentifyer("error") + if self.Errorbox.RetryButton then + self.Errorbox.RetryButton:Remove() + self.Errorbox.RetryButton = nil + end + if IsValid(self.Errorbox.CloseButton) and CLIENT then -- The error box is handled on the server, so the client shouldn't touch it. self.Errorbox.CloseButton.DoClick = nil @@ -113,10 +118,6 @@ function CLASS:Create() self:GoUpPath() end - self.Errorbox.OnRetry = function() - self:Refresh() - end - self.Errorbox:SetZPos(100) self.Errorbox:Close() @@ -412,6 +413,43 @@ function CLASS:PlayPrevious() self.ListPlaylist:PlayPrevious() end +function CLASS:_PerformButtonLayout(buttonx, buttony) + if not self.SideButtons then return end + + local w, h = self:GetClientSize() + local buttonw = 0 + + for k, v in ipairs(self.SideButtons) do + if not IsValid(v) then continue end + if not v.Layout.Visible then continue end + + if buttonw <= 0 then + buttonw = v:GetWidth() + break + end + end + + local margin = self:GetMargin() + + for k, v in ipairs(self.SideButtons) do + if not IsValid(v) then continue end + if not v.Layout.Visible then continue end + + local newbutteny = buttony + (buttonw + margin) + if newbutteny >= h then + v:SetPos(0, 0) + v:SetHeight(0) + continue + end + + v:SetPos(buttonx, buttony) + v:SetSize(buttonw, buttonw) + buttony = newbutteny + end + + return buttonw, buttony +end + function CLASS:PerformLayout(...) BASE.PerformLayout(self, ...) @@ -422,26 +460,18 @@ function CLASS:PerformLayout(...) if not IsValid(self.WireButton) then return end if not IsValid(self.ListFiles) then return end if not IsValid(self.ListPlaylist) then return end + if not self.SideButtons then return end local w, h = self:GetClientSize() - local bw = 0 - - for k, v in pairs(self.SideButtons) do - if not IsValid(v) then continue end - if not v.Layout.Visible then continue end - - if bw <= 0 then - bw = v:GetWidth() - end - end - local headerw, headerh = self.HeaderPanel:GetSize() + local buttonw = self:_PerformButtonLayout(0, headerh) + local margin = self:GetMargin() local listx = 0 - if bw > 0 then - listx = bw + margin + if buttonw > 0 then + listx = buttonw + margin end local listy = headerh + margin @@ -451,24 +481,6 @@ function CLASS:PerformLayout(...) headerw = listw - local buttony = listy - - for k, v in pairs(self.SideButtons) do - if not IsValid(v) then continue end - if not v.Layout.Visible then continue end - - local newbutteny = buttony + (bw + margin) - if newbutteny >= h then - v:SetPos(0, 0) - v:SetHeight(0) - continue - end - - v:SetPos(0, buttony) - v:SetSize(bw, bw) - buttony = newbutteny - end - self.ListFiles:SetSize(listw, listh) self.ListPlaylist:SetSize(listw, listh) diff --git a/lua/streamradio_core/classes/ui/radio/gui_errorbox.lua b/lua/streamradio_core/classes/ui/radio/gui_errorbox.lua index fc44614..f63ecd4 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_errorbox.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_errorbox.lua @@ -3,6 +3,8 @@ if not istable(CLASS) then return end +local LIBError = StreamRadioLib.Error + local BASE = CLASS:GetBaseClass() local g_mat_help = StreamRadioLib.GetPNGIcon("help") @@ -22,9 +24,10 @@ function CLASS:Create() self.BodyPanelText:SetSkinIdentifyer("textbox") self.HelpButton = self:AddPanelByClassname("button", true) - self.HelpButton:SetSize(1, 40) + self.HelpButton:SetSize(40, 40) self.HelpButton:SetIcon(g_mat_help) - self.HelpButton:SetText("Help") + self.HelpButton:SetDrawAlpha(0.5) + self.HelpButton:SetTooltip("Help") self.HelpButton:SetName("help") self.HelpButton:SetNWName("hlp") self.HelpButton:SetSkinIdentifyer("button") @@ -32,18 +35,14 @@ function CLASS:Create() self:CallHook("OnHelp") if SERVER then return end - if self.Error == "playlist" then - StreamRadioLib.ShowPlaylistErrorHelp() - return - end - StreamRadioLib.ShowErrorHelp( self.Error, self.URL ) end self.CloseButton = self:AddPanelByClassname("button", true) - self.CloseButton:SetSize(1, 40) + self.CloseButton:SetSize(40, 40) self.CloseButton:SetIcon(g_mat_cross) - self.CloseButton:SetText("Close") + self.CloseButton:SetDrawAlpha(0.5) + self.CloseButton:SetTooltip("Close") self.CloseButton:SetName("close") self.CloseButton:SetNWName("cls") self.CloseButton:SetSkinIdentifyer("button") @@ -53,15 +52,22 @@ function CLASS:Create() end self.RetryButton = self:AddPanelByClassname("button", true) - self.RetryButton:SetSize(1, 40) + self.RetryButton:SetSize(40, 40) self.RetryButton:SetIcon(g_mat_arrow_refresh) - self.RetryButton:SetText("Retry") + self.RetryButton:SetDrawAlpha(0.5) + self.RetryButton:SetTooltip("Retry") self.RetryButton:SetName("retry") self.RetryButton:SetNWName("rty") self.RetryButton:SetSkinIdentifyer("button") self.RetryButton.DoClick = function() self:CallHook("OnRetry") end + + self.SideButtons = { + self.CloseButton, + self.RetryButton, + self.HelpButton, + } end function CLASS:SetPlaylistError(url) @@ -71,15 +77,20 @@ function CLASS:SetPlaylistError(url) url = "" end - self.Error = "playlist" + self.Error = LIBError.PLAYLIST_ERROR_INVALID_FILE self.URL = url + local errorInfo = LIBError.GetStreamErrorInfo(self.Error) + + local code = errorInfo.id + local name = errorInfo.name + local text if url ~= "" then - text = string.format("Error: Could not open playlist:\n%s\n\nMake sure the file is valid and not Empty\n\nClick help for more details.", url) + text = string.format("Error: %i (%s)\n\nCould not open playlist:\n%s\n\nMake sure the file is valid and not Empty\n\nClick the '?' button for more details.", code, name, url) else - text = "Error: Could not open playlist!\n\nMake sure the file is valid and not Empty\n\nClick help for more details." + text = string.format("Error: %i (%s)\n\nCould not open playlist!\n\nMake sure the file is valid and not Empty\n\nClick the '?' button for more details.", code, name) end if IsValid(self.BodyPanelText) then @@ -100,13 +111,18 @@ function CLASS:SetErrorCode(err, url) self.Error = err self.URL = url - local errordesc = StreamRadioLib.DecodeErrorCode(err) + local errorInfo = LIBError.GetStreamErrorInfo(err) + + local code = errorInfo.id + local name = errorInfo.name + local description = errorInfo.description or "" + local text if url ~= "" then - text = string.format("Error: %i\n\nCould not play stream:\n%s\n\n%s\n\nClick help for more details.", err, url, errordesc) + text = string.format("Error: %i (%s)\n\nCould not play stream:\n%s\n\n%s\n\nClick the '?' button for more details.", code, name, url, description) else - text = string.format("Error: %i\n\nCould not play stream!\n\n%s\n\nClick help for more details.", err, errordesc) + text = string.format("Error: %i (%s)\n\nCould not play stream!\n\n%s\n\nClick the '?' button for more details.", code, name, description) end if IsValid(self.BodyPanelText) then @@ -120,70 +136,54 @@ function CLASS:SetErrorCode(err, url) end end -function CLASS:PerformLayout(...) - BASE.PerformLayout(self, ...) +function CLASS:_PerformButtonLayout(buttonx, buttony) + if not self.SideButtons then return end local w, h = self:GetClientSize() - local margin = self:GetMargin() - - local bodyheight = h + local buttonw = 0 - local buttonh = 0 - local buttonw = w - 100 - local buttonc = 0 + for k, v in ipairs(self.SideButtons) do + if not IsValid(v) then continue end + if not v.Layout.Visible then continue end - if IsValid(self.CloseButton) and self.CloseButton.Layout.Visible then - buttonc = buttonc + 1 - buttonh = self.CloseButton:GetHeight() + if buttonw <= 0 then + buttonw = v:GetWidth() + break + end end - if IsValid(self.RetryButton) and self.RetryButton.Layout.Visible then - buttonc = buttonc + 1 - buttonh = self.RetryButton:GetHeight() - end + local margin = self:GetMargin() - if IsValid(self.HelpButton) and self.RetryButton.Layout.Visible then - buttonc = buttonc + 1 - buttonh = self.HelpButton:GetHeight() - end + for k, v in ipairs(self.SideButtons) do + if not IsValid(v) then continue end + if not v.Layout.Visible then continue end - if buttonc > 0 then - buttonw = math.max(buttonw / buttonc, buttonh * 2.5) - else - buttonw = 0 - end + local newbutteny = buttony + (buttonw + margin) + if newbutteny >= h then + v:SetPos(0, 0) + v:SetHeight(0) + continue + end - local buttonbarw = buttonw * buttonc - if buttonbarw > 0 then - buttonbarw = buttonbarw + (buttonc - 1) * margin + v:SetPos(buttonx, buttony) + v:SetSize(buttonw, buttonw) + buttony = newbutteny end - if buttonh > 0 then - bodyheight = bodyheight - buttonh - margin - end + return buttonw, buttony +end - local buttonx = (w - buttonbarw) / 2 - local buttony = h - buttonh +function CLASS:PerformLayout(...) + BASE.PerformLayout(self, ...) - if IsValid(self.CloseButton) and self.CloseButton.Layout.Visible then - self.CloseButton:SetSize(buttonw, buttonh) - self.CloseButton:SetPos(buttonx, buttony) - buttonx = buttonx + (buttonw + margin) - end + local w, h = self:GetClientSize() - if IsValid(self.RetryButton) and self.RetryButton.Layout.Visible then - self.RetryButton:SetSize(buttonw, buttonh) - self.RetryButton:SetPos(buttonx, buttony) - buttonx = buttonx + (buttonw + margin) - end + local margin = self:GetMargin() - if IsValid(self.HelpButton) and self.HelpButton.Layout.Visible then - self.HelpButton:SetSize(buttonw, buttonh) - self.HelpButton:SetPos(buttonx, buttony) - end + self:_PerformButtonLayout(margin, margin) if IsValid(self.BodyPanelText) and self.BodyPanelText.Layout.Visible then self.BodyPanelText:SetPos(0, 0) - self.BodyPanelText:SetSize(w, bodyheight) + self.BodyPanelText:SetSize(w, h) end -end +end \ No newline at end of file diff --git a/lua/streamradio_core/classes/ui/radio/gui_player.lua b/lua/streamradio_core/classes/ui/radio/gui_player.lua index a5db65e..f67cd76 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_player.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_player.lua @@ -4,6 +4,7 @@ if not istable(CLASS) then end local LIBNetwork = StreamRadioLib.Network +local LIBError = StreamRadioLib.Error local BASE = CLASS:GetBaseClass() @@ -142,6 +143,10 @@ function CLASS:Create() self.State = self:CreateListener({ Error = 0, }, function(this, k, v) + if not IsValid(self.Errorbox) then + return + end + local err = tonumber(v or 0) or 0 local url = nil @@ -175,6 +180,7 @@ function CLASS:Remove() self.StreamOBJ:RemoveEvent("OnConnect", self:GetID()) self.StreamOBJ:RemoveEvent("OnError", self:GetID()) self.StreamOBJ:RemoveEvent("OnSearch", self:GetID()) + self.StreamOBJ:RemoveEvent("OnMute", self:GetID()) end end @@ -202,7 +208,7 @@ function CLASS:SetStream(stream) self.ControlPanel:SetStream(stream) end - if IsValid(self.ControlPanel) then + if IsValid(self.SpectrumPanel) then self.SpectrumPanel:SetStream(stream) end @@ -234,30 +240,42 @@ function CLASS:SetStream(stream) end if CLIENT then - self.StreamOBJ:SetEvent("OnSearch", self:GetID(), function() + local updateErrorState = function(err) if not IsValid(self) then return end if not self.State then return end - if not IsValid(self.Errorbox) then return end - self.State.Error = 0 + if err == LIBError.STREAM_OK then + self.State.Error = LIBError.STREAM_OK + else + if IsValid(self.Errorbox) then + self.Errorbox:SetErrorCode(err, self.StreamOBJ:GetURL()) + end + + self.State.Error = err + end + end + + self.StreamOBJ:SetEvent("OnClose", self:GetID(), function() + updateErrorState(LIBError.STREAM_OK) end) - self.StreamOBJ:SetEvent("OnConnect", self:GetID(), function() - if not IsValid(self) then return end - if not self.State then return end - if not IsValid(self.Errorbox) then return end + self.StreamOBJ:SetEvent("OnSearch", self:GetID(), function() + updateErrorState(LIBError.STREAM_OK) + end) - self.State.Error = 0 + self.StreamOBJ:SetEvent("OnConnect", self:GetID(), function() + updateErrorState(LIBError.STREAM_OK) end) self.StreamOBJ:SetEvent("OnError", self:GetID(), function(this, err) - if not IsValid(self) then return end - if not self.State then return end - if not IsValid(self.Errorbox) then return end + updateErrorState(err) + end) - self.Errorbox:SetErrorCode(err, self.StreamOBJ:GetURL()) - self.State.Error = err + self.StreamOBJ:SetEvent("OnMute", self:GetID(), function() + updateErrorState(LIBError.STREAM_OK) end) + + updateErrorState(self.StreamOBJ:GetError()) end self:UpdateFromStream() @@ -275,9 +293,11 @@ function CLASS:Think() end function CLASS:UpdateFromStream() - if not IsValid(self.StreamOBJ) then return end if SERVER then return end + if not IsValid(self.StreamOBJ) then return end + if not IsValid(self.HeaderText) then return end + local textlist = {} local name = self.StreamOBJ:GetStreamName() @@ -423,14 +443,26 @@ function CLASS:PerformLayout(...) end function CLASS:EnablePlaylist(...) + if not IsValid(self.ControlPanel) then + return + end + self.ControlPanel:EnablePlaylist(...) end function CLASS:IsPlaylistEnabled() + if not IsValid(self.ControlPanel) then + return + end + return self.ControlPanel:IsPlaylistEnabled() end function CLASS:UpdatePlaybackLoopMode(...) + if not IsValid(self.ControlPanel) then + return + end + self.ControlPanel:UpdatePlaybackLoopMode(...) end diff --git a/lua/streamradio_core/classes/ui/radio/gui_player_controls.lua b/lua/streamradio_core/classes/ui/radio/gui_player_controls.lua index 19376c5..7258a25 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_player_controls.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_player_controls.lua @@ -243,7 +243,7 @@ function CLASS:Create() if not IsValid(self.StreamOBJ) then return end if self.StreamOBJ:GetMuted() then - return "Muted..." + return "Muted" end if self.StreamOBJ:IsBuffering() then @@ -251,10 +251,10 @@ function CLASS:Create() end if self.StreamOBJ:IsStopMode() then - return "Stopped..." + return "Stopped" end - if self.StreamOBJ:GetError() ~= 0 then + if self.StreamOBJ:HasError() then return "Error!" end @@ -272,7 +272,7 @@ function CLASS:Create() return FormatTimeleft(time, len) end - -- @TODO: fix that seaking is CLIENT -> SERVER instead if SERVER -> CLIENT + -- @TODO: Fix that seaking is CLIENT -> SERVER instead if SERVER -> CLIENT self.PlayBar.OnFractionChangeEdit = function(this, v) if not IsValid(self.StreamOBJ) then return end @@ -329,7 +329,7 @@ function CLASS:PerformLayout(...) local hasplaybar = IsValid(self.PlayBar) and self.PlayBar.Layout.Visible local buttons = self.Buttons or {} - for k, v in pairs(buttons) do + for k, v in ipairs(buttons) do if not IsValid(v) then continue end if not v.Layout.Visible then continue end @@ -349,7 +349,7 @@ function CLASS:PerformLayout(...) buttonw = (w - margin * (buttoncount - 1)) / buttoncount end - for k, v in pairs(buttons) do + for k, v in ipairs(buttons) do if not IsValid(v) then continue end if not v.Layout.Visible then continue end @@ -371,7 +371,7 @@ function CLASS:PerformLayout(...) hpos = hasplaybar and buttonh + margin or 0 - for k, v in pairs(buttons) do + for k, v in ipairs(buttons) do if not IsValid(v) then continue end if not v.Layout.Visible then continue end @@ -549,7 +549,7 @@ function CLASS:UpdateFromStream() local len = StreamOBJ:GetLength() local time = StreamOBJ:GetTime() - local isEndlessOrNoStream = StreamOBJ:IsEndless() or StreamOBJ:IsLoading() or StreamOBJ:GetError() ~= 0 or StreamOBJ:GetMuted() + local isEndlessOrNoStream = StreamOBJ:IsEndless() or StreamOBJ:IsLoading() or StreamOBJ:HasError() or StreamOBJ:GetMuted() -- @TODO: Fix that seaking is CLIENT -> SERVER instead if SERVER -> CLIENT. -- That's because self.PlayBar is disabled on the server as BASS streams don't exist on servers. @@ -572,6 +572,22 @@ function CLASS:ShouldPerformRerender() if SERVER then return false end if not IsValid(self.StreamOBJ) then return false end + if not IsValid(self.PlayBar) then + return false + end + + if not self.PlayBar:IsVisible() then + return false + end + + if self.StreamOBJ:GetMuted() then + return false + end + + if self.StreamOBJ:HasError() then + return false + end + if not self.StreamOBJ:IsPlaying() then return false end diff --git a/lua/streamradio_core/classes/ui/radio/gui_player_spectrum.lua b/lua/streamradio_core/classes/ui/radio/gui_player_spectrum.lua index 34b73e6..96bcc49 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_player_spectrum.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_player_spectrum.lua @@ -40,29 +40,47 @@ end function CLASS:SetForegroundColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Foreground = color end function CLASS:GetForegroundColor() if SERVER then return end - local col = self.Colors.Foreground - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.Foreground + return col end function CLASS:SetIconColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Icon = color end function CLASS:GetIconColor() if SERVER then return end - local col = self.Colors.Icon - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.Icon + return col end -local function RenderSpectrumBar(index, level, bars, soundlevel, x, y, w, h, color) +local function RenderSpectrumBar(index, level, bars, x, y, w, h, cR, cG, cB, cA) if ( index > w ) then return false end if ( bars > w ) then @@ -80,7 +98,7 @@ local function RenderSpectrumBar(index, level, bars, soundlevel, x, y, w, h, col local BarY = h + y local barheight = math.floor( math.Clamp( level * h, 0, h ) ) - surface.SetDrawColor( color ) + surface.SetDrawColor( cR, cG, cB, cA ) surface.DrawRect( BarX, BarY - barheight, barwide, barheight ) return true @@ -100,11 +118,13 @@ function CLASS:RenderSpectrum() soundlevel = math.Clamp( soundlevel ^ 2, 0, 1 ) soundlevel = ( soundlevel * 0.5 ) + 0.5 - color.r = color.r * soundlevel - color.g = color.g * soundlevel - color.b = color.b * soundlevel + local cR, cG, cB, cA = color:Unpack() + + cR = cR * soundlevel + cG = cG * soundlevel + cB = cB * soundlevel - self.StreamOBJ:GetSpectrumTable(StreamRadioLib.GetSpectrumBars(), self.Spectrum, RenderSpectrumBar, soundlevel, x, y, w, h, color) + self.StreamOBJ:GetSpectrumTable(StreamRadioLib.GetSpectrumBars(), self.Spectrum, RenderSpectrumBar, x, y, w, h, cR, cG, cB, cA) end function CLASS:RenderLoader() @@ -124,7 +144,7 @@ function CLASS:RenderLoader() end function CLASS:RenderIcon(icon) - local color = self.Colors.Icon + local colIcon = self.Colors.Icon or color_white local x, y = self:GetRenderPos() local p = self:GetPadding() @@ -136,7 +156,7 @@ function CLASS:RenderIcon(icon) local sqmax, sqmin = math.max(w, h), math.min(w, h) local isq = math.min(sqmax * 0.125, sqmin * 0.5) - surface.SetDrawColor( color ) + surface.SetDrawColor( colIcon:Unpack() ) surface.SetMaterial( icon ) surface.DrawTexturedRectUV( x + (w - isq) / 2, y + (h - isq) / 2, isq, isq, 0, 0, 1, 1 ) end @@ -202,6 +222,10 @@ function CLASS:ShouldPerformRerender() return false end + if self.StreamOBJ:HasError() then + return false + end + if self.StreamOBJ:IsLoading() then return true end diff --git a/lua/streamradio_core/classes/ui/shadow_panel.lua b/lua/streamradio_core/classes/ui/shadow_panel.lua index d7481a6..a65badd 100644 --- a/lua/streamradio_core/classes/ui/shadow_panel.lua +++ b/lua/streamradio_core/classes/ui/shadow_panel.lua @@ -81,22 +81,26 @@ function CLASS:Render() local x, y = self:GetRenderPos() local w, h = self:GetSize() - local ShadowWidth = self:GetShadowWidth() + local shadowWidth = self:GetShadowWidth() - if ShadowWidth <= 0 then - surface.SetDrawColor(self.Colors.Main or color_white) + local colMain = self.Colors.Main or color_white + + if shadowWidth <= 0 then + surface.SetDrawColor(colMain:Unpack()) surface.DrawRect(x, y, w, h) BASE.Render(self) return end - local sx, sy = x + ShadowWidth, y + ShadowWidth - local sw, sh = w - ShadowWidth, h - ShadowWidth + local sx, sy = x + shadowWidth, y + shadowWidth + local sw, sh = w - shadowWidth, h - shadowWidth + + local colShadow = self.Colors.Shadow or color_black - surface.SetDrawColor(self.Colors.Shadow or color_black) + surface.SetDrawColor(colShadow:Unpack()) surface.DrawRect(sx, sy, sw, sh) - surface.SetDrawColor(self.Colors.Main or color_white) + surface.SetDrawColor(colMain:Unpack()) surface.DrawRect(x, y, sw, sh) BASE.Render(self) @@ -105,8 +109,11 @@ end function CLASS:PerformLayout(...) BASE.PerformLayout(self, ...) - local text_panel = self.TextPanel + if not self.CanHaveLabel then + return + end + local text_panel = self.TextPanel if not IsValid(text_panel) then return end @@ -117,14 +124,23 @@ end function CLASS:SetShadowColor(color) if SERVER then return end + + color = color or {} + color = Color( + color.r or 0, + color.g or 0, + color.b or 0, + color.a or 0 + ) + self.Colors.Shadow = color end function CLASS:GetShadowColor() if SERVER then return end - local col = self.Colors.Shadow - return Color(col.r or 0, col.g or 0, col.b or 0, col.a or 0) + local col = self.Colors.Shadow + return col end function CLASS:GetShadowWidth() diff --git a/lua/streamradio_core/classes/ui/text.lua b/lua/streamradio_core/classes/ui/text.lua index 028362c..cffdb55 100644 --- a/lua/streamradio_core/classes/ui/text.lua +++ b/lua/streamradio_core/classes/ui/text.lua @@ -187,11 +187,11 @@ function CLASS:Render() local w, h = self:GetSize() local xalign, yalign = self.TextData.AlignX, self.TextData.AlignY - local col = self.Colors.Main + local col = self.Colors.Main or color_black local font = self.TextData.Font - + surface.SetFont( font ) - surface.SetTextColor( col ) + surface.SetTextColor( col:Unpack() ) local lines = self:GetVisibleLines() local textw, texth = self:GetTextSize() diff --git a/lua/streamradio_core/classes/ui/textview.lua b/lua/streamradio_core/classes/ui/textview.lua index 6669827..db507cd 100644 --- a/lua/streamradio_core/classes/ui/textview.lua +++ b/lua/streamradio_core/classes/ui/textview.lua @@ -8,6 +8,8 @@ local BASE = CLASS:GetBaseClass() function CLASS:Create() BASE.Create(self) + self.CanHaveLabel = false + self.TextPanel = self:AddPanelByClassname("text", true) self.TextPanel:SetPos(0, 0) self.TextPanel:SetAlign(TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) diff --git a/lua/streamradio_core/classes/ui/tooltip.lua b/lua/streamradio_core/classes/ui/tooltip.lua index a02227f..928d49f 100644 --- a/lua/streamradio_core/classes/ui/tooltip.lua +++ b/lua/streamradio_core/classes/ui/tooltip.lua @@ -87,12 +87,15 @@ function CLASS:Render() local x, y = self:GetRenderPos() local w, h = self:GetSize() - local textcol = self:GetTextColor() + local colText = self:GetTextColor() + colText = colText or color_white + + local colMain = self.Colors.Main or color_black local thickness = 2 local padding = 2 - surface.SetDrawColor(textcol or color_white) + surface.SetDrawColor(colText:Unpack()) for i = 0, thickness - 1 do local t = i + padding @@ -101,7 +104,7 @@ function CLASS:Render() surface.DrawOutlinedRect(x + t, y + t, w - tt, h - tt) end - surface.SetDrawColor(self.Colors.Main or color_black) + surface.SetDrawColor(colMain:Unpack()) surface.DrawRect(x, y, w, h) BASE.Render(self) diff --git a/lua/streamradio_core/client/cl_help.lua b/lua/streamradio_core/client/cl_help.lua index a7adcc7..0e243be 100644 --- a/lua/streamradio_core/client/cl_help.lua +++ b/lua/streamradio_core/client/cl_help.lua @@ -1,353 +1,106 @@ -local IsValid = IsValid -local type = type -local Vector = Vector -local GetViewEntity = GetViewEntity -local unpack = unpack -local tonumber = tonumber -local tostring = tostring -local Color = Color -local LocalPlayer = LocalPlayer -local concommand = concommand -local hook = hook -local math = math -local debug = debug -local string = string -local vgui = vgui -local net = net - -local NoHelpText = [[ -There is no help text for this error. - -Please report this! Include the URL and the Errorcode in the report! -]] - -NoHelpText = string.gsub(NoHelpText, "\r", "") -NoHelpText = string.Trim(NoHelpText) - -local ErrorHelpPlaylistText = [[ -The Playlist file you are trying to load is invalid. - -This could be the problem: - - The playlist could not be found or read. - - Its format is not supported. - - It is broken. - - It is empty. - -Supported playlist formats: - M3U, PLS, VDF, JSON - -Playlists are located at "/garrysmod/data/streamradio/playlists/". - -Hint: Use the playlist editor to make playlists. -]] - -ErrorHelpPlaylistText = string.gsub(ErrorHelpPlaylistText, "\r", "") -ErrorHelpPlaylistText = string.Trim(ErrorHelpPlaylistText) - -local ErrorHelps = {} - -ErrorHelps[-1] = { - text = [[ -The exact cause of this error is unknown. - -This error is usually caused by: - - Invalid file pathes or URLs without the protocol prefix such as 'http://'. - - Attempting to play self-looping *.WAV files. - -]], - helpurl = "" -} - -ErrorHelps[0] = { - text = [[ -Everything should be fine. You should not see this. -]], - helpurl = "" -} - -ErrorHelps[1] = { - text = [[ -A memory error is always bad. -You proably ran out of it. -]], - helpurl = "" -} - -ErrorHelps[2] = { - text = [[ -There was no file or content found at the given path. - -If you try to play an online file: - - Do not forget the protocol prefix such as 'http://'. - - Make sure the file exist at the given URL. It should be downloadable. - - Make sure the format is supported and the file is not broken. (See below.) - -If you try to play a local file: - - Make sure the file exist at the given path. - - Make sure the file is readable for Garry's Mod. - - The path must be relative your "/garrysmod/sound/" folder. (See below.) - - The file must be in "/garrysmod/sound/" folder. (See below.) - - You can play mounted stuff in "/garrysmod/sound/". - - You can not play sound scripts or sound properties. - - Make sure the format is supported and the file is not broken. (See below.) - -Supported formats: - MP3, OGG, AAC, WAV, WMA, FLAC - *.WAV files must be not self-looping in game as the API does not support these. - -How local or mounted file paths work: - - If you have a file located "/garrysmod/sound/mymusic/song.mp3" you access it with these urls: - * file://mymusic/song.mp3 - * mymusic/song.mp3" - - - For files in "/garrysmod/sound/filename.mp3" you get them like this: - * file://filename.mp3 - * filename.mp3 - - - Files outside the game folder are forbidden to be accessed by the game. - - Do not enter absolute paths. - - Only people who also have the same file localed there, will be able to hear the music too. - - Create folders if they are missing. -]], - helpurl = "https://steamcommunity.com/workshop/filedetails/discussion/246756300/523897277918001392/" -} - -ErrorHelps[3] = { - text = [[ -Something is wrong with your sound hardware or your sound drivers. -]], - helpurl = "" -} - -ErrorHelps[4] = { - text = [[ -Your sound driver/interface was lost. - -To fix it you need to do this: -- Plugin your speakers or head phones. -- Enable the sound device. -- Restart the game. Do not just disconnect! -- Restart your PC, if it still not works. -]], - helpurl = "" -} - -ErrorHelps[18] = { - text = [[ -A memory error is always bad. -You proably ran out of it. -]], - helpurl = "" -} - -ErrorHelps[21] = { - text = [[ -Something is wrong with your sound hardware or your sound drivers. -It does not support 3D world sound. -]], - helpurl = "" -} - -ErrorHelps[22] = { - text = [[ -Something is wrong with your sound hardware or your sound drivers. -It does not support EAX-effects. -]], - helpurl = "" -} - -ErrorHelps[29] = { - text = [[ -Something is wrong with your sound hardware. Out of memory? -]], - helpurl = "" -} - -ErrorHelps[32] = { - text = [[ -You internet connection is not working. -Check your network devices and your firewall. -]], - helpurl = "" -} - -ErrorHelps[34] = { - text = [[ -Something is wrong with your sound hardware or your sound drivers. -It does not support EAX-effects. -]], - helpurl = "" -} - -ErrorHelps[37] = { - text = [[4]], -} - -ErrorHelps[39] = { - text = [[ -Something is wrong with your sound hardware or your sound drivers. -DirectX seems to be outdated or not installed. -]], - helpurl = "" -} - -ErrorHelps[40] = { - text = [[ -The connection seems being slow. Just try again in a few minutes. -If it does not work, the server you are trying to stream from is down. -]], - helpurl = "" -} - -ErrorHelps[41] = { - text = [[ -You are trying to play something that the streaming API of GMod (and so the radio) does not support. - -These things will NOT work: - - HTML pages that play sound. - - Flash players/games/applications that are playing sound. - - Anything that requires any kind of login to access. - - Anything that is not public. - - Sound scripts or sound properties. - - Broken files or unsupported formats. (See below.) - -These things will work: - - URLs to sound files (aka. DIRECT download). - - URLs to playlist files of radio stations. If they do not offer them, you will be not able to play them. - - URLs inside these playlists files. - - Local sound files inside your "/garrysmod/sound/" folder. Examble: "music/hl1_song10.mp3" - - You may have to install addional codices to your OS. - - Formats that are listed below. - -Supported formats: - MP3, OGG, AAC, WAV, WMA, FLAC - *.WAV files must be not self-looping ingame as the API does not support these. -]], - helpurl = "https://steamcommunity.com/workshop/filedetails/discussion/246756300/523897277918028290/" -} - -ErrorHelps[42] = { - text = [[ -Something is wrong with your sound hardware or your sound drivers. -Do you even have speakers? -]], - helpurl = "" -} - -ErrorHelps[44] = { - text = [[41]] -} - -ErrorHelps[1000] = { - text = [[ -The server does not allow playback of custom URLs. -You can ask an admin to enable it, but there is probably a reason why it is forbidden. - -All online URLs from these sources are blocked: - - Toolgun input - - Wiremod input - - From duplications and saves - -The Convar is: sv_streamradio_allow_customurls 0/1 -]], - helpurl = "" -} - -ErrorHelps[37] = ErrorHelps[4] -ErrorHelps[44] = ErrorHelps[41] - -local HelpPanel = StreamRadioLib.g_HelpPanel - -if IsValid(HelpPanel) then - StreamRadioLib.VR.CloseMenu(HelpPanel) - HelpPanel:Remove() - - HelpPanel = nil +local LIBError = StreamRadioLib.Error + +local g_helpPanel = StreamRadioLib.g_HelpPanel + +if IsValid(g_helpPanel) then + StreamRadioLib.VR.CloseMenu(g_helpPanel) + g_helpPanel:Remove() + + g_helpPanel = nil StreamRadioLib.g_HelpPanel = nil end -local function CreateErrorHelpPanel( ErrorHeader, ErrorText, url, ErrorOnlineHelp, ErrorData ) - ErrorHeader = ErrorHeader or "" - ErrorText = ErrorText or "" +local function CreateErrorHelpPanel() + if IsValid( g_helpPanel ) then + return g_helpPanel + end + + local ErrorHelpFont = StreamRadioLib.Surface.AddFont(14, 1000, "Lucida Console") + local HelpPanel = vgui.Create( "DFrame" ) -- The main frame. + + HelpPanel:SetPos( 25, 25 ) + HelpPanel:SetSize( 900, 600 ) + + HelpPanel:SetMinWidth( 575 ) + HelpPanel:SetMinHeight( 200 ) + HelpPanel:SetSizable( true ) + HelpPanel:SetDeleteOnClose( false ) + HelpPanel:SetVisible( false ) + HelpPanel:SetTitle( "Stream Radio Error Information" ) + HelpPanel:SetZPos(150) + HelpPanel:GetParent():SetWorldClicker( true ) + + HelpPanel.HelpTextPanel = vgui.Create( "Streamradio_VGUI_ReadOnlyTextEntry", HelpPanel ) + HelpPanel.HelpTextPanel:SetDrawBorder( true ) + HelpPanel.HelpTextPanel:SetPaintBackground( true ) + HelpPanel.HelpTextPanel:SetVerticalScrollbarEnabled( true ) + HelpPanel.HelpTextPanel:SetFont( ErrorHelpFont ) + HelpPanel.HelpTextPanel:SetZPos(100) + HelpPanel.HelpTextPanel:SetCursor( "beam" ) + HelpPanel.HelpTextPanel:Dock( FILL ) + + local ControlPanel = vgui.Create( "DPanel", HelpPanel ) + ControlPanel:SetPaintBackground( false ) + ControlPanel:SetTall( 30 ) + ControlPanel:DockMargin( 0, 5, 0, 0 ) + ControlPanel:SetZPos(200) + ControlPanel:Dock( BOTTOM ) + + local OkButton = vgui.Create( "DButton", ControlPanel ) + OkButton:SetWide( 100 ) + OkButton:SetText( "OK" ) + OkButton:DockMargin( 5, 0, 0, 0 ) + OkButton:SetZPos(300) + OkButton:Dock( RIGHT ) + + OkButton.DoClick = function( self ) + StreamRadioLib.VR.CloseMenu(HelpPanel) + end + + HelpPanel.CopyButton = vgui.Create( "DButton", ControlPanel ) + HelpPanel.CopyButton:SetWide( 100 ) + HelpPanel.CopyButton:SetText( "Copy to clipboard" ) + HelpPanel.CopyButton:DockMargin( 5, 0, 0, 0 ) + HelpPanel.CopyButton:SetZPos(400) + HelpPanel.CopyButton:Dock( RIGHT ) + + HelpPanel.OnlineHelpButton = StreamRadioLib.Menu.GetLinkButton("View online help") + HelpPanel.OnlineHelpButton:SetParent(ControlPanel) + HelpPanel.OnlineHelpButton:SetWide( 175 ) + HelpPanel.OnlineHelpButton:DockMargin( 5, 0, 20, 0 ) + HelpPanel.OnlineHelpButton:SetZPos(500) + HelpPanel.OnlineHelpButton:Dock( RIGHT ) + + HelpPanel.OptionToggleTick = vgui.Create( "DCheckBoxLabel", ControlPanel ) + HelpPanel.OptionToggleTick:SetWide( 125 ) + HelpPanel.OptionToggleTick:SetText( "" ) + HelpPanel.OptionToggleTick:DockMargin( 10, 0, 0, 0 ) + HelpPanel.OptionToggleTick:SetZPos(600) + HelpPanel.OptionToggleTick:Dock( LEFT ) + + g_helpPanel = HelpPanel + return HelpPanel +end + +local function OpenErrorHelpPanel( header, helptext, url, helpurl, userdata ) + header = header or "" + helptext = helptext or "" url = url or "" - ErrorOnlineHelp = ErrorOnlineHelp or "" - ErrorData = ErrorData or {} + helpurl = helpurl or "" + userdata = userdata or {} - local tickboxdata = ErrorData.userdata or {} + local tickboxdata = userdata.userdata or {} tickboxdata = tickboxdata.tickbox - if not IsValid( HelpPanel ) then - local ErrorHelpFont = StreamRadioLib.Surface.AddFont(14, 1000, "Lucida Console") - HelpPanel = vgui.Create( "DFrame" ) -- The main frame. - - HelpPanel:SetPos( 25, 25 ) - HelpPanel:SetSize( 700, 400 ) - - HelpPanel:SetMinWidth( 575 ) - HelpPanel:SetMinHeight( 200 ) - HelpPanel:SetSizable( true ) - HelpPanel:SetDeleteOnClose( false ) - HelpPanel:SetVisible( false ) - HelpPanel:SetTitle( "Stream Radio Error Information" ) - HelpPanel:SetZPos(150) - HelpPanel:GetParent():SetWorldClicker( true ) - - HelpPanel.HelpTextPanel = vgui.Create( "Streamradio_VGUI_ReadOnlyTextEntry", HelpPanel ) - HelpPanel.HelpTextPanel:SetDrawBorder( true ) - HelpPanel.HelpTextPanel:SetPaintBackground( true ) - HelpPanel.HelpTextPanel:SetVerticalScrollbarEnabled( true ) - HelpPanel.HelpTextPanel:SetFont( ErrorHelpFont ) - HelpPanel.HelpTextPanel:SetZPos(100) - HelpPanel.HelpTextPanel:SetCursor( "beam" ) - HelpPanel.HelpTextPanel:Dock( FILL ) - - local ControlPanel = vgui.Create( "DPanel", HelpPanel ) - ControlPanel:SetPaintBackground( false ) - ControlPanel:SetTall( 30 ) - ControlPanel:DockMargin( 0, 5, 0, 0 ) - ControlPanel:SetZPos(200) - ControlPanel:Dock( BOTTOM ) - - local OkButton = vgui.Create( "DButton", ControlPanel ) - OkButton:SetWide( 100 ) - OkButton:SetText( "OK" ) - OkButton:DockMargin( 5, 0, 0, 0 ) - OkButton:SetZPos(300) - OkButton:Dock( RIGHT ) - - OkButton.DoClick = function( self ) - StreamRadioLib.VR.CloseMenu(HelpPanel) - end - - HelpPanel.CopyButton = vgui.Create( "DButton", ControlPanel ) - HelpPanel.CopyButton:SetWide( 100 ) - HelpPanel.CopyButton:SetText( "Copy to clipboard" ) - HelpPanel.CopyButton:DockMargin( 5, 0, 0, 0 ) - HelpPanel.CopyButton:SetZPos(400) - HelpPanel.CopyButton:Dock( RIGHT ) - - HelpPanel.OnlineHelpButton = StreamRadioLib.Menu.GetLinkButton("View online help") - HelpPanel.OnlineHelpButton:SetParent(ControlPanel) - HelpPanel.OnlineHelpButton:SetWide( 175 ) - HelpPanel.OnlineHelpButton:DockMargin( 5, 0, 20, 0 ) - HelpPanel.OnlineHelpButton:SetZPos(500) - HelpPanel.OnlineHelpButton:Dock( RIGHT ) - - HelpPanel.OptionToggleTick = vgui.Create( "DCheckBoxLabel", ControlPanel ) - HelpPanel.OptionToggleTick:SetWide( 125 ) - HelpPanel.OptionToggleTick:SetText( "" ) - HelpPanel.OptionToggleTick:DockMargin( 10, 0, 0, 0 ) - HelpPanel.OptionToggleTick:SetZPos(600) - HelpPanel.OptionToggleTick:Dock( LEFT ) - end + local HelpPanel = CreateErrorHelpPanel() - if ( not IsValid( HelpPanel ) ) then return end - if ( not IsValid( HelpPanel.HelpTextPanel ) ) then return end - if ( not IsValid( HelpPanel.CopyButton ) ) then return end - if ( not IsValid( HelpPanel.OnlineHelpButton ) ) then return end - if ( not IsValid( HelpPanel.OptionToggleTick ) ) then return end + if not IsValid( HelpPanel ) then return end + if not IsValid( HelpPanel.HelpTextPanel ) then return end + if not IsValid( HelpPanel.CopyButton ) then return end + if not IsValid( HelpPanel.OnlineHelpButton ) then return end + if not IsValid( HelpPanel.OptionToggleTick ) then return end - HelpPanel:SetTitle( "Stream Radio Error Information | " .. ErrorHeader ) + HelpPanel:SetTitle( "Stream Radio Error Information | " .. header ) if not StreamRadioLib.VR.IsActive() then local X, Y = HelpPanel:GetPos() @@ -371,7 +124,7 @@ local function CreateErrorHelpPanel( ErrorHeader, ErrorText, url, ErrorOnlineHel HelpPanel:GetParent():SetWorldClicker(true) else HelpPanel:SetPos(0, 0) - HelpPanel:SetSize(700, 400) + HelpPanel:SetSize(900, 600) HelpPanel:SetSizable(false) HelpPanel:SetDraggable(false) HelpPanel:GetParent():SetWorldClicker(false) @@ -384,14 +137,14 @@ local function CreateErrorHelpPanel( ErrorHeader, ErrorText, url, ErrorOnlineHel ) if url ~= "" then - ErrorText = ErrorHeader .. "\nURL: " .. url .. "\n\n" .. ErrorText + helptext = string.format("%s\n\n%s\n\n%s", header, url, helptext) else - ErrorText = ErrorHeader .. "\n\n" .. ErrorText + helptext = string.format("%s\n\n%s", header, helptext) end - HelpPanel.HelpTextPanel:SetText( ErrorText ) + HelpPanel.HelpTextPanel:SetText( helptext ) - local CopyText = string.gsub( ErrorText or "", "\n", "\r\n" ) + local CopyText = string.gsub( helptext or "", "\n", "\r\n" ) CopyText = string.Trim( CopyText ) HelpPanel.CopyButton:SetVisible(CopyText ~= "") @@ -402,8 +155,8 @@ local function CreateErrorHelpPanel( ErrorHeader, ErrorText, url, ErrorOnlineHel SetClipboardText( CopyText ) end - HelpPanel.OnlineHelpButton:SetVisible( ErrorOnlineHelp ~= "" ) - HelpPanel.OnlineHelpButton:SetURL( ErrorOnlineHelp ) + HelpPanel.OnlineHelpButton:SetVisible( helpurl ~= "" ) + HelpPanel.OnlineHelpButton:SetURL( helpurl ) HelpPanel.OptionToggleTick:SetVisible(tickboxdata ~= nil) @@ -422,34 +175,26 @@ function StreamRadioLib.ShowErrorHelp( errorcode, url ) errorcode = tonumber(errorcode or -1) or -1 if errorcode == 0 then return end - local errorheader = "Error " .. errorcode .. ": " .. StreamRadioLib.DecodeErrorCode( errorcode ) + local errorInfo = LIBError.GetStreamErrorInfo(errorcode) - local errordata = ErrorHelps[errorcode] or {} - local errortext = errordata.text or "" - errortext = string.gsub( errortext, "\r", "" ) - errortext = string.Trim( errortext ) + local code = errorInfo.id + local name = errorInfo.name + local description = errorInfo.description or "" + local userdata = errorInfo.userdata - if errortext == "" then - errordata = StreamRadioLib.Interface.GetErrorData(errorcode) or {} - errortext = errordata.text or "" - errortext = string.gsub( errortext, "\r", "" ) - errortext = string.Trim( errortext ) - end - - if errortext == "" then - errortext = NoHelpText - end + local header = string.format("Error %i (%s): %s", code, name, description) - local erroronlinehelp = errordata.helpurl or errordata.url or "" + local helptext = errorInfo.helptext or "" + local helpurl = errorInfo.helpurl or "" - url = url or "" + url = tostring(url or "") if StreamRadioLib.IsBlockedURLCode(url) then url = "" end - CreateErrorHelpPanel( errorheader, errortext, url, erroronlinehelp, errordata ) + OpenErrorHelpPanel( header, helptext, url, helpurl, userdata ) end function StreamRadioLib.ShowPlaylistErrorHelp( ) - CreateErrorHelpPanel( "Error: Invalid Playlist", ErrorHelpPlaylistText, nil, "https://steamcommunity.com/workshop/filedetails/discussion/246756300/523897277917951293/" ) + StreamRadioLib.ShowErrorHelp(LIBError.PLAYLIST_ERROR_INVALID_FILE) end diff --git a/lua/streamradio_core/client/cl_lib.lua b/lua/streamradio_core/client/cl_lib.lua index c8c4875..637828f 100644 --- a/lua/streamradio_core/client/cl_lib.lua +++ b/lua/streamradio_core/client/cl_lib.lua @@ -13,6 +13,7 @@ local string = string local net = net local LIBNet = StreamRadioLib.Net +local LIBError = StreamRadioLib.Error LIBNet.Receive("PlaylistMenu", function( length ) if ( not istable( StreamRadioLib ) ) then return end @@ -291,7 +292,12 @@ do end TestChannel.OnError = function(self, err) - print( "OnError", self, err, StreamRadioLib.DecodeErrorCode( err ) ) + local errorInfo = LIBError.GetStreamErrorInfo(err) + + local errorName = errorInfo.name + local errorDescription = errorInfo.description + + print("OnError", self, err, errorName, errorDescription) end StreamRadioLib._TestChannel = TestChannel diff --git a/lua/streamradio_core/client/cl_menu.lua b/lua/streamradio_core/client/cl_menu.lua index 0b13347..346d612 100644 --- a/lua/streamradio_core/client/cl_menu.lua +++ b/lua/streamradio_core/client/cl_menu.lua @@ -133,6 +133,21 @@ function LIB.GetSpacer(height) return spacer end +function LIB.GetSpacerLine() + local spacer = vgui.Create("DPanel") + + spacer.Paint = function( p, w, h ) + derma.SkinHook( "Paint", "MenuSpacer", p, w, h ) + end + + spacer:DockMargin(0, 0, 0, 0) + spacer:DockPadding(0, 0, 0, 0) + + spacer:SetHeight(1) + + return spacer +end + function LIB.GetFAQButton() local button = LIB.GetLinkButton("Open FAQ (Workshop)", "https://steamcommunity.com/workshop/filedetails/discussion/246756300/368542844488661960/") diff --git a/lua/streamradio_core/client/cl_playlist_edit.lua b/lua/streamradio_core/client/cl_playlist_edit.lua index f9f0869..83fd4cc 100644 --- a/lua/streamradio_core/client/cl_playlist_edit.lua +++ b/lua/streamradio_core/client/cl_playlist_edit.lua @@ -236,12 +236,12 @@ do if not IsValid(ply) then return end if not ply:IsAdmin() then - StreamRadioLib.Msg(ply, "You must be admin to use the playlist editor.") + StreamRadioLib.Print.Msg(ply, "You must be admin to use the playlist editor.") return end if StreamRadioLib.VR.IsActive(ply) then - StreamRadioLib.Msg(ply, "The playlist editor is not available in VR.") + StreamRadioLib.Print.Msg(ply, "The playlist editor is not available in VR.") return end diff --git a/lua/streamradio_core/client/cl_surface.lua b/lua/streamradio_core/client/cl_surface.lua index 7028d4c..c121629 100644 --- a/lua/streamradio_core/client/cl_surface.lua +++ b/lua/streamradio_core/client/cl_surface.lua @@ -55,7 +55,7 @@ function LIB.Loading( x, y, w, h, color, cycles ) --local posang = pi * 2 / cycles * i + time local cx = math.cos( pi * 2 / cycles * i + time ) * ( midw - cw / 2 ) + x - cw / 2 + midw local cy = math.sin( pi * 2 / cycles * i + time ) * ( midh - cw / 2 ) + y - ch / 2 + midh - surface.SetDrawColor( color ) + surface.SetDrawColor( color:Unpack() ) surface.DrawTexturedRect( cx, cy, cw, ch ) end end diff --git a/lua/streamradio_core/client/cl_vgui.lua b/lua/streamradio_core/client/cl_vgui.lua index 1c261b7..2699a68 100644 --- a/lua/streamradio_core/client/cl_vgui.lua +++ b/lua/streamradio_core/client/cl_vgui.lua @@ -1,4 +1,6 @@ local StreamRadioLib = StreamRadioLib or {} +local LIBError = StreamRadioLib.Error + local PANEL = {} AccessorFunc( PANEL, "m_strValue", "Value" ) @@ -242,9 +244,15 @@ function PANEL:UpdateURLState(state) if err ~= 0 and url ~= "" then self.URLIcon:SetImage("icon16/cross.png") - local errinfo = "\nError " .. err .. ": " .. StreamRadioLib.DecodeErrorCode(err) - tooltip = tooltipbase .. errinfo .. "\n\nRight click for more details." - tooltipurl = tooltipbase .. errinfo .. "\n\nRight click on the red cross button for more details." + local errorInfo = LIBError.GetStreamErrorInfo(err) + + local errorName = errorInfo.name + local errorDescription = errorInfo.description + + local errorString = string.format("Error %i (%s): %s", err, errorName, errorDescription) + + tooltip = tooltipbase .. "\n" .. errorString .. "\n\nRight click for more details." + tooltipurl = tooltipbase .. "\n" .. errorString .. "\n\nRight click on the red cross button for more details." else self.URLIcon:SetImage("icon16/information.png") tooltip = "The URL is empty!" diff --git a/lua/streamradio_core/client/settings/general.lua b/lua/streamradio_core/client/settings/general.lua index 5c6274b..65e519b 100644 --- a/lua/streamradio_core/client/settings/general.lua +++ b/lua/streamradio_core/client/settings/general.lua @@ -4,12 +4,19 @@ local LIB = StreamRadioLib.Settings LIB.g_CV_List["general"] = {} LIB.AddConVar("general", "mute", "cl_streamradio_mute", "0", { - label = "Mute radios", + label = "Mute all radios", help = "Mutes all radios when set to 1. Default: 0", type = "bool", userdata = true, }) +LIB.AddConVar("general", "mute_foreign", "cl_streamradio_mute_foreign", "0", { + label = "Mute all radios from other players", + help = "Mutes all radios from other players when set to 1. Default: 0", + type = "bool", + userdata = true, +}) + LIB.AddConVar("general", "muteunfocused", "cl_streamradio_muteunfocused", "0", { label = "Mute radios on game unfocus", help = "Mutes all radios when the game is not in focus if set to 1. Default: 0", @@ -94,6 +101,7 @@ LIB.AddConVar("general", "volume", "cl_streamradio_volume", "1", { label = "Global volume", help = "Set the global volume factor for all radios. Default: 1, Min: 0, Max: 1 or 10", type = "float", + userdata = true, min = 0, max = 10, }) @@ -118,6 +126,12 @@ LIB.AddConVar("general", "no3dsound", "cl_streamradio_no3dsound", "0", { type = "bool", }) +LIB.AddConVar("general", "bass3_enable", "cl_streamradio_bass3_enable", "1", { + label = "Use GM_BASS3 if installed", + help = "When set to 1, it uses GM_BASS3 if installed and allowed on the server. Default: 1", + type = "bool", +}) + LIB.AddConVar("general", "youtubesupport", "cl_streamradio_youtubesupport", "0", { label = "Enable YouTube support (slow and unreliable!)", help = "Enable YouTube support when set to 1. (slow and unreliable!) Default: 0", @@ -148,6 +162,9 @@ local function BuildMenuPanel(CPanel) return end + local cvBass3Enable = LIB.GetConVar("bass3_enable") + cvBass3Enable:SetDisabled(not StreamRadioLib.Bass.IsInstalled()) + CPanel:Button( "Clear Client Stream Cache", "cl_streamradio_cacheclear" diff --git a/lua/streamradio_core/enum.lua b/lua/streamradio_core/enum.lua index 1fa98f2..3819f00 100644 --- a/lua/streamradio_core/enum.lua +++ b/lua/streamradio_core/enum.lua @@ -8,10 +8,14 @@ StreamRadioLib.STREAM_URLTYPE_CACHE = 1 StreamRadioLib.STREAM_URLTYPE_ONLINE = 2 StreamRadioLib.STREAM_URLTYPE_ONLINE_NOCACHE = 3 - -- Placeholder for Blocked URLs with non-Keyboard chars StreamRadioLib.BlockedURLCode = string.char(124, 245, 142, 188, 5, 6, 2, 1, 2, 54, 12, 7, 5) .. "___blocked_url" +StreamRadioLib.PLAYBACK_LOOP_MODE_NONE = 0 +StreamRadioLib.PLAYBACK_LOOP_MODE_SONG = 1 +StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST = 2 + + StreamRadioLib.EDITOR_ERROR_OK = 0 StreamRadioLib.EDITOR_ERROR_WRITE_OK = 1 StreamRadioLib.EDITOR_ERROR_READ_OK = 2 @@ -92,10 +96,6 @@ local EditorErrors = { [StreamRadioLib.EDITOR_ERROR_UNKNOWN] = "Unknown Error" } -StreamRadioLib.PLAYBACK_LOOP_MODE_NONE = 0 -StreamRadioLib.PLAYBACK_LOOP_MODE_SONG = 1 -StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST = 2 - function StreamRadioLib.DecodeEditorErrorCode( err ) err = tonumber(err) or StreamRadioLib.EDITOR_ERROR_UNKNOWN local errorText = EditorErrors[err] or EditorErrors[StreamRadioLib.EDITOR_ERROR_UNKNOWN] @@ -105,94 +105,4 @@ function StreamRadioLib.DecodeEditorErrorCode( err ) end return errorText -end - -local Errors = { - -- Code // Error - [-1] = "Unknown Error", - [0] = "OK", - [1] = "Memory Error", - [2] = "Can't open the file", - [3] = "Can't find a free/valid driver", - [4] = "The sample buffer was lost", - [5] = "Invalid handle", - [6] = "Unsupported sample format", - [7] = "Invalid position", - [8] = "BASS_Init has not been successfully called", - [9] = "BASS_Start has not been successfully called", - [14] = "Already initialized/paused/whatever", - [18] = "Can't get a free channel", - [19] = "An illegal type was specified", - [20] = "An illegal parameter was specified", - [21] = "No 3D support", - [22] = "No EAX support", - [23] = "Illegal device number", - [24] = "Not playing", - [25] = "Illegal sample rate", - [27] = "The stream is not a file stream", - [29] = "No hardware voices available", - [31] = "The MOD music has no sequence data", - [32] = "No internet connection could be opened", - [33] = "Couldn't create the file", - [34] = "Effects are not available", - [37] = "Requested data is not available", - [38] = "The channel is a 'decoding channel'", - [39] = "A sufficient DirectX version is not installed", - [40] = "Connection timedout", - [41] = "Unsupported file format", - [42] = "Unavailable speaker", - [43] = "Invalid BASS version (used by add-ons)", - [44] = "Codec is not available/supported", - [45] = "The channel/file has ended", - - [1000] = "Custom URLs are blocked on this server", -} - -function StreamRadioLib.DecodeErrorCode(errorcode) - errorcode = tonumber(errorcode or -1) or -1 - - if BASS3 and BASS3.DecodeErrorCode and errorcode < 200 and errorcode >= -1 then - return BASS3.DecodeErrorCode(errorcode) - end - - if Errors[errorcode] then - return Errors[errorcode] - end - - local errordata = StreamRadioLib.Interface.GetErrorData(errorcode) or {} - local errordesc = string.Trim(errordata.desc or "") - - if errordesc == "" then - errordesc = Errors[-1] - end - - if not errordata.interface then - return errordesc - end - - local iname = errordata.interface.name - - if errordata.subinterface then - iname = iname .. "/" .. errordata.subinterface.name - end - - errordesc = "[" .. iname .. "] " .. errordesc - return errordesc -end - -do - local function ShowErrorInfo( ply, cmd, args ) - if ( not args[1] or ( args[1] == "" ) ) then - StreamRadioLib.Msg(ply, "You need to enter a valid error code.") - - return - end - - local err = tonumber( args[1] ) or -1 - local errstr = StreamRadioLib.DecodeErrorCode( err ) - local msgstring = StreamRadioLib.AddonPrefix .. "Error code " .. err .. " = " .. errstr - StreamRadioLib.Msg( ply, msgstring ) - end - - concommand.Add( "info_streamradio_errorcode", ShowErrorInfo ) end \ No newline at end of file diff --git a/lua/streamradio_core/error.lua b/lua/streamradio_core/error.lua new file mode 100644 index 0000000..a65b77d --- /dev/null +++ b/lua/streamradio_core/error.lua @@ -0,0 +1,771 @@ +StreamRadioLib.Error = StreamRadioLib.Error or {} +local LIB = StreamRadioLib.Error + +local g_errorListById = {} +local g_errorListByName = {} + +local g_emptyDescription = "Error {{ERROR_CODE}} is unknown" +local g_emptyHelpText = [[ +There is no help text for error {{ERROR_CODE}} ({{ERROR_NAME}}). + +Please report this! Include the URL and the error code in the report! +]] + +local g_commonErrorFile = [[ +There was no file or content found at the given path. + +If you try to play an online file: + - Do not forget the protocol prefix such as 'http://'. + - Make sure the file exist at the given URL. It should be downloadable. + - Make sure the format is supported and the file is not broken. (See below.) + +If you try to play a local file: + - Make sure the file exist at the given path. + - Make sure the file is readable for Garry's Mod. + - The path must be relative your "/garrysmod/sound/" folder. (See below.) + - The file must be in "/garrysmod/sound/" folder. (See below.) + - You can play mounted stuff in "/garrysmod/sound/". + - You can not play sound scripts or sound properties. + - Make sure the format is supported and the file is not broken. (See below.) + +Supported formats: + MP3, OGG, AAC, WAV, WMA, FLAC + *.WAV files must be not self-looping in game as the API does not support these. + +How local or mounted file paths work: + - If you have a file located "/garrysmod/sound/mymusic/song.mp3" you access it with these urls: + * file://mymusic/song.mp3 + * mymusic/song.mp3" + + - For files in "/garrysmod/sound/filename.mp3" you get them like this: + * file://filename.mp3 + * filename.mp3 + + - Files outside the game folder are forbidden to be accessed by the game. + - Do not enter absolute paths. + - Only people who also have the same file localed there, will be able to hear the music too. + - Create folders if they are missing. +]] + +local g_commonErrorFileUrl = "https://steamcommunity.com/workshop/filedetails/discussion/246756300/523897277918001392/" + +local g_commonErrorFormat = [[ +You are trying to play something that the streaming API of GMod (and so the radio) does not support. + +These things will NOT work: + - HTML pages that play sound. + - Flash players/games/applications that are playing sound. + - Anything that requires any kind of login to access. + - Anything that is not public. + - Sound scripts or sound properties. + - Broken files or unsupported formats. (See below.) + +These things will work: + - URLs to sound files (aka. DIRECT download). + - URLs to playlist files of radio stations. If they do not offer them, you will be not able to play them. + - URLs inside these playlists files. + - Local sound files inside your "/garrysmod/sound/" folder. Examble: "music/hl1_song10.mp3" + - You may have to install addional codices to your OS. + - Formats that are listed below. + +Supported formats: + MP3, OGG, AAC, WAV, WMA, FLAC + *.WAV files must be not self-looping ingame as the API does not support these. +]] + +local g_commonErrorFormatUrl = "https://steamcommunity.com/workshop/filedetails/discussion/246756300/523897277918028290/" + +local g_commonErrorBrokenUrl = [[ +Something went wrong with parsing the URL. +It could have been blocked by the server to prevent abuse. + +Please talk to an admin about this before you report this issue. +]] + +local function replacePlaceholder(subject, placeholder, value) + subject = tostring(subject or "") + placeholder = tostring(placeholder or "") + value = tostring(value or "") + + return string.Replace(subject, placeholder, value) +end + +local function cleanNewLines(str) + str = string.gsub(str, "\r", "") + str = string.Trim(str) + + return str +end + +local function processErrorInfo(info) + local id = info.id + local name = info.name + local description = info.description or "" + local helptext = info.helptext or "" + local helpurl = info.helpurl or "" + + description = replacePlaceholder(description, "{{ERROR_CODE}}", id) + description = replacePlaceholder(description, "{{ERROR_NAME}}", name) + + helptext = replacePlaceholder(helptext, "{{ERROR_CODE}}", id) + helptext = replacePlaceholder(helptext, "{{ERROR_NAME}}", name) + helptext = replacePlaceholder(helptext, "{{ERROR_DESCRIPTION}}", description) + helptext = replacePlaceholder(helptext, "{{ERROR_HELPURL}}", helpurl) + + helptext = cleanNewLines(helptext) + + info.description = description + info.helptext = helptext +end + +local function createUnknownErrorInfo(idOrName) + local info = {} + + if isstring(idOrName) then + info.id = LIB.STREAM_ERROR_UNKNOWN + info.name = idOrName + else + info.id = idOrName + info.name = "STREAM_ERROR_UNKNOWN" + end + + info.description = g_emptyDescription + info.helptext = g_emptyHelpText + + processErrorInfo(info) + return info +end + + +function LIB.AddStreamErrorCode(data) + local id = data.id + local name = data.name + local description = data.description + local helptext = data.helptext + local helpurl = data.helpurl + local userdata = data.userdata + + if not id then + error("id is missing") + end + + if not name or name == "" then + error("name is missing or empty") + end + + id = tonumber(id) or -1 + name = tostring(name) + name = string.upper(name) + + local info = { + id = id, + name = name, + } + + if userdata then + info.userdata = table.Copy(userdata) + end + + LIB[name] = id + + g_errorListById[id] = info + g_errorListByName[name] = info + + LIB.AddStreamDescription(id, description) + LIB.AddStreamErrorHelp(id, helptext, helpurl) +end + +function LIB.AddStreamDescription(idOrName, description) + local info = LIB.GetStreamErrorInfo(idOrName) + if not info then + return + end + + local id = info.id + local name = info.name + + description = tostring(description or "") + + if description == "" then + description = g_emptyDescription + end + + description = replacePlaceholder(description, "{{ERROR_CODE}}", id) + description = replacePlaceholder(description, "{{ERROR_NAME}}", name) + + info.description = description +end + +function LIB.AddStreamErrorHelp(idOrName, helptext, helpurl) + local info = LIB.GetStreamErrorInfo(idOrName) + if not info then + return + end + + local id = info.id + local name = info.name + local description = info.description + + helptext = tostring(helptext or "") + helpurl = tostring(helpurl or "") + + if helptext == "" then + helptext = g_emptyHelpText + end + + helptext = replacePlaceholder(helptext, "{{ERROR_CODE}}", id) + helptext = replacePlaceholder(helptext, "{{ERROR_NAME}}", name) + helptext = replacePlaceholder(helptext, "{{ERROR_DESCRIPTION}}", description) + helptext = replacePlaceholder(helptext, "{{ERROR_HELPURL}}", helpurl) + + helptext = cleanNewLines(helptext) + + info.helptext = helptext + info.helpurl = helpurl +end + +function LIB.GetStreamErrorInfo(idOrName) + if not idOrName or idOrName == "" then + idOrName = LIB.STREAM_ERROR_UNKNOWN + end + + local errorList = nil + + if isstring(idOrName) then + errorList = g_errorListByName + else + errorList = g_errorListById + end + + local info = errorList[idOrName] + if not info then + info = createUnknownErrorInfo(idOrName) + end + + return info +end + +function LIB.GetStreamErrorId(idOrName) + local info = LIB.GetStreamErrorInfo(idOrName) + if not info then + return + end + + return info.id +end + +function LIB.GetStreamErrorName(idOrName) + local info = LIB.GetStreamErrorInfo(idOrName) + if not info then + return + end + + return info.name +end + +function LIB.GetStreamErrorDescription(idOrName) + local info = LIB.GetStreamErrorInfo(idOrName) + if not info then + return + end + + return info.description +end + +LIB.AddStreamErrorCode({ + id = -1, + name = "STREAM_ERROR_UNKNOWN", + description = "Unknown Error", + helptext = [[ +The exact cause of this error is unknown. + +This error is usually caused by: + - Invalid file pathes or URLs without the protocol prefix such as 'http://'. + - Attempting to play self-looping *.WAV files. +]], +}) + +LIB.AddStreamErrorCode({ + id = 0, + name = "STREAM_OK", + description = "OK", + helptext = [[ +Everything should be fine. You should not see this. +]], +}) + +LIB.AddStreamErrorCode({ + id = 1, + name = "STREAM_ERROR_MEM", + description = "Memory Error", + helptext = [[ +A memory error is always bad. +You proably ran out of it. +]], +}) + +LIB.AddStreamErrorCode({ + id = 2, + name = "STREAM_ERROR_FILEOPEN", + description = "Can't open the file", + helptext = g_commonErrorFile, + helpurl = g_commonErrorFileUrl +}) + +LIB.AddStreamErrorCode({ + id = 3, + name = "STREAM_ERROR_DRIVER", + description = "Can't find a free/valid driver", + helptext = [[ +Something is wrong with your sound hardware or your sound drivers. +]], +}) + +LIB.AddStreamErrorCode({ + id = 4, + name = "STREAM_ERROR_BUFLOST", + description = "The sample buffer was lost", + helptext = [[ +Your sound driver/interface was lost. + +To fix it you need to do this: +- Plugin your speakers or head phones. +- Enable the sound device. +- Restart the game. Do not just disconnect! +- Restart your PC, if it still not works. +]], +}) + +LIB.AddStreamErrorCode({ + id = 5, + name = "STREAM_ERROR_HANDLE", + description = "Invalid handle", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 6, + name = "STREAM_ERROR_FORMAT", + description = "Unsupported sample format", + helptext = g_commonErrorFormat, + helpurl = g_commonErrorFormatUrl, +}) + +LIB.AddStreamErrorCode({ + id = 7, + name = "STREAM_ERROR_POSITION", + description = "Invalid position", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 8, + name = "STREAM_ERROR_INIT", + description = "BASS_Init has not been successfully called", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 9, + name = "STREAM_ERROR_START", + description = "BASS_Start has not been successfully called", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 10, + name = "STREAM_ERROR_SSL", + description = "SSL/HTTPS support isn't available", + helptext = [[ +The SSL handshake for HTTPS did failed to validate the connection. +Please check the URL being legit and your operating system to be up to date. +]], +}) + +LIB.AddStreamErrorCode({ + id = 11, + name = "STREAM_ERROR_REINIT", + description = "Device needs to be reinitialized", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 14, + name = "STREAM_ERROR_ALREADY", + description = "Already initialized/paused/whatever", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 17, + name = "STREAM_ERROR_NOTAUDIO", + description = "File does not contain audio", + helptext = g_commonErrorFormat, + helpurl = g_commonErrorFormatUrl, +}) + +LIB.AddStreamErrorCode({ + id = 18, + name = "STREAM_ERROR_NOCHAN", + description = "Can't get a free channel", + helptext = [[ +A memory error is always bad. +You proably ran out of it. +]], +}) + +LIB.AddStreamErrorCode({ + id = 19, + name = "STREAM_ERROR_ILLTYPE", + description = "An illegal type was specified", + helptext = g_commonErrorBrokenUrl, +}) + +LIB.AddStreamErrorCode({ + id = 20, + name = "STREAM_ERROR_ILLPARAM", + description = "An illegal parameter was specified", + helptext = g_commonErrorBrokenUrl, +}) + +LIB.AddStreamErrorCode({ + id = 21, + name = "STREAM_ERROR_NO3D", + description = "No 3D support", + helptext = [[ +Something is wrong with your sound hardware or your sound drivers. +It does not support 3D world sound. +]], +}) + +LIB.AddStreamErrorCode({ + id = 22, + name = "STREAM_ERROR_NOEAX", + description = "No EAX support", + helptext = [[ +Something is wrong with your sound hardware or your sound drivers. +It does not support EAX-effects. +]], +}) + +LIB.AddStreamErrorCode({ + id = 23, + name = "STREAM_ERROR_DEVICE", + description = "Illegal device number", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 24, + name = "STREAM_ERROR_NOPLAY", + description = "Not playing", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 25, + name = "STREAM_ERROR_FREQ", + description = "Illegal sample rate", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 27, + name = "STREAM_ERROR_NOTFILE", + description = "The stream is not a file stream", + helptext = g_commonErrorFile, + helpurl = g_commonErrorFileUrl +}) + +LIB.AddStreamErrorCode({ + id = 29, + name = "STREAM_ERROR_NOHW", + description = "No hardware voices available", + helptext = [[ +Something is wrong with your sound hardware. Out of memory? +]], +}) + +LIB.AddStreamErrorCode({ + id = 31, + name = "STREAM_ERROR_EMPTY", + description = "The MOD music has no sequence data", + helptext = g_commonErrorFormat, + helpurl = g_commonErrorFormatUrl, +}) + +LIB.AddStreamErrorCode({ + id = 32, + name = "STREAM_ERROR_NONET", + description = "No internet connection could be opened", + helptext = [[ +You internet connection is not working. +Please check your network devices and your firewall. +]], +}) + +LIB.AddStreamErrorCode({ + id = 33, + name = "STREAM_ERROR_CREATE", + description = "Couldn't create the file", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 34, + name = "STREAM_ERROR_NOFX", + description = "Effects are not available", + helptext = [[ +Something is wrong with your sound hardware or your sound drivers. +It does not support EAX-effects. +]], +}) + +LIB.AddStreamErrorCode({ + id = 37, + name = "STREAM_ERROR_NOTAVAIL", + description = "Requested data/action is not available", + helptext = [[ +Your sound driver/interface was lost. + +To fix it you need to do this: +- Plugin your speakers or head phones. +- Enable the sound device. +- Restart the game. Do not just disconnect! +- Restart your PC, if it still not works. +]], +}) + +LIB.AddStreamErrorCode({ + id = 38, + name = "STREAM_ERROR_DECODE", + description = "The channel is a 'decoding channel'", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 39, + name = "STREAM_ERROR_DX", + description = "A sufficient DirectX version is not installed", + helptext = [[ +Something is wrong with your sound hardware or your sound drivers. +DirectX seems to be outdated or not installed. +]], +}) + +LIB.AddStreamErrorCode({ + id = 40, + name = "STREAM_ERROR_TIMEOUT", + description = "Connection timedout", + helptext = [[ +The connection seems being slow. Just try again in a few minutes. +If it does not work, the server you are trying to stream from is available. +]], +}) + +LIB.AddStreamErrorCode({ + id = 41, + name = "STREAM_ERROR_FILEFORM", + description = "Unsupported file format", + helptext = g_commonErrorFormat, + helpurl = g_commonErrorFormatUrl, +}) + +LIB.AddStreamErrorCode({ + id = 42, + name = "STREAM_ERROR_SPEAKER", + description = "Unavailable speaker", + helptext = [[ +Something is wrong with your sound hardware or your sound drivers. +Do you even have speakers? +]], +}) + +LIB.AddStreamErrorCode({ + id = 43, + name = "STREAM_ERROR_VERSION", + description = "Invalid BASS version (used by add-ons)", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 44, + name = "STREAM_ERROR_CODEC", + description = "Codec is not available/supported", + helptext = g_commonErrorFormat, + helpurl = g_commonErrorFormatUrl, +}) + +LIB.AddStreamErrorCode({ + id = 45, + name = "STREAM_ERROR_ENDED", + description = "The channel/file has ended", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 46, + name = "STREAM_ERROR_BUSY", + description = "The device is busy", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 47, + name = "STREAM_ERROR_UNSTREAMABLE", + description = "Unstreamable file", + helptext = g_commonErrorFormat, + helpurl = g_commonErrorFormatUrl, +}) + +LIB.AddStreamErrorCode({ + id = 48, + name = "STREAM_ERROR_PROTOCOL", + description = "Unsupported protocol", + helptext = g_commonErrorBrokenUrl, +}) + +LIB.AddStreamErrorCode({ + id = 49, + name = "STREAM_ERROR_DENIED", + description = "Access denied", + helptext = [[ +Can not access the resource. Login credentials required, but not supported. + +CAUTION: Do not try to access private resources! Credentials could leak to other connected players or the server!! + +Better use public resources only. +]], +}) + + +LIB.AddStreamErrorCode({ + id = 1000, + name = "STREAM_ERROR_CUSTOM_URI_BLOCKED", + description = "Custom URLs are blocked on this server", + helptext = [[ +The server does not allow playback of custom URLs to prevent abuse. +You can ask an admin to enable it, but there is probably a reason why it is forbidden. + +All online URLs from these sources are blocked: + - Toolgun input + - Wiremod input + - From duplications and saves + +The Convar is: sv_streamradio_allow_customurls 0/1 +]], +}) + +LIB.AddStreamErrorCode({ + id = 1001, + name = "STREAM_ERROR_WIRE_ADVOUT_DISABLED", + description = "Advanced outputs are disabled", + helptext = "", +}) + +LIB.AddStreamErrorCode({ + id = 2000, + name = "PLAYLIST_ERROR_INVALID_FILE", + description = "Invalid Playlist", + helptext = [[ +The Playlist file you are trying to load is invalid. + +This could be the problem: + - The playlist could not be found or read. + - Its format is not supported. + - It is broken. + - It is empty. + +Supported playlist formats: + M3U, PLS, VDF, JSON + +Playlists are located at "/garrysmod/data/streamradio/playlists/". + +Hint: Use the playlist editor to make playlists. +]], + helpurl = "https://steamcommunity.com/workshop/filedetails/discussion/246756300/523897277917951293/", +}) + + +if istable(CFCHTTP) then + -- Handle CFC HTTP Whitelist error cases + -- https://github.com/CFC-Servers/cfc_cl_http_whitelist + + LIB.AddStreamErrorCode({ + id = CFCHTTP.BASS_ERROR_BLOCKED_URI, + name = "STREAM_ERROR_CFCHTTP_BLOCKED_URI", + description = "URI has been blocked by CFC HTTP Whitelist", + helptext = [[ +The server has blocked this URL via CFC HTTP Whitelist to prevent abuse. +You can ask an admin to whitelist the URL above in their CFC tool. + +Keep in mind that there is probably a reason why it is forbidden on this server. +]], + }) + + LIB.AddStreamErrorCode({ + id = CFCHTTP.BASS_ERROR_BLOCKED_CONTENT, + name = "STREAM_ERROR_CFCHTTP_BLOCKED_CONTENT", + description = "Content has been blocked by CFC HTTP Whitelist", + helptext = [[ +The server has blocked this content via CFC HTTP Whitelist to prevent abuse. +You can ask an admin to whitelist the content from the URL above in their CFC tool. + +Keep in mind that there is probably a reason why it is forbidden on this server. +]], + }) +end + +if CLIENT then + local function ShowErrorInfo( ply, cmd, args ) + local param = args[1] + + if not param or param == "" then + MsgN("You need to enter a valid error code.") + return + end + + local errorcode = tonumber(param) or tostring(param) + local errorInfo = LIB.GetStreamErrorInfo(errorcode) + + local id = errorInfo.id + local name = errorInfo.name + local description = errorInfo.description + local helptext = errorInfo.helptext + local helpurl = errorInfo.helpurl or "" + + if helpurl == "" then + helpurl = "(no url)" + end + + local format = [[ +Getting info for error code "%s": + +Id: %i +Name: %s +Description: %s + +Help text: +%s + +Help URL: %s +]] + + format = cleanNewLines(format) + + local errstr = string.format( + format, + errorcode, + id, + name, + description, + helptext, + helpurl + ) + + local message = StreamRadioLib.AddonPrefix .. errstr + MsgN(message) + end + + concommand.Add( "info_streamradio_errorcode", ShowErrorInfo ) +end \ No newline at end of file diff --git a/lua/streamradio_core/interface.lua b/lua/streamradio_core/interface.lua index c9d3194..16332cc 100644 --- a/lua/streamradio_core/interface.lua +++ b/lua/streamradio_core/interface.lua @@ -191,60 +191,6 @@ local function AddCommonFunctions(interface) if not added then return nil end return stack end - - function interface:GetErrorData(errorcode) - errorcode = tonumber(errorcode or -1) or -1 - - if errorcode == -1 then - return nil - end - - local function geterror(this) - if not this then return nil end - if not this.Errorcodes then return nil end - - local err = this.Errorcodes[errorcode] - if not err then return nil end - - local nerr = {} - - nerr.interface = self - - if this ~= self then - nerr.subinterface = this - end - - nerr.code = errorcode - nerr.desc = err.desc or "" - nerr.text = err.text or "" - nerr.url = err.url or "" - - if err.userdata then - nerr.userdata = table.Copy(err.userdata) - end - - return nerr - end - - local err = geterror(self) - if err then - return err - end - - if not self.subinterfaces then - return nil - end - - for i, v in ipairs(self.subinterfaces) do - local err = geterror(v) - if not err then continue end - - return err - end - - return nil - end - end local function AddSubInterfaces(interface) @@ -273,8 +219,6 @@ local function AddSubInterfaces(interface) RADIOIFACE.scriptfile = scriptfile RADIOIFACE.parent = interface - RADIOIFACE.Errorcodes = {} - AddCommonFunctions(RADIOIFACE) StreamRadioLib.LoadSH(scriptfile, true) @@ -314,7 +258,6 @@ local function AddInterface(script) RADIOIFACE.scriptpath = scriptpath RADIOIFACE.scriptfile = scriptfile - RADIOIFACE.Errorcodes = {} RADIOIFACE.subinterfaces = {} AddCommonFunctions(RADIOIFACE) @@ -360,31 +303,6 @@ local function GetInterfaceFromURL(url) return nil end -function LIB.GetErrorData(errorcode) - errorcode = tonumber(errorcode or -1) or -1 - - if errorcode == -1 then - return nil - end - - if err_cache[errorcode] then - return err_cache[errorcode] - end - - for i, v in ipairs(Intefaces) do - if not v then continue end - if not v.GetErrorData then continue end - - local err = v:GetErrorData(errorcode) - if not err then continue end - - err_cache[errorcode] = err - return err - end - - return nil -end - function LIB.Load() local files = file.Find(LuaInterfaceDirectory .. "/*", "LUA") Intefaces = {} diff --git a/lua/streamradio_core/interfaces/dropbox.lua b/lua/streamradio_core/interfaces/dropbox.lua index 68f3ccb..fe0b52c 100644 --- a/lua/streamradio_core/interfaces/dropbox.lua +++ b/lua/streamradio_core/interfaces/dropbox.lua @@ -5,18 +5,19 @@ if not istable( RADIOIFACE ) then end RADIOIFACE.name = "Dropbox" -RADIOIFACE.subinterfaces_folder = "dropbox" RADIOIFACE.download = true RADIOIFACE.download_timeout = 5 -local ERROR_NO_PATH = 20000 +local ERROR_NO_PATH = 130000 -RADIOIFACE.Errorcodes[ERROR_NO_PATH] = { - desc = "Dropbox url has no path", - text = [[ +StreamRadioLib.Error.AddStreamErrorCode({ + id = ERROR_NO_PATH, + name = "STREAM_ERROR_DROPBOX_NO_PATH", + description = "[Dropbox] Url has no path", + helptext = [[ Make sure your Dropbox has a valid path in it. ]], -} +}) local DropboxPatterns = { "dropbox%://(.+)", diff --git a/lua/streamradio_core/interfaces/shoutcast.lua b/lua/streamradio_core/interfaces/shoutcast.lua index c72e2e2..10e8cab 100644 --- a/lua/streamradio_core/interfaces/shoutcast.lua +++ b/lua/streamradio_core/interfaces/shoutcast.lua @@ -4,29 +4,30 @@ if not istable( RADIOIFACE ) then return end -local ERROR_NO_ID = 11000 - RADIOIFACE.name = "SHOUTcast" -RADIOIFACE.subinterfaces_folder = "shoutcast" - -local ShoutcastPatterns = { - "shoutcast%://([%d]+)", -} -local ShoutcastURLs = { - "shoutcast://", -} +local ERROR_NO_ID = 120000 -RADIOIFACE.Errorcodes[ERROR_NO_ID] = { - desc = "Invalid stream ID", - text = [[ +StreamRadioLib.Error.AddStreamErrorCode({ + id = ERROR_NO_ID, + name = "STREAM_ERROR_SHOUTCAST_NO_ID", + description = "[SHOUTcast] Invalid stream ID", + helptext = [[ An invalid stream ID was given. Notes: - Make sure you enter a URL of an existing SHOUTcast stream. - The URL should look like this shoutcast://123456 - Only numbers are supported. -]] +]], +}) + +local ShoutcastPatterns = { + "shoutcast%://([%d]+)", +} + +local ShoutcastURLs = { + "shoutcast://", } function RADIOIFACE:CheckURL(url) diff --git a/lua/streamradio_core/interfaces/youtube.lua b/lua/streamradio_core/interfaces/youtube.lua index 9d5b346..fa5d0ad 100644 --- a/lua/streamradio_core/interfaces/youtube.lua +++ b/lua/streamradio_core/interfaces/youtube.lua @@ -7,58 +7,62 @@ end RADIOIFACE.name = "YouTube" RADIOIFACE.subinterfaces_folder = "youtube" -local ERROR_DISABLED = 10000 -local ERROR_UNSUPPORTED = 10001 -local ERROR_NO_ID = 10002 +local ERROR_DISABLED = 110000 +local ERROR_UNSUPPORTED = 110001 +local ERROR_NO_ID = 110002 -RADIOIFACE.youtube_help_url = "https://steamcommunity.com/workshop/filedetails/discussion/246756300/4523281307928803506/" -local youtube_help_url = RADIOIFACE.youtube_help_url +local youtube_help_url = "https://steamcommunity.com/workshop/filedetails/discussion/246756300/4523281307928803506/" -RADIOIFACE.Errorcodes[ERROR_UNSUPPORTED] = { - desc = "YouTube is not supported", - text = [[ +StreamRadioLib.Error.AddStreamErrorCode({ + id = ERROR_UNSUPPORTED, + name = "STREAM_ERROR_YOUTUBE_UNSUPPORTED", + description = "[YouTube] YouTube is not supported", + helptext = [[ YouTube is not supported. Please use other media sources. You can use a Youtube to MP3 converter, but it is not recommended. Notes: - - Reliable YouTube support can't be added. It is impossible. - - Please, don't ask me about it. - - View the online help link for more information. + - Reliable YouTube support can't be added. It is impossible. + - Please, don't ask me about it. + - View the online help link for more information. ]], - url = youtube_help_url, -} - -RADIOIFACE.Errorcodes[ERROR_NO_ID] = { - desc = "Invalid video ID", - text = [[ + helpurl = youtube_help_url, +}) + +StreamRadioLib.Error.AddStreamErrorCode({ + id = ERROR_NO_ID, + name = "STREAM_ERROR_YOUTUBE_NO_ID", + description = "[YouTube] Invalid video ID", + helptext = [[ An invalid video ID was given. Notes: - - Make sure you enter a YouTube URL of an existing video. - - Do not try to play from YouTube playlists or channels. Those are not supported. + - Make sure you enter a YouTube URL of an existing video. + - Do not try to play from YouTube playlists or channels. Those are not supported. ]], - url = youtube_help_url, -} - -RADIOIFACE.Errorcodes[ERROR_DISABLED] = { - desc = "YouTube support is not enabled", - text = [[ + helpurl = youtube_help_url, +}) + +StreamRadioLib.Error.AddStreamErrorCode({ + id = ERROR_DISABLED, + name = "STREAM_ERROR_YOUTUBE_DISABLED", + description = "[YouTube] Invalid video ID", + helptext = [[ Playback from YouTube is disabled. You can enable it with the tickbox below or in the Stream Radio settings. Notes: - - This is slow and unreliable. - - Use at your own risk. + - This is slow and unreliable. + - Use at your own risk. ]], - url = youtube_help_url, + helpurl = youtube_help_url, userdata = { tickbox = { text = "Enable YouTube support\n(slow and unreliable!)", cmd = "cl_streamradio_youtubesupport", }, }, -} - +}) local YoutubePatterns = { "youtube%://([%w%-%_]+)", @@ -84,9 +88,9 @@ local YoutubeURLs = { function RADIOIFACE:PrintError(url, code) StreamRadioLib.Debug([[ Error Converting YouTube URL: '%s' -Code: %d, %s +Code: %d (%s), %s Retrying with next module... -]], url, code, StreamRadioLib.DecodeErrorCode(code)) +]], url, code, StreamRadioLib.Error.GetStreamErrorName(code), StreamRadioLib.Error.GetStreamErrorDescription(code)) end diff --git a/lua/streamradio_core/json.lua b/lua/streamradio_core/json.lua index 8b2dbe1..c1f15fa 100644 --- a/lua/streamradio_core/json.lua +++ b/lua/streamradio_core/json.lua @@ -6,33 +6,44 @@ function LIB.Encode(data, prettyPrint) data = {data} end - local data = util.TableToJSON(data, prettyPrint) - data = StreamRadioLib.NormalizeNewlines(data, "\n") + local status, json = StreamRadioLib.CatchAndErrorNoHaltWithStack(util.TableToJSON, data) + if not status then + return nil + end - return data + if not json then + return nil + end + + json = StreamRadioLib.NormalizeNewlines(json, "\n") + return json end -function LIB.Decode(data) - data = tostring(data or "") - data = StreamRadioLib.NormalizeNewlines(data, "\n") +function LIB.Decode(json) + json = tostring(json or "") + json = StreamRadioLib.NormalizeNewlines(json, "\n") - data = string.gsub(data, "//.-\n" , "\n") -- singleline comment - data = string.gsub(data, "/%*.-%*/" , "\n") -- multiline comment + json = string.gsub(json, "//.-\n" , "\n") -- singleline comment + json = string.gsub(json, "/%*.-%*/" , "\n") -- multiline comment - data = string.gsub(data, ",([%s]*)([%]%}])","%1%2") -- trailing comma of arrays/objects + json = string.gsub(json, ",([%s]*)([%]%}])","%1%2") -- trailing comma of arrays/objects - data = string.gsub(data, "\n[%s]*","\n") -- remove all spaces at the start of lines - data = string.gsub(data, "[%s\n]*\n","\n") -- remove all empty lines and all spaces at the end of lines - data = string.gsub(data, "^\n","") -- remove first empty new line - data = string.gsub(data, "\n$","") -- remove last empty new line + json = string.gsub(json, "\n[%s]*","\n") -- remove all spaces at the start of lines + json = string.gsub(json, "[%s\n]*\n","\n") -- remove all empty lines and all spaces at the end of lines + json = string.gsub(json, "^\n","") -- remove first empty new line + json = string.gsub(json, "\n$","") -- remove last empty new line - data = string.Trim(data) + json = string.Trim(json) - if data == "" then + if json == "" then return {} end - data = util.JSONToTable(data) + local status, data = StreamRadioLib.CatchAndErrorNoHaltWithStack(util.JSONToTable, json) + + if not status then + return nil + end if not data then return nil @@ -45,5 +56,4 @@ function LIB.Decode(data) return data end - return LIB diff --git a/lua/streamradio_core/language.lua b/lua/streamradio_core/language.lua new file mode 100644 index 0000000..c2cf92c --- /dev/null +++ b/lua/streamradio_core/language.lua @@ -0,0 +1,72 @@ +StreamRadioLib.Language = StreamRadioLib.Language or {} +local LIB = StreamRadioLib.Language + +local g_nameprefix = "3dstreamradio." +local g_translated = {} + +function LIB.GetName(identifier) + identifier = g_nameprefix .. tostring(identifier or "") + identifier = string.lower(identifier) + + return identifier +end + +function LIB.GetPhrase(identifier) + identifier = LIB.GetName(identifier) + + if g_translated[identifier] then + return g_translated[identifier] + end + + local backup = '#' .. identifier + if g_translated[backup] then + return g_translated[backup] + end + + if CLIENT then + g_translated[identifier] = language.GetPhrase(identifier) or backup + else + g_translated[identifier] = backup + end + + g_translated[backup] = g_translated[identifier] + return g_translated[identifier] +end + +function LIB.Translate(identifier, defaultEnglishText) + identifier = LIB.GetName(identifier) + defaultEnglishText = tostring(defaultEnglishText or "") + + if #defaultEnglishText >= 1024 then + // Limit by GMod: https://github.com/Facepunch/garrysmod-issues/issues/5524 + error("defaultEnglishText is too long (length >= 1024)") + end + + if defaultEnglishText == "" then + defaultEnglishText = nil + end + + if g_translated[identifier] then + return g_translated[identifier] + end + + local backup = '#' .. identifier + if g_translated[backup] then + return g_translated[backup] + end + + if CLIENT then + if defaultEnglishText then + language.Add(identifier, defaultEnglishText) + end + + g_translated[identifier] = language.GetPhrase(identifier) or defaultEnglishText or backup + else + g_translated[identifier] = defaultEnglishText or backup + end + + g_translated[backup] = g_translated[identifier] + return g_translated[identifier] +end + +LIB.T = LIB.Translate \ No newline at end of file diff --git a/lua/streamradio_core/lib.lua b/lua/streamradio_core/lib.lua index 770d7be..768880f 100644 --- a/lua/streamradio_core/lib.lua +++ b/lua/streamradio_core/lib.lua @@ -18,55 +18,14 @@ local CLIENT = CLIENT local BASS3 = BASS3 or {} local StreamRadioLib = StreamRadioLib -local _, NetURL = StreamRadioLib.LoadSH('streamradio_core/neturl.lua') +local _, NetURL = StreamRadioLib.LoadSH("streamradio_core/neturl.lua") StreamRadioLib.NetURL = NetURL -function StreamRadioLib.Msg(ply, msgstring) - msgstring = tostring(msgstring or "") - if msgstring == "" then return end +function StreamRadioLib.IsDebug() + local devconvar = GetConVar("developer") + if not devconvar then return end - if IsValid(ply) then - ply:PrintMessage(HUD_PRINTTALK, msgstring) - else - MsgN(msgstring) - end -end - -local colorSeparator = Color(255,255,255) -local colorDateTime = Color(180,180,180) -local colorAddonName = Color(0,200,0) -local colorPlayer = Color(200,200,0) - -function StreamRadioLib.Log(ply, msgstring) - msgstring = tostring(msgstring or "") - if msgstring == "" then return end - - local playerStr = "" - - if IsValid(ply) then - playerStr = string.format("%s - %s", tostring(ply), ply:SteamID()) - end - - local Timestamp = os.time() - local TimeString = os.date("%Y-%m-%d %H:%M:%S" , Timestamp) - - MsgC(colorSeparator, "[") - MsgC(colorDateTime, TimeString) - MsgC(colorSeparator, "]") - - MsgC(colorSeparator, "[") - MsgC(colorAddonName, StreamRadioLib.AddonTitle) - MsgC(colorSeparator, "]") - - if playerStr ~= "" then - MsgC(colorSeparator, "[") - MsgC(colorPlayer, playerStr) - MsgC(colorSeparator, "]") - end - - Msg(" ") - - MsgN(msgstring) + return devconvar:GetInt() > 0 end function StreamRadioLib.ErrorNoHaltWithStack(err) @@ -116,7 +75,7 @@ end function StreamRadioLib.Hash(str) str = tostring(str or "") - local salt = "StreamRadioLib_Hash230609" + local salt = "StreamRadioLib_Hash230628" local data = string.format( "[%s][%s]", @@ -130,7 +89,7 @@ end local g_uid = 0 function StreamRadioLib.Uid() - g_uid = (g_uid + 1) % 2 ^ 31 + g_uid = (g_uid + 1) % (2 ^ 31 - 1) return g_uid end @@ -171,7 +130,7 @@ function StreamRadioLib.IsGUIHidden(ply) return tobool(ply:GetInfo("cl_streamradio_hidegui")) end -function StreamRadioLib.IsMuted(ply) +function StreamRadioLib.IsMuted(ply, owner) if not IsValid(ply) and CLIENT then ply = LocalPlayer() end @@ -181,17 +140,68 @@ function StreamRadioLib.IsMuted(ply) if ply:IsBot() then return true end local muted = tobool(ply:GetInfo("cl_streamradio_mute")) - if muted then return true end + local volume = tonumber(ply:GetInfo("cl_streamradio_volume") or 0) or 0 + if volume <= 0 then + return true + end + + if IsValid(owner) and owner:IsPlayer() and not owner:IsBot() and owner ~= ply then + local mutedForeign = tobool(ply:GetInfo("cl_streamradio_mute_foreign")) + if mutedForeign then + return true + end + end + if SERVER then return false end local muteunfocused = tobool(ply:GetInfo("cl_streamradio_muteunfocused")) + if not muteunfocused then + return false + end + if system.HasFocus() then + return false + end + + return true +end +/* +function StreamRadioLib.IsMuted(ply, owner) + if not IsValid(ply) and CLIENT then + ply = LocalPlayer() + end + + if not IsValid(ply) then return true end + if not ply:IsPlayer() then return true end + if ply:IsBot() then return true end + + if not IsValid(owner) then return true end + if not owner:IsPlayer() then return true end + if owner:IsBot() then return true end + + local mutedForeign = tobool(ply:GetInfo("cl_streamradio_mute_foreign")) + if not mutedForeign then + return false + end + + if IsValid(owner) and owner:IsPlayer() and not owner:IsBot() and owner ~= ply then + local mutedForeign = tobool(ply:GetInfo("cl_streamradio_mute_foreign")) + if mutedForeign then + return true + end + end + + if SERVER then + return false + end + + local muteunfocused = tobool(ply:GetInfo("cl_streamradio_muteunfocused")) if not muteunfocused then return false end @@ -202,6 +212,7 @@ function StreamRadioLib.IsMuted(ply) return true end +*/ function StreamRadioLib.HasYoutubeSupport(ply) if not IsValid(ply) and CLIENT then @@ -434,7 +445,7 @@ function StreamRadioLib.GetControlPosDir(ent) pos = camera:GetPos() -- This is not a mistake - -- This allowes UI clicks/use via C-Menu aim + -- This allows UI clicks/use via C-Menu aim dir = ent:GetAimVector() end @@ -973,54 +984,6 @@ function StreamRadioLib.IsPlayerNetworkable(plyOrId) return IsValid(_GetPlayerFromId(plyOrId)) end -local function toUnicode(s) - local s1, s2, s3, s4 = s:byte( 1, -1 ) - s = {s1, s2, s3, s4} - - local en = "" - for i = 1, #s do - local v = s[i] - if ( not v ) then continue end - - en = en .. string.format( "%%%02X", v ) - end - - return en -end - -local function URLEncode( str ) - str = str or "" - if (str == "") then - return "" - end - - str = string.gsub( str, "\n", "\r\n" ) - str = string.gsub( str, "([^%w ])", toUnicode ) - str = string.gsub( str, " ", "+" ) - - return str -end - -local hex={} - -for i = 0, 255 do - hex[string.format( "%0x", i)] = string.char( i ) - hex[string.format( "%0X", i)] = string.char( i ) -end - -local function URLDecode( str ) - str = str or "" - if (str == "") then - return "" - end - - str = string.gsub( str, '%%(%x%x)', hex ) - return str -end - -StreamRadioLib.URLEncode = URLEncode -StreamRadioLib.URLDecode = URLDecode - local function NormalizeOfflineFilename( path ) path = path or "" path = string.Replace( path, "\r", "" ) diff --git a/lua/streamradio_core/load.lua b/lua/streamradio_core/load.lua index db601c6..30aec33 100644 --- a/lua/streamradio_core/load.lua +++ b/lua/streamradio_core/load.lua @@ -33,8 +33,11 @@ local ok = true ok = ok and loadSH("streamradio_core/timedpairs.lua") ok = ok and loadSH("streamradio_core/api.lua") +ok = ok and loadSH("streamradio_core/language.lua") +ok = ok and loadSH("streamradio_core/bass3.lua") ok = ok and loadSH("streamradio_core/lib.lua") ok = ok and loadSH("streamradio_core/enum.lua") +ok = ok and loadSH("streamradio_core/error.lua") ok = ok and loadSH("streamradio_core/json.lua") ok = ok and loadSH("streamradio_core/network.lua") ok = ok and loadSH("streamradio_core/net.lua") @@ -47,6 +50,7 @@ ok = ok and loadSH("streamradio_core/interface.lua") ok = ok and loadSH("streamradio_core/filesystem.lua") ok = ok and loadSH("streamradio_core/cache.lua") ok = ok and loadSH("streamradio_core/classes.lua") +ok = ok and loadSH("streamradio_core/properties.lua") ok = ok and loadSH("streamradio_core/vr.lua") ok = ok and loadSH("streamradio_core/wire.lua") ok = ok and loadSH("streamradio_core/shoutcast.lua") diff --git a/lua/streamradio_core/models/111as_h500_radio.lua b/lua/streamradio_core/models/111as_h500_radio.lua index e3da19d..cc94577 100644 --- a/lua/streamradio_core/models/111as_h500_radio.lua +++ b/lua/streamradio_core/models/111as_h500_radio.lua @@ -70,8 +70,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 35) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 35) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 16) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/cs_office_radio.lua b/lua/streamradio_core/models/cs_office_radio.lua index 2d2a2a5..bcd897d 100644 --- a/lua/streamradio_core/models/cs_office_radio.lua +++ b/lua/streamradio_core/models/cs_office_radio.lua @@ -69,8 +69,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 35) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 35) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 16) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/cs_office_tv.lua b/lua/streamradio_core/models/cs_office_tv.lua index 4c7c989..45184c0 100644 --- a/lua/streamradio_core/models/cs_office_tv.lua +++ b/lua/streamradio_core/models/cs_office_tv.lua @@ -70,8 +70,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 75) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 100) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 100) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 100) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 100) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 100) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/csgo_italy_radio.lua b/lua/streamradio_core/models/csgo_italy_radio.lua index b823bd4..2e5d772 100644 --- a/lua/streamradio_core/models/csgo_italy_radio.lua +++ b/lua/streamradio_core/models/csgo_italy_radio.lua @@ -72,8 +72,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 25) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 25) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 25) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 50) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 50) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 16) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/fallout3_jukebox.lua b/lua/streamradio_core/models/fallout3_jukebox.lua index 1cd0f3b..f962972 100644 --- a/lua/streamradio_core/models/fallout3_jukebox.lua +++ b/lua/streamradio_core/models/fallout3_jukebox.lua @@ -70,8 +70,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 40) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 50) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 40) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 40) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 40) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 40) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/hl2_crtscreen_big.lua b/lua/streamradio_core/models/hl2_crtscreen_big.lua index aa42747..058289b 100644 --- a/lua/streamradio_core/models/hl2_crtscreen_big.lua +++ b/lua/streamradio_core/models/hl2_crtscreen_big.lua @@ -70,8 +70,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 60) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 90) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 80) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 80) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 80) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 80) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 32) diff --git a/lua/streamradio_core/models/hl2_crtscreen_small.lua b/lua/streamradio_core/models/hl2_crtscreen_small.lua index e4f0b4f..81fd2ae 100644 --- a/lua/streamradio_core/models/hl2_crtscreen_small.lua +++ b/lua/streamradio_core/models/hl2_crtscreen_small.lua @@ -70,8 +70,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 45) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 45) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 45) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/hl2_radio.lua b/lua/streamradio_core/models/hl2_radio.lua index 888dfff..c116741 100644 --- a/lua/streamradio_core/models/hl2_radio.lua +++ b/lua/streamradio_core/models/hl2_radio.lua @@ -76,8 +76,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 40) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 30) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/kankan_radio.lua b/lua/streamradio_core/models/kankan_radio.lua index c06100c..9984425 100644 --- a/lua/streamradio_core/models/kankan_radio.lua +++ b/lua/streamradio_core/models/kankan_radio.lua @@ -75,8 +75,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 35) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 35) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 16) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/kresopolski_radio.lua b/lua/streamradio_core/models/kresopolski_radio.lua index 3d46736..f99c3d9 100644 --- a/lua/streamradio_core/models/kresopolski_radio.lua +++ b/lua/streamradio_core/models/kresopolski_radio.lua @@ -29,11 +29,11 @@ RADIOMDL.DisplayHeight, RADIOMDL.DisplayScale = RADIOMDL:GetDisplayHeight( RADIOMDL.FontSizes = { -- Name = Size, Weight, Parentname - Header = {23, 1000}, + Header = {20, 1000}, Error = {17, 700}, - Default = {22, 700}, - Tooltip = {22, 1000}, - Big = {23, 700}, + Default = {20, 700}, + Tooltip = {20, 1000}, + Big = {20, 700}, } function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) @@ -63,21 +63,21 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "tooltip", "font", self.Fonts.Tooltip) end - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/header", "sizeh", 35) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/header", "sizeh", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/header", "sizeh", 27) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/header", "sizeh", 27) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/list-playlists", "gridsize", {x = 3, y = 4}) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/list-playlistview", "gridsize", {x = 2, y = 4}) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/list-playlists/scrollbar", "sizew", 40) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/list-playlistview/scrollbar", "sizew", 40) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/sidebutton", "sizew", 40) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/list-playlists/scrollbar", "sizew", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/list-playlistview/scrollbar", "sizew", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/sidebutton", "sizew", 30) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/textbox/scrollbar", "sizew", 30) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/textbox/scrollbar", "sizew", 25) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 25) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 40) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 30) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 16) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/minecraft_jukebox.lua b/lua/streamradio_core/models/minecraft_jukebox.lua index 00d5143..84aa271 100644 --- a/lua/streamradio_core/models/minecraft_jukebox.lua +++ b/lua/streamradio_core/models/minecraft_jukebox.lua @@ -76,8 +76,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 40) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 40) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 40) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 40) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 10) diff --git a/lua/streamradio_core/models/nm_screen.lua b/lua/streamradio_core/models/nm_screen.lua index 6605a91..bde6ff9 100644 --- a/lua/streamradio_core/models/nm_screen.lua +++ b/lua/streamradio_core/models/nm_screen.lua @@ -71,8 +71,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 75) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 80) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 80) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 80) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 80) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 80) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/plasma_tv.lua b/lua/streamradio_core/models/plasma_tv.lua index 28c38cd..fd8064c 100644 --- a/lua/streamradio_core/models/plasma_tv.lua +++ b/lua/streamradio_core/models/plasma_tv.lua @@ -70,8 +70,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 75) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 100) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 100) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 100) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 100) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 100) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/portal_radio.lua b/lua/streamradio_core/models/portal_radio.lua index 95927ce..4983701 100644 --- a/lua/streamradio_core/models/portal_radio.lua +++ b/lua/streamradio_core/models/portal_radio.lua @@ -69,8 +69,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 35) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 35) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 16) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/sw_ghettoblaster.lua b/lua/streamradio_core/models/sw_ghettoblaster.lua index f35eb8b..ff2c813 100644 --- a/lua/streamradio_core/models/sw_ghettoblaster.lua +++ b/lua/streamradio_core/models/sw_ghettoblaster.lua @@ -70,8 +70,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 40) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 30) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 40) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 40) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/sw_gramophone.lua b/lua/streamradio_core/models/sw_gramophone.lua index b78aa98..3651969 100644 --- a/lua/streamradio_core/models/sw_gramophone.lua +++ b/lua/streamradio_core/models/sw_gramophone.lua @@ -70,8 +70,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 50) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 40) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 40) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 40) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 40) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 32) diff --git a/lua/streamradio_core/models/sw_jukebox.lua b/lua/streamradio_core/models/sw_jukebox.lua index c68362f..891bf57 100644 --- a/lua/streamradio_core/models/sw_jukebox.lua +++ b/lua/streamradio_core/models/sw_jukebox.lua @@ -71,8 +71,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 40) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 30) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 30) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/sw_radio.lua b/lua/streamradio_core/models/sw_radio.lua index 906afd9..16f9c21 100644 --- a/lua/streamradio_core/models/sw_radio.lua +++ b/lua/streamradio_core/models/sw_radio.lua @@ -70,8 +70,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 35) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 35) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 35) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/wire_monitor_big.lua b/lua/streamradio_core/models/wire_monitor_big.lua index 5d4b077..cb9d843 100644 --- a/lua/streamradio_core/models/wire_monitor_big.lua +++ b/lua/streamradio_core/models/wire_monitor_big.lua @@ -71,8 +71,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 60) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 90) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 80) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 80) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 80) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 80) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/models/wire_monitor_small.lua b/lua/streamradio_core/models/wire_monitor_small.lua index 0d4285e..61b1a85 100644 --- a/lua/streamradio_core/models/wire_monitor_small.lua +++ b/lua/streamradio_core/models/wire_monitor_small.lua @@ -72,8 +72,8 @@ function RADIOMDL:SetupGUI(ent, gui_controller, mainpanel) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/textbox/scrollbar", "sizew", 30) StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizeh", 45) - StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizeh", 45) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/player/spectrum/error/button", "sizew", 45) + StreamRadioLib.SetSkinTableProperty(modelsetup, "main/browser/error/button", "sizew", 45) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "cornersize", 0) StreamRadioLib.SetSkinTableProperty(modelsetup, "", "borderwidth", 16) diff --git a/lua/streamradio_core/network.lua b/lua/streamradio_core/network.lua index 3eda936..61edd8e 100644 --- a/lua/streamradio_core/network.lua +++ b/lua/streamradio_core/network.lua @@ -7,6 +7,11 @@ local StreamRadioLib = StreamRadioLib local g_addonprefix = "3DStreamRadio/" local g_maxIdentifierLen = 44 +local g_networkStack = {} + +local g_networkMaxStackSize = 2^16 +local g_networkStackBatchSize = 128 + local g_types = { ["Angle"] = { check = function(value) @@ -200,7 +205,7 @@ do local nwGetter = dtd.nwGetter local nwSetter = dtd.nwSetter - local nwGetterFunc = function(ent, key, defaultvalue, ...) + local nwGetterFunc = function(ent, key, defaultvalue) key = LIB.TransformNWIdentifier(key) if defaultvalue ~= nil then @@ -208,7 +213,7 @@ do defaultvalue = convertfunc and convertfunc(defaultvalue) or defaultvalue end - local r = ent[nwGetter](ent, key, defaultvalue, ...) + local r = ent[nwGetter](ent, key, defaultvalue) if r == nil and defaultvalue ~= nil then r = defaultvalue end @@ -216,7 +221,7 @@ do return r end - local nwSetterFunc = function(ent, key, value, ...) + local nwSetterFunc = function(ent, key, value) if CLIENT then return nil end @@ -226,7 +231,8 @@ do assert(checkfunc(value), "invalid datatype of value at '" .. key .. "', '" .. datatype .. "' was expected, got '" .. type(value) .. "'") - return ent[nwSetter](ent, key, value, ...) + local data = {ent, ent[nwSetter], key, value} + table.insert(g_networkStack, data) end LIB["GetNW" .. datatype] = nwGetterFunc @@ -288,6 +294,9 @@ function LIB.SetupDataTables(ent) end end +local StreamRadioLib_CatchAndErrorNoHaltWithStack = StreamRadioLib.CatchAndErrorNoHaltWithStack +local StreamRadioLib_ErrorNoHaltWithStack = StreamRadioLib.ErrorNoHaltWithStack + local function pullNWVars(ent) local NW = ent.StreamRadioNW if not NW then return end @@ -301,7 +310,7 @@ local function pullNWVars(ent) local newvalue = LIB.GetNWVar(ent, data.datatype, name) if oldvalue == newvalue then return end - StreamRadioLib.CatchAndErrorNoHaltWithStack(data.callback, ent, name, oldvalue, newvalue) + StreamRadioLib_CatchAndErrorNoHaltWithStack(data.callback, ent, name, oldvalue, newvalue) NW.Names[name].oldvalue = newvalue end @@ -326,7 +335,7 @@ local function pullDTVars(ent) local newvalue = LIB.GetDTNetworkVar(ent, name) if oldvalue == newvalue then return end - StreamRadioLib.CatchAndErrorNoHaltWithStack(data.callback, ent, name, oldvalue, newvalue) + StreamRadioLib_CatchAndErrorNoHaltWithStack(data.callback, ent, name, oldvalue, newvalue) NW.Names[name].oldvalue = newvalue end @@ -336,6 +345,44 @@ local function pullDTVars(ent) end end +function LIB.PullNwStack() + if CLIENT then + return + end + + local loopThis = function(stackItem, i) + if not stackItem then return end + + local ent = stackItem[1] + local setter = stackItem[2] + local key = stackItem[3] + local value = stackItem[4] + + if not IsValid(ent) then return end + if not setter then return end + + StreamRadioLib_CatchAndErrorNoHaltWithStack(setter, ent, key, value) + end + + local count = 0 + + for pointer, stackItem in pairs(g_networkStack) do + if pointer >= g_networkMaxStackSize then + StreamRadioLib_ErrorNoHaltWithStack("Network overflow, dumping stack!") + table.Empty(g_networkStack) + break + end + + g_networkStack[pointer] = nil + loopThis(stackItem, pointer) + + count = count + 1 + if count >= g_networkStackBatchSize then + break + end + end +end + function LIB.Pull(ent) pullNWVars(ent) pullDTVars(ent) @@ -457,6 +504,48 @@ function LIB.SetNWVarCallback(ent, datatype, name, func) data.oldvalue = nil end +local function hashToBin(str) + str = string.gsub(str, "..", function(cc) + local c = tonumber(cc, 16) + + if c == 0 then + -- avoid zero termination + return "\\0" + end + + return string.char(c) + end) + + return str +end + +function LIB.Hash(str) + local hash = StreamRadioLib.Hash(str) + hash = hashToBin(hash) + + return hash +end + +hook.Add("Tick", "Streamradio_Entity_Network_Tick", function() + if not StreamRadioLib then return end + if not StreamRadioLib.Loaded then return end + if not StreamRadioLib.SpawnedRadios then return end + + StreamRadioLib.Network.PullNwStack() + + for index, ent in pairs(StreamRadioLib.SpawnedRadios) do + if not IsValid(ent) then + continue + end + + if not ent.__IsRadio then + continue + end + + StreamRadioLib.Network.Pull(ent) + end +end) + function LIB.Debug.DumpDTNetworkStats(ent) local NW = ent.StreamRadioDT or {} local Count = NW.Count or {} diff --git a/lua/streamradio_core/print.lua b/lua/streamradio_core/print.lua new file mode 100644 index 0000000..0f8bf22 --- /dev/null +++ b/lua/streamradio_core/print.lua @@ -0,0 +1,194 @@ +StreamRadioLib.Print = StreamRadioLib.Print or {} +local LIB = StreamRadioLib.Print + +local function getTextWithoutColor(text) + text = tostring(text or "") + text = string.gsub(text, "%[color%:[ ]?%d+[ %,][ ]?%d+[ %,][ ]?%d+%]", "") + + return text +end + +local function printColored(text) + text = tostring(text or "") + + local default = "[color:255,255,255]" + local lastcolor = default + local curcolor = Color(255, 255, 255, 255) + + text = default .. text .. default + + for data, color in string.gmatch(text, "(.-)(%[color%:[ ]?%d+[ %,][ ]?%d+[ %,][ ]?%d+%])") do + data = data or "" + color = color or "" + + if color ~= "" then + local r, g, b = string.match(lastcolor, "%[color%:[ ]?(%d+)[ %,][ ]?(%d+)[ %,][ ]?(%d+)%]") + + if r and g and b then + r = math.Clamp(tonumber(r) or 0, 0, 255) + g = math.Clamp(tonumber(g) or 0, 0, 255) + b = math.Clamp(tonumber(b) or 0, 0, 255) + + curcolor.r = r + curcolor.g = g + curcolor.b = b + end + end + + if data ~= "" then + MsgC(curcolor, data) + end + + lastcolor = color + end +end + +function LIB.IndentText(text, spaces) + text = tostring(text or "") + spaces = tonumber(spaces or 2) or 2 + + spaces = string.rep(" ", spaces) + text = string.gsub(spaces .. text, "\n", "\n" .. spaces) + + return text +end + +function LIB.Wrapped(texts, ...) + if not istable(texts) then + texts = {texts} + end + + texts = table.Add(texts, {...}) + + local textlines = {} + + local longestline = 0 + + for k, v in pairs(texts) do + v = tostring(v or "") + + local lines = string.Explode("\n", v, false) + textlines[#textlines + 1] = lines + + for i, u in ipairs(lines) do + local collessu = getTextWithoutColor(u) + local len = #collessu + + if len <= longestline then + continue + end + + longestline = len + end + end + + local border_color = SERVER and "[color:137,222,255]" or "[color:255,222,102]" + local text_color = "[color:255,255,255]" + + local borderside_l = "=== " + local borderside_r = " ===" + + local border = border_color .. string.rep("=", longestline + #borderside_l + #borderside_r) + local border_inner = border_color .. string.rep("-", longestline) + + borderside_l = border_color .. "=== " + borderside_r = border_color .. " ===" + + local function group(lines, addborder) + if addborder then + printColored(borderside_l .. border_inner .. borderside_r .. "\n") + end + + for i, v in ipairs(lines) do + local collessv = getTextWithoutColor(v) + local len = #collessv + local slen = math.Clamp(longestline - len, 0, longestline) + local spaces = string.rep(" ", slen) + + local line = v .. spaces + printColored(borderside_l .. text_color .. line .. borderside_r .. "\n") + end + end + + printColored(border .. "\n") + + for i, v in ipairs(textlines) do + group(v, i > 1) + end + + printColored(border .. "\n") +end + +function LIB.Debug(format, ...) + if not StreamRadioLib.IsDebug() then return end + + format = tostring(format or "") + if format == "" then return end + + local msgstring = string.format(format, ...) + msgstring = string.Trim(msgstring) + + if msgstring == "" then return end + + local tmp = string.Explode("\n", msgstring, false) + for i, v in ipairs(tmp) do + tmp[i] = " " .. v .. "\n" + end + + msgstring = table.concat(tmp) + msgstring = string.Trim(StreamRadioLib.AddonPrefix .. msgstring) .. "\n" + + if StreamRadioLib.VR.IsActive() then + StreamRadioLib.VR.Debug(msgstring) + else + MsgN(msgstring) + end +end + +function LIB.Msg(ply, msgstring) + msgstring = tostring(msgstring or "") + if msgstring == "" then return end + + if IsValid(ply) then + ply:PrintMessage(HUD_PRINTTALK, msgstring) + else + MsgN(msgstring) + end +end + +local g_colorSeparator = Color(255,255,255) +local g_colorDateTime = Color(180,180,180) +local g_colorAddonName = Color(0,200,0) +local g_colorPlayer = Color(200,200,0) + +function LIB.Log(ply, msgstring) + msgstring = tostring(msgstring or "") + if msgstring == "" then return end + + local playerStr = "" + + if IsValid(ply) then + playerStr = string.format("%s - %s", tostring(ply), ply:SteamID()) + end + + local Timestamp = os.time() + local TimeString = os.date("%Y-%m-%d %H:%M:%S" , Timestamp) + + MsgC(g_colorSeparator, "[") + MsgC(g_colorDateTime, TimeString) + MsgC(g_colorSeparator, "]") + + MsgC(g_colorSeparator, "[") + MsgC(g_colorAddonName, StreamRadioLib.AddonTitle) + MsgC(g_colorSeparator, "]") + + if playerStr ~= "" then + MsgC(g_colorSeparator, "[") + MsgC(g_colorPlayer, playerStr) + MsgC(g_colorSeparator, "]") + end + + Msg(" ") + + MsgN(msgstring) +end diff --git a/lua/streamradio_core/properties.lua b/lua/streamradio_core/properties.lua new file mode 100644 index 0000000..ceb97cf --- /dev/null +++ b/lua/streamradio_core/properties.lua @@ -0,0 +1,625 @@ + +StreamRadioLib.properties = StreamRadioLib.properties or {} +local LIB = StreamRadioLib.properties + +local LIBNet = StreamRadioLib.Net +local LIBNetwork = StreamRadioLib.Network +local LIBError = StreamRadioLib.Error + +local g_mainOptionAdded = false +local g_subOptions = {} +local g_nameprefix = "3dstreamradio_properties_" + +if SERVER then + LIBNetwork.AddNetworkString("properties") + + LIBNet.Receive("properties", function(len, client) + if not IsValid(client) then return end + + local name = net.ReadString() + if not name then return end + + local subOption = g_subOptions[name] + if not subOption then return end + if not subOption.Receive then return end + + subOption:Receive(len, client) + end) +end + +function LIB.GetName(identifier) + identifier = g_nameprefix .. tostring(identifier or "") + identifier = string.lower(identifier) + + return identifier +end + +function LIB.Get(identifier) + identifier = LIB.GetName(identifier) + + return properties.List[identifier] or g_subOptions[identifier] +end + +function LIB.Add(identifier, propertyData) + identifier = LIB.GetName(identifier) + + return properties.Add(identifier, propertyData) +end + +function LIB.CanProperty(identifier, ent, ply ) + if not IsValid( ent ) then return false end + if not ent.__IsRadio then return false end + + identifier = LIB.GetName(identifier) + if not gamemode.Call( "CanProperty", ply, identifier, ent ) then return false end + + return true +end + +function LIB.CanBeTargeted(ent, ply) + if not IsValid( ent ) then return false end + if not ent.__IsRadio then return false end + if not properties.CanBeTargeted( ent, ply ) then return false end + + return true +end + +function LIB.CheckFilter(identifier, ent, ply) + local propertyData = LIB.Get(identifier) + + if not propertyData then + return true + end + + if not propertyData.Filter then + return true + end + + return propertyData:Filter(ent, ply) +end + +function LIB.CheckFilters(identifiers, ent, ply) + for i, identifier in ipairs(identifiers) do + if LIB.CheckFilter(identifier, ent, ply) then + return true + end + end + + return false +end + +local g_meta = { + MsgStart = function(self) + LIBNet.Start("properties") + net.WriteString(self.InternalName) + end, + + MsgEnd = function(self) + net.SendToServer() + end +} + +g_meta.__index = g_meta + +local function addMainOption() + if g_mainOptionAdded then + return + end + + LIB.Add("radio_options", { + MenuLabel = "Radio Options", + Order = 10000, + MenuIcon = "3dstreamradio/icon16/format_radio.png", + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + return true + end, + + MenuOpen = function( self, option, ent, tr ) + local ply = LocalPlayer() + if not self:Filter(ent, ply) then return end + + local submenuPanel = option:AddSubMenu() + + submenuPanel:SetMinimumWidth(200) + + for k, subOption in SortedPairsByMemberValue( g_subOptions, "Order" ) do + if not subOption.Filter then continue end + if not subOption:Filter(ent, ply) then continue end + + if subOption.PrependSpacer then + submenuPanel:AddSpacer() + end + + local label = subOption.MenuLabel or subOption.InternalName + + local optionPanel = submenuPanel:AddOption( + label, + function(panel) + if not subOption:Filter(ent, ply) then + return + end + + subOption:Action(ent, tr) + + panel:Think() + end + ) + + if subOption.OnCreate then + subOption:OnCreate(submenuPanel, optionPanel) + end + + if subOption.MenuIcon then + optionPanel:SetImage(subOption.MenuIcon) + end + + if subOption.MenuOpen then + subOption:MenuOpen(optionPanel, ent, tr) + end + + optionPanel._oldThink = optionPanel.Think + optionPanel.Think = function(panel, ...) + if not subOption.Think then + return + end + + if not subOption:Filter(ent, ply) then + return + end + + subOption:Think(panel, ent) + + if panel._oldThink then + return panel:_oldThink(...) + end + end + end + end, + + Action = function( self, ent ) + end, + + Receive = function( self, length, ply ) + end + }) + + g_mainOptionAdded = true +end + +function LIB.AddSubOption(identifier, propertyData) + addMainOption() + + identifier = LIB.GetName(identifier) + + propertyData = table.Copy(propertyData) + propertyData.InternalName = identifier + + setmetatable(propertyData, g_meta) + + g_subOptions[identifier] = propertyData +end + +local function g_emptyFunction() +end + +local g_titleOnCreate = function( self, submenuPanel, optionPanel ) + optionPanel.OnMousePressed = g_emptyFunction + optionPanel.OnMouseReleased = g_emptyFunction + optionPanel.DoClickInternal = g_emptyFunction + + optionPanel:SetDisabled(true) + optionPanel:SetTextInset(2, 0) + optionPanel:SetContentAlignment(5) +end + +local g_VolumeMenuOpen = function( self, optionPanel, ent ) + optionPanel.OnMousePressed = g_emptyFunction + optionPanel.OnMouseReleased = g_emptyFunction + optionPanel.DoClickInternal = g_emptyFunction + + optionPanel:SetTextInset(5, 0) + optionPanel:DockPadding(5,5,5,5) + + local ply = LocalPlayer() + + local upButton = vgui.Create( "DButton", optionPanel ) + optionPanel._upButton = upButton + + upButton:Dock(RIGHT) + upButton:SetImage(StreamRadioLib.GetPNGIconPath("sound_add")) + upButton:DockMargin(5,0,0,0) + upButton:SetTooltip("Increase volume") + + upButton.DoClick = function(panel) + if not self.VolumeUp then + return + end + + if not self:Filter(ent, ply) then + return + end + + self:VolumeUp(ent) + panel:Think() + end + + local downButton = vgui.Create( "DButton", optionPanel ) + optionPanel._downButton = downButton + + downButton:Dock(RIGHT) + downButton:SetImage(StreamRadioLib.GetPNGIconPath("sound_delete")) + downButton:DockMargin(0,0,0,0) + downButton:SetTooltip("Decrease volume") + + downButton.DoClick = function(panel) + if not self.VolumeDown then + return + end + + if not self:Filter(ent, ply) then + return + end + + self:VolumeDown(ent) + panel:Think() + end + + -- bypass hardcoded size in internal PerformLayout + optionPanel._SetSize = optionPanel.SetSize + + optionPanel.SetSize = function(panel, x, y) + y = 40 + local buttonSize = y - 10 + + upButton:SetSize(buttonSize, buttonSize) + downButton:SetSize(buttonSize, buttonSize) + + return panel:_SetSize(x, y) + end +end + +LIB.AddSubOption("clientside_title", { + MenuLabel = "Clientside Options", + Order = 100, + PrependSpacer = true, + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + + local allowed = LIB.CheckFilters( + { + "copy_url", + "error_info", + "clientside_mute", + "clientside_unmute", + "clientside_volume", + }, + ent, + ply + ) + + return allowed + end, + + Action = function( self, ent ) + end, + + OnCreate = g_titleOnCreate, +}) + +LIB.AddSubOption("copy_url", { + MenuLabel = "Copy Stream URL to clipboard", + Order = 101, + MenuIcon = StreamRadioLib.GetPNGIconPath("page_copy"), + PrependSpacer = true, + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + + local url = ent:GetStreamURL() + if url == "" then return false end + + return true + end, + + Action = function( self, ent ) + local url = ent:GetStreamURL() + SetClipboardText(url) + end, +}) + +LIB.AddSubOption("error_info", { + MenuLabel = "Error", + Order = 102, + MenuIcon = StreamRadioLib.GetPNGIconPath("error"), + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + + local stream = ent:GetStreamObject() + if not stream then return false end + if not stream:HasError() then return false end + + return true + end, + + Action = function( self, ent ) + local stream = ent:GetStreamObject() + + local err = stream:GetError() + local url = stream:GetURL() + + StreamRadioLib.ShowErrorHelp(err, url) + end, + + Think = function( self, optionPanel, ent ) + local stream = ent:GetStreamObject() + + local err = stream:GetError() + local url = stream:GetURL() + + local errorInfo = LIBError.GetStreamErrorInfo(err) + local errorName = errorInfo.name + local errorDescription = errorInfo.description + + local label = string.format("%s: %i (%s)", self.MenuLabel, err, errorName) + local tooltip = string.format("Error %i (%s): %s\n\nCan not play this URL:\n%s\n\nClick for more details.", err, errorName, errorDescription, url) + + optionPanel:SetText(label) + optionPanel:SetTooltip(tooltip) + end, +}) + +LIB.AddSubOption("clientside_mute", { + MenuLabel = "Mute", + Order = 111, + MenuIcon = StreamRadioLib.GetPNGIconPath("sound_mute"), + PrependSpacer = true, + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + if ent:GetCLMute() then return false end + + return true + end, + + Action = function( self, ent ) + ent:SetCLMute(true) + end, +}) + +LIB.AddSubOption("clientside_unmute", { + MenuLabel = "Unmute", + Order = 112, + MenuIcon = StreamRadioLib.GetPNGIconPath("sound"), + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + if not ent:GetCLMute() then return false end + + return true + end, + + Action = function( self, ent ) + ent:SetCLMute(false) + end, +}) + + +LIB.AddSubOption("clientside_volume", { + MenuLabel = "Volume", + Order = 113, + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + return true + end, + + Action = function( self, ent ) + end, + + ChangeVolume = function( self, ent, up ) + local volume = ent:GetCLVolume() + local value = up and 0.2 or -0.2 + + volume = math.Clamp(volume + value, 0, 1) + volume = math.Round(volume, 2) + + ent:SetCLVolume(volume) + end, + + VolumeUp = function( self, ent ) + self:ChangeVolume(ent, true) + end, + + VolumeDown = function( self, ent ) + self:ChangeVolume(ent, false) + end, + + Think = function( self, optionPanel, ent ) + local volume = ent:GetCLVolume() + + local label = string.format("%s: %3i%%", self.MenuLabel, volume * 100) + + optionPanel:SetText(label) + + local upButton = optionPanel._upButton + local downButton = optionPanel._downButton + + if IsValid(upButton) then + if volume >= 1 then + upButton:SetDisabled(true) + else + upButton:SetDisabled(false) + end + end + + if IsValid(downButton) then + if volume <= 0 then + downButton:SetDisabled(true) + else + downButton:SetDisabled(false) + end + end + end, + + MenuOpen = g_VolumeMenuOpen, +}) + +LIB.AddSubOption("serverside_title", { + MenuLabel = "Entity Options", + Order = 200, + PrependSpacer = true, + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + + local allowed = LIB.CheckFilters( + { + "serverside_mute", + "serverside_unmute", + "serverside_volume", + }, + ent, + ply + ) + + return allowed + end, + + Action = function( self, ent ) + end, + + OnCreate = g_titleOnCreate, +}) + +LIB.AddSubOption("serverside_mute", { + MenuLabel = "Mute", + Order = 201, + MenuIcon = StreamRadioLib.GetPNGIconPath("sound_mute"), + PrependSpacer = true, + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + if not LIB.CanProperty("serverside_mute", ent, ply ) then return false end + if ent:GetSVMute() then return false end + + return true + end, + + Action = function( self, ent ) + self:MsgStart() + net.WriteEntity( ent ) + self:MsgEnd() + end, + + Receive = function( self, length, ply ) + local ent = net.ReadEntity() + if not self:Filter( ent, ply ) then return end + + ent:SetSVMute(true) + end +}) + +LIB.AddSubOption("serverside_unmute", { + MenuLabel = "Unmute", + Order = 202, + MenuIcon = StreamRadioLib.GetPNGIconPath("sound"), + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + if not LIB.CanProperty("serverside_unmute", ent, ply ) then return false end + if not ent:GetSVMute() then return false end + + return true + end, + + Action = function( self, ent ) + self:MsgStart() + net.WriteEntity( ent ) + self:MsgEnd() + end, + + Receive = function( self, length, ply ) + local ent = net.ReadEntity() + if not self:Filter( ent, ply ) then return end + + ent:SetSVMute(false) + end +}) + +LIB.AddSubOption("serverside_volume", { + MenuLabel = "Volume", + Order = 203, + + Filter = function( self, ent, ply ) + if not LIB.CanBeTargeted( ent, ply ) then return false end + if not LIB.CanProperty("serverside_volume", ent, ply ) then return false end + + return true + end, + + Action = function( self, ent ) + end, + + ChangeVolume = function( self, ent, up ) + self:MsgStart() + net.WriteEntity( ent ) + net.WriteBool( up ) + self:MsgEnd() + end, + + VolumeUp = function( self, ent ) + self:ChangeVolume(ent, true) + end, + + VolumeDown = function( self, ent ) + self:ChangeVolume(ent, false) + end, + + Think = function( self, optionPanel, ent ) + local volume = ent:GetVolume() + + local label = string.format("%s: %3i%%", self.MenuLabel, volume * 100) + + optionPanel:SetText(label) + + local upButton = optionPanel._upButton + local downButton = optionPanel._downButton + + if IsValid(upButton) then + if volume >= 1 then + upButton:SetDisabled(true) + else + upButton:SetDisabled(false) + end + end + + if IsValid(downButton) then + if volume <= 0 then + downButton:SetDisabled(true) + else + downButton:SetDisabled(false) + end + end + end, + + MenuOpen = g_VolumeMenuOpen, + + Receive = function( self, length, ply ) + local ent = net.ReadEntity() + local up = net.ReadBool() + + local value = up and 0.2 or -0.2 + if not self:Filter( ent, ply ) then return end + + local volume = ent:GetVolume() + + volume = math.Clamp(volume + value, 0, 1) + volume = math.Round(volume, 2) + + ent:SetVolume(volume) + end +}) \ No newline at end of file diff --git a/lua/streamradio_core/server/sv_lib.lua b/lua/streamradio_core/server/sv_lib.lua index 0c1cda6..aa7fc38 100644 --- a/lua/streamradio_core/server/sv_lib.lua +++ b/lua/streamradio_core/server/sv_lib.lua @@ -6,11 +6,11 @@ local LIBNet = StreamRadioLib.Net local MaxServerSpectrum = CreateConVar( "sv_streamradio_max_spectrums", "5", bit.bor( FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_GAMEDLL ), "Sets the maximum count of radios that can have advanced wire outputs such as FFT spectrum or song tags. -1 = Infinite, 0 = Off, Default: 5" ) local AllowCustomURLs = CreateConVar( "sv_streamradio_allow_customurls", "1", bit.bor( FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_GAMEDLL ), "Allow or disallow custom URLs to be played. 1 = Allow, 0 = Disallow, Default: 1" ) -local RebuildCommunityPlaylists = CreateConVar( "sv_streamradio_rebuildplaylists_community_auto", "1", bit.bor( FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_GAMEDLL ), "Set how the community playlists are rebuild on gamestart. 0 = Off, 1 = Rebuild only, 2 = Delete and rebuild, Default: 1" ) +local RebuildCommunityPlaylists = CreateConVar( "sv_streamradio_rebuildplaylists_community_auto", "2", bit.bor( FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_GAMEDLL ), "Set how the community playlists are rebuild on server start. 0 = Off, 1 = Rebuild only, 2 = Delete and rebuild, Default: 2" ) function StreamRadioLib.AllowSpectrum() if not WireAddon then return false end - if not StreamRadioLib.HasBass then return false end + if not StreamRadioLib.Bass.CanLoadDLL() then return false end local max = MaxServerSpectrum:GetInt() if max == 0 then return false end diff --git a/lua/streamradio_core/server/sv_playlist_edit.lua b/lua/streamradio_core/server/sv_playlist_edit.lua index a8a3e3e..3de89c9 100644 --- a/lua/streamradio_core/server/sv_playlist_edit.lua +++ b/lua/streamradio_core/server/sv_playlist_edit.lua @@ -38,7 +38,7 @@ local function EditorLog(ply, msgstring, ...) msgstring = string.format(msgstring, ...) msgstring = "PLAYLIST EDITOR - " .. msgstring - StreamRadioLib.Log(ply, msgstring) + StreamRadioLib.Print.Log(ply, msgstring) end local function EditorError( ply, path, code ) diff --git a/lua/streamradio_core/server/sv_res.lua b/lua/streamradio_core/server/sv_res.lua index 003adfa..d7b0b83 100644 --- a/lua/streamradio_core/server/sv_res.lua +++ b/lua/streamradio_core/server/sv_res.lua @@ -31,7 +31,7 @@ do if not StreamRadioLib.Loaded then return end if not ( not ply or ( IsValid( ply ) and ply:IsAdmin( ) ) ) then - StreamRadioLib.Msg( ply, "You need to be an admin to rebuild the playlists." ) + StreamRadioLib.Print.Msg( ply, "You need to be an admin to rebuild the playlists." ) return end @@ -40,7 +40,7 @@ do StreamRadioLib.Editor.Reset( ply ) local msgstring = StreamRadioLib.AddonPrefix .. "Playlists rebuilt" - StreamRadioLib.Msg( ply, msgstring ) + StreamRadioLib.Print.Msg( ply, msgstring ) end local function Rebuild_CommunityPlaylists( ply, cmd, args ) @@ -48,7 +48,7 @@ do if not StreamRadioLib.Loaded then return end if not ( not ply or ( IsValid( ply ) and ply:IsAdmin( ) ) ) then - StreamRadioLib.Msg( ply, "You need to be an admin to rebuild the community playlists." ) + StreamRadioLib.Print.Msg( ply, "You need to be an admin to rebuild the community playlists." ) return end @@ -57,7 +57,7 @@ do StreamRadioLib.Editor.Reset( ply ) local msgstring = StreamRadioLib.AddonPrefix .. "Community playlists rebuilt" - StreamRadioLib.Msg( ply, msgstring ) + StreamRadioLib.Print.Msg( ply, msgstring ) end concommand.Add( "sv_streamradio_rebuildplaylists", Rebuild_Playlists ) @@ -69,7 +69,7 @@ do if not StreamRadioLib.DataDirectory then return end if not ( not ply or ( IsValid( ply ) and ply:IsAdmin( ) ) ) then - StreamRadioLib.Msg( ply, "You need to be an admin to reset the playlists." ) + StreamRadioLib.Print.Msg( ply, "You need to be an admin to reset the playlists." ) return end @@ -77,12 +77,12 @@ do local deleted = StreamRadioLib.DeleteFolder( StreamRadioLib.DataDirectory .. "/playlists" ) if not deleted then local msgstring = StreamRadioLib.AddonPrefix .. "Playlists could not be rebuilt" - StreamRadioLib.Msg( ply, msgstring ) + StreamRadioLib.Print.Msg( ply, msgstring ) return end local msgstring = StreamRadioLib.AddonPrefix .. "Playlists deleted" - StreamRadioLib.Msg( ply, msgstring ) + StreamRadioLib.Print.Msg( ply, msgstring ) Rebuild_Playlists( ply, cmd, args ) end @@ -92,7 +92,7 @@ do if not StreamRadioLib.DataDirectory then return end if not ( not ply or ( IsValid( ply ) and ply:IsAdmin( ) ) ) then - StreamRadioLib.Msg( ply, "You need to be an admin to reset the community playlists." ) + StreamRadioLib.Print.Msg( ply, "You need to be an admin to reset the community playlists." ) return end @@ -100,12 +100,12 @@ do local deleted = StreamRadioLib.DeleteFolder( StreamRadioLib.DataDirectory .. "/playlists/community" ) if not deleted then local msgstring = StreamRadioLib.AddonPrefix .. "Community playlists could not be rebuilt" - StreamRadioLib.Msg( ply, msgstring ) + StreamRadioLib.Print.Msg( ply, msgstring ) return end local msgstring = StreamRadioLib.AddonPrefix .. "Community playlists deleted" - StreamRadioLib.Msg( ply, msgstring ) + StreamRadioLib.Print.Msg( ply, msgstring ) Rebuild_CommunityPlaylists( ply, cmd, args ) end diff --git a/lua/streamradio_core/timer.lua b/lua/streamradio_core/timer.lua index 413f274..79ec2fe 100644 --- a/lua/streamradio_core/timer.lua +++ b/lua/streamradio_core/timer.lua @@ -1,11 +1,11 @@ StreamRadioLib.Timer = StreamRadioLib.Timer or {} local LIB = StreamRadioLib.Timer -local nameprefix = "3DStreamRadio_Timer_" +local g_nameprefix = "3DStreamRadio_Timer_" function LIB.GetName(identifier) - local name = nameprefix .. tostring(identifier or "") - return name + identifier = g_nameprefix .. tostring(identifier or "") + return identifier end function LIB.Interval(identifier, delay, repetitions, func) @@ -37,7 +37,7 @@ function LIB.Util(identifier, delay, func) if not endtimer then return end timer.Remove(name) - end) + end) end function LIB.NextFrame(identifier, func) diff --git a/lua/streamradio_core/tool.lua b/lua/streamradio_core/tool.lua index bd207a3..4f5be9f 100644 --- a/lua/streamradio_core/tool.lua +++ b/lua/streamradio_core/tool.lua @@ -143,23 +143,10 @@ function LIB.Setup(toolobj) local ply = self:GetOwner() if not IsValid(ply) then return true end - local realpl = ent.pl + local radioOwner = ent:GetRealRadioOwner() - if isfunction(ent.CPPIGetOwner) then - -- Some authors can't follow standards... - local pl, id = ent:CPPIGetOwner() - - if not pl or isentity( pl ) then - realpl = pl - else - if not id or isentity( id ) then - realpl = id - end - end - end - - if not IsValid(realpl) then return true end - if realpl ~= ply then return false end + if not IsValid(radioOwner) then return true end + if radioOwner ~= ply then return false end return true end diff --git a/lua/weapons/gmod_tool/stools/streamradio.lua b/lua/weapons/gmod_tool/stools/streamradio.lua index 1a60134..95d9300 100644 --- a/lua/weapons/gmod_tool/stools/streamradio.lua +++ b/lua/weapons/gmod_tool/stools/streamradio.lua @@ -17,12 +17,14 @@ TOOL.ClientConVar["model"] = StreamRadioLib.GetDefaultModel() TOOL.ClientConVar["streamurl"] = "" TOOL.ClientConVar["play"] = "1" TOOL.ClientConVar["3dsound"] = "1" +TOOL.ClientConVar["mute"] = "0" TOOL.ClientConVar["volume"] = "1" TOOL.ClientConVar["radius"] = "1200" TOOL.ClientConVar["playbackloopmode"] = tostring(StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST) TOOL.ClientConVar["nodisplay"] = "0" TOOL.ClientConVar["noinput"] = "0" +TOOL.ClientConVar["nospectrum"] = "0" TOOL.ClientConVar["noadvwire"] = "1" TOOL.ClientConVar["freeze"] = "1" @@ -52,19 +54,22 @@ if StreamRadioLib and StreamRadioLib.Loaded then StreamRadioLib.Tool.AddLocale(TOOL, "modelinfo.desc", "Some models (usually speakers) don't have a display.\nUse this tool or Wiremod to control those.") StreamRadioLib.Tool.AddLocale(TOOL, "modelinfo_mp", "Some selectable models might not be available\non the server. It will be replaced by a default model.") StreamRadioLib.Tool.AddLocale(TOOL, "modelinfo_mp.desc", "Some selectable models might not be available\non the server. It will be replaced by a default model.") - StreamRadioLib.Tool.AddLocale(TOOL, "play", "Play on spawn or set") + StreamRadioLib.Tool.AddLocale(TOOL, "play", "Play on spawn or on apply") StreamRadioLib.Tool.AddLocale(TOOL, "play.desc", "If set, the radio will try to play a given URL on spawn or apply.\nThe URL can be set by this Tools or via Wiremod.") StreamRadioLib.Tool.AddLocale(TOOL, "nodisplay", "Disable display") StreamRadioLib.Tool.AddLocale(TOOL, "noadvwire", "Disable advanced wire outputs") StreamRadioLib.Tool.AddLocale(TOOL, "noadvwire.desc", "Disables the advanced wire outputs.\nIt's always disabled if Wiremod or GM_BASS3 is not installed on the Server.") StreamRadioLib.Tool.AddLocale(TOOL, "noinput", "Disable control") StreamRadioLib.Tool.AddLocale(TOOL, "noinput.desc", "Disable the control of the display.\nWiremod controlling will still work.") + StreamRadioLib.Tool.AddLocale(TOOL, "nospectrum", "Disable spectrum visualization") + StreamRadioLib.Tool.AddLocale(TOOL, "nospectrum.desc", "Disable rendering of the spectrum visualization on the display.") StreamRadioLib.Tool.AddLocale(TOOL, "playbackloopmode", "Loop Playback:") StreamRadioLib.Tool.AddLocale(TOOL, "playbackloopmode.desc", "Set what happens after a song ends.") StreamRadioLib.Tool.AddLocale(TOOL, "playbackloopmode.option.none", "No loop") StreamRadioLib.Tool.AddLocale(TOOL, "playbackloopmode.option.song", "Loop song") StreamRadioLib.Tool.AddLocale(TOOL, "playbackloopmode.option.playlist", "Loop playlist") StreamRadioLib.Tool.AddLocale(TOOL, "3dsound", "Enable 3D Sound") + StreamRadioLib.Tool.AddLocale(TOOL, "mute", "Mute Radio") StreamRadioLib.Tool.AddLocale(TOOL, "volume", "Volume:") StreamRadioLib.Tool.AddLocale(TOOL, "radius", "Radius:") StreamRadioLib.Tool.AddLocale(TOOL, "radius.desc", "The radius in units the radio sound volume will drop down to 0% of the volume setting.") @@ -127,6 +132,7 @@ function TOOL:BuildToolPanel(CPanel) self:AddCheckbox( CPanel, "play", true ) self:AddCheckbox( CPanel, "nodisplay", false ) self:AddCheckbox( CPanel, "noinput", true ) + self:AddCheckbox( CPanel, "nospectrum", true ) self:AddCheckbox( CPanel, "noadvwire", true ) self:AddCheckbox( CPanel, "3dsound", false ) @@ -139,9 +145,12 @@ function TOOL:BuildToolPanel(CPanel) PlaybackLoopModeComboBox:AddChoice(StreamRadioLib.Tool.GetLocale(self, "playbackloopmode.option.song"), StreamRadioLib.PLAYBACK_LOOP_MODE_SONG, false, g_icon_playbackloopmode_song) PlaybackLoopModeComboBox:AddChoice(StreamRadioLib.Tool.GetLocale(self, "playbackloopmode.option.playlist"), StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST, false, g_icon_playbackloopmode_playlist) - + CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer(5)) + CPanel:AddPanel(StreamRadioLib.Menu.GetSpacerLine()) CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer(5)) + self:AddCheckbox( CPanel, "mute", false ) + local VolumeNumSlider = self:AddNumSlider( CPanel, "volume", false ) VolumeNumSlider:SetMin( 0 ) VolumeNumSlider:SetMax( 1 ) @@ -152,6 +161,8 @@ function TOOL:BuildToolPanel(CPanel) RadiusNumSlider:SetMax( 5000 ) RadiusNumSlider:SetDecimals( 0 ) + CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer(5)) + CPanel:AddPanel(StreamRadioLib.Menu.GetSpacerLine()) CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer(5)) self:AddLabel( CPanel, "spawnsettings", false ) @@ -268,12 +279,14 @@ end function TOOL:GetSettings() local settings = {} + settings.StreamMute = self:GetClientBool("mute") settings.StreamVolume = self:GetClientNumberMinMax("volume", 0, 1) settings.Radius = self:GetClientNumberMinMax("radius", 0, 5000) settings.PlaybackLoopMode = self:GetClientNumber("playbackloopmode", StreamRadioLib.PLAYBACK_LOOP_MODE_NONE) settings.Sound3D = self:GetClientBool("3dsound") settings.DisableDisplay = self:GetClientBool("nodisplay") settings.DisableInput = self:GetClientBool("noinput") + settings.DisableSpectrum = self:GetClientBool("nospectrum") settings.DisableAdvancedOutputs = self:GetClientBool("noadvwire") return settings @@ -288,12 +301,14 @@ function TOOL:SetSettings(settings) self:SetClientInfo("streamurl", url) + self:SetClientBool("mute", settings.StreamMute) self:SetClientNumber("volume", settings.StreamVolume or 1) self:SetClientNumber("radius", settings.Radius or 1200) self:SetClientNumber("playbackloopmode", settings.PlaybackLoopMode or StreamRadioLib.PLAYBACK_LOOP_MODE_NONE) self:SetClientBool("3dsound", settings.Sound3D) self:SetClientBool("nodisplay", settings.DisableDisplay) self:SetClientBool("noinput", settings.DisableInput) + self:SetClientBool("nospectrum", settings.DisableSpectrum) self:SetClientBool("noadvwire", settings.DisableAdvancedOutputs) end diff --git a/materials/3dstreamradio/_data/version.vmt b/materials/3dstreamradio/_data/version.vmt index 6281361..f0182e6 100644 --- a/materials/3dstreamradio/_data/version.vmt +++ b/materials/3dstreamradio/_data/version.vmt @@ -1,2 +1,2 @@ -422 -1687559271 +423 +1689800322