From e950f54a0132a84d66b31642574a93d66b6ae53e Mon Sep 17 00:00:00 2001 From: Grocel Date: Thu, 8 Jun 2023 04:34:26 +0200 Subject: [PATCH] New Features, Better Networking, Faster Performance - Added option for cursor in VR. It was requested by user Peekofwar on Steam. - Added button for switching playback mode (no loop, song loop, playlist loop). Fixes #12 - Added option for the playback mode to the Toolgun. - Added/Changed Wiremod ports represent the playback mode. - Added support (including the GUI) for the Wiremod User entity. It can be used in contraptions to trigger use operations (+use) as like as play would do on other entities. - Changed playlist icons to a new custom one. - Moved networking from NW2Vars to NWVars, because NW2 is actually a buggy unfinished mess. - Fixed multiple cases of networking being unreliable casing the radio being wonky in multiplayer. It should be much more robust now. - Reduced networking overhead, by utilizing string tables for repeated strings. - Improved overall performance. - Fixed huge FPS drops when seeking songs. This happened also when the radio synchronizes between players or other radios (master / slave setup). - The GUI is no longer re-rendered every frame when radio playback is paused or stopped. It still has too when it play backs a song, though. - Fixed several UI / UX bugs, causing hiccups, stucked playback and other wonky behaviors. - Replaced hacky hashing algorithms (for networking). Because the game actually ships with decent official ones now, we no longer have to abuse util.CRC() --- glualint.json | 2 +- lua/autorun/streamradio_loader.lua | 30 +- lua/entities/base_streamradio.lua | 76 +- lua/entities/base_streamradio_gui.lua | 385 ++++++---- lua/entities/sent_streamradio/cl_init.lua | 98 ++- lua/entities/sent_streamradio/init.lua | 91 ++- lua/entities/sent_streamradio/shared.lua | 147 +++- lua/streamradio_core/api.lua | 62 +- lua/streamradio_core/cache.lua | 74 +- .../classes/base_listener.lua | 157 ++-- .../classes/gui_controller.lua | 85 ++- .../classes/skin_controller.lua | 52 +- lua/streamradio_core/classes/stream.lua | 189 ++--- lua/streamradio_core/classes/ui/button.lua | 2 + lua/streamradio_core/classes/ui/debug.lua | 3 +- .../classes/ui/label_fade.lua | 35 +- lua/streamradio_core/classes/ui/list.lua | 190 ++--- .../classes/ui/list_files.lua | 4 +- lua/streamradio_core/classes/ui/panel.lua | 208 ++++-- .../classes/ui/progressbar.lua | 44 +- .../classes/ui/radio/gui_browser.lua | 178 +++-- .../classes/ui/radio/gui_errorbox.lua | 20 +- .../classes/ui/radio/gui_main.lua | 12 +- .../classes/ui/radio/gui_player.lua | 112 +-- .../classes/ui/radio/gui_player_controls.lua | 361 +++++++--- .../classes/ui/radio/gui_player_spectrum.lua | 59 +- .../classes/ui/radio/list_playlists.lua | 23 +- .../classes/ui/radio/list_playlistview.lua | 104 ++- lua/streamradio_core/classes/ui/scrollbar.lua | 7 +- .../classes/ui/shadow_panel.lua | 1 + lua/streamradio_core/classes/ui/textview.lua | 2 + lua/streamradio_core/classes/ui/tooltip.lua | 1 + lua/streamradio_core/client/cl_lib.lua | 170 ++--- .../client/cl_playlist_edit.lua | 75 +- lua/streamradio_core/client/cl_surface.lua | 4 +- .../client/cl_vgui_editor.lua | 10 +- .../client/settings/general.lua | 35 +- lua/streamradio_core/client/settings/vr.lua | 12 +- lua/streamradio_core/enum.lua | 198 ++++++ lua/streamradio_core/filesystem.lua | 33 +- lua/streamradio_core/filesystem/_generic.lua | 2 +- lua/streamradio_core/filesystem/json.lua | 2 +- lua/streamradio_core/filesystem/m3u.lua | 2 +- lua/streamradio_core/filesystem/pls.lua | 2 +- lua/streamradio_core/filesystem/vdf.lua | 2 +- lua/streamradio_core/lib.lua | 672 ++++++++---------- lua/streamradio_core/load.lua | 4 +- lua/streamradio_core/models.lua | 3 +- lua/streamradio_core/models/portal_turret.lua | 2 - lua/streamradio_core/net.lua | 209 +++--- lua/streamradio_core/network.lua | 510 ++++++++----- lua/streamradio_core/server/sv_lib.lua | 27 +- .../server/sv_playlist_edit.lua | 29 +- lua/streamradio_core/server/sv_res.lua | 120 ++-- lua/streamradio_core/tool.lua | 47 +- lua/streamradio_core/vr.lua | 28 +- lua/streamradio_core/wire.lua | 205 ++++++ lua/weapons/gmod_tool/stools/streamradio.lua | 72 +- .../stools/streamradio_gui_color_global.lua | 8 +- .../streamradio_gui_color_individual.lua | 8 +- materials/3dstreamradio/_data/version.vmt | 4 +- .../icon16/arrow_not_refresh.png | Bin 0 -> 907 bytes materials/3dstreamradio/icon16/format_pls.png | Bin 543 -> 0 bytes .../3dstreamradio/icon16/format_pplay.png | Bin 521 -> 0 bytes .../3dstreamradio/icon16/table_sound.png | Bin 0 -> 709 bytes 65 files changed, 3222 insertions(+), 2087 deletions(-) create mode 100644 lua/streamradio_core/enum.lua create mode 100644 lua/streamradio_core/wire.lua create mode 100644 materials/3dstreamradio/icon16/arrow_not_refresh.png delete mode 100644 materials/3dstreamradio/icon16/format_pls.png delete mode 100644 materials/3dstreamradio/icon16/format_pplay.png create mode 100644 materials/3dstreamradio/icon16/table_sound.png diff --git a/glualint.json b/glualint.json index d728074..5e77f2c 100644 --- a/glualint.json +++ b/glualint.json @@ -10,7 +10,7 @@ "lint_shadowing": false, "lint_gotos": true, "lint_doubleNegations": true, - "lint_redundantIfStatements": true, + "lint_redundantIfStatements": false, "lint_redundantParentheses": true, "lint_duplicateTableKeys": true, "lint_profanity": true, diff --git a/lua/autorun/streamradio_loader.lua b/lua/autorun/streamradio_loader.lua index f5be2c7..d66aa69 100644 --- a/lua/autorun/streamradio_loader.lua +++ b/lua/autorun/streamradio_loader.lua @@ -239,31 +239,26 @@ local function saveinclude(lua, force) -- Anything below is advanced error handling. -- It is to ensure that the addon has loaded correctly and completely without errors. - -- CompileFile gives your control in this regard if force then g_loaded[lua] = nil end if g_loaded[lua] then + -- Prevent loading twice return true, g_loaded[lua] end - local status, err = pcall(function() + local status, errOrResult = pcall(function() if not file.Exists(lua, "LUA") then error("Couldn't include file '" .. lua .. "' (File not found)", 0) end - local func = CompileFile(lua) - if not func then - error("Couldn't include file '" .. lua .. "' (Syntax error)", 0) - end - - return func() + return include(lua) end) if not status then - err = tostring(err or "") + local err = tostring(errOrResult or "") if err == "" then err = "Unknown error" @@ -285,8 +280,8 @@ local function saveinclude(lua, force) return nil end - g_loaded[lua] = err - return status, err + g_loaded[lua] = errOrResult + return status, errOrResult end local function loadBASS3() @@ -532,9 +527,9 @@ else end if SV then - util.AddNetworkString("3D_StreamRadio_LoadError") + util.AddNetworkString("3DStreamRadio/LoadError") - hook.Add("PlayerInitialSpawn", "3D_StreamRadio_LoadError", function(ply) + hook.Add("PlayerInitialSpawn", "3DStreamRadio/LoadError", function(ply) if not IsValid(ply) then return end @@ -547,12 +542,12 @@ if SV then return end - net.Start("3D_StreamRadio_LoadError") + net.Start("3DStreamRadio/LoadError") net.WriteString(StreamRadioLib.ErrorString or "") net.Send(ply) end) else - net.Receive("3D_StreamRadio_LoadError", function() + net.Receive("3DStreamRadio/LoadError", function() local err = net.ReadString() if err == "" then return end if not StreamRadioLib then return end @@ -565,9 +560,4 @@ else end) end -concommand.Add("debug_streamradio_reload", function() - if not StreamRadioLib then return end - StreamRadioLib.LoadSH(thisfile) -end) - collectgarbage( "collect" ) diff --git a/lua/entities/base_streamradio.lua b/lua/entities/base_streamradio.lua index d1af4e2..6723c44 100644 --- a/lua/entities/base_streamradio.lua +++ b/lua/entities/base_streamradio.lua @@ -2,6 +2,10 @@ AddCSLuaFile() DEFINE_BASECLASS("base_anim") +local StreamRadioLib = StreamRadioLib +local LIBNetwork = StreamRadioLib.Network +local LIBWire = StreamRadioLib.Wire + local WireLib = WireLib local IsValid = IsValid @@ -28,7 +32,7 @@ local CLIENT = CLIENT ENT.__IsRadio = true ENT.__IsLibLoaded = StreamRadioLib and StreamRadioLib.Loaded -ENT.__IsWiremodLoaded = ENT.__IsLibLoaded and StreamRadioLib.HasWiremod() +ENT.__IsWiremodLoaded = ENT.__IsLibLoaded and LIBWire.HasWiremod() ENT.Editable = false ENT.Spawnable = false @@ -41,7 +45,7 @@ function ENT:AddDTNetworkVar(datatype, name, ...) return end - return StreamRadioLib.Network.AddDTNetworkVar(self, datatype, name, ...) + return LIBNetwork.AddDTNetworkVar(self, datatype, name, ...) end function ENT:SetupDataTables() @@ -49,7 +53,7 @@ function ENT:SetupDataTables() return end - StreamRadioLib.Network.SetupDataTables(self) + LIBNetwork.SetupDataTables(self) end function ENT:SetAnim( Animation, Frame, Rate ) @@ -162,6 +166,7 @@ function ENT:GetOrCreateStream() end) stream:SetName("stream") + stream:SetNWName("str") stream:SetEntity(self) stream:ActivateNetworkedMode() stream:OnClose() @@ -172,16 +177,19 @@ end function ENT:StreamOnConnect() self:CheckTransmitState() + return true end function ENT:StreamOnSearch() self:CheckTransmitState() + return true end function ENT:StreamOnRetry() self:CheckTransmitState() + return true end @@ -257,7 +265,11 @@ function ENT:GetSoundPosAng() return pos, ang end -function ENT:DistanceToPlayer(ply, pos1, pos2) +function ENT:DistanceToEntity(ent, pos1, pos2) + if not self.__IsLibLoaded then + return 0 + end + if not pos1 then pos1 = self:GetSoundPosAng() end @@ -266,9 +278,7 @@ function ENT:DistanceToPlayer(ply, pos1, pos2) return pos2:Distance(pos1) end - if self.__IsLibLoaded then - pos2 = StreamRadioLib.GetCameraPos(ply) - end + pos2 = StreamRadioLib.GetCameraPos(ent) if not pos2 then return 0 @@ -277,6 +287,39 @@ function ENT:DistanceToPlayer(ply, pos1, pos2) return pos2:Distance(pos1) end +function ENT:DistToSqrToEntity(ent, pos1, pos2) + if not self.__IsLibLoaded then + return 0 + end + + if not pos1 then + pos1 = self:GetSoundPosAng() + end + + if pos2 then + return pos2:DistToSqr(pos1) + end + + pos2 = StreamRadioLib.GetCameraPos(ent) + + if not pos2 then + return 0 + end + + return pos2:DistToSqr(pos1) +end + +function ENT:CheckDistanceToEntity(ent, maxDist, pos1, pos2) + local maxDistSqr = maxDist * maxDist + local distSqr = self:DistToSqrToEntity(ent, pos1, pos2) + + if distSqr > maxDistSqr then + return false + end + + return true +end + function ENT:Initialize() if self.__IsLibLoaded then StreamRadioLib.SpawnedRadios[self] = true @@ -287,7 +330,6 @@ function ENT:Initialize() end self:GetOrCreateStream() - self:CheckTransmitState() end @@ -305,14 +347,26 @@ function ENT:IsMutedForPlayer(ply) return true end + 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 StreamRadioLib.IsMuted(ply) then return true end - local playerdist = self:DistanceToPlayer(ply) local mutedist = math.min(self:GetRadius() + 1000, StreamRadioLib.GetMuteDistance(ply)) + local camPos = nil + + if CLIENT then + camPos = StreamRadioLib.GetCameraViewPos(ply) + end - if playerdist >= mutedist then + if not self:CheckDistanceToEntity(ply, mutedist, nil, camPos) then return true end @@ -403,7 +457,7 @@ function ENT:FastThink() return end - StreamRadioLib.Network.Pull(self) + LIBNetwork.Pull(self) if SERVER then if self.__IsWiremodLoaded then diff --git a/lua/entities/base_streamradio_gui.lua b/lua/entities/base_streamradio_gui.lua index 036ce4b..c71066e 100644 --- a/lua/entities/base_streamradio_gui.lua +++ b/lua/entities/base_streamradio_gui.lua @@ -1,6 +1,12 @@ AddCSLuaFile() DEFINE_BASECLASS("base_streamradio") +local StreamRadioLib = StreamRadioLib +local LIBNetwork = StreamRadioLib.Network +local LIBModel = StreamRadioLib.Model +local LIBSkin = StreamRadioLib.Skin +local LIBWire = StreamRadioLib.Wire + ENT.RenderGroup = RENDERGROUP_BOTH ENT.Spawnable = false ENT.AdminOnly = false @@ -21,129 +27,145 @@ function ENT:SetDisplayPosAng(pos, ang) self.DisplayAngles = ang end -function ENT:CanControlDisplay(ply) - if not self.__IsLibLoaded then return false end - if self:GetDisableInput() then return false end +function ENT:GetDisplayPos( ) + if not self:HasGUI() then return end + if self:GetDisableDisplay() then return end - if not self:HasGUI() then return false end - if self:GetDisableDisplay() then return false end + local pos = self:GetPos( ) + local ang = self:GetAngles( ) - if StreamRadioLib.IsGUIHidden(ply) then return false end - if not self:OnGUIShowCheck(ply) then return false end + local DisplayPosOffset = self.DisplayOffset or vec_zero + local DisplayAngOffset = self.DisplayAngles or ang_zero - local pos, ang = self:GetDisplayPos() - if not pos then return false end + pos, ang = LocalToWorld( DisplayPosOffset, DisplayAngOffset, pos, ang ) - local controlpos = StreamRadioLib.GetControlPosDir(ply) - if not controlpos then return false end + debugoverlay.Axis(pos, ang, 5, 0.05, color_white) + debugoverlay.EntityTextAtPosition(pos, 1, "Display pos", 0.05, color_white) - -- Return false if from the backside - local a = controlpos - pos - local b = ang:Up():Dot( a ) / a:Length() + return pos, ang +end - local displayVisAng = math.acos( b ) / math.pi * 180 - return displayVisAng < 90 +function ENT:CheckPropProtection(ply) + -- Support for prop protections + if self.CPPICanUse then + local use = self:CPPICanUse(ply) or false + if not use then + return false + end + end + + if SERVER then + local use = hook.Run("PlayerUse", ply, radio) + if not use then + return false + end + end + + return true end -function ENT:CanSeeDisplay(ply) - if not self.__IsLibLoaded then return false end +function ENT:CanControlInternal(ply, userEntity) + if self:GetDisableInput() then return false end if not self:HasGUI() then return false end if self:GetDisableDisplay() then return false end - if StreamRadioLib.IsGUIHidden(ply) then return false end - if not self:OnGUIShowCheck(ply) then return false end + -- Check the player for +use permission + if not self:CheckPropProtection(ply) then + return false + end + + if userEntity:IsPlayer() then + if not userEntity:Alive() then + return false + end + + if StreamRadioLib.IsGUIHidden(userEntity) then + return false + end + + if not self:OnGUIShowCheck(userEntity) then + return false + end + end + + local scale = self:GetScale() + if scale <= 0 then return false end local pos, ang = self:GetDisplayPos() if not pos then return false end + if not ang then return false end - local campos = StreamRadioLib.GetCameraPos(ply) - if not campos then return false end + local controlpos = StreamRadioLib.GetControlPosDir(userEntity) + if not controlpos then return false end -- Return false if from the backside - local a = campos - pos + local a = controlpos - pos local b = ang:Up():Dot( a ) / a:Length() local displayVisAng = math.acos( b ) / math.pi * 180 return displayVisAng < 90 end -function ENT:CursorInGUI(cx, cy) - if not IsValid(self.GUI) then return false end +function ENT:CanControl(ply, userEntity) + if not self.__IsLibLoaded then return false end - local px, py = self.GUI:GetAbsolutePos() - return self.GUI:IsInBounds(cx - px, cy - py) -end + if not IsValid(ply) then return false end + if not ply:IsPlayer() then return false end -function ENT:GetDisplayPos( ) - if not self:HasGUI() then return end - if self:GetDisableDisplay() then return end + if not IsValid(userEntity) then + userEntity = ply + end - local pos = self:GetPos( ) - local ang = self:GetAngles( ) + local cacheId = tostring(ply) .. "_" .. tostring(userEntity) - local DisplayPosOffset = self.DisplayOffset or vec_zero - local DisplayAngOffset = self.DisplayAngles or ang_zero + if self._canControlCache and self._canControlCache[cacheId] ~= nil then + return self._canControlCache[cacheId] + end - pos, ang = LocalToWorld( DisplayPosOffset, DisplayAngOffset, pos, ang ) + self._canControlCache = self._canControlCache or {} + self._canControlCache[cacheId] = nil - debugoverlay.Axis(pos, ang, 5, 0.05, color_white) - debugoverlay.EntityTextAtPosition(pos, 1, "Display pos", 0.05, color_white) + local result = self:CanControlInternal(ply, userEntity) - return pos, ang + self._canControlCache[cacheId] = result + return result end -function ENT:GetCursor( ply, trace ) - if not self.__IsLibLoaded then - return false - end - - local Scale = self:GetScale() - local Pos, Ang = self:GetDisplayPos() - - if Scale <= 0 then - return false - end +function ENT:CursorInGUI(cx, cy) + if not IsValid(self.GUI) then return false end - if not Pos then - return false - end + local px, py = self.GUI:GetAbsolutePos() + return self.GUI:IsInBounds(cx - px, cy - py) +end - if not IsValid(ply) then - return false - end +function ENT:GetCursor( ply, trace, userEntity ) + if not self.__IsLibLoaded then return false end - if not ply:IsPlayer() then - return false - end + if not IsValid(ply) then return false end + if not ply:IsPlayer() then return false end - if not ply:Alive() then - return false + if not IsValid(userEntity) then + userEntity = ply end - if not self:CanControlDisplay(ply) then + if not self:CanControl(ply, userEntity) then return false end - if not trace or not trace.Hit then - trace = StreamRadioLib.Trace(ply) + if not trace then + trace = StreamRadioLib.Trace(userEntity) if not trace or not trace.Hit then return false end end - if not self:OnGUIInteractionCheck(ply, trace) then + if not self:OnGUIInteractionCheck(ply, trace, userEntity) then return false end - if self.CPPICanUse then - local allowuse = self:CPPICanUse(ply) or false - if not allowuse then - return false - end - end - - if self:DistanceToPlayer(ply, trace.HitPos) > self.MaxCursorTraceDist then + -- Ignore distances when we are using via an entity that is not a player + if userEntity:IsPlayer() and not self:CheckDistanceToEntity(userEntity, self.MaxCursorTraceDist, trace.HitPos) then return false end @@ -153,15 +175,18 @@ function ENT:GetCursor( ply, trace ) return false end - local TraceHitPos = util.IntersectRayWithPlane( trace.StartPos, trace.Normal, Pos, Ang:Up( ) ) + local scale = self:GetScale() + local pos, ang = self:GetDisplayPos() + + local TraceHitPos = util.IntersectRayWithPlane( trace.StartPos, trace.Normal, pos, ang:Up( ) ) if not TraceHitPos then return false end - local HitPos = WorldToLocal( TraceHitPos, ang_zero, Pos, Ang ) - local CursorX = math.Round( HitPos.x / Scale ) - local CursorY = math.Round( -HitPos.y / Scale ) + local HitPos = WorldToLocal( TraceHitPos, ang_zero, pos, ang ) + local CursorX = math.Round( HitPos.x / scale ) + local CursorY = math.Round( -HitPos.y / scale ) Cursor = self:CursorInGUI(CursorX, CursorY) @@ -198,7 +223,7 @@ function ENT:SetUpModel() if not IsValid(self.StreamObj) then return end local model = self:GetModel() - self.ModelData = StreamRadioLib.Model.GetModelSettings(model) + self.ModelData = LIBModel.GetModelSettings(model) local MD = self.ModelData or {} self:CallModelFunction("Initialize", model) @@ -240,6 +265,7 @@ function ENT:SetUpModel() end self.GUI:SetName("gui") + self.GUI:SetNWName("g") self.GUI:SetEntity(self) self.GUI:ActivateNetworkedMode() @@ -255,6 +281,7 @@ function ENT:SetUpModel() self.GUI_Main:SetPos(0, 0) self.GUI_Main:SetName("main") + self.GUI_Main:SetNWName("m") self.GUI_Main:SetSkinIdentifyer("main") self.GUI_Main:SetStream(self.StreamObj) @@ -281,8 +308,8 @@ function ENT:SetUpModel() self:CallModelFunction("InitializeFonts", model) self:CallModelFunction("SetupGUI", self.GUI, self.GUI_Main) - self.GUI:SetSkin(StreamRadioLib.Skin.GetDefaultSkin()) - self.GUI:_PerformRerenderInternal() + self.GUI:SetSkin(LIBSkin.GetDefaultSkin()) + self.GUI:PerformRerender(true) if self.OnSetupModelSetup then self:OnSetupModelSetup() @@ -329,35 +356,58 @@ end function ENT:FastThink() BaseClass.FastThink(self) - self:ControlThink(self:GetLastUser()) + + self._canControlCache = {} + + self:ControlThink(self:GetLastUser(), self:GetLastUsingEntity()) end -function ENT:ControlThink(ply, tr) +function ENT:ControlThink(ply, userEntity) if not IsValid(self.GUI) then return end if not IsValid(ply) then return end - local Cursor, CursorX, CursorY = self:GetCursor(ply, tr) - self.GUI:SetCursor(CursorX or -1, CursorY or -1) -end + local Cursor, CursorX, CursorY = self:GetCursor(ply, nil, userEntity) -function ENT:Think() - BaseClass.Think(self) - return true + if not Cursor then + if self.GUI:GetCursor() ~= -1 then + self.GUI:Click(false) + self.GUI:SetCursor(-1, -1) + end + + return + end + + self.GUI:SetCursor(CursorX or -1, CursorY or -1) end -function ENT:Control(ply, tr, pressed) +function ENT:Control(ply, trace, pressed, userEntity) if not IsValid(self.GUI) then return end - - if not self:CanControlDisplay(ply) then return end + if not IsValid(ply) then return end if pressed then + if not self:CanControl(ply, userEntity) then + return + end + + -- anti click spam local now = RealTime() - if (now - (self._oldusetime or 0)) < 0.1 then return end + + if (now - (self._oldusetime or 0)) < 0.1 then + return + end + self._oldusetime = now end - local Cursor, CursorX, CursorY = self:GetCursor(ply, tr) - if not Cursor then return end + local Cursor, CursorX, CursorY = self:GetCursor(ply, trace, userEntity) + if not Cursor then + if self.GUI:GetCursor() ~= -1 then + self.GUI:Click(false) + self.GUI:SetCursor(-1, -1) + end + + return + end self.GUI:SetCursor(CursorX or -1, CursorY or -1) self.GUI:Click(pressed) @@ -366,6 +416,7 @@ function ENT:Control(ply, tr, pressed) self:EmitSoundIfExist(self.Sounds_Use, 50, 100, 0.40, CHAN_ITEM) self:SetLastUser(ply) + self:SetLastUsingEntity(userEntity) end end @@ -380,7 +431,7 @@ function ENT:SetupDataTables() category = "GUI", title = "Disable display", type = "Boolean", - order = 1 + order = 10 } }) @@ -390,7 +441,7 @@ function ENT:SetupDataTables() category = "GUI", title = "Disable input", type = "Boolean", - order = 2 + order = 11 } }) @@ -400,26 +451,30 @@ function ENT:SetupDataTables() category = "GUI", title = "Show debug panel", type = "Boolean", - order = 3 - } - }) - - self:AddDTNetworkVar( "Bool", "PlaylistLoop", { - KeyName = "PlaylistLoop", - Edit = { - category = "Playlist", - title = "Enable playlist track switch", - type = "Boolean", - order = 1 + order = 12 } }) - StreamRadioLib.Network.SetDTVarCallback(self, "EnableDebug", function(this, name, oldv, newv) + LIBNetwork.SetDTVarCallback(self, "EnableDebug", function(this, name, oldv, newv) if not IsValid(self.GUI) then return end self.GUI:SetDebug(newv) end) end +function ENT:IsPlaylistEnabled() + local GUI_Main = self.GUI_Main + + if not IsValid(GUI_Main) then + return false + end + + if not GUI_Main:IsPlaylistEnabled() then + return false + end + + return true +end + function ENT:Initialize() BaseClass.Initialize(self) self:SetUpModel() @@ -476,66 +531,80 @@ function ENT:OnGUIShowCheck(ply) return true end -function ENT:OnGUIInteractionCheck(ply, tr) +function ENT:OnGUIInteractionCheck(ply, trace, userEntity) -- Override me return true end if CLIENT then + function ENT:CanSeeDisplay() + if not self.__IsLibLoaded then return false end - function ENT:CanPlayerUse(ply) - if not IsValid(self.GUI) then return false end - if not IsValid(ply) then return false end + if not self:HasGUI() then return false end + if self:GetDisableDisplay() then return false end - if not self:CanControlDisplay(ply) then return false end - return true - end + local ply = LocalPlayer() + if StreamRadioLib.IsGUIHidden(ply) then return false end + if not self:OnGUIShowCheck(ply) then return false end - function ENT:GetCurserFromUser() - local lastuser = self:GetLastUser() - local lp = LocalPlayer() + local scale = self:GetScale() + if scale <= 0 then return false end - local Cursor, CursorX, CursorY + local pos, ang = self:GetDisplayPos() + if not pos then return false end - if self:CanPlayerUse(lastuser) then - Cursor, CursorX, CursorY = self:GetCursor(lastuser) - end + local campos = StreamRadioLib.GetCameraViewPos(ply) + if not campos then return false end - if Cursor then - return Cursor, CursorX, CursorY + -- Return false if from the backside + local a = campos - pos + local b = ang:Up():Dot( a ) / a:Length() + + local displayVisAng = math.acos( b ) / math.pi * 180 + return displayVisAng < 90 + end + + 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() + + if not IsValid(lastUser) then + lastUser = LocalPlayer() end - if self:CanPlayerUse(lp) then - Cursor, CursorX, CursorY = self:GetCursor(lp) + if not IsValid(userEntity) then + userEntity = lastUser end - return Cursor, CursorX, CursorY + return self:GetCursor(lastUser, nil, userEntity) end function ENT:DrawGUI() if not self.__IsLibLoaded then return end if not IsValid(self.GUI) then return end - local ply = LocalPlayer() - if not self:CanSeeDisplay(ply) then return end + if not self:CanSeeDisplay() then return end - local dist = self:DistanceToPlayer(ply) - if dist > StreamRadioLib.GetDrawDistance() then return end - - local Scale = self:GetScale() - local Pos, Ang = self:GetDisplayPos() + local ply = LocalPlayer() + if not self:CheckDistanceToEntity(ply, StreamRadioLib.GetDrawDistance(), nil, StreamRadioLib.GetCameraViewPos(ply)) then return end - if Scale <= 0 then return end - if not Pos then return end + local scale = self:GetScale() + local pos, ang = self:GetDisplayPos() - local Cursor, CursorX, CursorY = self:GetCurserFromUser() + local Cursor, CursorX, CursorY = self:GetCurserFromLastUser() local col = self:GetColor() - self.GUI:SetAllowCursor(not StreamRadioLib.IsCursorHidden()) + self.GUI:SetAllowCursor(StreamRadioLib.IsCursorEnabled()) self.GUI:SetDrawAlpha(col.a / 255) - self.GUI:SetCursor(CursorX or -1, CursorY or -1) - cam.Start3D2D( Pos, Ang, Scale ) + if Cursor then + self.GUI:SetCursor(CursorX or -1, CursorY or -1) + end + + cam.Start3D2D( pos, ang, scale ) self.GUI:RenderSystem() cam.End3D2D( ) end @@ -551,15 +620,43 @@ if CLIENT then if not IsValid(self.GUI) then return false end if StreamRadioLib.IsSpectrumHidden() then return false end - local ply = LocalPlayer() - if not self:CanSeeDisplay(ply) then return false end + if not self:CanSeeDisplay() then return false end - local dist = self:DistanceToPlayer(ply) - if dist > StreamRadioLib.GetSpectrumDistance() then return false end + local ply = LocalPlayer() + if not self:CheckDistanceToEntity(ply, StreamRadioLib.GetSpectrumDistance(), nil, StreamRadioLib.GetCameraViewPos(ply)) then return false end return true end else + function ENT:Use(activator, ...) + if not self.__IsWiremodLoaded then return false end + if not IsValid(self.GUI) then return end + + if not IsValid(activator) then + return false + end + + if not activator:IsPlayer() then + return false + end + + local data = LIBWire.FindCallingWireUserEntityData() + if not data then + return false + end + + local now = RealTime() + + if (now - (self._oldwireusetime or 0)) < 0.1 then + return false + end + + self._oldwireusetime = now + + StreamRadioLib.TabControl(activator, data.trace, data.userEntity) + return true + end + function ENT:OnSetupCopyData(data) data.GUI = nil data.GUI_Main = nil diff --git a/lua/entities/sent_streamradio/cl_init.lua b/lua/entities/sent_streamradio/cl_init.lua index 06a8924..231d41c 100644 --- a/lua/entities/sent_streamradio/cl_init.lua +++ b/lua/entities/sent_streamradio/cl_init.lua @@ -1,6 +1,8 @@ include("shared.lua") DEFINE_BASECLASS("base_streamradio_gui") +local StreamRadioLib = StreamRadioLib + function ENT:StopTuneSound() if not self.NoiseSound then return end @@ -130,6 +132,8 @@ function ENT:Initialize() end end) end + + self:MarkForUpdatePlaybackLoopMode() end function ENT:OnSetupModelSetup() @@ -158,22 +162,27 @@ function ENT:GetWallTraceParamenters() } end + self.WallTraceParamenters.output = self.WallTraceParamenters.output or {} + return self.WallTraceParamenters end function ENT:TraceToCamera(frompos) - local endpos = StreamRadioLib.GetCameraPos() + local endpos = StreamRadioLib.GetCameraViewPos() local traceparams = self:GetWallTraceParamenters() traceparams.start = frompos traceparams.endpos = endpos - local trace = util.TraceLine(traceparams) + util.TraceLine(traceparams) - --debugoverlay.Line(frompos, trace.HitPos or endpos, 0.1, color_white, false) - --debugoverlay.Line(trace.HitPos or endpos, endpos, 0.1, color_black, false) + local result = traceparams.output - return trace + -- Tracers Debug + -- debugoverlay.Line(frompos, result.HitPos or endpos, 0.1, color_white, false) + -- debugoverlay.Line(result.HitPos or endpos, endpos, 0.1, color_black, false) + + return result end function ENT:TraceWalls(radius) @@ -194,7 +203,6 @@ function ENT:TraceWalls(radius) traceparams.start = startpos local traces = StreamRadioLib.StarTrace(traceparams, radius, 16, 16) - local tracecount = #traces local blockcount = 0 local wallcount = 0 @@ -222,6 +230,10 @@ function ENT:TraceWalls(radius) return 1 end + if blockcount <= 0 then + return 1 + end + local f = blockcount / wallcount local volfactor = math.Clamp((1 - f) * 2, coveredvol, 1) @@ -239,6 +251,11 @@ function ENT:GetWallVolumeFactor() return 0 end + if StreamRadioLib.GetCoveredVolume() >= 1 then + self.wallvolcache = nil + return 1 + end + local now = RealTime() self.wallvolcache = self.wallvolcache or {} @@ -274,17 +291,39 @@ function ENT:GetWallVolumeFactorSmoothed() local speed = ticktime * 2 - if self._wallvolvalue > curwallvol then - self._wallvolvalue = math.max(self._wallvolvalue - speed, curwallvol) - return self._wallvolvalue + self._wallvolvalue = math.Approach(self._wallvolvalue, curwallvol, speed) + return self._wallvolvalue +end + +function ENT:IsMuted() + ply = LocalPlayer() + + if not IsValid(ply) then return true end + if not ply:IsPlayer() then return true end + if ply:IsBot() then return true end + + if StreamRadioLib.IsMuted(ply) then + return true end - if self._wallvolvalue < curwallvol then - self._wallvolvalue = math.min(self._wallvolvalue + speed, curwallvol) - return self._wallvolvalue + local willMute = self:IsMutedForPlayer(ply) + local now = RealTime() + + if willMute then + if not self._mutedTimer then + self._mutedTimer = now + 1 + end + + if self._mutedTimer < now then + return true + end + + return false + else + self._mutedTimer = nil end - return self._wallvolvalue + return false end function ENT:UpdateStream() @@ -299,28 +338,37 @@ function ENT:UpdateStream() end local ply = LocalPlayer() - local camerapos = StreamRadioLib.GetCameraPos() self.StreamObj:Set3D(StreamRadioLib.Is3DSound() and self:GetSound3D()) self.Sound3D = self.StreamObj:Get3D() - self.PlayerDistance = self:DistanceToPlayer(ply, nil, camerapos) - self.Radius = self:GetRadius() or 0 self.StreamObj:Set3DFadeDistance(self.Radius / 3) - local wallvol = self:GetWallVolumeFactorSmoothed() - local distVolume = StreamRadioLib.CalcDistanceVolume(self.PlayerDistance, self.Radius) + local muted = self:IsMuted() - self.Muted = self:IsMutedForPlayer(ply) + local wallvol = 0 + local distVolume = 0 + local playerDistance = nil + + if not muted then + playerDistance = self:DistanceToEntity(ply, nil, StreamRadioLib.GetCameraViewPos(ply)) + + wallvol = self:GetWallVolumeFactorSmoothed() + distVolume = StreamRadioLib.CalcDistanceVolume(playerDistance, self.Radius) + end + + self.PlayerDistance = playerDistance local StreamVol = distVolume * wallvol - self.StreamObj:SetMuted(self.Muted) + self.StreamObj:SetMuted(muted) self.StreamObj:SetClientVolume(StreamVol) + self.Muted = muted + if self.NoiseSound and self.NoiseSound_vol then - local global_vol = StreamRadioLib.Settings.GetConVarValue("volume") + 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) @@ -347,7 +395,12 @@ function ENT:StreamAnimModel() return end - if self.PlayerDistance >= StreamRadioLib.GetSpectrumDistance() then + if self.Muted then + self:StreamStopAnimModel() + return + end + + if not self.PlayerDistance or self.PlayerDistance >= StreamRadioLib.GetSpectrumDistance() then self:StreamStopAnimModel() return end @@ -415,6 +468,7 @@ end function ENT:Think() BaseClass.Think(self) + self:PlaybackLoopModeThink() self:MasterRadioSyncThink() self:PanelThink() self:UpdateStream() diff --git a/lua/entities/sent_streamradio/init.lua b/lua/entities/sent_streamradio/init.lua index 91e0ad7..550bbca 100644 --- a/lua/entities/sent_streamradio/init.lua +++ b/lua/entities/sent_streamradio/init.lua @@ -3,10 +3,19 @@ include( "shared.lua" ) DEFINE_BASECLASS( "base_streamradio_gui" ) -ENT.ModelVar = Model( "models/sligwolf/grocel/radio/radio.mdl" ) +local StreamRadioLib = StreamRadioLib + +function ENT:InitializeModel() + local model = self:GetModel() + + if not StreamRadioLib.IsValidModel(model) then + self:SetModel(StreamRadioLib.GetDefaultModel()) + end +end function ENT:Initialize( ) - self:SetModel( Model( self.ModelVar or "models/sligwolf/grocel/radio/radio.mdl" ) ) + self:InitializeModel() + self:SetUseType( SIMPLE_USE ) self:PhysicsInit( SOLID_VPHYSICS ) self:SetMoveType( MOVETYPE_VPHYSICS ) @@ -39,7 +48,7 @@ function ENT:Initialize( ) self:AddWireInput("Pause", "NORMAL") self:AddWireInput("Volume", "NORMAL") self:AddWireInput("Radius", "NORMAL") - self:AddWireInput("Loop", "NORMAL") + self:AddWireInput("LoopMode", "NORMAL") self:AddWireInput("Time", "NORMAL") self:AddWireInput("3D Sound", "NORMAL") self:AddWireInput("Stream URL", "STRING") @@ -51,7 +60,12 @@ function ENT:Initialize( ) self:AddWireOutput("Stopped", "NORMAL") self:AddWireOutput("Volume", "NORMAL") self:AddWireOutput("Radius", "NORMAL") - self:AddWireOutput("Loop", "NORMAL") + + self:AddWireOutput("LoopMode", "NORMAL") + self:AddWireOutput("LoopsSong", "NORMAL") + self:AddWireOutput("LoopsPlaylist", "NORMAL") + self:AddWireOutput("PlaylistAvailable", "NORMAL") + self:AddWireOutput("Time", "NORMAL") self:AddWireOutput("Length", "NORMAL") self:AddWireOutput("Ended", "NORMAL") @@ -59,7 +73,7 @@ function ENT:Initialize( ) self:AddWireOutput("Stream Name", "STRING") self:AddWireOutput("Stream URL", "STRING") - self:AddWireOutput("Advanced Outputs", "NORMAL") + self:AddWireOutput("Advanced Outputs", "NORMAL", "Advanced Outputs available? Needs GM_BASS3.") self:AddWireOutput("Playing", "NORMAL", "Adv. Output") self:AddWireOutput("Loading", "NORMAL", "Adv. Output") self:AddWireOutput("Tag", "ARRAY", "Adv. Output") @@ -74,17 +88,18 @@ function ENT:Initialize( ) self:InitWirePorts() self:SetSettings() + + self:MarkForUpdatePlaybackLoopMode() end function ENT:SetSettings(settings) if not self.__IsLibLoaded then return end settings = settings or {} - settings.StreamUrl = settings.StreamUrl or "" - settings.StreamName = settings.StreamName or "" + local url = settings.StreamUrl or "" - if settings.StreamUrl ~= "" then - self:SetStreamURL(settings.StreamUrl) + if url ~= "" then + self:SetStreamURL(url) end local sound3d = settings.Sound3D @@ -99,24 +114,32 @@ function ENT:SetSettings(settings) noadvoutputs = true end - local playlistloop = settings.PlaylistLoop - - if playlistloop == nil then - playlistloop = true - end - - self:SetStreamName(settings.StreamName) + self:SetStreamName(settings.StreamName or "") self:SetVolume(settings.StreamVolume or 1) - self:SetLoop(settings.StreamLoop or false) self:SetRadius(settings.Radius or 1200) self:SetSound3D(sound3d) - self:SetPlaylistLoop(playlistloop) self:SetDisableDisplay(settings.DisableDisplay or false) self:SetDisableInput(settings.DisableInput or false) self:SetDisableAdvancedOutputs(noadvoutputs) + + if settings.PlaybackLoopMode then + self:SetPlaybackLoopMode(settings.PlaybackLoopMode) + end + + -- @DEPRECATED + if not settings.PlaybackLoopMode then + local playlistloop = settings.PlaylistLoop + + if playlistloop == nil then + playlistloop = true + end + + self:SetPlaylistLoop(playlistloop) + self:SetLoop(settings.StreamLoop or false) + end end function ENT:GetSettings() @@ -127,16 +150,19 @@ function ENT:GetSettings() settings.StreamName = self:GetStreamName() settings.StreamVolume = self:GetVolume() - settings.StreamLoop = self:GetLoop() - settings.Radius = self:GetRadius() settings.Sound3D = self:GetSound3D() - settings.PlaylistLoop = self:GetPlaylistLoop() settings.DisableDisplay = self:GetDisableDisplay() settings.DisableInput = self:GetDisableInput() settings.DisableAdvancedOutputs = self:GetDisableAdvancedOutputs() + settings.PlaybackLoopMode = self:GetPlaybackLoopMode() + + -- @DEPRECATED + settings.StreamLoop = self:GetLoop() + settings.PlaylistLoop = self:GetPlaylistLoop() + return settings end @@ -201,6 +227,7 @@ end function ENT:FastThink() BaseClass.FastThink(self) + self:PlaybackLoopModeThink() self:MasterRadioSyncThink() self:PanelThink() self:UpdateVolume() @@ -246,7 +273,12 @@ function ENT:WiremodThink( ) self:TriggerWireOutput("Stopped", self.StreamObj:IsStopMode()) self:TriggerWireOutput("Volume", self:GetVolume()) self:TriggerWireOutput("Radius", self:GetRadius()) - self:TriggerWireOutput("Loop", self:GetLoop()) + + self:TriggerWireOutput("LoopMode", self:GetPlaybackLoopMode()) + self:TriggerWireOutput("LoopsSong", self:GetLoop()) + self:TriggerWireOutput("LoopsPlaylist", self:GetPlaylistLoop()) + self:TriggerWireOutput("PlaylistAvailable", self:IsPlaylistEnabled()) + self:TriggerWireOutput("Time", self.StreamObj:GetMasterTime()) self:TriggerWireOutput("Length", self.StreamObj:GetMasterLength()) self:TriggerWireOutput("Ended", self.StreamObj:HasEnded()) @@ -258,7 +290,7 @@ function ENT:WiremodThink( ) self:TriggerWireOutput("Playing", self.StreamObj:IsPlaying()) self:TriggerWireOutput("Loading", self.StreamObj:IsLoading() or self.StreamObj:IsBuffering() or self.StreamObj:IsSeeking()) - self:TriggerWireOutput("Tag", {}) -- todo + self:TriggerWireOutput("Tag", {}) -- @TODO self:TriggerWireOutput("Codec", self._codec) self:TriggerWireOutput("Spectrum", self._spectrum) self:TriggerWireOutput("Sound Level", self.StreamObj:GetAverageLevel()) @@ -311,7 +343,11 @@ function ENT:Think( ) if IsValid(self.StreamObj) then self.StreamObj:SetBASSEngineEnabled(self:CanHaveSpectrum()) - self.StreamObj:SetLoop(self:GetLoop()) + + -- Loop the song also when we are in playlist mode without a playlist. We pretend we have a playlist with a single item. + local shouldLoop = self:GetLoop() or (not self:IsPlaylistEnabled() and self:GetPlaylistLoop()) + + self.StreamObj:SetLoop(shouldLoop) end end @@ -450,8 +486,7 @@ function ENT:PermaPropLoad(data) if data.Model then local model = Model(data.Model) - if model ~= "" and util.IsValidModel(model) then - self.ModelVar = model + if StreamRadioLib.IsValidModel(model) then self:SetModel(model) end end @@ -588,14 +623,14 @@ function ENT:OnWireInputTrigger(name, value, wired) return end - if name == "Loop" then + if name == "LoopMode" then value = tobool(value) if not wired then value = false end - self:SetLoop(value) + self:SetPlaybackLoopMode(value) return end diff --git a/lua/entities/sent_streamradio/shared.lua b/lua/entities/sent_streamradio/shared.lua index a636187..a1ffdf6 100644 --- a/lua/entities/sent_streamradio/shared.lua +++ b/lua/entities/sent_streamradio/shared.lua @@ -1,6 +1,9 @@ AddCSLuaFile() DEFINE_BASECLASS( "base_streamradio_gui" ) +local StreamRadioLib = StreamRadioLib +local LIBNetwork = StreamRadioLib.Network + ENT.Spawnable = false ENT.AdminOnly = false ENT.Editable = true @@ -20,6 +23,7 @@ function ENT:SetupDataTables( ) self:AddDTNetworkVar("Bool", "WireMode") self:AddDTNetworkVar("Bool", "ToolMode") self:AddDTNetworkVar("Entity", "LastUser") + self:AddDTNetworkVar("Entity", "LastUsingEntity") self:AddDTNetworkVar("Entity", "MasterRadio") local adv_wire = nil @@ -44,7 +48,7 @@ function ENT:SetupDataTables( ) category = "Stream", title = "Volume", type = "Float", - order = 1, + order = 20, min = 0, max = 1, } @@ -56,7 +60,7 @@ function ENT:SetupDataTables( ) category = "Stream", title = "Radius", type = "Int", - order = 2, + order = 21, min = 0, max = 5000, } @@ -68,19 +72,99 @@ function ENT:SetupDataTables( ) category = "Stream", title = "Enable 3D sound", type = "Boolean", - order = 3 + order = 22 } }) self:AddDTNetworkVar("Bool", "Loop", { KeyName = "Loop", Edit = { - category = "Stream", - title = "Enable loop", + category = "Loop", + title = "Enable song loop", + type = "Boolean", + order = 30 + } + }) + + self:AddDTNetworkVar( "Bool", "PlaylistLoop", { + KeyName = "PlaylistLoop", + Edit = { + category = "Loop", + title = "Enable playlist loop", type = "Boolean", - order = 4 + order = 31 } }) + + LIBNetwork.SetDTVarCallback(self, "Loop", function(this, name, oldv, newv) + if not IsValid(self) then return end + + if newv then + self:SetPlaylistLoop(false) + end + + self:MarkForUpdatePlaybackLoopMode() + end) + + LIBNetwork.SetDTVarCallback(self, "PlaylistLoop", function(this, name, oldv, newv) + if not IsValid(self) then return end + + if newv then + self:SetLoop(false) + end + + self:MarkForUpdatePlaybackLoopMode() + end) +end + +function ENT:GetPlaybackLoopMode() + local loop = self:GetLoop() + local playlistLoop = self:GetPlaylistLoop() + + if loop then + return StreamRadioLib.PLAYBACK_LOOP_MODE_SONG + end + + if playlistLoop then + return StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST + end + + return StreamRadioLib.PLAYBACK_LOOP_MODE_NONE +end + +function ENT:SetPlaybackLoopMode(loopMode) + if CLIENT then return end + + self:SetLoop(false) + self:SetPlaylistLoop(false) + + if loopMode == StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST then + self:SetPlaylistLoop(true) + elseif loopMode == StreamRadioLib.PLAYBACK_LOOP_MODE_SONG then + self:SetLoop(true) + end + + self:MarkForUpdatePlaybackLoopMode() +end + +function ENT:MarkForUpdatePlaybackLoopMode() + self._callUpdatePlaybackLoopMode = true +end + +function ENT:UpdatePlaybackLoopMode() + self._callUpdatePlaybackLoopMode = nil + + local loopMode = self:GetPlaybackLoopMode() + + if IsValid(self.GUI_Main) then + self.GUI_Main:UpdatePlaybackLoopMode(loopMode) + end + + self.OnUpdatePlaybackLoopMode(loopMode) +end + +function ENT:OnUpdatePlaybackLoopMode(loopMode) + -- Override me end function ENT:GetMasterRadioRecursive() @@ -89,12 +173,8 @@ function ENT:GetMasterRadioRecursive() return nil end - if IsValid(self._supermasterradio) then - if self._supermasterradio.__IsRadio then - if IsValid(self._supermasterradio.StreamObj) then - return self._supermasterradio - end - end + if IsValid(self._supermasterradio) and self._supermasterradio.__IsRadio and IsValid(self._supermasterradio.StreamObj) then + return self._supermasterradio end self._supermasterradio = nil @@ -174,7 +254,7 @@ function ENT:IsMutedForPlayer(ply) for slave, v in pairs(slaves) do if not IsValid(slave) then continue end - if not slave:IsMutedForPlayer() then return false end + if not slave:IsMutedForPlayer(ply) then return false end end return true @@ -195,7 +275,7 @@ function ENT:OnGUIShowCheck(ply) return false end -function ENT:OnGUIInteractionCheck(ply, tr, pressed) +function ENT:OnGUIInteractionCheck(ply, trace, userEntity) local masterradio = self:GetMasterRadioRecursive() if not masterradio then return true end @@ -225,10 +305,10 @@ function ENT:MasterRadioSyncThink() if IsValid(self.GUI_Main) then self.GUI_Main:SetSyncMode(false) end + end - if self._StopInternal then - self:_StopInternal() - end + if self._StopInternal then + self:_StopInternal() end if IsValid(oldmasterradio) and oldmasterradio.slavesradios then @@ -248,9 +328,12 @@ function ENT:MasterRadioSyncThink() if not masterradio then return end local this_st = self.StreamObj + if not IsValid(this_st) then return end + local master_st = masterradio.StreamObj + if not IsValid(master_st) then return end - self:SetLoop(masterradio:GetLoop()) + self:SetPlaybackLoopMode(masterradio:GetPlaybackLoopMode()) local name = master_st:GetStreamName() local url = master_st:GetURL() @@ -261,20 +344,19 @@ function ENT:MasterRadioSyncThink() statechange = true end - if url ~= this_st:GetURL() then + if url ~= this_st:GetURL() or statechange then this_st:SetURL(url) + this_st:Update() statechange = true end this_st:SetPlayingState(playingstate) - if statechange then - if IsValid(self.GUI_Main) then - self.GUI_Main:SetSyncMode(true) + if statechange and IsValid(self.GUI_Main) then + self.GUI_Main:SetSyncMode(true) - self.GUI_Main:EnablePlaylist(false) - self.GUI_Main:Play(name, url) - end + self.GUI_Main:EnablePlaylist(false) + self.GUI_Main:Play(name, url) end if SERVER then @@ -315,6 +397,14 @@ function ENT:MasterRadioSyncThink() self._supermasterradio = nil end +function ENT:PlaybackLoopModeThink() + if not self._callUpdatePlaybackLoopMode then + return + end + + self:UpdatePlaybackLoopMode() +end + function ENT:PanelThink() if not IsValid(self.GUI_Main) then return @@ -364,6 +454,13 @@ function ENT:StreamStopAnimModel() 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 + end + self.AnimStopped = nil self:StreamStopAnimModel() end diff --git a/lua/streamradio_core/api.lua b/lua/streamradio_core/api.lua index e392546..06b06ed 100644 --- a/lua/streamradio_core/api.lua +++ b/lua/streamradio_core/api.lua @@ -1,4 +1,4 @@ -/* +--[[ The developer API for the use in external addons or in non-sandbox gamemodes. Make sure you check for StreamRadioLib.Loaded == true before using this API. @@ -6,7 +6,7 @@ bool StreamRadioLib.EditRadio( Entity Radio [, table settings] ) -- Set the Radio settings to the given settings. Returns true on success. - Entity StreamRadioLib.SpawnRadio( [Player player [, string model [, Vector pos [, Angle ang [, table settings]]]]] ) + Entity StreamRadioLib.SpawnRadio( [Player player] [, string model] [, Vector pos] [, Angle ang] [, table settings] ) -- Spawns a Radio and makes the given player to the owner for the tool and CPPI. It will have the given model and will be spawned at pos and ang. -- The settings table will set the radio's settings. The radio entity is returned on success. @@ -24,13 +24,19 @@ StreamVolume number 1 0 is muted and 1 is 100% volume Radius number 1200 Number in units of the sound range - StreamLoop boolean false True enables stream looping - PlaylistLoop boolean true True enables playlist looping - Sound3D boolean true True enables the 3D world sound + 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. DisableAdvancedOutputs boolean true True disables the Advanced Wire Outputs. -*/ + + PlaybackLoopMode number 1 Loop mode: + StreamRadioLib.PLAYBACK_LOOP_MODE_NONE, Value: 0, No loop (Invalid values falls back to this) + StreamRadioLib.PLAYBACK_LOOP_MODE_SONG, Value: 1, Loops song + StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST, Value: 2 + + StreamLoop boolean false True enables stream looping. DEPRECATED, use PlaybackLoopMode + PlaylistLoop boolean true True enables playlist looping. DEPRECATED, use PlaybackLoopMode +]] -- ====================================================================== -- === Don't edit anything below, unless you know what you are doing. === @@ -43,12 +49,16 @@ local ValidTypes = { StreamVolume = "number", Radius = "number", - StreamLoop = "boolean", - PlaylistLoop = "boolean", Sound3D = "boolean", DisableInput = "boolean", DisableDisplay = "boolean", DisableAdvancedOutputs = "boolean", + + PlaybackLoopMode = "number", + + -- @DEPRECATED + StreamLoop = "boolean", + PlaylistLoop = "boolean", } function StreamRadioLib.IsValidRadioSettings( settings ) @@ -67,8 +77,7 @@ local function ErrorCheckArg( var, tright, argn, funcname, level ) local t = type( var ) if t ~= tright then - error( string.format( "bad argument #%i to '%s' (%s or nil expected, got %s)", argn, funcname, tright, t ), level or 3 ) - + ErrorNoHaltWithStack( string.format( "bad argument #%i to '%s' (%s or nil expected, got %s)", argn, funcname, tright, t ), level or 3 ) return false end @@ -83,7 +92,7 @@ local function ErrorCheckRadioSettings( settings, argn, funcname, level ) local t = type( v ) local tright = ValidTypes[k] if not tright or t == tright then continue end - error( string.format( "bad datatype at index '%s' of argument #%i at '%s' (%s or nil expected, got %s)", k, argn, funcname, tright, t ), level ) + ErrorNoHaltWithStack( string.format( "bad datatype at index '%s' of argument #%i at '%s' (%s or nil expected, got %s)", k, argn, funcname, tright, t ), level ) return false end @@ -102,19 +111,6 @@ function StreamRadioLib.EditRadio( ent, settings ) settings = settings or {} if not ErrorCheckRadioSettings( settings, 2, "EditRadio", 3 ) then return false end - local StreamName = settings.StreamName or "" - local StreamUrl = settings.StreamUrl or "" - - if StreamName == "" then - StreamName = StreamUrl - end - - if StreamUrl == "" then - StreamUrl = StreamName - end - - settings.Sound3D = settings.Sound3D - if ent.SetSettings then ent:SetSettings(settings) end @@ -142,15 +138,12 @@ function StreamRadioLib.SpawnRadio( ply, model, pos, ang, settings ) if StreamRadioLib.Msg then StreamRadioLib.Msg( ply, err ) else - error( err, 2 ) + ErrorNoHaltWithStack( err, 2 ) end return end - local ent = ents.Create( "sent_streamradio" ) - if not IsValid(ent) then return end - if not IsValid(ply) or ply:IsWorld() then ply = nil end @@ -164,9 +157,16 @@ function StreamRadioLib.SpawnRadio( ply, model, pos, ang, settings ) settings = settings or {} if not ErrorCheckRadioSettings(settings, 5, "SpawnRadio", 3) then return end + local ent = ents.Create( "sent_streamradio" ) + if not IsValid(ent) then return end + + if StreamRadioLib.IsValidModel(model) then + ent:SetModel(model) + end + ent:SetPos(pos or Vec_Zero) ent:SetAngles(ang or Ang_Zero) - ent.ModelVar = model + ent:Spawn() ent:Activate() @@ -178,6 +178,10 @@ function StreamRadioLib.SpawnRadio( ply, model, pos, ang, settings ) ent:CPPISetOwner(ply) end + if isfunction(ent.SetLastUser) then + ent:SetLastUser(ply) + end + for k, v in pairs(data) do ent[k] = v end diff --git a/lua/streamradio_core/cache.lua b/lua/streamradio_core/cache.lua index 7193e30..cda69fa 100644 --- a/lua/streamradio_core/cache.lua +++ b/lua/streamradio_core/cache.lua @@ -18,56 +18,58 @@ local function CreateBaseFolder( dir ) end end -local function Cache_Clear( ply, cmd, args ) - if game.SinglePlayer() then - StreamRadioLib.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!" ) - return - end - - if ( not ply or ( IsValid( ply ) and ply:IsAdmin( ) ) ) then - if not StreamRadioLib.DataDirectory then +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!" ) return end - if not StreamRadioLib.DeleteFolder( MainDir ) then - StreamRadioLib.Msg( ply, "Server stream cache could not be cleared!" ) + if not g_isDedicatedServer then + StreamRadioLib.Msg( ply, "A server stream cache does not exist on listen servers!" ) return end - LIB.lastloaded = {} - LIB.forbidden = {} + if ( not ply or ( IsValid( ply ) and ply:IsAdmin( ) ) ) then + if not StreamRadioLib.DataDirectory then + return + end - StreamRadioLib.Msg( ply, "Server stream cache cleared!" ) - else - StreamRadioLib.Msg( ply, "You need to be an admin clear the server stream cache." ) - end -end + if not StreamRadioLib.DeleteFolder( MainDir ) then + StreamRadioLib.Msg( ply, "Server stream cache could not be cleared!" ) + return + end -concommand.Add( "sv_streamradio_cacheclear", Cache_Clear ) + LIB.lastloaded = {} + LIB.forbidden = {} -if ( CLIENT ) then - local function Cache_Clear( ply, cmd, args ) - if not StreamRadioLib.DataDirectory then - return + StreamRadioLib.Msg( ply, "Server stream cache cleared!" ) + else + StreamRadioLib.Msg( ply, "You need to be an admin clear the server stream cache." ) end + end - if not StreamRadioLib.DeleteFolder( MainDir ) then - StreamRadioLib.Msg( ply, "Client stream cache could not be cleared!" ) - return - end + concommand.Add( "sv_streamradio_cacheclear", Cache_Clear ) - LIB.lastloaded = {} - LIB.forbidden = {} + if ( CLIENT ) then + local function Cache_Clear( ply, cmd, args ) + if not StreamRadioLib.DataDirectory then + return + end - StreamRadioLib.Msg( ply, "Client stream cache cleared!" ) - end + if not StreamRadioLib.DeleteFolder( MainDir ) then + StreamRadioLib.Msg( ply, "Client stream cache could not be cleared!" ) + return + end + + LIB.lastloaded = {} + LIB.forbidden = {} - concommand.Add( "cl_streamradio_cacheclear", Cache_Clear ) + StreamRadioLib.Msg( ply, "Client stream cache cleared!" ) + end + + concommand.Add( "cl_streamradio_cacheclear", Cache_Clear ) + end end local function IsValidFile( File ) diff --git a/lua/streamradio_core/classes/base_listener.lua b/lua/streamradio_core/classes/base_listener.lua index 41ef0c5..8c7328c 100644 --- a/lua/streamradio_core/classes/base_listener.lua +++ b/lua/streamradio_core/classes/base_listener.lua @@ -3,6 +3,9 @@ if not istable(CLASS) then return end +local LIBNetwork = StreamRadioLib.Network +local LIBNet = StreamRadioLib.Net + local BASE = CLASS:GetBaseClass() local g_listeners = CLASS:GetGlobalVar("base_listener_listeners", {}) @@ -13,7 +16,7 @@ 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 = "3dstreamradio_classsystem_listen" +local g_networkhookname = "classsystem_listen" local g_listengroups = SERVER and 6 or 4 local g_lastgroup = 1 local g_hookruns = false @@ -125,36 +128,37 @@ local function g_superlistenfunc() end end -if SERVER then - util.AddNetworkString(g_networkhookname) -end +LIBNetwork.AddNetworkString(g_networkhookname) -net.Receive(g_networkhookname, function(len, ply) +LIBNet.Receive(g_networkhookname, function(len, ply) if SERVER and not IsValid(ply) then return end local nwent = net.ReadEntity() - local name = StreamRadioLib.Net.ReceiveStringHash() - local id = StreamRadioLib.Net.ReceiveStringHash() or "" + local nwname = LIBNet.ReceiveIdentifier() + local id = LIBNet.ReceiveIdentifier() if not IsValid(nwent) then return end - if not name then return end + if not nwname then return end + if not id then return end + + if nwname == "" then return end if id == "" then return end if not nwent._3dstraemradio_classobjs_nw_register then return end - local this = nwent._3dstraemradio_classobjs_nw_register[name] + local this = nwent._3dstraemradio_classobjs_nw_register[nwname] if not IsValid(this) then if this then - nwent._3dstraemradio_classobjs_nw_register[name] = nil + nwent._3dstraemradio_classobjs_nw_register[nwname] = nil end return end if not this._netreceivefuncs then - nwent._3dstraemradio_classobjs_nw_register[name] = nil + nwent._3dstraemradio_classobjs_nw_register[nwname] = nil return end @@ -163,8 +167,8 @@ net.Receive(g_networkhookname, function(len, ply) return end - local thisname = this:GetName() - if name ~= thisname then + local thisnwname = this:GetNWName() + if nwname ~= thisnwname then return end @@ -191,6 +195,7 @@ function CLASS:Create() self._netreceivefuncs = {} self.CanListen = true self.Entity = nil + self.NWName = '' self.Network = self:CreateListener({ Active = false, @@ -678,20 +683,16 @@ function CLASS:SetName(name) if ent._3dstreamradio_classobjs then ent._3dstreamradio_classobjs[oldname] = nil end - - if ent._3dstraemradio_classobjs_nw_register then - ent._3dstraemradio_classobjs_nw_register[oldname] = nil - end end self.Name = name self:RegisterForDupe() - self:ApplyNetworkedMode() end function CLASS:SetEntity(ent) local oldent = self:GetEntity() local name = self:GetName() + local nwname = self:GetNWName() if IsValid(oldent) then if oldent._3dstreamradio_classobjs then @@ -699,7 +700,7 @@ function CLASS:SetEntity(ent) end if oldent._3dstraemradio_classobjs_nw_register then - oldent._3dstraemradio_classobjs_nw_register[name] = nil + oldent._3dstraemradio_classobjs_nw_register[nwname] = nil end end @@ -712,59 +713,58 @@ function CLASS:GetEntity() return self.Entity end -for k, v in pairs(StreamRadioLib.Network) do - if not string.find(k, "^[G|S]etNW") then continue end - - if not v then continue end - if k == "SetNWVarProxy" then continue end - if k == "SetNWHashProxy" then continue end - - CLASS[k] = function(this, key, value, ...) - if not this.Valid then return value end - local ent = this:GetEntity() - if not IsValid(ent) then return value end +function CLASS:SetNWName(nwname) + nwname = tostring(nwname or "") + nwname = string.gsub(nwname, "[%/%\\%s]", "_") - local prefix = this:GetName() .. "/" - key = prefix .. tostring(key or "") - - local r = v(ent, key, value, ...) + local ent = self:GetEntity() + local oldnwname = self:GetNWName() - if r == nil then - r = value + if IsValid(ent) then + if ent._3dstraemradio_classobjs_nw_register then + ent._3dstraemradio_classobjs_nw_register[oldnwname] = nil end - - return r end + + self.NWName = nwname + self:ApplyNetworkedMode() end -function CLASS:SetNWVarProxy(key, func, ...) - if not self.Valid then return end - local ent = self:GetEntity() - if not IsValid(ent) then return end +function CLASS:GetNWName(name) + return self.NWName or "" +end - func = self:GetFunction(func) - assert(func, "argument #2 must be a function!") +do + local loopThis = function(k, v) + if not string.find(k, "^[G|S]etNW") then return end - local prefix = self:GetName() .. "/" - key = prefix .. tostring(key or "") + if not v then return end + if k == "SetNWVarCallback" then return end - local proxyfunc = function(this, nwkey, ...) - if not IsValid(self) then return end - if not self.Network.Active then return end + CLASS[k] = function(this, key, value, ...) + if not this.Valid then return value end + local ent = this:GetEntity() + if not IsValid(ent) then return value end - nwkey = string.gsub(nwkey, "^" .. string.PatternSafe(prefix), "", 1 ) + local prefix = this:GetNWName() .. "/" + key = prefix .. tostring(key or "") - self._nw_proxycall = true - local ret = {func(self, nwkey, ...)} - self._nw_proxycall = nil + local r = v(ent, key, value, ...) - return unpack(ret) + if r == nil then + r = value + end + + return r + end end - return StreamRadioLib.Network.SetNWVarProxy(ent, key, proxyfunc, ...) + for k, v in pairs(LIBNetwork) do + loopThis(k, v) + end end -function CLASS:SetNWHashProxy(key, func, ...) +function CLASS:SetNWVarCallback(key, datatype, func, ...) if not self.Valid then return end local ent = self:GetEntity() if not IsValid(ent) then return end @@ -772,7 +772,7 @@ function CLASS:SetNWHashProxy(key, func, ...) func = self:GetFunction(func) assert(func, "argument #2 must be a function!") - local prefix = self:GetName() .. "/" + local prefix = self:GetNWName() .. "/" key = prefix .. tostring(key or "") local proxyfunc = function(this, nwkey, ...) @@ -780,17 +780,28 @@ function CLASS:SetNWHashProxy(key, func, ...) if not self.Network.Active then return end nwkey = string.gsub(nwkey, "^" .. string.PatternSafe(prefix), "", 1 ) - return func(self, nwkey, ...) + + self._nw_proxycall = true + local ret = {func(self, nwkey, ...)} + self._nw_proxycall = nil + + return unpack(ret) end - return StreamRadioLib.Network.SetNWHashProxy(ent, key, proxyfunc, ...) + return LIBNetwork.SetNWVarCallback(ent, datatype, key, proxyfunc, ...) end function CLASS:NetReceive(id, func) id = tostring(id or "") if id == "" then return end - StreamRadioLib.Net.ToHash(id) + local nwname = self:GetNWName() + + if nwname and nwname ~= "" then + LIBNetwork.AddNetworkString(nwname) + end + + LIBNetwork.AddNetworkString(id) self._netreceivefuncs[id] = func end @@ -801,18 +812,20 @@ function CLASS:NetSend(id, func, send, ...) if id == "" then return end local ent = self:GetEntity() - local name = self:GetName() + local nwname = self:GetNWName() if not IsValid(ent) then return end - if not name then return end + if not nwname then return end + if nwname == "" then return end - id = tostring(id or "") + LIBNetwork.AddNetworkString(nwname) + LIBNetwork.AddNetworkString(id) - net.Start(g_networkhookname, false) + LIBNet.Start(g_networkhookname, false) net.WriteEntity(ent) - StreamRadioLib.Net.SendStringHash(name) - StreamRadioLib.Net.SendStringHash(id) + LIBNet.SendIdentifier(nwname) + LIBNet.SendIdentifier(id) func = self:GetFunction(func) if func then @@ -859,24 +872,24 @@ function CLASS:ActivateNetworkedMode() self._nw_applycall = nil end - local name = self:GetName() + local nwname = self:GetNWName() + LIBNetwork.AddNetworkString(nwname) + local ent = self:GetEntity() - StreamRadioLib.Net.ToHash(name) if not IsValid(ent) then return end ent._3dstraemradio_classobjs_nw_register = ent._3dstraemradio_classobjs_nw_register or {} - ent._3dstraemradio_classobjs_nw_register[name] = self + ent._3dstraemradio_classobjs_nw_register[nwname] = self end function CLASS:DeactivateNetworkedMode() self.Network.Active = false - local name = self:GetName() + local nwname = self:GetNWName() local ent = self:GetEntity() - StreamRadioLib.Net.ToHash(name) if not IsValid(ent) then return @@ -886,7 +899,7 @@ function CLASS:DeactivateNetworkedMode() return end - ent._3dstraemradio_classobjs_nw_register[name] = nil + ent._3dstraemradio_classobjs_nw_register[nwname] = nil end function CLASS:PostDupeInternal(ent, name, data) diff --git a/lua/streamradio_core/classes/gui_controller.lua b/lua/streamradio_core/classes/gui_controller.lua index 34e9dd2..1d72b7f 100644 --- a/lua/streamradio_core/classes/gui_controller.lua +++ b/lua/streamradio_core/classes/gui_controller.lua @@ -8,7 +8,7 @@ local BASE = CLASS:GetBaseClass() local ColR = Color(255,0,0, 255) local ColY = Color(255,255,0, 60) local tune_nohdr = Vector( 0.80, 0, 0 ) -local CursorMat = StreamRadioLib.GetPNG("cursor") +local CursorMat = StreamRadioLib.GetCustomPNG("cursor") local catchAndErrorNoHalt = StreamRadioLib.CatchAndErrorNoHalt @@ -35,14 +35,15 @@ function CLASS:Create() end) if CLIENT then - self.ToolTip = self:AddPanelByClassname("tooltip") - self.ToolTip:SetPos(0, 0) - self.ToolTip:SetSize(1, 1) - self.ToolTip:SetName("tooltip") - self.ToolTip:SetSkinIdentifyer("tooltip") - self.ToolTip:SetText("") - self.ToolTip:SetZPos(1000) - self.ToolTip:Close() + self.Tooltip = self:AddPanelByClassname("tooltip") + self.Tooltip:SetPos(0, 0) + self.Tooltip:SetSize(1, 1) + self.Tooltip:SetName("tooltip") + self.Tooltip:SetNWName("tip") + self.Tooltip:SetSkinIdentifyer("tooltip") + self.Tooltip:SetText("") + self.Tooltip:SetZPos(1000) + self.Tooltip:Close() end self._Skin = StreamRadioLib.CreateOBJ("skin_controller") @@ -103,8 +104,8 @@ function CLASS:Create() self:SetCursorSize(csq, csq) - if IsValid(self.ToolTip) then - self.ToolTip:SetMaxWidth(w / 3) + if IsValid(self.Tooltip) then + self.Tooltip:SetMaxWidth(w / 3) end end @@ -180,45 +181,60 @@ function CLASS:GetRenderPos() return x, y end -function CLASS:GetToolTipPanel() - return self.ToolTip +function CLASS:GetTooltipPanel() + return self.Tooltip end -function CLASS:OpenToolTip(text) +function CLASS:UpdateTooltip(text) if SERVER then return end - if not IsValid(self.ToolTip) then return end + if not IsValid(self.Tooltip) then return end + if not self.Tooltip:IsVisible() then return end text = tostring(text or "") - self.ToolTip:SetText(text) + self.Tooltip:SetText(text) + + if text == "" then + self.Tooltip:Close() + end + + return self.Tooltip +end + +function CLASS:OpenTooltip(text) + if SERVER then return end + if not IsValid(self.Tooltip) then return end + + text = tostring(text or "") + self.Tooltip:SetText(text) if text ~= "" then - self.ToolTip:Open() - self:PosToolTipToCursor(true) + self.Tooltip:Open() + self:PosTooltipToCursor(true) else - self.ToolTip:Close() + self.Tooltip:Close() end - return self.ToolTip + return self.Tooltip end -function CLASS:CloseToolTip(text) +function CLASS:CloseTooltip(text) if SERVER then return end - if not IsValid(self.ToolTip) then return end + if not IsValid(self.Tooltip) then return end - self.ToolTip:Close() + self.Tooltip:Close() end -function CLASS:PosToolTipToCursor(force) +function CLASS:PosTooltipToCursor(force) if SERVER then return end - if not IsValid(self.ToolTip) then return end - if not force and not self.ToolTip:IsVisible() then return end + if not IsValid(self.Tooltip) then return end + if not force and not self.Tooltip:IsVisible() then return end local x, y = self:GetPos() local cx, cy = self:GetCursor() local cw, ch = self:GetCursorSize() local pw, ph = self:GetClientSize() - local tw, th = self.ToolTip:GetSize() + local tw, th = self.Tooltip:GetSize() cx = cx - x cy = cy - y @@ -235,19 +251,19 @@ function CLASS:PosToolTipToCursor(force) tx = math.Clamp(tx, 0, pw - tw) ty = math.Clamp(ty, 0, ph - th) - self.ToolTip:SetPos(tx, ty) + self.Tooltip:SetPos(tx, ty) end -function CLASS:OpenToolTipDelay(text, delay, callback) +function CLASS:OpenTooltipDelay(text, delay, callback) if SERVER then return end - if not IsValid(self.ToolTip) then return end + if not IsValid(self.Tooltip) then return end self:TimerOnce("tooltip", delay or 3, function() callback = self:GetFunction(callback) if not callback then return end if not callback(self) then return end - self:OpenToolTip(text) + self:OpenTooltip(text) end) end @@ -379,7 +395,7 @@ function CLASS:Think() self._RT:SetFramerate(StreamRadioLib.RenderTargetFPS()) self._RT:SetEnabled(StreamRadioLib.IsRenderTarget()) - self:PosToolTipToCursor() + self:PosTooltipToCursor() end function CLASS:SuperThink() @@ -496,6 +512,7 @@ function CLASS:SetDebug(allowdebug) self._Debug = self:AddPanelByClassname("debug") self._Debug:SetName("debug") + self._Debug:SetNWName("debug") self:InvalidateLayout() end @@ -582,12 +599,16 @@ function CLASS:SetName(...) BASE.SetName(self, ...) local name = self:GetName() + local nwname = self:GetName() + if IsValid(self._Skin) then self._Skin:SetName(name .. "/skin") + self._Skin:SetNWName(nwname .. "/sk") end if IsValid(self._RT) then self._RT:SetName(name .. "/rendertarget") + self._RT:SetNWName(nwname .. "/rt") end end diff --git a/lua/streamradio_core/classes/skin_controller.lua b/lua/streamradio_core/classes/skin_controller.lua index a5956b9..b05e472 100644 --- a/lua/streamradio_core/classes/skin_controller.lua +++ b/lua/streamradio_core/classes/skin_controller.lua @@ -3,6 +3,8 @@ if not istable(CLASS) then return end +local LIBNetwork = StreamRadioLib.Network + local BASE = CLASS:GetBaseClass() local function g_encode(value) @@ -33,15 +35,13 @@ function CLASS:Create() self.Skin = {} self.Hash = self:CreateListener({ - hex = "", - crc = 0, - raw = {}, + value = "", }, function(this, k, v, oldv) if CLIENT then self:NetworkSkin() else self:UpdateSkin() - self:SetNWHash("Hash", self.Hash) + self:SetNWString("Hash", v) end end) @@ -54,6 +54,10 @@ function CLASS:Create() self:SetSkin(skin) end) else + LIBNetwork.AddNetworkString("skin") + LIBNetwork.AddNetworkString("skinrequest") + LIBNetwork.AddNetworkString("skintoserver") + self:NetReceive("skinrequest", function(this, id, len, ply) self.NetworkPlayerList = self.NetworkPlayerList or {} self.NetworkPlayerList[ply] = true @@ -188,37 +192,17 @@ function CLASS:CalcHash() if not self.Network.Active then return end local hash = StreamRadioLib.Hash(self:GetSkinEncoded()) - self.Hash.raw = hash.raw or {} - self.Hash.hex = hash.hex or "" - self.Hash.crc = hash.crc or 0 + self.Hash.value = hash or "" end function CLASS:GetHash() - local curhash = self.Hash + local curhash = self.Hash.value or "" if CLIENT and self.Network.Active then - curhash = self:GetNWHash("Hash", {}) + curhash = self:GetNWString("Hash", "") end - local hash = { - raw = curhash.raw or {}, - hex = curhash.hex or "", - crc = curhash.crc or 0, - } - - return hash -end - -function CLASS:GetHashID(hash) - hash = hash or self:GetHash() or {} - - local hex = hash.hex or "" - local crc = hash.crc or 0 - - if hex == "" then return "" end - if crc == 0 then return "" end - - return "[" .. hex .. "]_[" .. crc .. "]" + return curhash end function CLASS:ActivateNetworkedMode() @@ -229,15 +213,11 @@ function CLASS:ActivateNetworkedMode() return end - local hash = self:GetNWHash("Hash", {}) - self.Hash.raw = hash.raw or {} - self.Hash.hex = hash.hex or "" - self.Hash.crc = hash.crc or 0 + local hash = self:GetNWString("Hash", "") + self.Hash.value = hash - self:SetNWHashProxy("Hash", function(this, nwkey, oldvar, newvar) - self.Hash.raw = newvar.raw or {} - self.Hash.hex = newvar.hex or "" - self.Hash.crc = newvar.crc or 0 + self:SetNWVarCallback("Hash", "String", function(this, nwkey, oldvar, newvar) + self.Hash.value = newvar or "" end) self:NetworkSkin() diff --git a/lua/streamradio_core/classes/stream.lua b/lua/streamradio_core/classes/stream.lua index a663911..3675c84 100644 --- a/lua/streamradio_core/classes/stream.lua +++ b/lua/streamradio_core/classes/stream.lua @@ -1,3 +1,8 @@ +if not istable(CLASS) then + StreamRadioLib.ReloadClasses() + return +end + local tostring = tostring local tonumber = tonumber local type = type @@ -17,23 +22,9 @@ local CLIENT = CLIENT local EmptyVector = Vector() local catchAndErrorNoHalt = StreamRadioLib.CatchAndErrorNoHalt - local BASS3 = BASS3 or {} -StreamRadioLib.STREAM_PLAYMODE_STOP = 0 -StreamRadioLib.STREAM_PLAYMODE_PAUSE = 1 -StreamRadioLib.STREAM_PLAYMODE_PLAY = 2 -StreamRadioLib.STREAM_PLAYMODE_PLAY_RESTART = 3 - -StreamRadioLib.STREAM_URLTYPE_FILE = 0 -StreamRadioLib.STREAM_URLTYPE_CACHE = 1 -StreamRadioLib.STREAM_URLTYPE_ONLINE = 2 -StreamRadioLib.STREAM_URLTYPE_ONLINE_NOCACHE = 3 - -if not istable(CLASS) then - StreamRadioLib.ReloadClasses() - return -end +local LIBNetwork = StreamRadioLib.Network local BASE = CLASS:GetBaseClass() @@ -83,9 +74,9 @@ function CLASS:Create() self.ChannelChanged = false if CLIENT then - self.CV_Volume = StreamRadioLib.Settings.GetConVar("volume") - if IsValid(self.CV_Volume) then - self.CV_Volume:SetEvent("OnChange", self:GetID(), function() + self.ConVarGlobalVolume = StreamRadioLib.Settings.GetConVar("volume") + if IsValid(self.ConVarGlobalVolume) then + self.ConVarGlobalVolume:SetEvent("OnChange", self:GetID(), function() self:UpdateChannelVolume() end) end @@ -296,6 +287,8 @@ function CLASS:Create() end if SERVER then + LIBNetwork.AddNetworkString("clientstate") + self:NetReceive("clientstate", function(this, id, len, ply) local bufferlen = net.ReadUInt(16) @@ -339,23 +332,23 @@ function CLASS:ActivateNetworkedMode() return end - self:SetNWVarProxy("Volume", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("Volume", "Float", function(this, nwkey, oldvar, newvar) self.Volume.SVMul = newvar end) - self:SetNWVarProxy("URL", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("URL", "String", function(this, nwkey, oldvar, newvar) self.URL.extern = newvar end) - self:SetNWVarProxy("PlayMode", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("PlayMode", "Int", function(this, nwkey, oldvar, newvar) self.State.PlayMode = newvar end) - self:SetNWVarProxy("Loop", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("Loop", "Bool", function(this, nwkey, oldvar, newvar) self.State.Loop = newvar end) - self:SetNWVarProxy("Name", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("Name", "String", function(this, nwkey, oldvar, newvar) self.State.Name = newvar end) @@ -416,7 +409,6 @@ function CLASS:NetworkClientState() for i, v in pairs(sendbuffer) do local key = v.key local value = v.value - local key_index = v.key_index net.WriteUInt(v.key_index, 4) @@ -620,6 +612,8 @@ end function CLASS:SuperThink() self:CalcTime() + local masterLength = self:GetMasterLength() + self.State.Ended = self:HasEnded() self.State.Seeking = self:_IsSeekingInternal() self.State.Length = self:GetLength() @@ -633,7 +627,15 @@ function CLASS:SuperThink() else local timeB = self:GetNWFloat("MasterTime", 0) local dt = math.abs(timeA - timeB) - local maxDt = engine.TickInterval() * 4 + local tickTime = engine.TickInterval() + + -- add random noise to avoid uneven network load + local random = math.random() * 0.2 + local maxDt = 0.4 + random + + if masterLength > 0 then + maxDt = math.min(math.max(masterLength / 4, tickTime * 4), maxDt) + end if dt >= maxDt then self:SetNWFloat("MasterTime", timeA) @@ -701,7 +703,7 @@ end function CLASS:UpdateChannelVolume() if SERVER then return end if not self.Valid then return end - if not IsValid( self.CV_Volume ) then return end + if not IsValid( self.ConVarGlobalVolume ) then return end if not IsValid( self.Channel ) then return end local boost3d = self:Is3DChannel() and 2.00 or 1 @@ -713,7 +715,7 @@ function CLASS:UpdateChannelVolume() local volume = 0 if not MuteSlide then - volume = SVvol * CLvol * self.CV_Volume:GetValue() * boost3d + volume = SVvol * CLvol * self.ConVarGlobalVolume:GetValue() * boost3d end -- Max 5000% normal volume on all cases. @@ -806,8 +808,8 @@ end function CLASS:Remove() self:RemoveChannel(true) - if IsValid(self.CV_Volume) then - self.CV_Volume:RemoveEvent("OnChange", self:GetID()) + if IsValid(self.ConVarGlobalVolume) then + self.ConVarGlobalVolume:RemoveEvent("OnChange", self:GetID()) end BASE.Remove(self) @@ -1514,17 +1516,17 @@ function CLASS:GetMasterTime() local loop = self:GetLoop() local offset = thistime - timestamp - local calctime = time + offset - - if loop and len > 0 then - return calctime % len - end + local calctime = math.max(time + offset, 0) if len > 0 then + if loop then + calctime = calctime % len + end + calctime = math.min(calctime, len) end - return math.max(calctime, 0) + return calctime end return self:GetNWFloat("MasterTime", 0) @@ -1558,10 +1560,7 @@ function CLASS:GetTime() time = time + self.TimeOffset end - if time < 0 then - time = 0 - end - + time = math.max(time, 0) return time end @@ -1591,7 +1590,7 @@ function CLASS:SetTime(time, force) if not IsValid(self.Channel) then return end - if self:IsBlockStreamed() then + if not self:CanSeek() then return end @@ -1601,9 +1600,7 @@ end function CLASS:_SetTimeInternal(time) time = tonumber(time) or 0 - if time < 0 then - time = 0 - end + time = math.max(time, 0) local length = self:GetLength() @@ -1614,7 +1611,7 @@ function CLASS:_SetTimeInternal(time) self.TimeOffset = 0 - if self:IsBlockStreamed() then + if not self:CanSeek() then return end @@ -1622,15 +1619,10 @@ function CLASS:_SetTimeInternal(time) self._isseeking = true if self:GetLoop() then - self._targettime = time % length - self:_SetTimeToTargetInternal() - - return + time = time % length end - if time > length then - time = length - end + time = math.min(time, length) self._targettime = time self:_SetTimeToTargetInternal() @@ -1642,7 +1634,7 @@ function CLASS:_SetTimeToTargetInternal() self:TimerRemove("SetTimeToTargetInternal") - if self:IsBlockStreamed() then + if not self:CanSeek() then return end @@ -1651,66 +1643,89 @@ function CLASS:_SetTimeToTargetInternal() return end - -- avoid game hiccup during track seeking - self:TimerUtil("SetTimeToTargetInternal", 0.001, function() + local seakToFunc = function() if not IsValid(self.Channel) then return true end if not self._targettime then return true end - if self:IsBlockStreamed() then + if not self:CanSeek() then return true end + local length = self:GetLength() + local thistime = self.Channel:GetTime() + thistime = math.Clamp(thistime, 0, length) + local targettime = self._targettime + targettime = math.Clamp(targettime, 0, length) + + if thistime == targettime then + return true + end - if thistime == targettime then return true end + -- an attempt to ease it a bit on the performance impact + local random = math.random() * 2 + local step = math.Clamp(StreamRadioLib.RealTimeFps() * 0.03 + random, 2, 15) + local time = math.Approach(thistime, targettime, step) - local time = 0 - local step = 10 + time = math.Clamp(time, 0, length) - if thistime < targettime then - time = math.min(thistime + step, targettime) - else - time = math.max(thistime - step, targettime) - end + -- set the time in non-decode mode, so we keep sane frame rates + self.Channel:SetTime(time, true) - self.Channel:SetTime(time) - if time == targettime then return true end + if time == targettime then + return true + end return false - end) + end + + -- avoid game hiccup during track seeking + self:TimerUtil("SetTimeToTargetInternal", 0.001, seakToFunc) + seakToFunc() end function CLASS:SyncTime() if not self.Valid then return end if StreamRadioLib.GameIsPaused() then return end - if not self:IsPlayMode() then return end + if self:IsStopMode() then return end local maxdelta = 1.5 local time = self:GetMasterTime() - if self:IsEndless() then - return self:_SetTimeInternal(time) - end - local length = self:GetLength() - local curtime = self:GetTime() local loop = self:GetLoop() - local maxStartDelta = engine.TickInterval() * 4 - if length <= maxdelta and time > maxStartDelta then - return - end + if length > 0 then + local tickLen = engine.TickInterval() + local minDelta = tickLen * 2 + local maxStartDelta = tickLen * 4 + + if length <= maxStartDelta then + -- never time synchronize extremely short sounds + return + end - maxdelta = math.min(maxdelta, length) + if length <= maxdelta and time > maxStartDelta then + -- prevent permanent seeking loop for very short sounds (length less then 1.5s) + -- makes sure all clients start at the same time + -- but ignore further synchronisations past + + return + end + + -- limit to the length minus a small margin + maxdelta = math.min(maxdelta, math.max(length - minDelta, maxStartDelta)) + end local maxdelta_half = maxdelta / 2 local mintime = time - maxdelta_half local maxtime = time + maxdelta_half - if loop then + if loop and length > 0 then + -- make sure we wrap the time around start and end currently mintime = (length + mintime) % length maxtime = (length + maxtime) % length end @@ -1719,6 +1734,7 @@ function CLASS:SyncTime() maxtime = math.max(maxtime, 0) if maxtime > mintime then + -- classic in between check if curtime < mintime then return self:_SetTimeInternal(time) end @@ -1731,6 +1747,7 @@ function CLASS:SyncTime() end if curtime < mintime and curtime > maxtime then + -- in between check that wraps around looped time positions return self:_SetTimeInternal(time) end end @@ -1785,8 +1802,7 @@ function CLASS:_IsSeekingInternal() return self.Channel:IsSeeking() end - if self:IsEndless() then return false end - if self:IsBlockStreamed() then return false end + if not self:CanSeek() then return false end local targettime = self._targettime if not targettime then return false end @@ -1997,6 +2013,19 @@ function CLASS:IsBlockStreamed() return self.Channel:IsBlockStreamed( ) end +function CLASS:CanSeek() + if not self.Valid then return false end + if not IsValid( self.Channel ) then return false end + + if self:IsEndless() then return false end + if self:IsBlockStreamed() then return false end + + local minLen = engine.TickInterval() * 4 + if self:GetMasterLength() <= minLen then return false end + + return true +end + function CLASS:IsLooping() if not self.Valid then return false end if self:IsEndless() then return false end diff --git a/lua/streamradio_core/classes/ui/button.lua b/lua/streamradio_core/classes/ui/button.lua index 82f262b..6ab2b5e 100644 --- a/lua/streamradio_core/classes/ui/button.lua +++ b/lua/streamradio_core/classes/ui/button.lua @@ -16,6 +16,7 @@ function CLASS:Create() self.ImagePanel:SetPos(0, 0) self.ImagePanel:SetAlign(TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) self.ImagePanel:SetName("image") + self.ImagePanel:SetNWName("img") self.ImagePanel:SetSkinIdentifyer("image") self.ImagePanel.OnMaterialChange = function(pnl) @@ -34,6 +35,7 @@ function CLASS:Create() self.LabelPanel:SetPos(0, 0) self.LabelPanel:SetAlign(TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) self.LabelPanel:SetName("label") + self.LabelPanel:SetNWName("lbl") self.LabelPanel:SetSkinIdentifyer("label") self.LabelPanel.OnTextChange = function(pnl) diff --git a/lua/streamradio_core/classes/ui/debug.lua b/lua/streamradio_core/classes/ui/debug.lua index 4bb20ab..f11960a 100644 --- a/lua/streamradio_core/classes/ui/debug.lua +++ b/lua/streamradio_core/classes/ui/debug.lua @@ -79,7 +79,8 @@ function CLASS:Render() self.debugtexttab[12] = "Panel Info:" self.debugtexttab[13] = tostring(aimedpanel) self.debugtexttab[14] = string.format("Name Hierarchy: %s", aimedpanel:GetName()) - self.debugtexttab[15] = string.format("Skin ID Hierarchy: %s", aimedpanel:GetSkinIdentifyerHierarchy()) + self.debugtexttab[15] = string.format("NW Name Hierarchy: %s", aimedpanel:GetNWName()) + self.debugtexttab[16] = string.format("Skin ID Hierarchy: %s", aimedpanel:GetSkinIdentifyerHierarchy()) else self.bgwidth = 0 self.debugtexttab[11] = nil diff --git a/lua/streamradio_core/classes/ui/label_fade.lua b/lua/streamradio_core/classes/ui/label_fade.lua index e636d2d..69b4a80 100644 --- a/lua/streamradio_core/classes/ui/label_fade.lua +++ b/lua/streamradio_core/classes/ui/label_fade.lua @@ -4,20 +4,6 @@ if not istable(CLASS) then end local BASE = CLASS:GetBaseClass() -local changehooks = { - Text = "OnTextChange", - Font = "OnFontChange", - AlignX = "OnAlignChange", - AlignY = "OnAlignChange", -} - -local function normalize_text(text) - text = tostring(text or "") - text = string.gsub(text, "[\r\n]", "" ) - text = string.gsub(text, "\t", " " ) - - return text -end function CLASS:Create() BASE.Create(self) @@ -42,6 +28,8 @@ function CLASS:Create() self:SwitchText() end end + + self:StartSuperThink() end self.TextList = {} @@ -115,7 +103,6 @@ function CLASS:GetPhase() local fadeinend = fadetime local fadeoutstart = fadeinend + staytime - local fadeoutend = staytime + fadetime if time < fadeinend then return math.Clamp(time / fadetime, 0, 1) @@ -165,6 +152,16 @@ function CLASS:SetList(textlist) self:SwitchText() end +function CLASS:ShouldPerformRerender() + if SERVER then return false end + + local phase = self:GetPhase() + if phase >= 1 then return false end + if phase <= 0 then return false end + + return true +end + function CLASS:SuperThink() if SERVER then return end @@ -172,14 +169,14 @@ function CLASS:SuperThink() if not self:IsVisible() then return end self:CalcTime() + self.TextData.TextIndex = self:GetIndex() + + if not self:ShouldPerformRerender() then return end + self:PerformRerender(true) end function CLASS:Render() - self:CalcTime() - - self.TextData.TextIndex = self:GetIndex() - local x, y = self:GetRenderPos() local w, h = self:GetSize() diff --git a/lua/streamradio_core/classes/ui/list.lua b/lua/streamradio_core/classes/ui/list.lua index 3df9a11..2242846 100644 --- a/lua/streamradio_core/classes/ui/list.lua +++ b/lua/streamradio_core/classes/ui/list.lua @@ -3,6 +3,9 @@ if not istable(CLASS) then return end +local LIBNet = StreamRadioLib.Net +local LIBNetwork = StreamRadioLib.Network + local BASE = CLASS:GetBaseClass() local g_listcache = CLASS:GetGlobalVar("list_listcache", {}) @@ -13,6 +16,7 @@ function CLASS:Create() self.ScrollBar = self:AddPanelByClassname("scrollbar", true) self.ScrollBar:SetName("scrollbar") + self.ScrollBar:SetNWName("sbar") self.ScrollBar:SetSkinIdentifyer("scrollbar") self.ScrollBar:SetSize(30, 30) @@ -21,11 +25,9 @@ function CLASS:Create() end self.Hash = self:CreateListener({ - hex = "", - crc = 0, - raw = {}, + value = "", }, function(this, k, v, oldv) - self:SetNWHash("Hash", self.Hash) + self:SetNWString("Hash", v) if SERVER then return end self:NetworkButtons() @@ -74,29 +76,33 @@ function CLASS:Create() if CLIENT then self:NetReceive("data", function(this, id, len, ply) - self._waitfordata = nil local count = net.ReadUInt(16) - local data = {} + local newdata = {} for index = 1, count do - local text, icon = StreamRadioLib.Net.ReceiveListEntry( ) + local text, icon = LIBNet.ReceiveListEntry() - table.insert(data, { + table.insert(newdata, { text = text, icon = icon, }) end - local hash = self:GetHashID() - local datahash = self:GetHashID(self:GetHashFromData(data)) + local curhash = self:GetHash() + local newhash = self:GetHashFromData(newdata) - self:SetCache(data, datahash) + -- Store the result of our request for later use + self:SetCache(newhash, newdata) - if datahash == hash then - self:SetData(data) + if curhash == newhash then + -- Apply the data if the request is the most up to date one + self:SetData(newdata) end end) else + LIBNetwork.AddNetworkString("data") + LIBNetwork.AddNetworkString("datarequest") + self:NetReceive("datarequest", function(this, id, len, ply) self.NetworkPlayerList = self.NetworkPlayerList or {} self.NetworkPlayerList[ply] = true @@ -133,13 +139,14 @@ end function CLASS:ClearButtons() for k, v in pairs(self.Buttons or {}) do + if not v then + continue + end + v:Remove() end self.Buttons = {} - self._iscleared = true - self._curhash = nil - self._loadcounter = nil local scrollbar = self.ScrollBar if IsValid(scrollbar) then @@ -162,6 +169,7 @@ function CLASS:GetOrCreateButton(buttonindex) if not IsValid(button) then button = self:AddPanelByClassname("button", true) button:SetName("button" .. buttonindex) + button:SetNWName("but" .. buttonindex) button:SetSkinIdentifyer("button") self:CallHook("OnItemCreate", button, buttonindex) @@ -185,16 +193,24 @@ function CLASS:NetworkButtons() end function CLASS:GetCache(hashstr) + if SERVER then return nil end + hashstr = hashstr or "" - if hashstr == "" then return nil end + if hashstr == "" then + return nil + end - local data = g_listcache[hashstr] or {} - if #data <= 0 then return nil end + local data = g_listcache[hashstr] + if not data then + return nil + end return data end -function CLASS:SetCache(data, hashstr) +function CLASS:SetCache(hashstr, data) + if SERVER then return end + hashstr = hashstr or "" if hashstr == "" then return end @@ -205,25 +221,16 @@ function CLASS:NetworkButtonsInternal() if not self:IsVisible() then return end if CLIENT then - local hash = self:GetHashID() + local hash = self:GetHash() local cache = self:GetCache(hash) if cache then - -- Workaround a glitch on first use on spawn - if self._firstcachedone then - self:SetData(cache) - return - end - - self._firstcachedone = true + self:SetData(cache) + return end self:NetSend("datarequest") - self.LastButtonNW = RealTime() - self._loadcounter = nil - self._waitfordata = true - return end @@ -231,68 +238,17 @@ function CLASS:NetworkButtonsInternal() local data = self.Data or {} self.NetworkPlayerList = nil - if #playerlist <= 0 then return end + if #playerlist <= 0 then + return + end self:NetSend("data", function() net.WriteUInt(#data, 16) - for k, v in pairs(data) do - StreamRadioLib.Net.SendListEntry(v.text, v.icon) + for i, v in ipairs(data) do + LIBNet.SendListEntry(v.text, v.icon) end - end, "Send", playerlist) - -end - -function CLASS:Think() - if SERVER then return end - if not self:IsVisible() then return end - if not self.Network.Active then return end - if self._waitfordata then return end - - local lastnw = self.LastButtonNW or RealTime() - local delta = RealTime() - lastnw - - local count = (self._loadcounter or 0) + 1 - local mindelta = ((count * 0.5) ^ 2) - - if delta < mindelta then return end - self.LastButtonNW = RealTime() - - local load = false - - if not load and self._iscleared then - load = true - end - - if not load and not self._curhash then - load = true - end - - if not load then - local h1 = self:GetHashID() - local h2 = self:GetHashID(self._curhash) - - if not h1 then - load = true - end - - if not h2 then - load = true - end - - if h1 ~= h2 then - load = true - end - end - - if not load then - self._loadcounter = nil - return - end - - self:NetworkButtonsInternal() - self._loadcounter = count end function CLASS:UpdateButtonsInternal() @@ -325,10 +281,6 @@ function CLASS:UpdateButtonsInternal() return end - self._iscleared = false - self._curhash = self:GetHashFromData(data) - self._loadcounter = nil - local ishorizontal = scrollbar:GetHorizontal() local listviewsize = ListSizeX * ListSizeY local startindex = 0 @@ -447,18 +399,16 @@ end function CLASS:GetHashFromData(data) data = data or {} local datastring = {} - local count = 0 - for k, v in ipairs(data) do + for i, v in ipairs(data) do local text = v.text or "" local icon = v.icon or -1 - table.insert(datastring, "{[" .. text .. "][" .. icon .. "][" .. k .. "]}") - count = count + 1 + table.insert(datastring, string.format("{[%s][%d][%d]}", text, icon, i)) end + table.insert(datastring, string.format("[%d]", #data)) datastring = table.concat(datastring, "\n") - datastring = datastring .. "\nlen: {[" .. count .. "][" .. #datastring .. "]}" local hash = StreamRadioLib.Hash(datastring) return hash @@ -469,9 +419,7 @@ function CLASS:CalcHash() if not self.Network.Active then return end local hash = self:GetHashFromData(self.Data) - self.Hash.raw = hash.raw or {} - self.Hash.hex = hash.hex or "" - self.Hash.crc = hash.crc or 0 + self.Hash.value = hash or "" end function CLASS:SetData(data) @@ -579,27 +527,23 @@ function CLASS:ActivateNetworkedMode() self:SetGridSize(self:GetNWInt("ListGridX", 0), self:GetNWInt("ListGridY", 0)) self:SetHorizontal(self:GetNWBool("IsHorizontal", false)) - local hash = self:GetNWHash("Hash") or {} - self.Hash.raw = hash.raw or {} - self.Hash.hex = hash.hex or "" - self.Hash.crc = hash.crc or 0 + local hash = self:GetNWString("Hash", "") + self.Hash.value = hash - self:SetNWVarProxy("ListGridX", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("ListGridX", "Int", function(this, nwkey, oldvar, newvar) self.Layout.ListGridX = newvar end) - self:SetNWVarProxy("ListGridY", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("ListGridY", "Int", function(this, nwkey, oldvar, newvar) self.Layout.ListGridY = newvar end) - self:SetNWVarProxy("IsHorizontal", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("IsHorizontal", "Bool", function(this, nwkey, oldvar, newvar) self:SetHorizontal(newvar) end) - self:SetNWHashProxy("Hash", function(this, nwkey, oldvar, newvar) - self.Hash.raw = newvar.raw or {} - self.Hash.hex = newvar.hex or "" - self.Hash.crc = newvar.crc or 0 + self:SetNWVarCallback("Hash", "String", function(this, nwkey, oldvar, newvar) + self.Hash.value = newvar or "" end) self:NetworkButtons() @@ -622,31 +566,13 @@ function CLASS:GetIDIcon(ID) end function CLASS:GetHash() - local curhash = self.Hash or {} + local curhash = self.Hash.value or "" if CLIENT and self.Network.Active then - curhash = self:GetNWHash("Hash") or {} + curhash = self:GetNWString("Hash", "") end - local hash = { - raw = curhash.raw or {}, - hex = curhash.hex or "", - crc = curhash.crc or 0, - } - - return hash -end - -function CLASS:GetHashID(hash) - hash = hash or self:GetHash() or {} - - local hex = hash.hex or "" - local crc = hash.crc or 0 - - if hex == "" then return "" end - if crc == 0 then return "" end - - return "[" .. hex .. "]_[" .. crc .. "]" + return curhash end function CLASS:OnModelSetup(setup) diff --git a/lua/streamradio_core/classes/ui/list_files.lua b/lua/streamradio_core/classes/ui/list_files.lua index e7c5c5e..6b04046 100644 --- a/lua/streamradio_core/classes/ui/list_files.lua +++ b/lua/streamradio_core/classes/ui/list_files.lua @@ -72,7 +72,7 @@ function CLASS:BuildList() end function CLASS:BuildListInternal() - + -- override me end function CLASS:GetUpPath() @@ -132,7 +132,7 @@ function CLASS:ActivateNetworkedMode() return end - self:SetNWVarProxy("Path", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("Path", "String", function(this, nwkey, oldvar, newvar) self.Path.Value = newvar end) diff --git a/lua/streamradio_core/classes/ui/panel.lua b/lua/streamradio_core/classes/ui/panel.lua index 2a57543..25d7a81 100644 --- a/lua/streamradio_core/classes/ui/panel.lua +++ b/lua/streamradio_core/classes/ui/panel.lua @@ -82,6 +82,10 @@ function CLASS:Create() self:StopListenRecursive() end end + + if k == "Tooltip" then + self:UpdateTooltip(v) + end end) self._ChildrenPanels = {} @@ -103,7 +107,7 @@ function CLASS:Create() self.Colors = self:CreateListener({ Main = Color(255,255,255) }, function(...) - self:_PerformRerenderInternal() + self:QueueCall("PerformRerender") end) end @@ -149,10 +153,6 @@ function CLASS:InvalidateLayout(layoutnow, nochildren) self:QueueCall("PerformLayout", nochildren) end -function CLASS:_PerformRerenderInternal() - self:QueueCall("PerformRerender") -end - function CLASS:PerformRerender(force) if not force and not self._rendered then return end self._rendered = false @@ -172,7 +172,7 @@ function CLASS:CursorChangedInternal(nochildren) self:DelCacheValue("GetPanelsAtCursor") self:DelCacheValue("GetTopmostPanelAtCursor") - self:_OpenToolTipPanel() + self:_OpenTooltipPanel() if nochildren then return end @@ -938,37 +938,48 @@ function CLASS:CalcSuperParent() return self.SuperParent end -function CLASS:SetToolTip(text) +function CLASS:SetTooltip(text) if SERVER then return end - self.Layout.ToolTip = tostring(text or "") + self.Layout.Tooltip = tostring(text or "") end -function CLASS:GetToolTip() +function CLASS:GetTooltip() if SERVER then return "" end - return self.Layout.ToolTip or "" + return self.Layout.Tooltip or "" end --- Alias -CLASS.SetTooltip = CLASS.SetToolTip -CLASS.GetTooltip = CLASS.GetToolTip +function CLASS:GetTooltipPanel() + if SERVER then return nil end + + local sp = self:GetSuperParent() + return sp.Tooltip +end -function CLASS:GetToolTipPanel() +function CLASS:UpdateTooltip(text) if SERVER then return nil end local sp = self:GetSuperParent() - return sp.ToolTip + if not IsValid(sp) then return end + if not IsValid(sp.Tooltip) then return end + if not sp.Tooltip:IsVisible() then return end + if not sp.UpdateTooltip then return end + + local onpanel = self:IsCursorOnPanel() + if not onpanel then return end + + return sp:UpdateTooltip(text) end -function CLASS:_OpenToolTipPanel() +function CLASS:_OpenTooltipPanel() if SERVER then return end - local text = self:GetToolTip() + local text = self:GetTooltip() if text == "" then return end local sp = self:GetSuperParent() if not IsValid(sp) then return end - if not IsValid(sp.ToolTip) then return end - if not sp.OpenToolTipDelay then return end + if not IsValid(sp.Tooltip) then return end + if not sp.OpenTooltipDelay then return end local onpanel = self:IsCursorOnPanel() @@ -978,20 +989,20 @@ function CLASS:_OpenToolTipPanel() if onpanel == oldonpanel then return end if not onpanel then - sp:CloseToolTip() + sp:CloseTooltip() return end - sp:CloseToolTip() - sp:OpenToolTipDelay(text, 0.75, function() - local text = self:GetToolTip() + sp:CloseTooltip() + sp:OpenTooltipDelay(text, 0.75, function() + local text = self:GetTooltip() if text == "" then return false end local onpanel = self:IsCursorOnPanel() if not onpanel then - sp:CloseToolTip() + sp:CloseTooltip() end return onpanel @@ -1135,6 +1146,31 @@ function CLASS:GetCursorRelative() return cx - posx, cy - posy end +function CLASS:CalcHierarchy(func) + local thisfunc = self:GetFunction(func) + if not thisfunc then return end + + local hierarchy = {} + table.insert(hierarchy, thisfunc(self)) + + self:ForEachParent(function(this, parent) + local thisfunc = parent:GetFunction(func) + if not thisfunc then return end + + table.insert(hierarchy, thisfunc(parent)) + end) + + hierarchy = table.Reverse(hierarchy) + return hierarchy +end + +function CLASS:ApplyHierarchy() + self:CalcSuperParent() + self:CalcName() + self:CalcNWName() + self:CalcSkinIdentifyer() +end + function CLASS:GetEntity() local superparent = self:GetSuperParent() return superparent.Entity @@ -1146,9 +1182,16 @@ function CLASS:SetEntity(ent) local superparent = self:GetSuperParent() local oldent = self:GetEntity() local name = self:GetName() + local nwname = self:GetNWName() - if IsValid(oldent) and oldent._3dstreamradio_classobjs then - oldent._3dstreamradio_classobjs[name] = nil + if IsValid(oldent) then + if oldent._3dstreamradio_classobjs then + oldent._3dstreamradio_classobjs[name] = nil + end + + if oldent._3dstraemradio_classobjs_nw_register then + oldent._3dstraemradio_classobjs_nw_register[nwname] = nil + end end superparent.Entity = ent @@ -1156,10 +1199,6 @@ function CLASS:SetEntity(ent) self:ApplyNetworkedMode() end -function CLASS:GetNameWithoutHierarchy() - return self.Name or "" -end - function CLASS:SetName(name) self:ApplyHierarchy() @@ -1169,8 +1208,10 @@ function CLASS:SetName(name) local ent = self:GetEntity() local oldname = self:GetName() - if IsValid(ent) and ent._3dstreamradio_classobjs then - ent._3dstreamradio_classobjs[oldname] = nil + if IsValid(ent) then + if ent._3dstreamradio_classobjs then + ent._3dstreamradio_classobjs[oldname] = nil + end end self.Name = name @@ -1185,53 +1226,50 @@ function CLASS:SetName(name) end self:RegisterForDupe() - self:ApplyNetworkedMode() end -function CLASS:GetName() - return self.HierarchyName or "" +function CLASS:GetNameWithoutHierarchy() + return self.Name or "" end -function CLASS:ApplyHierarchy() - self:CalcSuperParent() - self:CalcName() - self:CalcSkinIdentifyer() +function CLASS:GetName() + return self.HierarchyName or "" end -function CLASS:CalcName() - local hierarchy = self:CalcHierarchy("GetNameWithoutHierarchy") - local name = table.concat(hierarchy, "/") +function CLASS:SetNWName(nwname) + self:ApplyHierarchy() - self.HierarchyName = name - return self.HierarchyName -end + nwname = tostring(nwname or "") + nwname = string.gsub(nwname, "[%/%\\%s]", "_") -function CLASS:CalcHierarchy(func) - local thisfunc = self:GetFunction(func) - if not thisfunc then return end + local ent = self:GetEntity() + local oldnwname = self:GetNWName() - local hierarchy = {} - table.insert(hierarchy, thisfunc(self)) + if IsValid(ent) then + if ent._3dstraemradio_classobjs_nw_register then + ent._3dstraemradio_classobjs_nw_register[oldnwname] = nil + end + end - self:ForEachParent(function(this, parent) - local thisfunc = parent:GetFunction(func) - if not thisfunc then return end + self.NWName = nwname + self:CalcNWName() - table.insert(hierarchy, thisfunc(parent)) - end) + local parent = self:GetParent() + if IsValid(parent) then + parent._panelmap = parent._panelmap or {} + parent._panelmap.nwnames = parent._panelmap.nwnames or {} + parent._panelmap.nwnames[nwname] = self + end - hierarchy = table.Reverse(hierarchy) - return hierarchy + self:ApplyNetworkedMode() end -function CLASS:CalcSkinIdentifyer() - local hierarchy = self:CalcHierarchy("GetSkinIdentifyer") - table.remove(hierarchy, 1) - - local name = table.concat(hierarchy, "/") +function CLASS:GetNWNameWithoutHierarchy() + return self.NWName or "" +end - self.HierarchySkinIdentifyer = name - return self.HierarchySkinIdentifyer +function CLASS:GetNWName() + return self.HierarchyNWName or "" end function CLASS:SetSkinIdentifyer(name) @@ -1251,10 +1289,44 @@ function CLASS:SetSkinIdentifyer(name) self:CalcSkinIdentifyer() end +function CLASS:GetSkinIdentifyerHierarchy() + if not self.HierarchySkinIdentifyer then + return self:CalcSkinIdentifyer() + end + + return self.HierarchySkinIdentifyer +end + function CLASS:GetSkinIdentifyer(name) return self.SkinName or "" end +function CLASS:CalcName() + local hierarchy = self:CalcHierarchy("GetNameWithoutHierarchy") + local name = table.concat(hierarchy, "/") + + self.HierarchyName = name + return self.HierarchyName +end + +function CLASS:CalcNWName() + local hierarchy = self:CalcHierarchy("GetNWNameWithoutHierarchy") + local name = table.concat(hierarchy, "/") + + self.HierarchyNWName = name + return self.HierarchyNWName +end + +function CLASS:CalcSkinIdentifyer() + local hierarchy = self:CalcHierarchy("GetSkinIdentifyer") + table.remove(hierarchy, 1) + + local name = table.concat(hierarchy, "/") + + self.HierarchySkinIdentifyer = name + return self.HierarchySkinIdentifyer +end + function CLASS:IsSkinAble() if self:GetSkinIdentifyer() == "" then return false end if not self.SkinAble then return false end @@ -1267,14 +1339,6 @@ function CLASS:SetSkinAble(bool) self.SkinAble = bool or false end -function CLASS:GetSkinIdentifyerHierarchy() - if not self.HierarchySkinIdentifyer then - return self:CalcSkinIdentifyer() - end - - return self.HierarchySkinIdentifyer -end - function CLASS:_SetSkinAfterAddedPanel() if not self._skindata then return end self:SetSkin(self._skindata) diff --git a/lua/streamradio_core/classes/ui/progressbar.lua b/lua/streamradio_core/classes/ui/progressbar.lua index 628acfe..44ab4ad 100644 --- a/lua/streamradio_core/classes/ui/progressbar.lua +++ b/lua/streamradio_core/classes/ui/progressbar.lua @@ -26,7 +26,7 @@ function CLASS:Create() self:CallHook("OnFractionChange", v) self:UpdateText() - self:SetNWFloat(k, v) + self:SetNWFloat("Fraction", v) self:InvalidateLayout(true) end @@ -71,10 +71,21 @@ function CLASS:Create() self.Colors = self.Colors + function(this, k, v) if k == "Main" then - self.Colors.Secondary = Color(v.r * 0.65, v.g * 0.65, v.b * 0.65, v.a * 0.75) - else - self:QueueCall("UpdateColor") + self.Colors.Secondary = Color( + v.r * 0.65, + v.g * 0.65, + v.b * 0.65, + v.a * 0.75 + ) + + return end + + if k == "Secondary" then + return + end + + self:QueueCall("UpdateColor") end end @@ -104,6 +115,7 @@ function CLASS:UpdateColor() if self:IsDisabled() then self.Colors.Main = self.Colors.Disabled + if IsValid(self.TextPanel) then self.TextPanel:SetColor(self.Colors.DisabledText) end @@ -111,25 +123,23 @@ function CLASS:UpdateColor() return end - if self.Progress.AllowEdit then - if self:IsCursorOnPanel() then - self.Colors.Main = self.Colors.Hover - - if IsValid(self.TextPanel) then - self.TextPanel:SetColor(self.Colors.HoverText) - end + if self.Progress.AllowEdit and self:IsCursorOnPanel() then + self.Colors.Main = self.Colors.Hover - return + if IsValid(self.TextPanel) then + self.TextPanel:SetColor(self.Colors.HoverText) end + + return end self.Colors.Main = self.Colors.NoHover + if IsValid(self.TextPanel) then self.TextPanel:SetColor(self.Colors.NoHoverText) end end - function CLASS:Render() local x, y = self:GetRenderPos() local w, h = self:GetSize() @@ -139,7 +149,6 @@ function CLASS:Render() local col2 = self.Colors.Secondary or color_white local fraction1 = self.Progress.Fraction - local fraction2 = 1 - fraction1 if ShadowWidth <= 0 then surface.SetDrawColor(col1) @@ -162,7 +171,7 @@ function CLASS:Render() surface.DrawRect(x, y, sw * fraction1, sh) end -function CLASS:DoEditProgress() +function CLASS:DoEditProgress(force) if self:IsDisabled() then return end @@ -186,6 +195,7 @@ function CLASS:DoEditProgress() end fraction = self:CallHook("OnFractionChangeEdit", fraction) or fraction + self:SetFraction(fraction) end @@ -312,11 +322,11 @@ function CLASS:ActivateNetworkedMode() return end - self:SetNWVarProxy("Fraction", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("Fraction", "Float", function(this, nwkey, oldvar, newvar) self:SetFraction(newvar) end) - self:SetNWVarProxy("AllowEdit", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("AllowEdit", "Bool", function(this, nwkey, oldvar, newvar) self:SetAllowFractionEdit(newvar) end) diff --git a/lua/streamradio_core/classes/ui/radio/gui_browser.lua b/lua/streamradio_core/classes/ui/radio/gui_browser.lua index 569c2e3..6529804 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_browser.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_browser.lua @@ -16,6 +16,7 @@ function CLASS:Create() self.HeaderPanel = self:AddPanelByClassname("shadow_panel", true) self.HeaderPanel:SetSize(1, 30) self.HeaderPanel:SetName("header") + self.HeaderPanel:SetNWName("hdr") self.HeaderPanel:SetSkinIdentifyer("header") self.HeaderPanelTextPre = self.HeaderPanel:AddPanelByClassname("label", true) @@ -23,11 +24,13 @@ function CLASS:Create() self.HeaderPanelTextPre:SetSize(1, 30) self.HeaderPanelTextPre:SetAlign(TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) self.HeaderPanelTextPre:SetName("pretext") + self.HeaderPanelTextPre:SetNWName("ptxt") self.HeaderPanelText = self.HeaderPanel:AddPanelByClassname("label", true) self.HeaderPanelText:SetShorterAtEnd(false) self.HeaderPanelText:SetAlign(TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) self.HeaderPanelText:SetName("text") + self.HeaderPanelText:SetNWName("txt") self.HeaderPanel.SetTextColor = function(this, color) if IsValid(self.HeaderPanelText) then @@ -55,51 +58,63 @@ function CLASS:Create() self.UpButton:SetIcon(g_mat_upbutton) self.UpButton:SetSize(50, 50) self.UpButton:SetName("backbutton") + self.UpButton:SetNWName("bk") self.UpButton:SetSkinIdentifyer("sidebutton") - self.UpButton:SetToolTip("Go to parent directory") + self.UpButton:SetTooltip("Go to parent directory") self.RefreshButton = self:AddPanelByClassname("button", true) self.RefreshButton:SetIcon(g_mat_refresh) self.RefreshButton:SetSize(50, 50) self.RefreshButton:SetName("refreshbutton") + self.RefreshButton:SetNWName("rfsh") self.RefreshButton:SetSkinIdentifyer("sidebutton") - self.RefreshButton:SetToolTip("Refresh view") + self.RefreshButton:SetTooltip("Refresh view") self.ToolButton = self:AddPanelByClassname("button", true) self.ToolButton:SetIcon(g_mat_toolbutton) self.ToolButton:SetSize(50, 50) self.ToolButton:SetName("toolbutton") + self.ToolButton:SetNWName("tool") self.ToolButton:SetSkinIdentifyer("sidebutton") - self.ToolButton:SetToolTip("Play URL from Toolgun") + self.ToolButton:SetTooltip("Play URL from Toolgun") self.WireButton = self:AddPanelByClassname("button", true) self.WireButton:SetIcon(g_mat_wirebutton) self.WireButton:SetSize(50, 50) self.WireButton:SetName("wirebutton") + self.WireButton:SetNWName("wire") self.WireButton:SetSkinIdentifyer("sidebutton") - self.WireButton:SetVisible(StreamRadioLib.HasWiremod()) - self.WireButton:SetToolTip("Play URL from Wiremod") + self.WireButton:SetVisible(StreamRadioLib.Wire.HasWiremod()) + self.WireButton:SetTooltip("Play URL from Wiremod") self.ListFiles = self:AddPanelByClassname("radio/list_playlists", true) self.ListFiles:SetName("list-playlists") + self.ListFiles:SetNWName("lstp") self.ListFiles:Open() self.ListFiles:SetSkinIdentifyer("list") self.ListPlaylist = self:AddPanelByClassname("radio/list_playlistview", true) self.ListPlaylist:SetName("list-playlistview") + self.ListPlaylist:SetNWName("lstpv") self.ListPlaylist:Close() self.ListPlaylist:SetSkinIdentifyer("list") self.Errorbox = self:AddPanelByClassname("radio/gui_errorbox", true) self.Errorbox:SetName("error") + self.Errorbox:SetNWName("err") self.Errorbox:SetSkinIdentifyer("error") - self.Errorbox.OnClose = function() - if not self.State then return end - self.State.PlaylistError = false - self.State.PlaylistOpened = false + self.Errorbox.OnCloseClick = function() + self:GoUpPath() + end + + self.Errorbox.OnRetry = function() + self:Refresh() end + self.Errorbox:SetZPos(100) + self.Errorbox:Close() + self.SideButtons = { self.UpButton, self.RefreshButton, @@ -107,44 +122,34 @@ function CLASS:Create() self.WireButton, } - self.Errorbox.OnRetry = function() - if not self.State then return end - - self.State.PlaylistOpened = true - self.ListPlaylist:Refresh() - end - - self.Errorbox:SetZPos(100) - self.Errorbox:Close() - self.State = self:CreateListener({ PlaylistOpened = false, - PlaylistError = false, }, function(this, k, v) - if k == "PlaylistOpened" then - if not v then - self.State.PlaylistError = false + if not v then + if IsValid(self.ListPlaylist) then self.ListPlaylist:ClearData() self.ListPlaylist:Close() + end + + if IsValid(self.ListFiles) then self.ListFiles:ActivateNetworkedMode() self.ListFiles:Open() end - - if v then + else + if IsValid(self.ListFiles) then self.ListFiles:ClearData() self.ListFiles:Close() + end + + if IsValid(self.ListPlaylist) then self.ListPlaylist:ActivateNetworkedMode() self.ListPlaylist:Open() end - - self:Refresh() - self:SetNWBool(k, v) - self:UpdatePath() end - if k == "PlaylistError" and not v then - self.Errorbox:Close() - end + self:Refresh() + self:SetNWBool(k, v) + self:UpdatePath() self:ApplyNetworkVars() self:InvalidateLayout() @@ -156,8 +161,6 @@ function CLASS:Create() end self.ListPlaylist.OnError = function(this, filename, filetype, ...) - self.State.PlaylistError = true - if IsValid(self.Errorbox) then self.Errorbox:SetPlaylistError(filename) self:InvalidateLayout() @@ -166,14 +169,24 @@ function CLASS:Create() return self:CallHook("OnError", filename, filetype, ...) end - self.ListPlaylist.OnErrorClose = function(this, filename, filetype, ...) - self.State.PlaylistError = false + self.ListPlaylist.OnErrorRelease = function(this, filename, filetype, ...) + if IsValid(self.Errorbox) then + self.Errorbox:Close() + self:InvalidateLayout() + end + + return self:CallHook("OnErrorRelease", filename, filetype, ...) end self.ListPlaylist.OnInvalidDupeFilepath = function(this, filename, filetype, ...) self.InValidPlaylistDupe = true self.State.PlaylistOpened = false + if IsValid(self.Errorbox) then + self.Errorbox:Close() + self:InvalidateLayout() + end + self:Refresh() self:QueueCall("Refresh") @@ -184,6 +197,11 @@ function CLASS:Create() self.InValidPlaylistDupe = true self.State.PlaylistOpened = false + if IsValid(self.Errorbox) then + self.Errorbox:Close() + self:InvalidateLayout() + end + self:Refresh() self:QueueCall("Refresh") @@ -201,7 +219,10 @@ function CLASS:Create() if r == false then return end self.State.PlaylistOpened = true - self.ListPlaylist:SetFile(value.path, value.type) + + if IsValid(self.ListPlaylist) then + self.ListPlaylist:SetFile(value.path, value.type) + end end self.ListFiles.OnPathChange = function(this, ...) @@ -231,21 +252,30 @@ function CLASS:Create() end self:SetEvent("OnClose", "SaveScrollPos", function() - self.ListPlaylist:SaveScrollPos() - self.ListFiles:SaveScrollPos() + if IsValid(self.ListPlaylist) then + self.ListPlaylist:SaveScrollPos() + end + + if IsValid(self.ListFiles) then + self.ListFiles:SaveScrollPos() + end end) self:UpdatePath() end function CLASS:IsSingleItem() + if not IsValid(self.ListPlaylist) then + return false + end + return self.ListPlaylist:IsSingleItem() end function CLASS:CloseSingleItem() + if CLIENT then return end if not self:IsSingleItem() then return end - self.State.PlaylistError = false self.State.PlaylistOpened = false end @@ -291,12 +321,20 @@ function CLASS:GetHeaderTextPanel() return self.HeaderPanelText end -function CLASS:IsPlaylistOpend() - return self.State.PlaylistOpened or self.State.PlaylistError or false +function CLASS:IsPlaylistOpen() + if self.State.PlaylistOpened then + return true + end + + if IsValid(self.ListPlaylist) and self.ListPlaylist:HasError() then + return true + end + + return false end function CLASS:GetPath() - if self:IsPlaylistOpend() then + if self:IsPlaylistOpen() then return self.ListPlaylist:GetFile() end @@ -304,29 +342,59 @@ function CLASS:GetPath() end function CLASS:GoUpPath() + if CLIENT then return end if not self.State then return end - if self.State.PlaylistOpened or self.State.PlaylistError then + if self:IsPlaylistOpen() then self.State.PlaylistOpened = false - self.State.PlaylistError = false return end - if CLIENT then return end - self.ListFiles:GoUpPath() + if IsValid(self.ListFiles) then + self.ListFiles:GoUpPath() + end end function CLASS:Refresh() - if not self.State then return end + local antiSpamTime = 1 - if self.State.PlaylistError then - self.State.PlaylistError = false - self.State.PlaylistOpened = false - self.State.PlaylistOpened = true + if IsValid(self.RefreshButton) then + self.RefreshButton:SetDisabled(true) + + self:TimerOnce("RefreshButtonAntiSpam", antiSpamTime, function() + if not IsValid(self.RefreshButton) then + return + end + + self.RefreshButton:SetDisabled(false) + end) end - self.ListPlaylist:Refresh() - self.ListFiles:Refresh() + if IsValid(self.Errorbox) and IsValid(self.Errorbox.RetryButton) then + self.Errorbox.RetryButton:SetDisabled(true) + + self:TimerOnce("RetryButtonAntiSpam", antiSpamTime, function() + if not IsValid(self.Errorbox) then + return + end + + if not IsValid(self.Errorbox.RetryButton) then + return + end + + self.Errorbox.RetryButton:SetDisabled(false) + end) + end + + if CLIENT then return end + + if IsValid(self.ListPlaylist) and self.ListPlaylist:IsVisible() then + self.ListPlaylist:Refresh() + end + + if IsValid(self.ListFiles) and self.ListFiles:IsVisible() then + self.ListFiles:Refresh() + end end function CLASS:PlayNext() @@ -433,7 +501,7 @@ function CLASS:ActivateNetworkedMode() return end - self:SetNWVarProxy("PlaylistOpened", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("PlaylistOpened", "Bool", function(this, nwkey, oldvar, newvar) self.State.PlaylistOpened = newvar end) end diff --git a/lua/streamradio_core/classes/ui/radio/gui_errorbox.lua b/lua/streamradio_core/classes/ui/radio/gui_errorbox.lua index aeb6fd4..fc44614 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_errorbox.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_errorbox.lua @@ -18,6 +18,7 @@ function CLASS:Create() self.BodyPanelText = self:AddPanelByClassname("textview", true) self.BodyPanelText:SetAlign(TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) self.BodyPanelText:SetName("textbox") + self.BodyPanelText:SetNWName("txt") self.BodyPanelText:SetSkinIdentifyer("textbox") self.HelpButton = self:AddPanelByClassname("button", true) @@ -25,6 +26,7 @@ function CLASS:Create() self.HelpButton:SetIcon(g_mat_help) self.HelpButton:SetText("Help") self.HelpButton:SetName("help") + self.HelpButton:SetNWName("hlp") self.HelpButton:SetSkinIdentifyer("button") self.HelpButton.DoClick = function() self:CallHook("OnHelp") @@ -43,9 +45,11 @@ function CLASS:Create() self.CloseButton:SetIcon(g_mat_cross) self.CloseButton:SetText("Close") self.CloseButton:SetName("close") + self.CloseButton:SetNWName("cls") self.CloseButton:SetSkinIdentifyer("button") self.CloseButton.DoClick = function() self:Close() + self:CallHook("OnCloseClick") end self.RetryButton = self:AddPanelByClassname("button", true) @@ -53,9 +57,9 @@ function CLASS:Create() self.RetryButton:SetIcon(g_mat_arrow_refresh) self.RetryButton:SetText("Retry") self.RetryButton:SetName("retry") + self.RetryButton:SetNWName("rty") self.RetryButton:SetSkinIdentifyer("button") self.RetryButton.DoClick = function() - self:Close() self:CallHook("OnRetry") end end @@ -109,7 +113,11 @@ function CLASS:SetErrorCode(err, url) self.BodyPanelText:SetText(text) end - self:Open() + if err ~= 0 then + self:Open() + else + self:Close() + end end function CLASS:PerformLayout(...) @@ -157,24 +165,24 @@ function CLASS:PerformLayout(...) local buttonx = (w - buttonbarw) / 2 local buttony = h - buttonh - if IsValid(self.CloseButton) then + 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 - if IsValid(self.RetryButton) then + 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 - if IsValid(self.HelpButton) then + if IsValid(self.HelpButton) and self.HelpButton.Layout.Visible then self.HelpButton:SetSize(buttonw, buttonh) self.HelpButton:SetPos(buttonx, buttony) end - if IsValid(self.BodyPanelText) then + if IsValid(self.BodyPanelText) and self.BodyPanelText.Layout.Visible then self.BodyPanelText:SetPos(0, 0) self.BodyPanelText:SetSize(w, bodyheight) end diff --git a/lua/streamradio_core/classes/ui/radio/gui_main.lua b/lua/streamradio_core/classes/ui/radio/gui_main.lua index 9a6fff7..ab3faaa 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_main.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_main.lua @@ -10,12 +10,14 @@ function CLASS:Create() self.Browser = self:AddPanelByClassname("radio/gui_browser", true) self.Browser:SetName("browser") + self.Browser:SetNWName("brw") self.Browser:SetZPos(50) self.Browser:Open() self.Browser:SetSkinIdentifyer("browser") self.Player = self:AddPanelByClassname("radio/gui_player", true) self.Player:SetName("player") + self.Player:SetNWName("ply") self.Player:SetZPos(100) self.Player:Close() self.Player:SetSkinIdentifyer("player") @@ -86,6 +88,10 @@ function CLASS:Create() self.Browser:PlayNext() end + self.Player.OnPlaybackLoopModeChange = function(this, newLoopMode) + self:CallHook("OnPlaybackLoopModeChange", newLoopMode) + end + self:QueueCall("ActivateNetworkedMode") self:InvalidateLayout() end @@ -147,6 +153,10 @@ function CLASS:EnablePlaylist(bool) self.Player:EnablePlaylist(not self.Browser:IsSingleItem() and self._showplaylist) end +function CLASS:UpdatePlaybackLoopMode(...) + self.Player:UpdatePlaybackLoopMode(...) +end + function CLASS:SetSyncMode(...) self.Player:SetSyncMode(...) end @@ -169,7 +179,7 @@ function CLASS:ActivateNetworkedMode() return end - self:SetNWVarProxy("PlayerOpened", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("PlayerOpened", "Bool", function(this, nwkey, oldvar, newvar) self.State.PlayerOpened = newvar end) end diff --git a/lua/streamradio_core/classes/ui/radio/gui_player.lua b/lua/streamradio_core/classes/ui/radio/gui_player.lua index e963a9f..66132cc 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_player.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_player.lua @@ -3,6 +3,8 @@ if not istable(CLASS) then return end +local LIBNetwork = StreamRadioLib.Network + local BASE = CLASS:GetBaseClass() local g_mat_closebutton = StreamRadioLib.GetPNGIcon("door_in") @@ -13,6 +15,7 @@ function CLASS:Create() self.HeaderPanel = self:AddPanelByClassname("shadow_panel", true) self.HeaderPanel:SetSize(1, 40) self.HeaderPanel:SetName("header") + self.HeaderPanel:SetNWName("hdr") self.HeaderPanel:SetSkinIdentifyer("header") self.HeaderText = self.HeaderPanel:CreateText("label_fade") @@ -21,11 +24,13 @@ function CLASS:Create() self.SpectrumPanel = self:AddPanelByClassname("radio/gui_player_spectrum", true) self.SpectrumPanel:SetSize(1, 1) self.SpectrumPanel:SetName("spectrum") + self.SpectrumPanel:SetNWName("spc") self.SpectrumPanel:SetSkinIdentifyer("spectrum") self.VolumePanel = self.SpectrumPanel:AddPanelByClassname("shadow_panel") self.VolumePanel:SetSize(1, 60) self.VolumePanel:SetName("volume") + self.VolumePanel:SetNWName("vol") self.VolumePanel:SetSkinIdentifyer("volume") self.VolumePanel:SetShadowWidth(0) self.VolumePanel:SetColor(Color(128, 128, 128, 160)) @@ -35,6 +40,7 @@ function CLASS:Create() self.VolumeBar = self.VolumePanel:AddPanelByClassname("progressbar", true) self.VolumeBar:SetName("progressbar") + self.VolumeBar:SetNWName("bar") self.VolumeBar:SetSkinIdentifyer("bar") self.VolumeBar:SetAllowFractionEdit(true) self.VolumeBar:SetShadowWidth(0) @@ -56,6 +62,7 @@ function CLASS:Create() self.ControlPanel = self:AddPanelByClassname("radio/gui_player_controls", true) self.ControlPanel:SetSize(1, 1) self.ControlPanel:SetName("controls") + self.ControlPanel:SetNWName("ctrl") self.ControlPanel:SetSkinIdentifyer("controls") self.ControlPanel.OnPlaylistBack = function() @@ -66,30 +73,47 @@ function CLASS:Create() self:CallHook("OnPlaylistForward") end - self.Errorbox = self.SpectrumPanel:AddPanelByClassname("radio/gui_errorbox") - self.Errorbox:SetName("error") - self.Errorbox:SetSkinIdentifyer("error") - self.Errorbox.OnClose = function() - if not IsValid(self.StreamOBJ) then return end - if not self.State then return end + self.ControlPanel.OnPlaybackLoopModeChange = function(this, newLoopMode) + self:CallHook("OnPlaybackLoopModeChange", newLoopMode) + end + + if CLIENT then + self.Errorbox = self.SpectrumPanel:AddPanelByClassname("radio/gui_errorbox") + self.Errorbox:SetName("error") + self.Errorbox:SetNWName("err") + self.Errorbox:SetSkinIdentifyer("error") + + self.Errorbox.OnRetry = function() + if not IsValid(self.Errorbox) then + return + end - if not self.State.Error then return end - if self.State.Error == 0 then return end + self.Errorbox:Close() + end - self.State.Error = 0 - self:ResetStream() - end + self.Errorbox.OnClose = function() + if not IsValid(self.StreamOBJ) then return end + if not self.State then return end - self.Errorbox:SetZPos(100) - self.Errorbox:Close() + if not self.State.Error then return end + if self.State.Error == 0 then return end - if self.Errorbox.CloseButton then - self.Errorbox.CloseButton:Remove() - self.Errorbox.CloseButton = nil + self.State.Error = 0 + self:ResetStream() + end + + self.Errorbox:SetZPos(100) + self.Errorbox:Close() + + if self.Errorbox.CloseButton then + self.Errorbox.CloseButton:Remove() + self.Errorbox.CloseButton = nil + end end self.CloseButton = self:AddPanelByClassname("button", true) self.CloseButton:SetName("backbutton") + self.CloseButton:SetNWName("bk") self.CloseButton:SetSkinIdentifyer("button") self.CloseButton:SetIcon(g_mat_closebutton) self.CloseButton:SetAlign(TEXT_ALIGN_RIGHT) @@ -113,31 +137,26 @@ function CLASS:Create() self.State = self:CreateListener({ Error = 0, }, function(this, k, v) - if not IsValid(self.StreamOBJ) then return end local err = tonumber(v or 0) or 0 - local haserror = err ~= 0 - - self.State.Error = err + local url = nil - if haserror then - self.Errorbox:SetErrorCode(err, self.StreamOBJ:GetURL()) - self.Errorbox:Open() - else - self.Errorbox:Close() + if IsValid(self.StreamOBJ) then + url = self.StreamOBJ:GetURL() end - self:InvalidateLayout() + self.Errorbox:SetErrorCode(err, url) end) end if SERVER then - self:NetReceive("streamreset_sv", function(this, id, len, ply) + LIBNetwork.AddNetworkString("streamreset_on_sv") + LIBNetwork.AddNetworkString("streamreset_on_cl") + + self:NetReceive("streamreset_on_sv", function(this, id, len, ply) self:ResetStream() end) else - self:StartSuperThink() - - self:NetReceive("streamreset_cl", function(this, id, len, ply) + self:NetReceive("streamreset_on_cl", function(this, id, len, ply) self:ResetStream(true) end) end @@ -146,9 +165,12 @@ end function CLASS:Remove() if IsValid(self.StreamOBJ) then self.StreamOBJ:RemoveEvent("OnVolumeChange", self:GetID()) - self.StreamOBJ:RemoveEvent("OnConnect", self:GetID()) - self.StreamOBJ:RemoveEvent("OnError", self:GetID()) - self.StreamOBJ:RemoveEvent("OnSearch", self:GetID()) + + if CLIENT then + self.StreamOBJ:RemoveEvent("OnConnect", self:GetID()) + self.StreamOBJ:RemoveEvent("OnError", self:GetID()) + self.StreamOBJ:RemoveEvent("OnSearch", self:GetID()) + end end BASE.Remove(self) @@ -157,9 +179,9 @@ end function CLASS:ResetStream(nosend) if not nosend then if SERVER then - self:NetSend("streamreset_cl") + self:NetSend("streamreset_on_cl") else - self:NetSend("streamreset_sv") + self:NetSend("streamreset_on_sv") return end end @@ -212,7 +234,7 @@ function CLASS:SetStream(stream) if not self.State then return end if not IsValid(self.Errorbox) then return end - -- self.State.Error = 0 + self.State.Error = 0 end) self.StreamOBJ:SetEvent("OnConnect", self:GetID(), function() @@ -247,20 +269,6 @@ function CLASS:Think() self:UpdateFromStream() end -function CLASS:SuperThink() - if SERVER then return end - if not IsValid(self.StreamOBJ) then return end - - if not self:IsSeen() then return end - if not self:IsVisible() then return end - - if IsValid(self.ControlPanel) then - self.ControlPanel:UpdateFromStream() - end - - self:PerformRerender(true) -end - function CLASS:UpdateFromStream() if not IsValid(self.StreamOBJ) then return end if SERVER then return end @@ -417,6 +425,10 @@ function CLASS:IsPlaylistEnabled() return self.ControlPanel:IsPlaylistEnabled() end +function CLASS:UpdatePlaybackLoopMode(...) + self.ControlPanel:UpdatePlaybackLoopMode(...) +end + function CLASS:SetSyncMode(bool) self._syncmode = bool or false 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 b9737a5..db167d2 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_player_controls.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_player_controls.lua @@ -13,85 +13,80 @@ local g_mat_forward = StreamRadioLib.GetPNGIcon("control_end") local g_mat_volumedown = StreamRadioLib.GetPNGIcon("sound_delete") local g_mat_volumeup = StreamRadioLib.GetPNGIcon("sound_add") -local function Timeformat(f, u) - f = f or {} - u = u or 0 - - local ms = f.ms or 0 - local s = f.s or 0 - local m = f.m or 0 - local rh = f.h or 0 +local g_mat_playback_modes = { + [StreamRadioLib.PLAYBACK_LOOP_MODE_NONE] = StreamRadioLib.GetPNGIcon("arrow_not_refresh", true), + [StreamRadioLib.PLAYBACK_LOOP_MODE_SONG] = StreamRadioLib.GetPNGIcon("arrow_refresh"), + [StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST] = StreamRadioLib.GetPNGIcon("table_refresh"), +} + +local g_tooltip_playback_modes = { + [StreamRadioLib.PLAYBACK_LOOP_MODE_NONE] = "Change loop mode\n(currently: No loop)", + [StreamRadioLib.PLAYBACK_LOOP_MODE_SONG] = "Change loop mode\n(currently: Song loop)", + [StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST] = "Change loop mode\n(currently: Playlist loop)", +} + +local g_next_playback_modes = { + [StreamRadioLib.PLAYBACK_LOOP_MODE_NONE] = StreamRadioLib.PLAYBACK_LOOP_MODE_SONG, + [StreamRadioLib.PLAYBACK_LOOP_MODE_SONG] = StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST, + [StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST] = StreamRadioLib.PLAYBACK_LOOP_MODE_NONE, +} + +local function FormatTime(seconds) + seconds = tonumber(seconds or 0) or 0 + + local rs, ms = math.modf(seconds) + ms = math.floor(ms * 100) + + local s = rs % 60 + local m = math.floor((rs / 60) % 60) + + local rh = math.floor(rs / 3600) local h = rh % 24 local d = math.floor(rh / 24) - if u <= 0 then - return nil - end - - if u <= 3 then - return m, s, ms - end - - if u == 4 then - return h, m, s, ms - end - return d, h, m, s, ms end +local function GetTimeFormated(seconds, timeScale) + seconds = tonumber(seconds or 0) or 0 + timeScale = tonumber(timeScale or 0) or 0 -local function GetTimeformat(timef, lenf) - lenf = lenf or timef or {} - - local m = lenf.m or 0 - local rh = lenf.h or 0 - local h = rh % 24 - local d = math.floor(rh / 24) - - if d <= 0 then - if h <= 0 then - if m >= 10 then - return "%02i:%02i.%02i", 3 - end - - return "%01i:%02i.%02i", 3 - end + if timeScale <= 0 then + timeScale = seconds + end - if h >= 10 then - return "%02i:%02i:%02i.%02i", 4 - end + local d, h, m, s, ms = FormatTime(seconds) - return "%01i:%02i:%02i.%02i", 4 - end + local scale_1m = 60 + local scale_10m = scale_1m * 10 + local scale_1h = scale_1m * 60 + local scale_10h = scale_1h * 10 + local scale_1d = scale_1h * 24 - if d >= 100 then - return "%03i:%02i:%02i:%02i.%02i", 5 + if timeScale < scale_1h then + return string.format("%01i:%02i.%02i", m, s, ms) end - if d >= 10 then - return "%02i:%02i:%02i:%02i.%02i", 5 + if timeScale < scale_1d then + return string.format("%01i:%02i:%02i", h, m, s) end - return "%01i:%02i:%02i:%02i.%02i", 5 + return string.format("%01i:%02i:%02i:%02i", d, h, m, s) end local function FormatTimeleft(time, len) time = time or 0 len = len or 0 - local timef = string.FormattedTime(time) + local timef = GetTimeFormated(time, len) local lenf = nil if len > 0 then - lenf = string.FormattedTime(len) + lenf = GetTimeFormated(len) end - local format, units = GetTimeformat(timef, lenf) - timef = string.format(format, Timeformat(timef, units)) - if lenf then - lenf = string.format(format, Timeformat(lenf, units)) - return timef .. " / " .. lenf + return string.format("%s / %s" , timef, lenf) end return timef @@ -105,6 +100,7 @@ function CLASS:Create() self.PlayPauseButton = self:AddPanelByClassname("button", true) self.PlayPauseButton:SetIcon(g_mat_play) self.PlayPauseButton:SetName("play") + self.PlayPauseButton:SetNWName("pl") self.PlayPauseButton:SetSkinIdentifyer("button") self.PlayPauseButton.DoClick = function() if not IsValid(self.StreamOBJ) then return end @@ -116,15 +112,15 @@ function CLASS:Create() return end - self:CallHook("OnPlay") - self.StreamOBJ:Play(self.StreamOBJ:HasEnded()) + self:TriggerPlay() end self.BackButton = self:AddPanelByClassname("button", true) self.BackButton:SetIcon(g_mat_back) self.BackButton:SetName("back") + self.BackButton:SetNWName("bk") self.BackButton:SetSkinIdentifyer("button") - self.BackButton:SetToolTip("Go to previous playlist track") + self.BackButton:SetTooltip("Go to previous playlist track") self.BackButton.DoClick = function() self:CallHook("OnPlaylistBack") end @@ -132,8 +128,9 @@ function CLASS:Create() self.ForwardButton = self:AddPanelByClassname("button", true) self.ForwardButton:SetIcon(g_mat_forward) self.ForwardButton:SetName("forward") + self.ForwardButton:SetNWName("fw") self.ForwardButton:SetSkinIdentifyer("button") - self.ForwardButton:SetToolTip("Go to next playlist track") + self.ForwardButton:SetTooltip("Go to next playlist track") self.ForwardButton.DoClick = function() self:CallHook("OnPlaylistForward") end @@ -141,8 +138,9 @@ function CLASS:Create() self.StopButton = self:AddPanelByClassname("button", true) self.StopButton:SetIcon(g_mat_stop) self.StopButton:SetName("stop") + self.StopButton:SetNWName("sp") self.StopButton:SetSkinIdentifyer("button") - self.StopButton:SetToolTip("Stop playback") + self.StopButton:SetTooltip("Stop playback") self.StopButton.DoClick = function() if not IsValid(self.StreamOBJ) then return end self:CallHook("OnStop") @@ -152,8 +150,9 @@ function CLASS:Create() self.VolumeDownButton = self:AddPanelByClassname("button", true) self.VolumeDownButton:SetIcon(g_mat_volumedown) self.VolumeDownButton:SetName("volumedown") + self.VolumeDownButton:SetNWName("vdn") self.VolumeDownButton:SetSkinIdentifyer("button") - self.VolumeDownButton:SetToolTip("Decrease volume") + self.VolumeDownButton:SetTooltip("Decrease volume") self.VolumeDownButton.OnMousePressed = function() if not IsValid(self.StreamOBJ) then return end @@ -168,6 +167,7 @@ function CLASS:Create() self.VolumeUpButton = self:AddPanelByClassname("button", true) self.VolumeUpButton:SetIcon(g_mat_volumeup) self.VolumeUpButton:SetName("volumeup") + self.VolumeUpButton:SetNWName("vup") self.VolumeUpButton:SetSkinIdentifyer("button") self.VolumeUpButton:SetTooltip("Increase volume") self.VolumeUpButton.OnMousePressed = function() @@ -212,6 +212,18 @@ function CLASS:Create() self.VolumeDownButton.OnMouseReleased = self.VolumeUpButton.OnMouseReleased self.VolumeDownButton.Think = self.VolumeUpButton.Think + self.PlaybackLoopModeButton = self:AddPanelByClassname("button", true) + self.PlaybackLoopModeButton:SetName("playback-mode") + self.PlaybackLoopModeButton:SetNWName("pm") + self.PlaybackLoopModeButton:SetSkinIdentifyer("button") + + self.PlaybackLoopModeButton.DoClick = function() + local loopMode = self._currentLoopMode or StreamRadioLib.PLAYBACK_LOOP_MODE_NONE + local newLoopMode = g_next_playback_modes[loopMode] or StreamRadioLib.PLAYBACK_LOOP_MODE_NONE + + self:CallHook("OnPlaybackLoopModeChange", newLoopMode) + end + self.Buttons = {} table.insert(self.Buttons, self.PlayPauseButton) table.insert(self.Buttons, self.BackButton) @@ -219,9 +231,11 @@ function CLASS:Create() table.insert(self.Buttons, self.StopButton) table.insert(self.Buttons, self.VolumeDownButton) table.insert(self.Buttons, self.VolumeUpButton) + table.insert(self.Buttons, self.PlaybackLoopModeButton) self.PlayBar = self:AddPanelByClassname("progressbar", true) self.PlayBar:SetName("progressbar") + self.PlayBar:SetNWName("pbar") self.PlayBar:SetSkinIdentifyer("progressbar") self.PlayBar.FractionChangeText = function(this, v) if not IsValid(self.StreamOBJ) then return end @@ -234,10 +248,6 @@ function CLASS:Create() return "Buffering..." end - if self.StreamOBJ:IsSeeking() then - return "Seeking..." - end - if self.StreamOBJ:IsStopMode() then return "Stopped..." end @@ -263,8 +273,11 @@ function CLASS:Create() self.PlayBar.OnFractionChangeEdit = function(this, v) if not IsValid(self.StreamOBJ) then return end + local noise = math.random() * 0.00001 local len = self.StreamOBJ:GetMasterLength() - self.StreamOBJ:SetTime(len * v, true) + + -- Set a fake value that is minimal off target to force a change detection when the right one is set + self.StreamOBJ:SetTime(len * v - noise, true) end self.State = self:CreateListener({ @@ -278,14 +291,24 @@ function CLASS:Create() self.ForwardButton:SetVisible(v) end + self:UpdatePlaybackLoopMode(self._currentLoopMode) + self:SetNWBool(k, v) self:ApplyNetworkVars() self:InvalidateLayout() end) + self:UpdatePlaybackLoopMode() + self:UpdatePlayBar() self.PlayBar:SetSize(1,1) self:QueueCall("ActivateNetworkedMode") + + if CLIENT then + self:StartSuperThink() + end + + self:InvalidateLayout() end function CLASS:PerformLayout(...) @@ -374,18 +397,61 @@ function CLASS:Remove() self.StreamOBJ:RemoveEvent("OnTrackEnd", self:GetID()) self.StreamOBJ:RemoveEvent("OnVolumeChange", self:GetID()) self.StreamOBJ:RemoveEvent("OnPlayModeChange", self:GetID()) + + if CLIENT then + self.StreamOBJ:RemoveEvent("OnSeekingStart", self:GetID()) + self.StreamOBJ:RemoveEvent("OnSeekingEnd", self:GetID()) + self.StreamOBJ:RemoveEvent("OnMute", self:GetID()) + self.StreamOBJ:RemoveEvent("OnClose", self:GetID()) + + self.StreamOBJ:RemoveEvent("OnSearch", self:GetID()) + self.StreamOBJ:RemoveEvent("OnConnect", self:GetID()) + self.StreamOBJ:RemoveEvent("OnError", self:GetID()) + end end BASE.Remove(self) end +function CLASS:UpdateButtons() + local StreamOBJ = self.StreamOBJ + if not IsValid(StreamOBJ) then return end + + local isPlayMode = StreamOBJ:IsPlayMode() + local isStopMode = StreamOBJ:IsStopMode() + local syncMode = self:GetSyncMode() + + if IsValid(self.PlayPauseButton) then + self.PlayPauseButton:SetIcon(isPlayMode and g_mat_pause or g_mat_play) + self.PlayPauseButton:SetTooltip(isPlayMode and "Pause playback" or "Start playback") + self.PlayPauseButton:SetDisabled(syncMode) + end + + if IsValid(self.StopButton) then + self.StopButton:SetDisabled(isStopMode or syncMode) + end +end + +function CLASS:UpdatePlayBar() + if SERVER then return end + + if not IsValid(self.PlayBar) then + return + end + + if not self.PlayBar:IsVisible() then + return + end + + self.PlayBar:UpdateText() +end + function CLASS:SetStream(stream) self.StreamOBJ = stream - self:UpdateFromStream() - if IsValid(self.PlayBar) and self.PlayBar:IsVisible() then - self.PlayBar:UpdateText() - end + self:UpdateFromStream() + self:UpdateButtons() + self:UpdatePlayBar() if not IsValid(self.StreamOBJ) then return end @@ -393,7 +459,12 @@ function CLASS:SetStream(stream) if not IsValid(self) then return end if not IsValid(self.StreamOBJ) then return end + self:QueueCall("UpdatePlayBar") + self:QueueCall("UpdateButtons") + + if self:GetSyncMode() then return end if not self.State.PlaylistEnabled then return end + if self._currentLoopMode ~= StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST then return end self:CallHook("OnPlaylistForward") self.StreamOBJ:Play() @@ -414,22 +485,52 @@ function CLASS:SetStream(stream) local function OnPlayModeChange(this, mode) if not IsValid(self) then return end - local isPlayMode = self.StreamOBJ:IsPlayMode() - local isStopMode = self.StreamOBJ:IsStopMode() + local StreamOBJ = self.StreamOBJ + if not IsValid(StreamOBJ) then return end + + local isPlayMode = StreamOBJ:IsPlayMode() + local isStopMode = StreamOBJ:IsStopMode() if IsValid(self.PlayPauseButton) then self.PlayPauseButton:SetIcon(isPlayMode and g_mat_pause or g_mat_play) - self.PlayPauseButton:SetToolTip(isPlayMode and "Pause playback" or "Start playback") + self.PlayPauseButton:SetTooltip(isPlayMode and "Pause playback" or "Start playback") end if IsValid(self.StopButton) then - self.StopButton:SetDisabled(isStopMode or self._syncmode) + self.StopButton:SetDisabled(isStopMode or self:GetSyncMode()) end + + self:QueueCall("UpdatePlayBar") + self:QueueCall("UpdateButtons") end self.StreamOBJ:SetEvent("OnVolumeChange", self:GetID(), OnVolumeChange) self.StreamOBJ:SetEvent("OnPlayModeChange", self:GetID(), OnPlayModeChange) + if CLIENT then + local function UpdatePlayBar() + if not IsValid(self) then + return false + end + + self:QueueCall("UpdateButtons") + self:QueueCall("UpdatePlayBar") + + return true + end + + self.StreamOBJ:SetEvent("OnSeekingStart", self:GetID(), UpdatePlayBar) + self.StreamOBJ:SetEvent("OnSeekingEnd", self:GetID(), UpdatePlayBar) + self.StreamOBJ:SetEvent("OnMute", self:GetID(), UpdatePlayBar) + self.StreamOBJ:SetEvent("OnClose", self:GetID(), UpdatePlayBar) + + self.StreamOBJ:SetEvent("OnDownload", self:GetID(), UpdatePlayBar) + self.StreamOBJ:SetEvent("OnRetry", self:GetID(), UpdatePlayBar) + self.StreamOBJ:SetEvent("OnSearch", self:GetID(), UpdatePlayBar) + self.StreamOBJ:SetEvent("OnConnect", self:GetID(), UpdatePlayBar) + self.StreamOBJ:SetEvent("OnError", self:GetID(), UpdatePlayBar) + end + OnVolumeChange(self.StreamOBJ, self.StreamOBJ:GetVolume()) OnPlayModeChange(self.StreamOBJ) end @@ -439,52 +540,117 @@ function CLASS:GetStream() end function CLASS:UpdateFromStream() - if not IsValid(self.StreamOBJ) then return end + local StreamOBJ = self.StreamOBJ + if not IsValid(StreamOBJ) then return end + + local len = StreamOBJ:GetLength() + local time = StreamOBJ:GetTime() - local len = self.StreamOBJ:GetLength() - local time = self.StreamOBJ:GetTime() + local isEndlessOrNoStream = StreamOBJ:IsEndless() or StreamOBJ:IsLoading() or StreamOBJ:GetError() ~= 0 or StreamOBJ:GetMuted() if IsValid(self.PlayBar) and self.PlayBar:IsVisible() then - if - self.StreamOBJ:IsEndless() or self.StreamOBJ:IsLoading() or - self.StreamOBJ:GetError() ~= 0 or self.StreamOBJ:GetMuted() - then + if isEndlessOrNoStream then self.PlayBar:SetFraction(0) self.PlayBar:SetAllowFractionEdit(false) self.PlayBar:SetDisabled(self:GetSyncMode()) else self.PlayBar:SetFraction(time / len) - self.PlayBar:SetAllowFractionEdit(not self.StreamOBJ:IsBlockStreamed()) - self.PlayBar:SetDisabled(self:GetSyncMode() or self.StreamOBJ:IsBlockStreamed()) + self.PlayBar:SetAllowFractionEdit(StreamOBJ:CanSeek()) + self.PlayBar:SetDisabled(self:GetSyncMode() or not StreamOBJ:CanSeek()) end + end +end + +function CLASS:ShouldPerformRerender() + if SERVER then return false end + if not IsValid(self.StreamOBJ) then return false end - self.PlayBar:UpdateText() + if not self.StreamOBJ:IsPlaying() then + return false end + + return true +end + +function CLASS:SuperThink() + if SERVER then return end + if not IsValid(self.StreamOBJ) then return end + + if not self:IsSeen() then return end + if not self:IsVisible() then return end + + self:UpdateFromStream() + + if not self:ShouldPerformRerender() then return end + + self:UpdatePlayBar() + self:PerformRerender(true) end function CLASS:EnablePlaylist(bool) - if CLIENT then return end self.State.PlaylistEnabled = bool end -function CLASS:IsPlaylistEnabled(bool) - if CLIENT then return end +function CLASS:IsPlaylistEnabled() return self.State.PlaylistEnabled or false end -function CLASS:SetSyncMode(bool) - self._syncmode = bool or false - +function CLASS:TriggerPlay() if not IsValid(self.StreamOBJ) then return end + local isPlayMode = self.StreamOBJ:IsPlayMode() - if IsValid(self.PlayPauseButton) then - self.PlayPauseButton:SetDisabled(bool) + if isPlayMode then + return end - if IsValid(self.StopButton) then - self.StopButton:SetDisabled(self.StreamOBJ:IsStopMode() and bool) + self:CallHook("OnPlay") + self.StreamOBJ:Play(self.StreamOBJ:HasEnded()) +end + +function CLASS:UpdatePlaybackLoopMode(loopMode) + loopMode = loopMode or StreamRadioLib.PLAYBACK_LOOP_MODE_NONE + self._currentLoopMode = loopMode + + if IsValid(self.PlaybackLoopModeButton) then + self.PlaybackLoopModeButton:SetIcon(g_mat_playback_modes[loopMode]) + self.PlaybackLoopModeButton:SetTooltip(g_tooltip_playback_modes[loopMode]) + + local antiSpamTime = 1 + + self.PlaybackLoopModeButton:SetDisabled(true) + + self:TimerOnce("PlaybackLoopModeButtonAntiSpam", antiSpamTime, function() + if not IsValid(self.PlaybackLoopModeButton) then + return + end + + self.PlaybackLoopModeButton:SetDisabled(false) + end) end + local StreamOBJ = self.StreamOBJ + + if not IsValid(StreamOBJ) then + return + end + + if not StreamOBJ:HasEnded() then + local time = StreamOBJ:GetMasterTime() + + -- make sure we reapply the time between mode changes, so it prevents jumping + StreamOBJ:SetTime(time, true) + else + if loopMode ~= StreamRadioLib.PLAYBACK_LOOP_MODE_NONE then + self:TriggerPlay() + end + end +end + +function CLASS:SetSyncMode(bool) + self._syncmode = bool or false + + if not IsValid(self.StreamOBJ) then return end + if IsValid(self.BackButton) then self.BackButton:SetDisabled(bool) end @@ -493,13 +659,20 @@ function CLASS:SetSyncMode(bool) self.ForwardButton:SetDisabled(bool) end + if IsValid(self.PlaybackLoopModeButton) then + self.PlaybackLoopModeButton:SetDisabled(bool) + end + if IsValid(self.PlayBar) then self.PlayBar:SetDisabled(bool) end + + self:UpdateButtons() + self:UpdatePlayBar() end function CLASS:GetSyncMode() - return self._syncmode or false + return self._syncmode or false end function CLASS:ActivateNetworkedMode() @@ -510,7 +683,7 @@ function CLASS:ActivateNetworkedMode() return end - self:SetNWVarProxy("PlaylistEnabled", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("PlaylistEnabled", "Bool", function(this, nwkey, oldvar, newvar) self.State.PlaylistEnabled = newvar end) 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 7f0d1cd..34b73e6 100644 --- a/lua/streamradio_core/classes/ui/radio/gui_player_spectrum.lua +++ b/lua/streamradio_core/classes/ui/radio/gui_player_spectrum.lua @@ -32,6 +32,10 @@ function CLASS:Create() self.CanHaveLabel = false self.SkinAble = true + + if CLIENT then + self:StartSuperThink() + end end function CLASS:SetForegroundColor(color) @@ -178,20 +182,65 @@ function CLASS:Render() end local ent = self:GetEntity() - if IsValid(ent) and ent.CanDrawSpectrum then - if not ent:CanDrawSpectrum() then - self:RenderSpectrumReplacement() - return - end + if IsValid(ent) and ent.CanDrawSpectrum and not ent:CanDrawSpectrum() then + self:RenderSpectrumReplacement() + return end if not self.StreamOBJ:IsPlayMode() then self:RenderIcon(g_mat_pause) + return end self:RenderSpectrum() end +function CLASS:ShouldPerformRerender() + if SERVER then return false end + + if self.StreamOBJ:GetMuted() then + return false + end + + if self.StreamOBJ:IsLoading() then + return true + end + + if self.StreamOBJ:IsBuffering() then + return true + end + + if self.StreamOBJ:IsSeeking() then + return true + end + + if StreamRadioLib.IsSpectrumHidden() then + return false + end + + local ent = self:GetEntity() + if IsValid(ent) and ent.CanDrawSpectrum and not ent:CanDrawSpectrum() then + return false + end + + if not self.StreamOBJ:IsPlaying() then + return false + end + + return true +end + +function CLASS:SuperThink() + if SERVER then return end + if not IsValid(self.StreamOBJ) then return end + + if not self:IsSeen() then return end + if not self:IsVisible() then return end + + if not self:ShouldPerformRerender() then return end + self:PerformRerender(true) +end + function CLASS:SetStream(stream) self.StreamOBJ = stream end diff --git a/lua/streamradio_core/classes/ui/radio/list_playlists.lua b/lua/streamradio_core/classes/ui/radio/list_playlists.lua index 2deaa16..ba035f5 100644 --- a/lua/streamradio_core/classes/ui/radio/list_playlists.lua +++ b/lua/streamradio_core/classes/ui/radio/list_playlists.lua @@ -21,31 +21,24 @@ function CLASS:BuildListInternal() return end - self._fs_files = nil - self._fs_curpath = self.Path.Value + self.PathUid = StreamRadioLib.Uid() + local uid = self.PathUid StreamRadioLib.Filesystem.Find(self.Path.Value, function(success, files) - if self._fs_curpath ~= self.Path.Value then + if uid ~= self.PathUid then return end - self._fs_files = files or {} - self:QueueCall("_BuildListInternalAsyc") + self:QueueCall("_BuildListInternalAsyc", uid, files or {}) end) end -function CLASS:_BuildListInternalAsyc() - if not self._fs_files then +function CLASS:_BuildListInternalAsyc(uid, files) + if uid ~= self.PathUid then return end - if self._fs_curpath ~= self.Path.Value then - return - end - - self:ClearData() - - for i, v in ipairs(self._fs_files) do + for i, v in ipairs(files) do local data = {} data.value = v @@ -55,8 +48,6 @@ function CLASS:_BuildListInternalAsyc() self:AddData(data, true) end - self._fs_files = nil - self:UpdateButtons() self:QueueCall("RestoreScrollPos") end diff --git a/lua/streamradio_core/classes/ui/radio/list_playlistview.lua b/lua/streamradio_core/classes/ui/radio/list_playlistview.lua index 77b4cec..9246328 100644 --- a/lua/streamradio_core/classes/ui/radio/list_playlistview.lua +++ b/lua/streamradio_core/classes/ui/radio/list_playlistview.lua @@ -82,7 +82,7 @@ function CLASS:CallErrorState() if self.State.Error then self:CallHook("OnError", self.Path.Value, self.Path.Type) else - self:CallHook("OnErrorClose", self.Path.Value, self.Path.Type) + self:CallHook("OnErrorRelease", self.Path.Value, self.Path.Type) end end @@ -91,6 +91,19 @@ function CLASS:UpdateErrorState() self.State.Error = self.tmperror or false end +function CLASS:HasError() + return self.State.Error +end + +function CLASS:ClearData() + if SERVER then + self.State.Error = false + self.tmperror = nil + end + + BASE.ClearData(self) +end + function CLASS:BuildListInternal() if CLIENT then return end if not self.Network.Active then return end @@ -98,11 +111,6 @@ function CLASS:BuildListInternal() self:ClearData() self:ApplaDataFromDupe() - self.State.Error = false - self.tmperror = nil - - self:QueueCall("UpdateErrorState") - if not self:IsVisible() then self:UpdateButtons() self:RestoreScrollPos() @@ -117,49 +125,39 @@ function CLASS:BuildListInternal() return end - self._read_playlist = nil - self._read_curdata = { - path = self.Path.Value, - type = self.Path.Type, - } - - StreamRadioLib.Filesystem.Read(self.Path.Value, self.Path.Type, function(success, data) - if self._read_curdata.path ~= self.Path.Value then - return - end + self.PathUid = StreamRadioLib.Uid() + local uid = self.PathUid - if self._read_curdata.type ~= self.Path.Type then + StreamRadioLib.Filesystem.Read(self.Path.Value, self.Path.Type, function(success, playlist) + if uid ~= self.PathUid then return end if not success then self.tmperror = true + self:QueueCall("UpdateErrorState") return end - self._read_playlist = data - self:QueueCall("_BuildListInternalAsyc") + self:QueueCall("_BuildListInternalAsyc", uid, playlist or {}) end) end -function CLASS:_BuildListInternalAsyc() - if not self._read_playlist then +function CLASS:_BuildListInternalAsyc(uid, playlist) + if uid ~= self.PathUid then return end - if self._read_curdata.path ~= self.Path.Value then - return - end + self.Playlist = {} - if self._read_curdata.type ~= self.Path.Type then + local len = #playlist + if len <= 0 then + self.tmperror = true + self:QueueCall("UpdateErrorState") return end - self:ClearData() - self:QueueCall("UpdateErrorState") - - self.Playlist = {} - for i, v in ipairs(self._read_playlist) do + for i, v in ipairs(playlist) do local entry = { name = v.name, url = v.url, @@ -175,12 +173,6 @@ function CLASS:_BuildListInternalAsyc() self:AddData(data, true) end - local len = #self.Playlist - if len <= 0 then - self.tmperror = true - return - end - if len == 1 then local entry = self.Playlist[1] self:Play(entry) @@ -245,17 +237,18 @@ end function CLASS:ActivateNetworkedMode() BASE.ActivateNetworkedMode(self) + if SERVER then self:SetNWInt("PathType", self.Path.Type) self:SetNWBool("Error", self.State.Error) return end - self:SetNWVarProxy("PathType", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("PathType", "Int", function(this, nwkey, oldvar, newvar) self.Path.Type = newvar end) - self:SetNWVarProxy("Error", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("Error", "Bool", function(this, nwkey, oldvar, newvar) self.State.Error = newvar end) @@ -280,11 +273,9 @@ function CLASS:ApplaDataFromDupe() local data = self.DupeData if not data then return end - local tmp = data.Playlist or {} - table.SortByMember(tmp, "index", true) - self.Playlist = {} - for k, v in pairs(data.Playlist or {}) do + + for i, v in ipairs(data.Playlist or {}) do local url = string.Trim(tostring(v.url or v.uri or v.link or v.source or v.path or "")) local name = string.Trim(tostring(v.name or v.title or "")) @@ -300,15 +291,15 @@ function CLASS:ApplaDataFromDupe() continue end - local i = #self.Playlist + 1 + local index = #self.Playlist + 1 local entry = { name = name, url = url, - index = i, + index = index, } - self.Playlist[i] = entry + self.Playlist[index] = entry end self.EntryOpen = math.Clamp(data.EntryOpen or 1, 1, #self.Playlist) @@ -319,30 +310,15 @@ function CLASS:PostDupe(ent, dupedata) local path = dupedata.Path local type = dupedata.PathType - self._read_curdata = { - path = path, - type = type, - } + self.PathUid = StreamRadioLib.Uid() + local uid = self.PathUid StreamRadioLib.Filesystem.Read(path, type, function(success, data) - if self._read_curdata.path ~= path then - return - end - - if self._read_curdata.type ~= type then - return - end - - if not success then - self:SetFile("", type) - self:CallHook("OnInvalidDupeFilepath") - - self.DupeData = dupedata - self:ApplaDataFromDupe() + if uid ~= self.PathUid then return end - if #data <= 0 then + if not success or #data <= 0 then self:SetFile("", type) self:CallHook("OnInvalidDupeFilepath") diff --git a/lua/streamradio_core/classes/ui/scrollbar.lua b/lua/streamradio_core/classes/ui/scrollbar.lua index e5b57c3..e7329be 100644 --- a/lua/streamradio_core/classes/ui/scrollbar.lua +++ b/lua/streamradio_core/classes/ui/scrollbar.lua @@ -46,6 +46,7 @@ function CLASS:Create() self.BarButton = self:AddPanelByClassname("button", true) self.BarButton:SetName("bar") + self.BarButton:SetNWName("bar") self.BarButton:SetSkinIdentifyer("bar") self:_TreatIconAsText(self.BarButton) @@ -67,6 +68,7 @@ function CLASS:Create() self.LeftUpButton = self:AddPanelByClassname("button", true) self.LeftUpButton:SetName("left-up") + self.LeftUpButton:SetNWName("lup") self.LeftUpButton:SetSkinIdentifyer("button") self:_TreatIconAsText(self.LeftUpButton) @@ -77,6 +79,7 @@ function CLASS:Create() self.RightDownButton = self:AddPanelByClassname("button", true) self.RightDownButton:SetName("right-down") + self.RightDownButton:SetNWName("rdn") self.RightDownButton:SetSkinIdentifyer("button") self:_TreatIconAsText(self.RightDownButton) @@ -391,11 +394,11 @@ function CLASS:ActivateNetworkedMode() return end - self:SetNWVarProxy("ScrollPos", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("ScrollPos", "Int", function(this, nwkey, oldvar, newvar) self.Scroll.Pos = newvar end) - self:SetNWVarProxy("ScrollMax", function(this, nwkey, oldvar, newvar) + self:SetNWVarCallback("ScrollMax", "Int", function(this, nwkey, oldvar, newvar) self:SetMaxScroll(newvar) end) diff --git a/lua/streamradio_core/classes/ui/shadow_panel.lua b/lua/streamradio_core/classes/ui/shadow_panel.lua index 4fa0130..d7481a6 100644 --- a/lua/streamradio_core/classes/ui/shadow_panel.lua +++ b/lua/streamradio_core/classes/ui/shadow_panel.lua @@ -47,6 +47,7 @@ function CLASS:CreateText(class) self.TextPanel:SetPos(0, 0) self.TextPanel:SetAlign(TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) self.TextPanel:SetName("label") + self.TextPanel:SetNWName("lbl") self.TextPanel:SetSkinIdentifyer("label") self.TextPanel.OnTextChange = function(pnl) diff --git a/lua/streamradio_core/classes/ui/textview.lua b/lua/streamradio_core/classes/ui/textview.lua index 7dda1b3..6669827 100644 --- a/lua/streamradio_core/classes/ui/textview.lua +++ b/lua/streamradio_core/classes/ui/textview.lua @@ -12,9 +12,11 @@ function CLASS:Create() self.TextPanel:SetPos(0, 0) self.TextPanel:SetAlign(TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) self.TextPanel:SetName("text") + self.TextPanel:SetNWName("txt") self.ScrollBar = self:AddPanelByClassname("scrollbar", true) self.ScrollBar:SetName("scrollbar") + self.ScrollBar:SetNWName("sbar") self.ScrollBar:SetSkinIdentifyer("scrollbar") self.ScrollBar:Hide() diff --git a/lua/streamradio_core/classes/ui/tooltip.lua b/lua/streamradio_core/classes/ui/tooltip.lua index 6ae00c1..a02227f 100644 --- a/lua/streamradio_core/classes/ui/tooltip.lua +++ b/lua/streamradio_core/classes/ui/tooltip.lua @@ -13,6 +13,7 @@ function CLASS:Create() self.TextPanel:SetSize(350, 1) self.TextPanel:SetAlign(TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) self.TextPanel:SetName("text") + self.TextPanel:SetNWName("txt") self.TextPanel:SetSkinIdentifyer("text") self.TextPanel:SetStartLine(0) diff --git a/lua/streamradio_core/client/cl_lib.lua b/lua/streamradio_core/client/cl_lib.lua index 094fd38..eaccd2b 100644 --- a/lua/streamradio_core/client/cl_lib.lua +++ b/lua/streamradio_core/client/cl_lib.lua @@ -12,7 +12,9 @@ local math = math local string = string local net = net -net.Receive( "Streamradio_Radio_PlaylistMenu", function( length ) +local LIBNet = StreamRadioLib.Net + +LIBNet.Receive("PlaylistMenu", function( length ) if ( not istable( StreamRadioLib ) ) then return end if ( not StreamRadioLib.NetReceiveFileEntry ) then return end local entity, name, type, x, y = StreamRadioLib.NetReceiveFileEntry( ) @@ -23,9 +25,9 @@ net.Receive( "Streamradio_Radio_PlaylistMenu", function( length ) fileinfo.filetype = type entity.PlaylistMenu[x] = entity.PlaylistMenu[x] or {} entity.PlaylistMenu[x][y] = fileinfo -end ) +end) -net.Receive( "Streamradio_Radio_Playlist", function( length ) +LIBNet.Receive("Playlist", function( length ) if ( not istable( StreamRadioLib ) ) then return end if ( not StreamRadioLib.NetReceivePlaylistEntry ) then return end local entity, name, x, y = StreamRadioLib.NetReceivePlaylistEntry( ) @@ -35,25 +37,25 @@ net.Receive( "Streamradio_Radio_Playlist", function( length ) fileinfo.filename = name entity.Playlist[x] = entity.Playlist[x] or {} entity.Playlist[x][y] = fileinfo -end ) +end) -local CamPos = nil -local InRenderScene = false +local g_camPos = nil +local g_inRenderScene = false hook.Add( "RenderScene", "Streamradio_CamInfo", function( origin, angles, fov ) if not StreamRadioLib then return end if not StreamRadioLib.Loaded then return end if StreamRadioLib.VR.IsActive() then - CamPos = nil + g_camPos = nil return end - if InRenderScene then return end + if g_inRenderScene then return end - InRenderScene = true - CamPos = origin - InRenderScene = false + g_inRenderScene = true + g_camPos = origin + g_inRenderScene = false end ) local g_pressed = false @@ -69,22 +71,22 @@ local function ReleaseLastRadioControl() if not g_lastradio.__IsRadio then return end if not g_lastradio.Control then return end - local pressed = g_pressed + local wasPressed = g_pressed g_pressed = false - if not pressed then return end + if not wasPressed then return end - net.Start( "Streamradio_Radio_Control" ) + local trace = StreamRadioLib.Trace( ply ) + + LIBNet.Start("Control") net.WriteBool( false ) net.SendToServer() - StreamRadioLib.Control( ply, nil, false ) + StreamRadioLib.Control( ply, trace, false ) g_lastradio = nil end local function GetPressed(ply) - local inVehicle = ply.InVehicle and ply:InVehicle() - if StreamRadioLib.GameIsPaused() then return false end @@ -112,6 +114,8 @@ local function GetPressed(ply) return false end + local inVehicle = ply.InVehicle and ply:InVehicle() + local key = StreamRadioLib.GetControlKey() if inVehicle then @@ -126,7 +130,7 @@ local function GetPressed(ply) return pressed end -hook.Add( "Think", "Streamradio_Control", function( ) +hook.Add("Think", "Streamradio_Control", function( ) if not StreamRadioLib then return end if not StreamRadioLib.Loaded then return end @@ -143,13 +147,13 @@ hook.Add( "Think", "Streamradio_Control", function( ) end g_pressed = pressed - local tr = StreamRadioLib.Trace( ply ) - if not tr then + local trace = StreamRadioLib.Trace( ply ) + if not trace then ReleaseLastRadioControl() return end - local Radio = tr.Entity + local Radio = trace.Entity if not IsValid( Radio ) then ReleaseLastRadioControl() return @@ -165,20 +169,27 @@ hook.Add( "Think", "Streamradio_Control", function( ) return end - if Radio ~= g_lastradio then + if IsValid(g_lastradio) and Radio ~= g_lastradio then ReleaseLastRadioControl() end - net.Start( "Streamradio_Radio_Control" ) + LIBNet.Start("Control") net.WriteBool( pressed ) net.SendToServer() - StreamRadioLib.Control( ply, tr, pressed ) + StreamRadioLib.Control( ply, trace, pressed ) g_lastradio = Radio -end ) +end) +function StreamRadioLib.IsCursorEnabled() + if StreamRadioLib.VR.IsActive() then + return StreamRadioLib.Settings.GetConVarValue("vr_enable_cursor") + end -function StreamRadioLib.GetCameraPos(ply) + return StreamRadioLib.Settings.GetConVarValue("enable_cursor") +end + +function StreamRadioLib.GetCameraViewPos(ply) local islocal = false if not IsValid(ply) then @@ -194,24 +205,14 @@ function StreamRadioLib.GetCameraPos(ply) return pos end - if not CamPos or not islocal then - local camera = StreamRadioLib.GetCameraEnt(ply) - if not IsValid(camera) then return nil end - - local viewpos = nil - if camera:IsPlayer() then - viewpos = camera:EyePos() - else - viewpos = camera:GetPos() - end - - return viewpos + if not g_camPos or not islocal then + local pos = StreamRadioLib.GetCameraPos(ply) + return pos end - return CamPos + return g_camPos end - function StreamRadioLib.CalcDistanceVolume( distance, max ) distance = distance or 0 local threshold = 0.25 @@ -229,7 +230,6 @@ function StreamRadioLib.CalcDistanceVolume( distance, max ) end local oldfloat = 0 -local oldvar = 0 function StreamRadioLib.PrintFloat( float, len, ... ) local float = math.Clamp( float, 0, 1 ) @@ -244,7 +244,7 @@ function StreamRadioLib.PrintFloat( float, len, ... ) local space1 = math.Round( ( oldfloat - float ) * len ) local space2 = space - space1 - 1 str = string.rep( "#", bar ) .. string.rep( " ", space1 ) .. ( math.Round( oldfloat * len ) < len and "|" or "" ) .. string.rep( " ", space2 ) - MsgC( Color( 510 * ( float ), 510 * ( 1 - ( float ) ), 0, 255 ), str, " ", string.format( "% 7.2f%%\t", float * 100 ), ..., "\n" ) + MsgC( Color( 510 * float, 510 * ( 1 - float ), 0, 255 ), str, " ", string.format( "% 7.2f%%\t", float * 100 ), ..., "\n" ) if ( float < oldfloat ) then oldfloat = oldfloat - 0.5 * RealFrameTime( ) @@ -255,61 +255,63 @@ function StreamRadioLib.PrintFloat( float, len, ... ) return str end -if ( StreamRadioLib.TestChannel ) then - StreamRadioLib.TestChannel:Remove() - StreamRadioLib.TestChannel = nil -end +do + if ( StreamRadioLib._TestChannel ) then + StreamRadioLib._TestChannel:Remove() + StreamRadioLib._TestChannel = nil + end -local function testchannel( args ) - local TestChannel = StreamRadioLib.TestChannel or StreamRadioLib.CreateOBJ("stream") + local function testchannel( args ) + local TestChannel = StreamRadioLib._TestChannel or StreamRadioLib.CreateOBJ("stream") - if not IsValid(TestChannel) then - ErrorNoHaltWithStack("Could not create the TestChannel!\n") - return - end + if not IsValid(TestChannel) then + ErrorNoHaltWithStack("Could not create the TestChannel!\n") + return + end - TestChannel:SetVolume( tonumber( args[2] ) ) - TestChannel:Play() - TestChannel:SetURL( tostring( args[1] ) ) - print( TestChannel ) + TestChannel:SetVolume( tonumber( args[2] ) ) + TestChannel:Play() + TestChannel:SetURL( tostring( args[1] ) ) + print( TestChannel ) - print("Online", TestChannel:IsOnline()) + print("Online", TestChannel:IsOnline()) - TestChannel.OnConnect = function(self, channel) - print("OnConnect", self, channel) - end + TestChannel.OnConnect = function(self, channel) + print("OnConnect", self, channel) + end - TestChannel.OnRetry = function(self, err) - print("OnRetry", self, err) - return true - end + TestChannel.OnRetry = function(self, err) + print("OnRetry", self, err) + return true + end + + TestChannel.OnError = function(self, err) + print( "OnError", self, err, StreamRadioLib.DecodeErrorCode( err ) ) + end - TestChannel.OnError = function(self, err) - print( "OnError", self, err, StreamRadioLib.DecodeErrorCode( err ) ) + StreamRadioLib._TestChannel = TestChannel end - StreamRadioLib.TestChannel = TestChannel -end + concommand.Add( "test_streamradio_channel_play", function( pl, cmd, args ) + testchannel( args or {} ) + end ) -concommand.Add( "test_streamradio_channel_play", function( pl, cmd, args ) - testchannel( args or {} ) -end ) + local function testchannel_vol( pl, cmd, args ) + if not StreamRadioLib._TestChannel then return end + args = args or {} -local function testchannel_vol( pl, cmd, args ) - if ( not StreamRadioLib.TestChannel ) then return end - args = args or {} + StreamRadioLib.TestChannel:SetVolume( tonumber( args[1] ) ) + end - StreamRadioLib.TestChannel:SetVolume( tonumber( args[1] ) ) -end + concommand.Add( "test_streamradio_channel_vol", testchannel_vol ) -concommand.Add( "test_streamradio_channel_vol", testchannel_vol ) + local function testchannel_stop( pl, cmd, args ) + if not StreamRadioLib._TestChannel then return end + args = args or {} -local function testchannel_stop( pl, cmd, args ) - if ( not StreamRadioLib.TestChannel ) then return end - args = args or {} + StreamRadioLib._TestChannel:Remove() + StreamRadioLib._TestChannel = nil + end - StreamRadioLib.TestChannel:Remove() - StreamRadioLib.TestChannel = nil + concommand.Add( "test_streamradio_channel_stop", testchannel_stop ) end - -concommand.Add( "test_streamradio_channel_stop", testchannel_stop ) diff --git a/lua/streamradio_core/client/cl_playlist_edit.lua b/lua/streamradio_core/client/cl_playlist_edit.lua index e01f2a6..f9f0869 100644 --- a/lua/streamradio_core/client/cl_playlist_edit.lua +++ b/lua/streamradio_core/client/cl_playlist_edit.lua @@ -1,5 +1,6 @@ StreamRadioLib.Editor = StreamRadioLib.Editor or {} local LIB = StreamRadioLib.Editor +local LIBNet = StreamRadioLib.Net local pairs = pairs local type = type @@ -30,7 +31,7 @@ function LIB.CreateDir( path ) if not path then return false end if path == "" then return false end - net.Start( "Streamradio_Editor_Request_Playlist" ) + LIBNet.Start("Editor_Request_Playlist") net.WriteUInt( 0, 4 ) net.WriteString( path ) net.SendToServer( ) @@ -51,7 +52,7 @@ function LIB.Save( path, DataTab ) if not ply:IsAdmin() then return false end --Start - net.Start("Streamradio_Editor_Request_Playlist") + LIBNet.Start("Editor_Request_Playlist") net.WriteUInt(1, 4) net.WriteString(path) net.SendToServer() @@ -63,7 +64,7 @@ function LIB.Save( path, DataTab ) if isstring(k) then return end --Body - net.Start("Streamradio_Editor_Request_Playlist") + LIBNet.Start("Editor_Request_Playlist") net.WriteUInt( 2, 4 ) StreamRadioLib.NetSendPlaylistEditor(v["url"], v["name"], path) net.SendToServer( ) @@ -72,7 +73,7 @@ function LIB.Save( path, DataTab ) if not ply:IsAdmin() then return false end --Finish - net.Start("Streamradio_Editor_Request_Playlist") + LIBNet.Start("Editor_Request_Playlist") net.WriteUInt(3, 4) net.WriteUInt(DataTab["format"], 8) net.WriteUInt(#DataTab, 16) @@ -92,7 +93,7 @@ function LIB.Remove(path, format) if not IsValid(ply) then return false end if not ply:IsAdmin() then return false end - net.Start("Streamradio_Editor_Request_Playlist") + LIBNet.Start("Editor_Request_Playlist") net.WriteUInt(4, 4) net.WriteUInt(format, 8) net.WriteString(path) @@ -113,7 +114,7 @@ function LIB.Copy(path_old, path_new) if not IsValid(ply) then return false end if not ply:IsAdmin() then return false end - net.Start("Streamradio_Editor_Request_Playlist") + LIBNet.Start("Editor_Request_Playlist") net.WriteUInt(5, 4) net.WriteString(path_old) net.WriteString(path_new) @@ -134,7 +135,7 @@ function LIB.Rename(path_old, path_new) if not IsValid(ply) then return false end if not ply:IsAdmin() then return false end - net.Start("Streamradio_Editor_Request_Playlist") + LIBNet.Start("Editor_Request_Playlist") net.WriteUInt(6, 4) net.WriteString(path_old) net.WriteString(path_new) @@ -157,7 +158,7 @@ function LIB.SetCallback(func, self, ...) CallbackSelf = self end -net.Receive( "Streamradio_Editor_Return_Files", function( length ) +LIBNet.Receive("Editor_Return_Files", function( length ) local path, name, type, filepath = StreamRadioLib.NetReceiveFileEditor( ) if not isfunction(CallbackFunc) then return end @@ -168,7 +169,7 @@ net.Receive( "Streamradio_Editor_Return_Files", function( length ) end end) -net.Receive( "Streamradio_Editor_Return_Playlist", function( length ) +LIBNet.Receive("Editor_Return_Playlist", function( length ) local url, name, filepath = StreamRadioLib.NetReceivePlaylistEditor( ) if not isfunction(CallbackFunc) then return end @@ -179,7 +180,7 @@ net.Receive( "Streamradio_Editor_Return_Playlist", function( length ) end end) -net.Receive( "Streamradio_Editor_Error", function( length ) +LIBNet.Receive("Editor_Error", function( length ) local path, code = StreamRadioLib.NetReceiveEditorError( ) if not isfunction(CallbackFunc) then return end @@ -222,39 +223,41 @@ local function CreateMainPanel( ) EditorPanel:Dock(FILL) end -local function ClosePanel( ply, cmd, args ) - if not IsValid(MainPanel) then - return +do + local function ClosePanel( ply, cmd, args ) + if not IsValid(MainPanel) then + return + end + + StreamRadioLib.VR.CloseMenu(MainPanel) end - StreamRadioLib.VR.CloseMenu(MainPanel) -end + local function OpenPanel( ply, cmd, args ) + if not IsValid(ply) then return end -local function OpenPanel( ply, cmd, args ) - if not IsValid(ply) then return end + if not ply:IsAdmin() then + StreamRadioLib.Msg(ply, "You must be admin to use the playlist editor.") + return + end - if not ply:IsAdmin() then - StreamRadioLib.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.") + return + end - if StreamRadioLib.VR.IsActive(ply) then - StreamRadioLib.Msg(ply, "The playlist editor is not available in VR.") - return - end + if not IsValid(MainPanel) then + CreateMainPanel() + end - if not IsValid(MainPanel) then - CreateMainPanel() - end + if not IsValid(MainPanel) then + return + end - if not IsValid(MainPanel) then - return + -- Open via VR lib regardless so we have smoother transitions without possible leftovers + StreamRadioLib.VR.MenuOpen("StreamradioPlaylistEditor", MainPanel, true) end - -- Open via VR lib regardless so we have smoother transitions without possible leftovers - StreamRadioLib.VR.MenuOpen("StreamradioPlaylistEditor", MainPanel, true) + concommand.Add("cl_streamradio_playlisteditor", OpenPanel) + concommand.Add("+cl_streamradio_playlisteditor", OpenPanel) + concommand.Add("-cl_streamradio_playlisteditor", ClosePanel) end - -concommand.Add("cl_streamradio_playlisteditor", OpenPanel) -concommand.Add("+cl_streamradio_playlisteditor", OpenPanel) -concommand.Add("-cl_streamradio_playlisteditor", ClosePanel) diff --git a/lua/streamradio_core/client/cl_surface.lua b/lua/streamradio_core/client/cl_surface.lua index 0437a33..7028d4c 100644 --- a/lua/streamradio_core/client/cl_surface.lua +++ b/lua/streamradio_core/client/cl_surface.lua @@ -29,7 +29,7 @@ local g_font_template = { outline = false } -local LoadingMat = StreamRadioLib.GetPNG("loading") +local LoadingMat = StreamRadioLib.GetCustomPNG("loading") local pi = math.pi local color_gray = Color( 160, 160, 160, 180 ) @@ -45,7 +45,7 @@ function LIB.Loading( x, y, w, h, color, cycles ) cycles = 5 end - local time = CurTime( ) + local time = RealTime( ) local midw = w / 2 local midh = h / 2 local cw = w / cycles * 2 diff --git a/lua/streamradio_core/client/cl_vgui_editor.lua b/lua/streamradio_core/client/cl_vgui_editor.lua index 5981cd9..a5c7096 100644 --- a/lua/streamradio_core/client/cl_vgui_editor.lua +++ b/lua/streamradio_core/client/cl_vgui_editor.lua @@ -1,10 +1,12 @@ +local StreamRadioLib = StreamRadioLib or {} +local LIBNet = StreamRadioLib.Net + local string = string local math = math local table = table local vgui = vgui -local surface = surface local net = net -local StreamRadioLib = StreamRadioLib or {} + local IsValid = IsValid local unpack = unpack local Derma_Query = Derma_Query @@ -451,7 +453,7 @@ local function FileMenu(self, item, path, name, filetype, parentpath) MenuItem:SetImage("icon16/folder_add.png") - --Copy/Paste (Todo) + -- Copy/Paste -- @TODO --[[ if (filetype ~= StreamRadioLib.TYPE_FOLDER and not newfile) then MenuItem = Menu:AddOption("Copy", function() @@ -2009,7 +2011,7 @@ function PANEL:SetPath( filepath, filetype, force, nofullclear ) local ListenID = StreamRadioLib.Editor.ListenToPath( filepath ) StreamRadioLib.Editor.SetCallback( self.Callback, self ) - net.Start( "Streamradio_Editor_Request_Files" ) + LIBNet.Start( "Editor_Request_Files" ) StreamRadioLib.NetSendFileEditor( filepath, "", filetype or StreamRadioLib.TYPE_FOLDER, ListenID ) net.SendToServer( ) end diff --git a/lua/streamradio_core/client/settings/general.lua b/lua/streamradio_core/client/settings/general.lua index 9a1e6cd..5c6274b 100644 --- a/lua/streamradio_core/client/settings/general.lua +++ b/lua/streamradio_core/client/settings/general.lua @@ -106,9 +106,9 @@ LIB.AddConVar("general", "coveredvolume", "cl_streamradio_coveredvolume", "0.33" max = 1, }) -LIB.AddConVar("general", "hidecursor", "cl_streamradio_hidecursor", "0", { - label = "Hide cursor", - help = "Hides the cursor on radio GUIs when set to 1. Default: 0", +LIB.AddConVar("general", "enable_cursor", "cl_streamradio_enable_cursor", "1", { + label = "Show cursor", + help = "Shows the cursor on radio GUIs when set to 1. Default: 1", type = "bool", }) @@ -148,6 +148,18 @@ local function BuildMenuPanel(CPanel) return end + CPanel:Button( + "Clear Client Stream Cache", + "cl_streamradio_cacheclear" + ) + + CPanel:Button( + "Clear Server Stream Cache (Admin only!)", + "sv_streamradio_cacheclear" + ) + + CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer()) + for i, v in ipairs(LIB.GetConVarListByNamespace("general")) do if not IsValid(v) then continue end @@ -158,21 +170,10 @@ local function BuildMenuPanel(CPanel) end CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer()) + CPanel:AddPanel(StreamRadioLib.Menu.GetOpenToolButton()) CPanel:AddPanel(StreamRadioLib.Menu.GetPlaylistEditorButton()) - CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer()) - - CPanel:Button( - "Clear Client Stream Cache", - "cl_streamradio_cacheclear" - ) - - CPanel:Button( - "Clear Server Stream Cache (Admin only!)", - "sv_streamradio_cacheclear" - ) - CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer(5)) CPanel:AddPanel(StreamRadioLib.Menu.GetFAQButton()) CPanel:AddPanel(StreamRadioLib.Menu.GetCreditsPanel()) @@ -212,10 +213,6 @@ function StreamRadioLib.RenderTargetFPS() return LIB.GetConVarValue("rendertarget_fps") end -function StreamRadioLib.IsCursorHidden() - return LIB.GetConVarValue("hidecursor") -end - function StreamRadioLib.Is3DSound() return not LIB.GetConVarValue("no3dsound") end diff --git a/lua/streamradio_core/client/settings/vr.lua b/lua/streamradio_core/client/settings/vr.lua index ac3b662..7edc1d0 100644 --- a/lua/streamradio_core/client/settings/vr.lua +++ b/lua/streamradio_core/client/settings/vr.lua @@ -17,6 +17,12 @@ LIB.AddConVar("vr", "vr_enable_trigger", "cl_streamradio_vr_enable_trigger", "1" userdata = true, }) +LIB.AddConVar("vr", "vr_enable_cursor", "cl_streamradi_vr_enable_cursor", "1", { + label = "Show cursor in VR", + help = "Shows the cursor on radio GUIs in VR when set to 1. Default: 1", + type = "bool", +}) + local function BuildMenuPanel(CPanel) if not IsValid(CPanel) then return end @@ -28,11 +34,11 @@ local function BuildMenuPanel(CPanel) if not StreamRadioLib or not StreamRadioLib.Loaded then local errorlabel = vgui.Create("DLabel") - errorlabel:SetDark(false) errorlabel:SetHighlight(true) errorlabel:SetText((StreamRadioLib.AddonPrefix or "") .. (StreamRadioLib.ErrorString or "") .. "\nThis menu could not be loaded.") errorlabel:SizeToContents() + CPanel:AddPanel(errorlabel) return @@ -40,11 +46,11 @@ local function BuildMenuPanel(CPanel) if not StreamRadioLib.VR.IsInstalled() then local errorlabel = vgui.Create("DLabel") - errorlabel:SetDark(false) errorlabel:SetHighlight(true) errorlabel:SetText((StreamRadioLib.AddonPrefix or "") .. "\nVRMod is not loaded.\n - Install VRMod to enable VR support.\n - VR Headset required!\n - VR is optional, this addon works without VR.") errorlabel:SizeToContents() + CPanel:AddPanel(errorlabel) CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer()) @@ -73,4 +79,4 @@ local function BuildMenuPanel(CPanel) CPanel:AddPanel(StreamRadioLib.Menu.GetCreditsPanel()) end -LIB.AddBuildMenuPanelHook("vr", "VR Settings", BuildMenuPanel) +LIB.AddBuildMenuPanelHook("vr", "VR Settings", BuildMenuPanel) \ No newline at end of file diff --git a/lua/streamradio_core/enum.lua b/lua/streamradio_core/enum.lua new file mode 100644 index 0000000..1fa98f2 --- /dev/null +++ b/lua/streamradio_core/enum.lua @@ -0,0 +1,198 @@ +StreamRadioLib.STREAM_PLAYMODE_STOP = 0 +StreamRadioLib.STREAM_PLAYMODE_PAUSE = 1 +StreamRadioLib.STREAM_PLAYMODE_PLAY = 2 +StreamRadioLib.STREAM_PLAYMODE_PLAY_RESTART = 3 + +StreamRadioLib.STREAM_URLTYPE_FILE = 0 +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.EDITOR_ERROR_OK = 0 +StreamRadioLib.EDITOR_ERROR_WRITE_OK = 1 +StreamRadioLib.EDITOR_ERROR_READ_OK = 2 +StreamRadioLib.EDITOR_ERROR_FILES_OK = 3 +StreamRadioLib.EDITOR_ERROR_DIR_OK = 4 +StreamRadioLib.EDITOR_ERROR_DEL_OK = 5 +StreamRadioLib.EDITOR_ERROR_COPY_OK = 6 +StreamRadioLib.EDITOR_ERROR_RENAME_OK = 7 + +StreamRadioLib.EDITOR_ERROR_WPATH = 10 +StreamRadioLib.EDITOR_ERROR_WDATA = 11 +StreamRadioLib.EDITOR_ERROR_WFORMAT = 12 +StreamRadioLib.EDITOR_ERROR_WVIRTUAL = 13 +StreamRadioLib.EDITOR_ERROR_WRITE = 14 + +StreamRadioLib.EDITOR_ERROR_DIR_WRITE = 14 +StreamRadioLib.EDITOR_ERROR_DIR_EXIST = 15 +StreamRadioLib.EDITOR_ERROR_FILE_EXIST = 16 +StreamRadioLib.EDITOR_ERROR_DEL_ACCES = 17 + +StreamRadioLib.EDITOR_ERROR_RPATH = 20 +StreamRadioLib.EDITOR_ERROR_RDATA = 21 +StreamRadioLib.EDITOR_ERROR_RFORMAT = 22 +StreamRadioLib.EDITOR_ERROR_READ = 23 + +StreamRadioLib.EDITOR_ERROR_COPY_DIR = 30 +StreamRadioLib.EDITOR_ERROR_COPY_EXIST = 31 +StreamRadioLib.EDITOR_ERROR_COPY_WRITE = 32 +StreamRadioLib.EDITOR_ERROR_COPY_READ = 33 + +StreamRadioLib.EDITOR_ERROR_RENAME_DIR = 40 +StreamRadioLib.EDITOR_ERROR_RENAME_EXIST = 41 +StreamRadioLib.EDITOR_ERROR_RENAME_WRITE = 42 +StreamRadioLib.EDITOR_ERROR_RENAME_READ = 43 + +StreamRadioLib.EDITOR_ERROR_COMMUNITY_PROTECTED = 50 +StreamRadioLib.EDITOR_ERROR_VIRTUAL_PROTECTED = 51 +StreamRadioLib.EDITOR_ERROR_NOADMIN = 252 +StreamRadioLib.EDITOR_ERROR_RESET = 253 +StreamRadioLib.EDITOR_ERROR_UNIMPLEMENTED = 254 +StreamRadioLib.EDITOR_ERROR_UNKNOWN = 255 + +local EditorErrors = { + -- Code // Error + [StreamRadioLib.EDITOR_ERROR_OK] = "OK", + [StreamRadioLib.EDITOR_ERROR_WRITE_OK] = "OK", + [StreamRadioLib.EDITOR_ERROR_READ_OK] = "OK", + [StreamRadioLib.EDITOR_ERROR_FILES_OK] = "OK", + [StreamRadioLib.EDITOR_ERROR_DIR_OK] = "OK", + [StreamRadioLib.EDITOR_ERROR_DEL_OK] = "OK", + [StreamRadioLib.EDITOR_ERROR_COPY_OK] = "OK", + [StreamRadioLib.EDITOR_ERROR_RENAME_OK] = "OK", + [StreamRadioLib.EDITOR_ERROR_WPATH] = "Invalid path!", + [StreamRadioLib.EDITOR_ERROR_WDATA] = "Invalid data!", + [StreamRadioLib.EDITOR_ERROR_WVIRTUAL] = "This virtual file is readonly!", + [StreamRadioLib.EDITOR_ERROR_WFORMAT] = "Invalid file format!\nValid formats are: %s", + [StreamRadioLib.EDITOR_ERROR_WRITE] = "Couldn't write the file!", + [StreamRadioLib.EDITOR_ERROR_DIR_WRITE] = "Couldn't create the directory!", + [StreamRadioLib.EDITOR_ERROR_DIR_EXIST] = "This directory already exists!", + [StreamRadioLib.EDITOR_ERROR_FILE_EXIST] = "This file already exists!", + [StreamRadioLib.EDITOR_ERROR_DEL_ACCES] = "Couldn't delete the file or the directory!", + [StreamRadioLib.EDITOR_ERROR_RPATH] = "Invalid path!", + [StreamRadioLib.EDITOR_ERROR_RDATA] = "Couldn't read the file!", + [StreamRadioLib.EDITOR_ERROR_RFORMAT] = "Couldn't read the file format!", + [StreamRadioLib.EDITOR_ERROR_READ] = "Couldn't read the file!", + [StreamRadioLib.EDITOR_ERROR_COPY_DIR] = "You can't copy a directory", + [StreamRadioLib.EDITOR_ERROR_COPY_EXIST] = "This file already exists!", + [StreamRadioLib.EDITOR_ERROR_COPY_WRITE] = "Couldn't create the copy!", + [StreamRadioLib.EDITOR_ERROR_COPY_READ] = "Couldn't read the source file!", + [StreamRadioLib.EDITOR_ERROR_RENAME_DIR] = "You can't rename a directory", + [StreamRadioLib.EDITOR_ERROR_RENAME_EXIST] = "This file already exists!", + [StreamRadioLib.EDITOR_ERROR_RENAME_WRITE] = "Couldn't rename/move the file!", + [StreamRadioLib.EDITOR_ERROR_RENAME_READ] = "Couldn't read the source file!", + [StreamRadioLib.EDITOR_ERROR_COMMUNITY_PROTECTED] = "You can not edit files inside the community folder!", + [StreamRadioLib.EDITOR_ERROR_VIRTUAL_PROTECTED] = "You can not add or remove files inside the virtual folders!", + [StreamRadioLib.EDITOR_ERROR_NOADMIN] = "You need admin rights!", + [StreamRadioLib.EDITOR_ERROR_UNIMPLEMENTED] = "This is not implemented!", + [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] + + if (err == StreamRadioLib.EDITOR_ERROR_WFORMAT) then + errorText = string.format(errorText, StreamRadioLib.VALID_FORMATS_EXTENSIONS_LIST) + 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/filesystem.lua b/lua/streamradio_core/filesystem.lua index ec22b3d..bcb8a1e 100644 --- a/lua/streamradio_core/filesystem.lua +++ b/lua/streamradio_core/filesystem.lua @@ -895,7 +895,6 @@ function LIB.Exists(vpath, filetype) end if not LIB.IsValidFilepath(vpath) then - callback(false, nil) return false end @@ -1071,29 +1070,31 @@ function LIB.GuessType(vpath) return nil end -local function ListFS() - MsgN("List of loaded filesystem") +do + local function ListFS() + MsgN("List of loaded filesystem") - local lineFormat = "%5s | %25s | %10s | %7s" - local topLine = string.format(lineFormat, "ID", "Name", "Type", "Active") + local lineFormat = "%5s | %25s | %10s | %7s" + local topLine = string.format(lineFormat, "ID", "Name", "Type", "Active") - MsgN(string.format(lineFormat, "ID", "Name", "Type", "Active")) - MsgN(string.rep("-", #topLine)) + MsgN(string.format(lineFormat, "ID", "Name", "Type", "Active")) + MsgN(string.rep("-", #topLine)) - for id, fs in ipairs(Filesystem.id) do - if not fs then continue end - if fs.id == g_GenericID then continue end - if fs.type == g_GenericID then continue end + for id, fs in ipairs(Filesystem.id) do + if not fs then continue end + if fs.id == g_GenericID then continue end + if fs.type == g_GenericID then continue end - local isActive = getFS(id) ~= nil - local line = string.format(lineFormat, fs.id, fs.name, fs.type, isActive and "yes" or "no") + local isActive = getFS(id) ~= nil + local line = string.format(lineFormat, fs.id, fs.name, fs.type, isActive and "yes" or "no") - MsgN(line) + MsgN(line) + end end -end -concommand.Add( "info_streamradio_playlist_filesystem_list", ListFS) + concommand.Add( "info_streamradio_playlist_filesystem_list", ListFS) +end local function updateBlacklistFromString(backlist) backlist = tostring(backlist or "") diff --git a/lua/streamradio_core/filesystem/_generic.lua b/lua/streamradio_core/filesystem/_generic.lua index 140a8e1..f0c9b85 100644 --- a/lua/streamradio_core/filesystem/_generic.lua +++ b/lua/streamradio_core/filesystem/_generic.lua @@ -6,7 +6,7 @@ end RADIOFS.name = "generic" RADIOFS.type = ":generic" -RADIOFS.icon = StreamRadioLib.GetPNGIcon("page") +RADIOFS.icon = StreamRadioLib.GetPNGIcon("table_sound", true) RADIOFS.priority = -1 diff --git a/lua/streamradio_core/filesystem/json.lua b/lua/streamradio_core/filesystem/json.lua index bdb1be4..d66334b 100644 --- a/lua/streamradio_core/filesystem/json.lua +++ b/lua/streamradio_core/filesystem/json.lua @@ -7,7 +7,7 @@ end RADIOFS.name = "JSON" RADIOFS.type = "json" RADIOFS.extension = "json" -RADIOFS.icon = StreamRadioLib.GetPNGIcon("table") +RADIOFS.icon = StreamRadioLib.GetPNGIcon("table_sound", true) RADIOFS.priority = 2000 diff --git a/lua/streamradio_core/filesystem/m3u.lua b/lua/streamradio_core/filesystem/m3u.lua index 9c15edc..3d74f76 100644 --- a/lua/streamradio_core/filesystem/m3u.lua +++ b/lua/streamradio_core/filesystem/m3u.lua @@ -7,7 +7,7 @@ end RADIOFS.name = "M3U" RADIOFS.type = "m3u" RADIOFS.extension = "m3u" -RADIOFS.icon = StreamRadioLib.GetPNGIcon("page") +RADIOFS.icon = StreamRadioLib.GetPNGIcon("table_sound", true) RADIOFS.priority = 10000 RADIOFS.default = true diff --git a/lua/streamradio_core/filesystem/pls.lua b/lua/streamradio_core/filesystem/pls.lua index b97fd8c..c1aa8f2 100644 --- a/lua/streamradio_core/filesystem/pls.lua +++ b/lua/streamradio_core/filesystem/pls.lua @@ -7,7 +7,7 @@ end RADIOFS.name = "PLS" RADIOFS.type = "pls" RADIOFS.extension = "pls" -RADIOFS.icon = StreamRadioLib.GetPNGIcon("format_pls", true) +RADIOFS.icon = StreamRadioLib.GetPNGIcon("table_sound", true) RADIOFS.priority = 9000 diff --git a/lua/streamradio_core/filesystem/vdf.lua b/lua/streamradio_core/filesystem/vdf.lua index fcfa4cc..c47cf53 100644 --- a/lua/streamradio_core/filesystem/vdf.lua +++ b/lua/streamradio_core/filesystem/vdf.lua @@ -7,7 +7,7 @@ end RADIOFS.name = "VDF" RADIOFS.type = "vdf" RADIOFS.extension = "vdf" -RADIOFS.icon = StreamRadioLib.GetPNGIcon("table") +RADIOFS.icon = StreamRadioLib.GetPNGIcon("table_sound", true) RADIOFS.priority = 1000 diff --git a/lua/streamradio_core/lib.lua b/lua/streamradio_core/lib.lua index ef0894e..af10507 100644 --- a/lua/streamradio_core/lib.lua +++ b/lua/streamradio_core/lib.lua @@ -21,100 +21,6 @@ local StreamRadioLib = StreamRadioLib local _, NetURL = StreamRadioLib.LoadSH('streamradio_core/neturl.lua') StreamRadioLib.NetURL = NetURL --- 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.EDITOR_ERROR_OK = 0 -StreamRadioLib.EDITOR_ERROR_WRITE_OK = 1 -StreamRadioLib.EDITOR_ERROR_READ_OK = 2 -StreamRadioLib.EDITOR_ERROR_FILES_OK = 3 -StreamRadioLib.EDITOR_ERROR_DIR_OK = 4 -StreamRadioLib.EDITOR_ERROR_DEL_OK = 5 -StreamRadioLib.EDITOR_ERROR_COPY_OK = 6 -StreamRadioLib.EDITOR_ERROR_RENAME_OK = 7 - -StreamRadioLib.EDITOR_ERROR_WPATH = 10 -StreamRadioLib.EDITOR_ERROR_WDATA = 11 -StreamRadioLib.EDITOR_ERROR_WFORMAT = 12 -StreamRadioLib.EDITOR_ERROR_WVIRTUAL = 13 -StreamRadioLib.EDITOR_ERROR_WRITE = 14 - -StreamRadioLib.EDITOR_ERROR_DIR_WRITE = 14 -StreamRadioLib.EDITOR_ERROR_DIR_EXIST = 15 -StreamRadioLib.EDITOR_ERROR_FILE_EXIST = 16 -StreamRadioLib.EDITOR_ERROR_DEL_ACCES = 17 - -StreamRadioLib.EDITOR_ERROR_RPATH = 20 -StreamRadioLib.EDITOR_ERROR_RDATA = 21 -StreamRadioLib.EDITOR_ERROR_RFORMAT = 22 -StreamRadioLib.EDITOR_ERROR_READ = 23 - -StreamRadioLib.EDITOR_ERROR_COPY_DIR = 30 -StreamRadioLib.EDITOR_ERROR_COPY_EXIST = 31 -StreamRadioLib.EDITOR_ERROR_COPY_WRITE = 32 -StreamRadioLib.EDITOR_ERROR_COPY_READ = 33 - -StreamRadioLib.EDITOR_ERROR_RENAME_DIR = 40 -StreamRadioLib.EDITOR_ERROR_RENAME_EXIST = 41 -StreamRadioLib.EDITOR_ERROR_RENAME_WRITE = 42 -StreamRadioLib.EDITOR_ERROR_RENAME_READ = 43 - -StreamRadioLib.EDITOR_ERROR_COMMUNITY_PROTECTED = 50 -StreamRadioLib.EDITOR_ERROR_VIRTUAL_PROTECTED = 51 -StreamRadioLib.EDITOR_ERROR_NOADMIN = 252 -StreamRadioLib.EDITOR_ERROR_RESET = 253 -StreamRadioLib.EDITOR_ERROR_UNIMPLEMENTED = 254 -StreamRadioLib.EDITOR_ERROR_UNKNOWN = 255 - -local EditorErrors = { - -- Code // Error - [StreamRadioLib.EDITOR_ERROR_OK] = "OK", - [StreamRadioLib.EDITOR_ERROR_WRITE_OK] = "OK", - [StreamRadioLib.EDITOR_ERROR_READ_OK] = "OK", - [StreamRadioLib.EDITOR_ERROR_FILES_OK] = "OK", - [StreamRadioLib.EDITOR_ERROR_DIR_OK] = "OK", - [StreamRadioLib.EDITOR_ERROR_DEL_OK] = "OK", - [StreamRadioLib.EDITOR_ERROR_COPY_OK] = "OK", - [StreamRadioLib.EDITOR_ERROR_RENAME_OK] = "OK", - [StreamRadioLib.EDITOR_ERROR_WPATH] = "Invalid path!", - [StreamRadioLib.EDITOR_ERROR_WDATA] = "Invalid data!", - [StreamRadioLib.EDITOR_ERROR_WVIRTUAL] = "This virtual file is readonly!", - [StreamRadioLib.EDITOR_ERROR_WFORMAT] = "Invalid file format!\nValid formats are: %s", - [StreamRadioLib.EDITOR_ERROR_WRITE] = "Couldn't write the file!", - [StreamRadioLib.EDITOR_ERROR_DIR_WRITE] = "Couldn't create the directory!", - [StreamRadioLib.EDITOR_ERROR_DIR_EXIST] = "This directory already exists!", - [StreamRadioLib.EDITOR_ERROR_FILE_EXIST] = "This file already exists!", - [StreamRadioLib.EDITOR_ERROR_DEL_ACCES] = "Couldn't delete the file or the directory!", - [StreamRadioLib.EDITOR_ERROR_RPATH] = "Invalid path!", - [StreamRadioLib.EDITOR_ERROR_RDATA] = "Couldn't read the file!", - [StreamRadioLib.EDITOR_ERROR_RFORMAT] = "Couldn't read the file format!", - [StreamRadioLib.EDITOR_ERROR_READ] = "Couldn't read the file!", - [StreamRadioLib.EDITOR_ERROR_COPY_DIR] = "You can't copy a directory", - [StreamRadioLib.EDITOR_ERROR_COPY_EXIST] = "This file already exists!", - [StreamRadioLib.EDITOR_ERROR_COPY_WRITE] = "Couldn't create the copy!", - [StreamRadioLib.EDITOR_ERROR_COPY_READ] = "Couldn't read the source file!", - [StreamRadioLib.EDITOR_ERROR_RENAME_DIR] = "You can't rename a directory", - [StreamRadioLib.EDITOR_ERROR_RENAME_EXIST] = "This file already exists!", - [StreamRadioLib.EDITOR_ERROR_RENAME_WRITE] = "Couldn't rename/move the file!", - [StreamRadioLib.EDITOR_ERROR_RENAME_READ] = "Couldn't read the source file!", - [StreamRadioLib.EDITOR_ERROR_COMMUNITY_PROTECTED] = "You can not edit files inside the community folder!", - [StreamRadioLib.EDITOR_ERROR_VIRTUAL_PROTECTED] = "You can not add or remove files inside the virtual folders!", - [StreamRadioLib.EDITOR_ERROR_NOADMIN] = "You need admin rights!", - [StreamRadioLib.EDITOR_ERROR_UNIMPLEMENTED] = "This is not implemented!", - [StreamRadioLib.EDITOR_ERROR_UNKNOWN] = "Unknown Error" -} - -function StreamRadioLib.DecodeEditorErrorCode( err ) - err = tonumber(err) or StreamRadioLib.EDITOR_ERROR_UNKNOWN - local errorText = EditorErrors[err] or EditorErrors[StreamRadioLib.EDITOR_ERROR_UNKNOWN] - - if (err == StreamRadioLib.EDITOR_ERROR_WFORMAT) then - errorText = string.format(errorText, StreamRadioLib.VALID_FORMATS_EXTENSIONS_LIST) - end - - return errorText -end - function StreamRadioLib.Msg(ply, msgstring) msgstring = tostring(msgstring or "") if msgstring == "" then return end @@ -202,94 +108,25 @@ function StreamRadioLib.Debug(format, ...) end end -local hashcache = {} - -local function CRCfloat32(str) +function StreamRadioLib.Hash(str) str = tostring(str or "") - local crc = util.CRC(str) - crc = tonumber(crc or 0) or 0 - - local a = #str + crc - local right = (a % 2) ~= 0 + local salt = "StreamRadioLib_Hash230603" - local bits = 24 - local shift = 32 - bits + local data = string.format( + "[%s][%s]", + salt, + str + ) - crc = bit.tobit(crc) - - if right then - crc = bit.rshift(crc, shift) - else - crc = bit.lshift(crc, shift) - crc = bit.rshift(crc, shift) - end - - crc = bit.tobit(crc) - crc = bit.band(crc, (2 ^ bits) - 1) - return crc + local hash = util.SHA256(data) + return hash end -function StreamRadioLib.Hash( str ) - str = tostring(str or "") - - if hashcache[str] then - return hashcache[str] - end - - local len = #str - local salt = "[Hash171202]" - salt = salt .. "[StreamRadioLib: '" .. str .. "']" .. salt .. "[len: '" .. len .. "']" .. salt - local hash = {} - - local rstr = string.reverse(str) - local rsalt = string.reverse(salt) - - -- There is no other 'hash' than CRC in GMod, - -- so inflate it a bit to avoid collisions. - - hash[1] = CRCfloat32(salt) - hash[2] = CRCfloat32("wdwer" .. rstr .. "jakwd") - hash[3] = CRCfloat32("sjreb" .. str .. "gkdzh") - hash[4] = CRCfloat32(rsalt) - hash[5] = CRCfloat32("heasw" .. len .. "xrjhw") - - hash[6] = CRCfloat32(table.concat({ - "awkjd", salt, hash[2], "ksdew", - "cajwe", rstr, hash[3], "nawjd", - "wakjd", str, hash[1], "vekhw", - "dklrg", rsalt, hash[4], "lecds", - })) - - local output = {} - output.raw = hash - output.hex, output.crc = StreamRadioLib.HashToHex(hash) - - hashcache[str] = output - return output -end - -function StreamRadioLib.HashToHex( h ) - h = h or {} - local hash = h.raw or h - - local hex = "" - local add = 0 - - for i = 1, 6 do - local v = tonumber(hash[i] or 0) or 0 - hex = hex .. string.format("%06x", v) - add = add + v - end - - local crc = "yhefs" .. hex .. "laxkr" .. add .. "eawts" - - crc = #crc .. crc - crc = crc .. #crc - - crc = "awedw" .. crc .. "pwfjh" - - return hex, CRCfloat32(crc) +local g_uid = 0 +function StreamRadioLib.Uid() + g_uid = (g_uid + 1) % 2 ^ 31 + return g_uid end function StreamRadioLib.NormalizeNewlines(text, nl) @@ -317,14 +154,6 @@ function StreamRadioLib.NormalizeNewlines(text, nl) return text end -function StreamRadioLib.HasWiremod() - local wmod = WireAddon or WIRE_CLIENT_INSTALLED - if not wmod then return false end - if not WireLib then return false end - - return true -end - function StreamRadioLib.IsGUIHidden(ply) if not IsValid(ply) and CLIENT then ply = LocalPlayer() @@ -391,12 +220,67 @@ function StreamRadioLib.GameIsPaused() return true end +function StreamRadioLib.GetDefaultModel() + local defaultModel = Model("models/sligwolf/grocel/radio/radio.mdl") + return defaultModel +end -local g_LastFrameRegister = {} -function StreamRadioLib.IsSameFrame(id) - local id = tostring(id or "") - local lastFrame = g_LastFrameRegister[id] +local g_cache_IsValidModel = {} +local g_cache_IsValidModelFile = {} + +function StreamRadioLib.IsValidModel(model) + model = tostring(model or "") + + if g_cache_IsValidModel[model] then + return true + end + + g_cache_IsValidModel[model] = nil + + if not StreamRadioLib.IsValidModelFile(model) then + return false + end + + util.PrecacheModel(model) + + if not util.IsValidModel(model) then + return false + end + + if not util.IsValidProp(model) then + return false + end + g_cache_IsValidModel[model] = true + return true +end + +function StreamRadioLib.IsValidModelFile(model) + model = tostring(model or "") + + if g_cache_IsValidModelFile[model] then + return true + end + + g_cache_IsValidModelFile[model] = nil + + if model == "" then + return false + end + + if IsUselessModel(model) then + return false + end + + if not file.Exists(model, "GAME") then + return false + end + + g_cache_IsValidModelFile[model] = true + return true +end + +function StreamRadioLib.FrameNumber() local frame = nil if CLIENT then @@ -405,8 +289,56 @@ function StreamRadioLib.IsSameFrame(id) frame = engine.TickCount() end + return frame +end + +function StreamRadioLib.RealFrameTime() + local frameTime = nil + + if CLIENT then + frameTime = RealFrameTime() + else + frameTime = FrameTime() + end + + return frameTime +end + +function StreamRadioLib.RealTimeFps() + local fps = StreamRadioLib.RealFrameTime() + + if fps <= 0 then + return 0 + end + + fps = 1 / fps + + return fps +end + +local g_LastFrameRegister = {} +local g_LastFrameRegisterCount = 0 + +function StreamRadioLib.IsSameFrame(id) + local id = tostring(id or "") + local lastFrame = g_LastFrameRegister[id] + + local frame = StreamRadioLib.FrameNumber() + if not lastFrame or frame ~= lastFrame then + + -- prevent the cache from overflowing + if g_LastFrameRegisterCount > 1024 then + g_LastFrameRegister = {} + g_LastFrameRegisterCount = 0 + end + g_LastFrameRegister[id] = frame + + if not lastFrame then + g_LastFrameRegisterCount = g_LastFrameRegisterCount + 1 + end + return false end @@ -430,27 +362,37 @@ function StreamRadioLib.GetCameraEnt(ply) ply = LocalPlayer() end - if not IsValid(ply) then return nil end + if not IsValid(ply) then + return nil + end local camera = ply:GetViewEntity() - if not IsValid(camera) then return ply end + if not IsValid(camera) then + return ply + end return camera end -function StreamRadioLib.GetCameraPos(ply) - if not IsValid(ply) and CLIENT then - ply = LocalPlayer() +function StreamRadioLib.GetCameraPos(ent) + if not IsValid(ent) and CLIENT then + ent = LocalPlayer() end - if StreamRadioLib.VR.IsActive(ply) then - local pos = StreamRadioLib.VR.GetCameraPos(ply) + if StreamRadioLib.VR.IsActive(ent) then + local pos = StreamRadioLib.VR.GetCameraPos(ent) return pos end - local camera = StreamRadioLib.GetCameraEnt(ply) + if StreamRadioLib.Wire.IsWireUser(ent) then + return StreamRadioLib.Wire.GetUserPos(ent) + end + + local camera = StreamRadioLib.GetCameraEnt(ent) if not IsValid(camera) then return nil end + local pos = nil + if camera:IsPlayer() then pos = camera:EyePos() else @@ -460,19 +402,24 @@ function StreamRadioLib.GetCameraPos(ply) return pos end -function StreamRadioLib.GetControlPosDir(ply) - if not IsValid(ply) and CLIENT then - ply = LocalPlayer() +function StreamRadioLib.GetControlPosDir(ent) + if not IsValid(ent) and CLIENT then + ent = LocalPlayer() + end + + if StreamRadioLib.VR.IsActive(ent) then + local pos, dir = StreamRadioLib.VR.GetControlPosDir(ent) + return pos, dir end - if StreamRadioLib.VR.IsActive(ply) then - local pos, dir = StreamRadioLib.VR.GetControlPosDir(ply) + if StreamRadioLib.Wire.IsWireUser(ent) then + local pos, dir = StreamRadioLib.Wire.GetUserPosDir(ent) return pos, dir end - local camera = StreamRadioLib.GetCameraEnt(ply) + local camera = StreamRadioLib.GetCameraEnt(ent) - if not IsValid(ply) then return nil end + if not IsValid(ent) then return nil end if not IsValid(camera) then return nil end if camera:IsPlayer() then @@ -480,21 +427,37 @@ function StreamRadioLib.GetControlPosDir(ply) dir = camera:GetAimVector() else pos = camera:GetPos() - dir = ply:GetAimVector() + + -- This is not a mistake + -- This allowes UI clicks/use via C-Menu aim + dir = ent:GetAimVector() end return pos, dir end local g_PlayerTraceCache = {} +local g_PlayerTraceCacheCount = 0 local g_PlayerTrace = {} -function StreamRadioLib.Trace(ply) - if not IsValid(ply) and CLIENT then - ply = LocalPlayer() +function StreamRadioLib.Trace(ent) + if not IsValid(ent) and CLIENT then + ent = LocalPlayer() + end + + if not IsValid(ent) then + return nil + end + + if StreamRadioLib.Wire.IsWireUser(ent) then + local trace = StreamRadioLib.Wire.WireUserTrace(ent) + return trace end - local cacheID = tostring(ply or "") + local camera = StreamRadioLib.GetCameraEnt(ent) + if not IsValid(camera) then return nil end + + local cacheID = tostring(ent or "") local cacheItem = g_PlayerTraceCache[cacheID] if cacheItem and StreamRadioLib.IsSameFrame("StreamRadioLib.Trace_" .. cacheID) then @@ -503,7 +466,7 @@ function StreamRadioLib.Trace(ply) g_PlayerTraceCache[cacheID] = nil - local pos, dir = StreamRadioLib.GetControlPosDir(ply) + local pos, dir = StreamRadioLib.GetControlPosDir(ent) if not pos then return nil @@ -513,34 +476,47 @@ function StreamRadioLib.Trace(ply) return nil end - local camera = StreamRadioLib.GetCameraEnt(ply) - if not IsValid(ply) then return nil end - if not IsValid(camera) then return nil end - local start_pos = pos local end_pos = pos + dir * 5000 g_PlayerTrace.start = start_pos g_PlayerTrace.endpos = end_pos - g_PlayerTrace.filter = function(ent) - if not IsValid(ent) then return false end - if not IsValid(ply) then return false end - if not IsValid(camera) then return false end + local entVehicle = ent.GetVehicle and ent:GetVehicle() or false + local cameraVehicle = camera.GetVehicle and camera:GetVehicle() or false - if ent == ply then return false end - if ent == camera then return false end + local tmp = {} - if ply.GetVehicle and ent == ply:GetVehicle() then return false end - if camera.GetVehicle and ent == camera:GetVehicle() then return false end + tmp[ent] = ent + tmp[camera] = camera + tmp[entVehicle] = entVehicle + tmp[cameraVehicle] = cameraVehicle - return true + filter = {} + + for _, filterEnt in pairs(tmp) do + if not IsValid(filterEnt) then continue end + + filter[#filter + 1] = filterEnt end - local tr = util.TraceLine(g_PlayerTrace) - g_PlayerTraceCache[cacheID] = tr + g_PlayerTrace.filter = filter - return tr + local trace = util.TraceLine(g_PlayerTrace) + + -- prevent the cache from overflowing + if g_PlayerTraceCacheCount > 1024 then + g_PlayerTraceCache = {} + g_PlayerTraceCacheCount = 0 + end + + g_PlayerTraceCache[cacheID] = trace + + if not cacheItem then + g_PlayerTraceCacheCount = g_PlayerTraceCacheCount + 1 + end + + return g_PlayerTraceCache[cacheID] end local g_PI = math.pi @@ -593,8 +569,9 @@ function StreamRadioLib.StarTrace(traceparams, size, edges, layers) local trace = util.TraceLine(traceparams) - --debugoverlay.Line(centerpos, trace.HitPos or endpos, 0.1, color_white, false) - --debugoverlay.Line(trace.HitPos or endpos, endpos, 0.1, color_black, false) + -- Tracers Debug + -- debugoverlay.Line(centerpos, trace.HitPos or endpos, 0.1, color_white, false) + -- debugoverlay.Line(trace.HitPos or endpos, endpos, 0.1, color_black, false) traces[#traces + 1] = trace end @@ -604,11 +581,21 @@ end local g_mat_cache = {} -function StreamRadioLib.GetPNG(name) +function StreamRadioLib.GetCustomPNGPath(name) if SERVER then return nil end if not name then return nil end local path = "3dstreamradio/" .. name .. ".png" + return path +end + +function StreamRadioLib.GetCustomPNG(name) + if SERVER then return nil end + if not name then return nil end + + local path = StreamRadioLib.GetCustomPNGPath(name) + if not path then return nil end + local mat = g_mat_cache[path] if mat then @@ -619,17 +606,27 @@ function StreamRadioLib.GetPNG(name) return mat end -function StreamRadioLib.GetPNGIcon(name, custom) +function StreamRadioLib.GetPNGIconPath(name, custom) if SERVER then return nil end if not name then return nil end local prepath = "icon16/" .. name if custom then - return StreamRadioLib.GetPNG(prepath) + return StreamRadioLib.GetCustomPNGPath(prepath) end local path = prepath .. ".png" + return path +end + +function StreamRadioLib.GetPNGIcon(name, custom) + if SERVER then return nil end + if not name then return nil end + + local path = StreamRadioLib.GetPNGIconPath(name, custom) + if not path then return nil end + local mat = g_mat_cache[path] if mat then @@ -693,81 +690,118 @@ function StreamRadioLib.SetSkinTableProperty(tab, hierarchy, property, value) return tab end -local g_pressed = false -local g_lastradio = nil -local g_addonnamespace = "_3dstreamradio" - -local function getplayervar(ply, name) - local arr = ply[g_addonnamespace] - if not arr then return nil end - return arr[name] -end - -local function setplayervar(ply, name, var) - ply[g_addonnamespace] = ply[g_addonnamespace] or {} - ply[g_addonnamespace][name] = var -end - -local function ReleaseLastRadioControl(ply) - local LastRadio = getplayervar(ply, "lastusedradio") - setplayervar(ply, "lastusedradio", nil) +local function ReleaseLastRadioControl(ply, trace, userEntity) + local LastRadio = userEntity._3dstreamradio_lastusedradio + userEntity._3dstreamradio_lastusedradio = nil if not IsValid( LastRadio ) then return end if not LastRadio.__IsRadio then return end if not LastRadio.Control then return end - LastRadio:Control( ply, tr, false ) + LastRadio:Control( ply, trace, false, userEntity ) end -local function CanUseRadio(ply, Radio) - if not IsValid( Radio ) then return false end - if not Radio.__IsRadio then return false end - if not Radio.Control then return false end +function StreamRadioLib.CanUseRadio(ply, radio, userEntity) + if not IsValid( ply ) then return false end + if not IsValid( radio ) then return false end + if not radio.__IsRadio then return false end + if not radio.Control then return false end + if not radio.CanControl then return false end - -- Support for prop protections - if Radio.CPPICanUse then - local use = Radio:CPPICanUse( ply ) or false - if not use then return false end + if not IsValid(userEntity) then + userEntity = ply end - if SERVER then - local use = hook.Run( "PlayerUse", ply, Radio ) - if not use then return false end + local use = radio:CanControl(ply, userEntity) + if not use then + return false end return true end -function StreamRadioLib.Control( ply, tr, keydown ) +function StreamRadioLib.Control( ply, trace, keydown, userEntity ) if not IsValid( ply ) then return end - tr = tr or StreamRadioLib.Trace( ply ) - if not tr then - ReleaseLastRadioControl( ply ) + if not IsValid(userEntity) then + userEntity = ply + end + + if not trace then + ReleaseLastRadioControl( ply, nil, userEntity ) return end if not keydown then - ReleaseLastRadioControl( ply ) + ReleaseLastRadioControl( ply, trace, userEntity ) return end - local Radio = tr.Entity - local LastRadio = getplayervar(ply, "lastusedradio") + local Radio = trace.Entity + local LastRadio = userEntity._3dstreamradio_lastusedradio + + if not StreamRadioLib.CanUseRadio( ply, Radio, userEntity ) then + ReleaseLastRadioControl( ply, trace, userEntity ) + return + end if Radio ~= LastRadio then - ReleaseLastRadioControl( ply ) + ReleaseLastRadioControl( ply, trace, userEntity ) end - if not CanUseRadio( ply, Radio ) then - ReleaseLastRadioControl( ply ) + local rv = Radio:Control( ply, trace, true, userEntity ) + userEntity._3dstreamradio_lastusedradio = Radio + + return rv +end + +function StreamRadioLib.TabControl( ply, trace, userEntity ) + if not IsValid(ply) and CLIENT then + ply = LocalPlayer() + end + + if not IsValid(ply) then + return + end + + if not trace then return end - local rv = Radio:Control( ply, tr, true ) - setplayervar(ply, "lastusedradio", Radio) + if not IsValid(userEntity) then + userEntity = ply + end - return rv + local ent = trace.Entity + if not IsValid( ent ) then + return + end + + if not ent.__IsRadio then + return + end + + local name = tostring(ent) .. "_" .. tostring(ply) .. "_TabControl" + + trace = table.Copy(trace) + + StreamRadioLib.Control(ply, trace, true, userEntity) + + StreamRadioLib.Timer.NextFrame(name, function() + if not IsValid(ply) then + return + end + + if not IsValid(ent) then + return + end + + if not IsValid(userEntity) then + return + end + + StreamRadioLib.Control(ply, trace, false, userEntity) + end) end local g_PlayerCache = {} @@ -787,7 +821,7 @@ function StreamRadioLib.GetPlayerId(ply) end if game.SinglePlayer() then - return 'LOCAL_CLIENT' + return "LOCAL_CLIENT" end if SERVER and not ply:IsConnected() then @@ -812,7 +846,7 @@ function StreamRadioLib.GetPlayerFromId(id) end if game.SinglePlayer() then - if id == 'LOCAL_CLIENT' then + if id == "LOCAL_CLIENT" then if not IsValid(g_LocalPlayer) then g_LocalPlayer = player.GetHumans()[1] end @@ -863,94 +897,6 @@ function StreamRadioLib.IsPlayerNetworkable(plyOrId) return IsValid(_GetPlayerFromId(plyOrId)) 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 - -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 ) - local function toUnicode(s) local s1, s2, s3, s4 = s:byte( 1, -1 ) s = {s1, s2, s3, s4} diff --git a/lua/streamradio_core/load.lua b/lua/streamradio_core/load.lua index ffa39d2..db601c6 100644 --- a/lua/streamradio_core/load.lua +++ b/lua/streamradio_core/load.lua @@ -34,9 +34,10 @@ ok = ok and loadSH("streamradio_core/timedpairs.lua") ok = ok and loadSH("streamradio_core/api.lua") ok = ok and loadSH("streamradio_core/lib.lua") +ok = ok and loadSH("streamradio_core/enum.lua") ok = ok and loadSH("streamradio_core/json.lua") -ok = ok and loadSH("streamradio_core/net.lua") ok = ok and loadSH("streamradio_core/network.lua") +ok = ok and loadSH("streamradio_core/net.lua") ok = ok and loadSH("streamradio_core/timer.lua") ok = ok and loadSH("streamradio_core/tool.lua") ok = ok and loadSH("streamradio_core/http.lua") @@ -47,6 +48,7 @@ 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/vr.lua") +ok = ok and loadSH("streamradio_core/wire.lua") ok = ok and loadSH("streamradio_core/shoutcast.lua") ok = ok and loadSV("streamradio_core/server/sv_lib.lua") diff --git a/lua/streamradio_core/models.lua b/lua/streamradio_core/models.lua index c399968..fd481e8 100644 --- a/lua/streamradio_core/models.lua +++ b/lua/streamradio_core/models.lua @@ -171,8 +171,7 @@ function LIB.RegisteredModels( ) for model, setting in pairs( Models ) do if model == "default" then continue end - if IsUselessModel( model ) then continue end - if not file.Exists( model, "GAME" ) then continue end + if not StreamRadioLib.IsValidModelFile(model) then continue end if setting.HiddenInTool then continue end ToolModels[model] = setting.tool or {} diff --git a/lua/streamradio_core/models/portal_turret.lua b/lua/streamradio_core/models/portal_turret.lua index 692d199..2ad778d 100644 --- a/lua/streamradio_core/models/portal_turret.lua +++ b/lua/streamradio_core/models/portal_turret.lua @@ -281,8 +281,6 @@ function RADIOMDL:Speaker(ent, speakerlevel) self.SL_LastTickTime = now - (self.SL_LastTick or now) self.SL_LastTick = now - local FrameTimeConst = self.SL_LastTickTime - if not speakerlevel then self:CloseWings(true) return diff --git a/lua/streamradio_core/net.lua b/lua/streamradio_core/net.lua index f602183..82fd6b8 100644 --- a/lua/streamradio_core/net.lua +++ b/lua/streamradio_core/net.lua @@ -1,99 +1,99 @@ StreamRadioLib.Net = {} local LIB = StreamRadioLib.Net - -local pairs = pairs -local type = type -local IsValid = IsValid -local file = file -local table = table -local string = string -local util = util -local player = player -local ents = ents - -if ( SERVER ) then - util.AddNetworkString( "Streamradio_Show_Functions" ) - - util.AddNetworkString( "Streamradio_Radio_Control" ) - util.AddNetworkString( "Streamradio_Radio_PlaylistMenu" ) - util.AddNetworkString( "Streamradio_Radio_Playlist" ) - - util.AddNetworkString( "Streamradio_Radio_MasterCallback" ) - util.AddNetworkString( "Streamradio_Radio_ClientError" ) - - util.AddNetworkString( "Streamradio_Editor_Return_Files" ) - util.AddNetworkString( "Streamradio_Editor_Return_Playlist" ) - util.AddNetworkString( "Streamradio_Editor_Request_Files" ) - util.AddNetworkString( "Streamradio_Editor_Request_Playlist" ) - util.AddNetworkString( "Streamradio_Editor_Error" ) -end - -local HashRegister = {} -HashRegister.To = {} -HashRegister.From = {} - -function LIB.ToHash( str ) - str = tostring(str or "") - local cachedhash = HashRegister.To[str] or {} - local cachedhashstr = cachedhash.hex - - if cachedhashstr and HashRegister.From[cachedhashstr] == str then - return cachedhash +local LIBNetwork = StreamRadioLib.Network + +LIBNetwork.AddNetworkString("Control") +LIBNetwork.AddNetworkString("PlaylistMenu") +LIBNetwork.AddNetworkString("Playlist") + +LIBNetwork.AddNetworkString("Editor_Return_Files") +LIBNetwork.AddNetworkString("Editor_Return_Playlist") +LIBNetwork.AddNetworkString("Editor_Request_Files") +LIBNetwork.AddNetworkString("Editor_Request_Playlist") +LIBNetwork.AddNetworkString("Editor_Error") + +do + -- Automaticly generated network string table map + + LIBNetwork.AddNetworkString("classsystem_listen") + LIBNetwork.AddNetworkString("LoadError") + LIBNetwork.AddNetworkString("ClientToolHook") + LIBNetwork.AddNetworkString("clientstate") + LIBNetwork.AddNetworkString("str") + LIBNetwork.AddNetworkString("str/Volume") + LIBNetwork.AddNetworkString("str/URL") + LIBNetwork.AddNetworkString("str/PlayMode") + LIBNetwork.AddNetworkString("str/Loop") + LIBNetwork.AddNetworkString("str/Name") + LIBNetwork.AddNetworkString("str/MasterTime") + LIBNetwork.AddNetworkString("skin") + LIBNetwork.AddNetworkString("skinrequest") + LIBNetwork.AddNetworkString("skintoserver") + LIBNetwork.AddNetworkString("g") + LIBNetwork.AddNetworkString("gui_sk") + LIBNetwork.AddNetworkString("gui_sk/Hash") + LIBNetwork.AddNetworkString("data") + LIBNetwork.AddNetworkString("datarequest") + LIBNetwork.AddNetworkString("streamreset_on_sv") + LIBNetwork.AddNetworkString("streamreset_on_cl") + LIBNetwork.AddNetworkString("g/m") + LIBNetwork.AddNetworkString("g/m/brw") + LIBNetwork.AddNetworkString("g/m/brw/lstp") + LIBNetwork.AddNetworkString("g/m/brw/lstp/sbar") + LIBNetwork.AddNetworkString("g/m/brw/lstp/sbar/ScrollPos") + LIBNetwork.AddNetworkString("g/m/brw/lstp/sbar/ScrollMax") + LIBNetwork.AddNetworkString("g/m/brw/lstp/ListGridX") + LIBNetwork.AddNetworkString("g/m/brw/lstp/ListGridY") + LIBNetwork.AddNetworkString("g/m/brw/lstp/IsHorizontal") + LIBNetwork.AddNetworkString("g/m/brw/lstp/Hash") + LIBNetwork.AddNetworkString("g/m/brw/lstp/Path") + LIBNetwork.AddNetworkString("g/m/brw/lstpv") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/sbar") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/sbar/ScrollPos") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/sbar/ScrollMax") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/ListGridX") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/ListGridY") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/IsHorizontal") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/Hash") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/Path") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/PathType") + LIBNetwork.AddNetworkString("g/m/brw/lstpv/Error") + LIBNetwork.AddNetworkString("g/m/brw/PlaylistOpened") + LIBNetwork.AddNetworkString("g/m/ply") + LIBNetwork.AddNetworkString("g/m/PlayerOpened") + LIBNetwork.AddNetworkString("g/m/ply/ctrl") + LIBNetwork.AddNetworkString("g/m/ply/ctrl/PlaylistEnabled") +end + +function LIB.Receive(name, ...) + name = LIBNetwork.TransformNWIdentifier(name) + return net.Receive(name, ...) +end + +function LIB.Start(name, ...) + name = LIBNetwork.TransformNWIdentifier(name) + return net.Start(name, ...) +end + +function LIB.SendIdentifier(identifier) + local identifierId = 0 + + if isstring(identifier) then + identifierId = LIBNetwork.NetworkStringToID(identifier) + + if identifierId == 0 then + ErrorNoHaltWithStack("Identifier '" .. identifier .. "' was not added via util.AddNetworkString() yet.") + end end - local hash = StreamRadioLib.Hash(str) - HashRegister.To[str] = hash - - local hashstr = hash.hex - HashRegister.From[hashstr] = str - - return hash + net.WriteUInt(identifierId, 12) end -function LIB.FromHash( hash ) - if not hash then return nil end - - local hashstr = hash.hex - if not hashstr then return nil end - - local str = HashRegister.From[hashstr] - - if not str then return nil end - if not HashRegister.To[str] then return nil end - - return str -end - -function LIB.SendHash( hash ) - if not hash then return end - hash = hash.raw or hash - - for i = 1, 6 do - net.WriteUInt( hash[i] or 0, 24 ) - end -end - -function LIB.ReceiveHash() - local hash = {} - hash.raw = {} - - for i = 1, 6 do - hash.raw[i] = net.ReadUInt( 24 ) or 0 - end - - hash.hex, hash.crc = StreamRadioLib.HashToHex( hash ) - return hash -end +function LIB.ReceiveIdentifier() + local identifierId = net.ReadUInt(12) or 0 + local identifier = LIBNetwork.NetworkIDToString(identifierId) -function LIB.SendStringHash( str ) - local hash = LIB.ToHash(str) - LIB.SendHash(hash) -end - -function LIB.ReceiveStringHash() - local hash = LIB.ReceiveHash() - local str = LIB.FromHash(hash) - return str + return identifier end function LIB.SendListEntry( text, iconid ) @@ -108,37 +108,6 @@ function LIB.ReceiveListEntry( ) return text, iconid end - -function StreamRadioLib.NetSendMasterCallback( ent, code ) - if ( not IsValid( ent ) ) then return end - if ( not code ) then return end - - net.WriteEntity( ent ) - net.WriteUInt( code, 8 ) -end - -function StreamRadioLib.NetReceiveMasterCallback( ) - local ent = net.ReadEntity( ) - local code = net.ReadUInt( 8 ) or 0 - - return ent, code -end - -function StreamRadioLib.NetSendClientError( ent, code ) - if ( not IsValid( ent ) ) then return end - if ( not code ) then return end - - net.WriteEntity( ent ) - net.WriteUInt( code, 16 ) -end - -function StreamRadioLib.NetReceiveClientError( ) - local ent = net.ReadEntity( ) - local code = net.ReadUInt( 16 ) or 0 - - return ent, code -end - function StreamRadioLib.NetSendEditorError( path, code ) if ( not path ) then return end if ( not code ) then return end diff --git a/lua/streamradio_core/network.lua b/lua/streamradio_core/network.lua index fa474c3..daceba4 100644 --- a/lua/streamradio_core/network.lua +++ b/lua/streamradio_core/network.lua @@ -1,6 +1,12 @@ StreamRadioLib.Network = {} +StreamRadioLib.Network.Debug = {} local LIB = StreamRadioLib.Network +local StreamRadioLib = StreamRadioLib + +local g_addonprefix = "3DStreamRadio/" +local g_maxIdentifierLen = 44 + local g_types = { ["Angle"] = { check = function(value) @@ -8,7 +14,8 @@ local g_types = { end, convert = nil, dtmaxcount = 32, - dtonly = true, + nwGetter = "GetNWAngle", + nwSetter = "SetNWAngle", }, ["Bool"] = { @@ -19,7 +26,8 @@ local g_types = { return tobool(v) end, dtmaxcount = 32, - dtonly = true, + nwGetter = "GetNWBool", + nwSetter = "SetNWBool", }, ["Entity"] = { @@ -28,7 +36,8 @@ local g_types = { end, convert = nil, dtmaxcount = 32, - dtonly = true, + nwGetter = "GetNWEntity", + nwSetter = "SetNWEntity", }, ["Float"] = { @@ -37,7 +46,8 @@ local g_types = { end, convert = nil, dtmaxcount = 32, - dtonly = true, + nwGetter = "GetNWFloat", + nwSetter = "SetNWFloat", }, ["Int"] = { @@ -48,7 +58,8 @@ local g_types = { return math.floor(v) end, dtmaxcount = 32, - dtonly = true, + nwGetter = "GetNWInt", + nwSetter = "SetNWInt", }, ["String"] = { @@ -57,7 +68,8 @@ local g_types = { end, convert = nil, dtmaxcount = 0, - dtonly = true, + nwGetter = "GetNWString", + nwSetter = "SetNWString", }, ["Vector"] = { @@ -66,19 +78,87 @@ local g_types = { end, convert = nil, dtmaxcount = 32, - dtonly = true, + nwGetter = "GetNWVector", + nwSetter = "SetNWVector", }, } -local _R = debug.getregistry() -local ENTITY = _R.Entity +function LIB.TransformNWIdentifier(str) + str = tostring(str or "") + assert(str ~= "", "identifier is empty") -local g_hasnw2 = isfunction(ENTITY.SetNW2Var) -local g_addonprefix = "3dstreamradio/" + str = g_addonprefix .. str -local function GetDTNetworkVarInternalIndex(name) - name = tostring(name or "") - return "_SRNWLib_[" .. name .. "]" + local strLen = #str + assert(strLen < g_maxIdentifierLen, "identifier '" .. str .. "' must shorter than " .. g_maxIdentifierLen .. " chars, got " .. strLen .. " chars") + + return str +end + +function LIB.UntransformNWIdentifier(str) + str = tostring(str or "") + assert(str ~= "", "identifier is empty") + + local strLen = #str + assert(strLen < g_maxIdentifierLen, "identifier '" .. str .. "' must shorter than " .. g_maxIdentifierLen .. " chars, got " .. strLen .. " chars") + + str = string.gsub(str, "^" .. string.PatternSafe(g_addonprefix), "", 1) + return str +end + +function LIB.AddNetworkStringRaw(str) + str = tostring(str or "") + assert(str ~= "", "identifier is empty") + + local strLen = #str + assert(strLen < g_maxIdentifierLen, "identifier '" .. str .. "' must shorter than " .. g_maxIdentifierLen .. " chars, got " .. strLen .. " chars") + + local currentId = util.NetworkStringToID(str) or 0 + + if CLIENT then + return currentId + end + + if currentId ~= 0 then + return currentId + end + + util.AddNetworkString(str) + + local newId = util.NetworkStringToID(str) or 0 + assert(newId ~= 0, "Could not add network string for '" .. str .. "'! Is network string table is full?") + assert(util.NetworkIDToString(newId) == str, "Could not add network string at ID '" .. newId .. "' for '" .. newId .. "'! Is network string table is full?") + + return newId +end + +function LIB.AddNetworkString(str) + str = LIB.TransformNWIdentifier(str) + + local id = LIB.AddNetworkStringRaw(str) + return id +end + +function LIB.NetworkStringToID(str) + str = LIB.TransformNWIdentifier(str) + + local id = util.NetworkStringToID(str) or 0 + return id +end + +function LIB.NetworkIDToString(id) + id = tonumber(id or 0) or 0 + if id == 0 then + return nil + end + + local str = util.NetworkIDToString(id) + if not str then + return nil + end + + str = LIB.UntransformNWIdentifier(str) + return str end local function DTNetworkVarExists(ent, name) @@ -113,197 +193,81 @@ local function CanAddDTNetworkVar(ent, datatype, name, ...) return true end -local function GetDTNetworkVarInternal(ent, name, defaultvalue, ...) - local index = GetDTNetworkVarInternalIndex(name) - return LIB.GetDTNetworkVar(ent, index, defaultvalue, ...) -end - -local function SetDTNetworkVarInternal(ent, name, value, ...) - if CLIENT then return end - local index = GetDTNetworkVarInternalIndex(name) - return LIB.SetDTNetworkVar(ent, index, value, ...) -end +do + local loopThis = function(datatype, dtd) + local checkfunc = dtd.check + local convertfunc = dtd.convert + local nwGetter = dtd.nwGetter + local nwSetter = dtd.nwSetter -local function AddDTNetworkVarInternal(ent, datatype, name, ...) - local index = GetDTNetworkVarInternalIndex(name) - return LIB.AddDTNetworkVar(ent, datatype, index, ...) -end + local nwGetterFunc = function(ent, key, defaultvalue, ...) + key = LIB.TransformNWIdentifier(key) -local function SetDTVarCallbackInternal(ent, name, func) - local index = GetDTNetworkVarInternalIndex(name) - return LIB.SetDTVarCallback(ent, index, func, name) -end - -for datatype, dtd in pairs(g_types) do - local checkfunc = dtd.check - local convertfunc = dtd.convert - local dtonly = dtd.dtonly - - LIB["SetNW" .. datatype] = function(ent, key, value, ...) - local prefix = g_addonprefix .. "/" - key = prefix .. tostring(key or "") - - assert(checkfunc(value), "invalid datatype of value at '" .. key .. "', '" .. datatype .. "' was expected, got '" .. type(value) .. "'", 2) - value = convertfunc and convertfunc(value) or value - - if not dtonly then - if AddDTNetworkVarInternal(ent, datatype, key) then - SetDTNetworkVarInternal(ent, key, value) - return + if defaultvalue ~= nil then + assert(checkfunc(defaultvalue), "invalid datatype of defaultvalue at '" .. key .. "', '" .. datatype .. "' was expected, got '" .. type(defaultvalue) .. "'") + defaultvalue = convertfunc and convertfunc(defaultvalue) or defaultvalue end - end - - - if CLIENT then return end - - local nw = g_hasnw2 and "SetNW2" or "SetNW" - ent[nw .. datatype](ent, key, value, ...) - end - - LIB["GetNW" .. datatype] = function(ent, key, defaultvalue, ...) - local prefix = g_addonprefix .. "/" - key = prefix .. tostring(key or "") - if defaultvalue ~= nil then - assert(checkfunc(defaultvalue), "invalid datatype of defaultvalue at '" .. key .. "', '" .. datatype .. "' was expected, got '" .. type(defaultvalue) .. "'", 2) - defaultvalue = convertfunc and convertfunc(defaultvalue) or defaultvalue - end - - if not dtonly then - if AddDTNetworkVarInternal(ent, datatype, key) then - return GetDTNetworkVarInternal(ent, key, defaultvalue) + local r = ent[nwGetter](ent, key, defaultvalue, ...) + if r == nil and defaultvalue ~= nil then + r = defaultvalue end - end - local nw = g_hasnw2 and "GetNW2" or "GetNW" - - local r = ent[nw .. datatype](ent, key, defaultvalue, ...) - if r == nil then - r = defaultvalue + return r end - return r - end -end - -function LIB.SetNWHash(ent, key, h) - if CLIENT then return end - - local setvector = LIB.SetNWVector - if not setvector then return end - - h = h or {} - - local hash = {} - hash.raw = h.raw or h - - for i = 1, 6 do - hash.raw[i] = hash.raw[i] or 0 - end - - hash.hex, hash.crc = StreamRadioLib.HashToHex( hash ) - - local crc = hash.crc - local raw = hash.raw - - local v1 = Vector(crc , raw[1], raw[4]) - local v2 = Vector(raw[2], crc , raw[5]) - local v3 = Vector(raw[3], raw[6], crc ) - - setvector(ent, key .. "-hashv1", v1) - setvector(ent, key .. "-hashv2", v2) - setvector(ent, key .. "-hashv3", v3) - - local test = LIB.GetNWHash(ent, key) -end - -function LIB.GetNWHash(ent, key) - local getvector = LIB.GetNWVector - if not getvector then return {} end - - ent.StreamRadioNWHashes = ent.StreamRadioNWHashes or {} - ent.StreamRadioNWHashes[key] = ent.StreamRadioNWHashes[key] or {} - - local hash = {} - hash.raw = {} - hash.crc = {} - - local v1 = getvector(ent, key .. "-hashv1") - local v2 = getvector(ent, key .. "-hashv2") - local v3 = getvector(ent, key .. "-hashv3") + local nwSetterFunc = function(ent, key, value, ...) + if CLIENT then + return nil + end - hash.raw[1] = v1.y - hash.raw[2] = v2.x - hash.raw[3] = v3.x - hash.raw[4] = v1.z - hash.raw[5] = v2.z - hash.raw[6] = v3.y + key = LIB.TransformNWIdentifier(key) + value = convertfunc and convertfunc(value) or value - local crc1 = v1.x - local crc2 = v2.y - local crc3 = v3.z + assert(checkfunc(value), "invalid datatype of value at '" .. key .. "', '" .. datatype .. "' was expected, got '" .. type(value) .. "'") - hash.hex, hash.crc = StreamRadioLib.HashToHex( hash ) + return ent[nwSetter](ent, key, value, ...) + end - if hash.crc ~= crc1 then - return ent.StreamRadioNWHashes[key] - end + LIB["GetNW" .. datatype] = nwGetterFunc + LIB["SetNW" .. datatype] = nwSetterFunc - if hash.crc ~= crc2 then - return ent.StreamRadioNWHashes[key] + dtd.nwSetterFunc = nwSetterFunc + dtd.nwGetterFunc = nwGetterFunc end - if hash.crc ~= crc3 then - return ent.StreamRadioNWHashes[key] + for datatype, dtd in pairs(g_types) do + loopThis(datatype, dtd) end - - ent.StreamRadioNWHashes[key] = hash - return ent.StreamRadioNWHashes[key] end -function LIB.SetNWHashProxy(ent, key, func, ...) - assert(isfunction(func), "argument #3 must be a function!") - +function LIB.GetNWVar(ent, datatype, key, defaultvalue) key = tostring(key or "") - ent.StreamRadioNWHashes = ent.StreamRadioNWHashes or {} - - for i = 1, 3 do - local postfix = "-hashv" .. i - - local proxyfunc = function(this, nwkey, ov, nv, ...) - if ov == nv then return end - - nwkey = string.gsub(nwkey, string.PatternSafe(postfix) .. "$", "", 1 ) - - local oldvar = this.StreamRadioNWHashes[nwkey] or {} - local newvar = LIB.GetNWHash(this, nwkey) + datatype = tostring(datatype or "") - if not newvar.hex then return end - if oldvar.hex == newvar.hex then return end + assert(g_types[datatype] ~= nil, "argument #2 must be a valid datatype! Got '" .. datatype .. "'") + assert(key ~= "", "argument #3 is an invalid name!") - return func(this, nwkey, oldvar, newvar, ...) - end + local dtd = g_types[datatype or ""] + if not dtd then return defaultvalue end + if not dtd.nwGetterFunc then return defaultvalue end - LIB.SetNWVarProxy(ent, key .. postfix, proxyfunc, ...) - end + local r = dtd.nwGetterFunc(ent, key, defaultvalue) + return r end -function LIB.SetNWVarProxy(ent, key, func, ...) - assert(isfunction(func), "argument #3 must be a function!") - local prefix = g_addonprefix .. "/" - key = prefix .. tostring(key or "") - - local proxyfunc = function(this, nwkey, ov, nv, ...) - if ov == nv then return end +function LIB.SetNWVar(ent, datatype, key, value) + key = tostring(key or "") + datatype = tostring(datatype or "") - nwkey = string.gsub(nwkey, "^" .. string.PatternSafe(prefix), "", 1 ) - return func(this, nwkey, ov, nv, ...) - end + assert(g_types[datatype] ~= nil, "argument #2 must be a valid datatype! Got '" .. datatype .. "'") + assert(key ~= "", "argument #3 is an invalid name!") - SetDTVarCallbackInternal(ent, key, proxyfunc) + local dtd = g_types[datatype or ""] + if not dtd then return end + if not dtd.nwSetterFunc then return end - local setNWVarProxy = g_hasnw2 and ent.SetNW2VarProxy or ent.SetNWVarProxy - setNWVarProxy(ent, key, proxyfunc, ...) + dtd.nwSetterFunc(ent, key, value) end function LIB.SetupDataTables(ent) @@ -324,7 +288,30 @@ function LIB.SetupDataTables(ent) end end -function LIB.Pull(ent) +local function pullNWVars(ent) + local NW = ent.StreamRadioNW + if not NW then return end + if not NW.Names then return end + + local loopThis = function(name, data) + if not data.callback then return end + if not data.datatype then return end + + local oldvalue = data.oldvalue + local newvalue = LIB.GetNWVar(ent, data.datatype, name) + + if oldvalue == newvalue then return end + StreamRadioLib.CatchAndErrorNoHalt(data.callback, ent, name, oldvalue, newvalue) + + NW.Names[name].oldvalue = newvalue + end + + for name, data in pairs(NW.Names) do + loopThis(name, data) + end +end + +local function pullDTVars(ent) local NW = ent.StreamRadioDT if not NW then return end if not NW.Names then return end @@ -332,7 +319,6 @@ function LIB.Pull(ent) local loopThis = function(name, data) if not data.callback then return end - if not data.callbackname then return end if not data.datatype then return end if not DTNetworkVarExists(ent, name) then return end @@ -340,7 +326,7 @@ function LIB.Pull(ent) local newvalue = LIB.GetDTNetworkVar(ent, name) if oldvalue == newvalue then return end - data.callback(ent, data.callbackname, oldvalue, newvalue) + StreamRadioLib.CatchAndErrorNoHalt(data.callback, ent, name, oldvalue, newvalue) NW.Names[name].oldvalue = newvalue end @@ -350,6 +336,11 @@ function LIB.Pull(ent) end end +function LIB.Pull(ent) + pullNWVars(ent) + pullDTVars(ent) +end + function LIB.AddDTNetworkVar(ent, datatype, name, ...) name = tostring(name or "") datatype = tostring(datatype or "") @@ -357,10 +348,7 @@ function LIB.AddDTNetworkVar(ent, datatype, name, ...) assert(g_types[datatype], "argument #1 is an invalid datatype!") assert(name ~= "", "argument #2 is an invalid name!") - - -- local exists = DTNetworkVarExists(ent, name) - - -- if DTNetworkVarExists(ent, name) then return true end + if DTNetworkVarExists(ent, name) then return true end if not CanAddDTNetworkVar(ent, datatype, name) then return false end ent.StreamRadioDT = ent.StreamRadioDT or {} @@ -432,7 +420,12 @@ function LIB.SetDTNetworkVar(ent, name, value) func(ent, value) end -function LIB.SetDTVarCallback(ent, name, func, callbackname) +function LIB.SetDTVarCallback(ent, name, func) + name = tostring(name or "") + + assert(name ~= "", "argument #2 is an invalid name!") + assert(isfunction(func), "argument #3 must be a function!") + ent.StreamRadioDT = ent.StreamRadioDT or {} local NW = ent.StreamRadioDT @@ -441,11 +434,30 @@ function LIB.SetDTVarCallback(ent, name, func, callbackname) local data = NW.Names[name] data.callback = func - data.callbackname = callbackname or name data.oldvalue = nil end -function LIB.DumpDTNetworkStats(ent) +function LIB.SetNWVarCallback(ent, datatype, name, func) + datatype = tostring(datatype or "") + name = tostring(name or "") + + assert(g_types[datatype] ~= nil, "argument #2 must be a valid datatype! Got '" .. datatype .. "'") + assert(name ~= "", "argument #3 is an invalid name!") + assert(isfunction(func), "argument #4 must be a function!") + + ent.StreamRadioNW = ent.StreamRadioNW or {} + local NW = ent.StreamRadioNW + + NW.Names = NW.Names or {} + NW.Names[name] = NW.Names[name] or {} + local data = NW.Names[name] + + data.callback = func + data.datatype = datatype + data.oldvalue = nil +end + +function LIB.Debug.DumpDTNetworkStats(ent) local NW = ent.StreamRadioDT or {} local Count = NW.Count or {} @@ -471,3 +483,125 @@ function LIB.DumpDTNetworkStats(ent) print("======================") end + +function LIB.Debug.DumpDTNetworkVars(ent) + local NW = ent.StreamRadioDT or {} + + print("DumpDTNetworkVars of: " .. tostring(ent)) + print("======================") + + for name, data in pairs(NW.Names) do + local line = string.format("%s (%s) [%i] | %s", name, data.datatype, data.index, LIB.GetDTNetworkVar(ent, name)) + print(line) + end + + print("======================") +end + +local function getAddonStringTable() + local max = 4096 + local result = {} + + for k = 1, max do + local name = util.NetworkIDToString(k) + + if not name then + break + end + + if not string.find(name, "^" .. string.PatternSafe(g_addonprefix)) then + continue + end + + result[#result + 1] = { + index = k, + name = name, + } + end + + return result +end + +function LIB.Debug.DumpDTNetworkStringTable() + print("DumpDTNetworkStringTable") + print("======================") + + local max = 4096 + local stringTable = getAddonStringTable() + local countAssigned = 0 + local countAddon = #stringTable + + for k = 1, max do + local name = util.NetworkIDToString(k) + + if not name then + break + end + + countAssigned = countAssigned + 1 + end + + for i, value in ipairs(stringTable) do + local index = value.index + local name = value.name + + print(index, name) + end + + local fractionMax = countAddon / max + local fractionAssigned = countAddon / countAssigned + + print("======================") + print(countAddon .. " of " .. max .. " slots total, " .. (math.Round(fractionMax, 3) * 100) .. '%') + print(countAddon .. " of " .. countAssigned .. " slots assigned, " .. (math.Round(fractionAssigned, 3) * 100) .. '%') + print("======================") +end + +function LIB.Debug.DumpDTNetworkStringTableCode() + print("DumpDTNetworkStringTableCode") + print("======================") + + print("") + print("local LIBNetwork = StreamRadioLib.Network") + print("") + print("do") + print(" -- Automaticly generated network string table map") + print("") + + local stringTable = getAddonStringTable() + + for i, value in ipairs(stringTable) do + local name = LIB.UntransformNWIdentifier(value.name) + + local code = string.format(" LIBNetwork.AddNetworkString(\"%s\")", name) + print(code) + end + + print("end") + print("") + print("======================") +end + +do + local concommandFlags = FCVAR_NONE + + if CLIENT then + concommandFlags = FCVAR_CHEAT + end + + concommand.Add("debug_streamradio_dump_nwstringtable", function(ply) + if IsValid(ply) and not ply:IsAdmin() then + return + end + + LIB.Debug.DumpDTNetworkStringTable() + end, nil, nil, concommandFlags) + + concommand.Add("debug_streamradio_dump_nwstringtable_code", function(ply) + if IsValid(ply) and not ply:IsAdmin() then + return + end + + LIB.Debug.DumpDTNetworkStringTableCode() + end, nil, nil, concommandFlags) +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 0d46505..7aaa742 100644 --- a/lua/streamradio_core/server/sv_lib.lua +++ b/lua/streamradio_core/server/sv_lib.lua @@ -1,17 +1,8 @@ local pairs = pairs -local type = type local IsValid = IsValid local CreateConVar = CreateConVar -local unpack = unpack -local tonumber = tonumber -local tostring = tostring -local file = file -local table = table -local util = util -local player = player -local ents = ents -local math = math -local debug = debug + +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" ) @@ -19,7 +10,6 @@ local RebuildCommunityPlaylists = CreateConVar( "sv_streamradio_rebuildplaylists StreamRadioLib._AllowSpectrumCountCache = nil --- Todo: Settings for Spectrum size function StreamRadioLib.AllowSpectrum() if not WireAddon then return false end if not StreamRadioLib.HasBass then return false end @@ -29,9 +19,9 @@ function StreamRadioLib.AllowSpectrum() if max < 0 then return true end if game.SinglePlayer() then return true end - local radios_count = StreamRadioLib._AllowSpectrumCountCache or 0 + local radios_count = StreamRadioLib._AllowSpectrumCountCache or 0 - if radios_count <= 0 then + if radios_count <= 0 then for ent, _ in pairs(StreamRadioLib.SpawnedRadios or {}) do if not IsValid(ent) then continue end if not ent.__IsRadio then continue end @@ -39,7 +29,7 @@ function StreamRadioLib.AllowSpectrum() radios_count = radios_count + 1 end - end + end StreamRadioLib._AllowSpectrumCountCache = radios_count return radios_count < max @@ -92,6 +82,7 @@ function StreamRadioLib.GetRebuildCommunityPlaylistsMode() return mode end -net.Receive( "Streamradio_Radio_Control", function( len, ply ) - StreamRadioLib.Control( ply, nil, net.ReadBool() ) -end ) +LIBNet.Receive("Control", function( len, ply ) + local trace = StreamRadioLib.Trace( ply ) + StreamRadioLib.Control(ply, trace, net.ReadBool()) +end) diff --git a/lua/streamradio_core/server/sv_playlist_edit.lua b/lua/streamradio_core/server/sv_playlist_edit.lua index 2a0995e..a8a3e3e 100644 --- a/lua/streamradio_core/server/sv_playlist_edit.lua +++ b/lua/streamradio_core/server/sv_playlist_edit.lua @@ -1,15 +1,10 @@ StreamRadioLib.Editor = StreamRadioLib.Editor or {} local LIB = StreamRadioLib.Editor +local LIBNet = StreamRadioLib.Net local pairs = pairs -local type = type local IsValid = IsValid -local file = file -local table = table local string = string -local util = util -local player = player -local ents = ents local net = net local ListenPath = "" @@ -17,12 +12,14 @@ function LIB.GetPath( ) return ListenPath end -local function StopLoading( ply, cmd, args ) - if ( IsValid( ply ) and not ply:IsAdmin( ) ) then return end - LIB.Reset( ply ) -end +do + local function StopLoading( ply, cmd, args ) + if ( IsValid( ply ) and not ply:IsAdmin( ) ) then return end + LIB.Reset( ply ) + end -concommand.Add( "sv_streamradio_playlisteditor_reset", StopLoading ) + concommand.Add( "sv_streamradio_playlisteditor_reset", StopLoading ) +end local OK_CODES = { [StreamRadioLib.EDITOR_ERROR_OK] = true, @@ -45,7 +42,7 @@ local function EditorLog(ply, msgstring, ...) end local function EditorError( ply, path, code ) - net.Start( "Streamradio_Editor_Error" ) + LIBNet.Start("Editor_Error") StreamRadioLib.NetSendEditorError( path, code ) if ( IsValid( ply ) ) then @@ -195,7 +192,7 @@ function LIB.OpenFolder( ply, path ) if not IsValid(ply) then return false end if not istable(v) then return true end - net.Start("Streamradio_Editor_Return_Files") + LIBNet.Start("Editor_Return_Files") StreamRadioLib.NetSendFileEditor(v.path, v.file, v.type, ListenPath) net.Send(ply) end, function() @@ -225,7 +222,7 @@ function LIB.OpenFile( ply, path, type ) if not IsValid(ply) then return false end if not istable(v) then return true end - net.Start("Streamradio_Editor_Return_Playlist") + LIBNet.Start("Editor_Return_Playlist") StreamRadioLib.NetSendPlaylistEditor( v.url, v.name, ListenPath ) net.Send(ply) end, function() @@ -234,7 +231,7 @@ function LIB.OpenFile( ply, path, type ) end) end -net.Receive( "Streamradio_Editor_Request_Files", function( len, ply ) +LIBNet.Receive( "Editor_Request_Files", function( len, ply ) if not IsValid(ply) then return false end if not ply:IsAdmin() then return false end local path, name, type, parentpath = StreamRadioLib.NetReceiveFileEditor( ) @@ -247,7 +244,7 @@ net.Receive( "Streamradio_Editor_Request_Files", function( len, ply ) end end ) -net.Receive( "Streamradio_Editor_Request_Playlist", function( len, ply ) +LIBNet.Receive( "Editor_Request_Playlist", function( len, ply ) if not IsValid(ply) then return end if not ply:IsAdmin() then return EditorError(ply, path, StreamRadioLib.EDITOR_ERROR_NOADMIN) end diff --git a/lua/streamradio_core/server/sv_res.lua b/lua/streamradio_core/server/sv_res.lua index d071669..003adfa 100644 --- a/lua/streamradio_core/server/sv_res.lua +++ b/lua/streamradio_core/server/sv_res.lua @@ -25,92 +25,94 @@ local function CopyFiles( dir ) end end -local function Rebuild_Playlists( ply, cmd, args ) - if not StreamRadioLib then return end - if not StreamRadioLib.Loaded then return end +do + local function Rebuild_Playlists( ply, cmd, args ) + if not StreamRadioLib then return end + 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." ) + if not ( not ply or ( IsValid( ply ) and ply:IsAdmin( ) ) ) then + StreamRadioLib.Msg( ply, "You need to be an admin to rebuild the playlists." ) - return + return + end + + CopyFiles( StreamRadioLib.DataDirectory .. "/playlists" ) + + StreamRadioLib.Editor.Reset( ply ) + local msgstring = StreamRadioLib.AddonPrefix .. "Playlists rebuilt" + StreamRadioLib.Msg( ply, msgstring ) end - CopyFiles( StreamRadioLib.DataDirectory .. "/playlists" ) + local function Rebuild_CommunityPlaylists( ply, cmd, args ) + if not StreamRadioLib then return end + if not StreamRadioLib.Loaded then return end - StreamRadioLib.Editor.Reset( ply ) - local msgstring = StreamRadioLib.AddonPrefix .. "Playlists rebuilt" - StreamRadioLib.Msg( ply, msgstring ) -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." ) -local function Rebuild_CommunityPlaylists( ply, cmd, args ) - if not StreamRadioLib then return end - if not StreamRadioLib.Loaded then return end + 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." ) + CopyFiles( StreamRadioLib.DataDirectory .. "/playlists/community" ) - return + StreamRadioLib.Editor.Reset( ply ) + local msgstring = StreamRadioLib.AddonPrefix .. "Community playlists rebuilt" + StreamRadioLib.Msg( ply, msgstring ) end - CopyFiles( StreamRadioLib.DataDirectory .. "/playlists/community" ) + concommand.Add( "sv_streamradio_rebuildplaylists", Rebuild_Playlists ) + concommand.Add( "sv_streamradio_rebuildplaylists_community", Rebuild_CommunityPlaylists ) - StreamRadioLib.Editor.Reset( ply ) - local msgstring = StreamRadioLib.AddonPrefix .. "Community playlists rebuilt" - StreamRadioLib.Msg( ply, msgstring ) -end + local function Reset_Playlists( ply, cmd, args ) + if not StreamRadioLib then return end + if not StreamRadioLib.Loaded then return end + if not StreamRadioLib.DataDirectory then return end -concommand.Add( "sv_streamradio_rebuildplaylists", Rebuild_Playlists ) -concommand.Add( "sv_streamradio_rebuildplaylists_community", Rebuild_CommunityPlaylists ) + if not ( not ply or ( IsValid( ply ) and ply:IsAdmin( ) ) ) then + StreamRadioLib.Msg( ply, "You need to be an admin to reset the playlists." ) -local function Reset_Playlists( ply, cmd, args ) - if not StreamRadioLib then return end - if not StreamRadioLib.Loaded then return end - if not StreamRadioLib.DataDirectory then return end + 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." ) + local deleted = StreamRadioLib.DeleteFolder( StreamRadioLib.DataDirectory .. "/playlists" ) + if not deleted then + local msgstring = StreamRadioLib.AddonPrefix .. "Playlists could not be rebuilt" + StreamRadioLib.Msg( ply, msgstring ) + return + end - return - end - - local deleted = StreamRadioLib.DeleteFolder( StreamRadioLib.DataDirectory .. "/playlists" ) - if not deleted then - local msgstring = StreamRadioLib.AddonPrefix .. "Playlists could not be rebuilt" + local msgstring = StreamRadioLib.AddonPrefix .. "Playlists deleted" StreamRadioLib.Msg( ply, msgstring ) - return + Rebuild_Playlists( ply, cmd, args ) end - local msgstring = StreamRadioLib.AddonPrefix .. "Playlists deleted" - StreamRadioLib.Msg( ply, msgstring ) - Rebuild_Playlists( ply, cmd, args ) -end + local function Reset_CommunityPlaylists( ply, cmd, args ) + if not StreamRadioLib then return end + if not StreamRadioLib.Loaded then return end + if not StreamRadioLib.DataDirectory then return end -local function Reset_CommunityPlaylists( ply, cmd, args ) - if not StreamRadioLib then return end - if not StreamRadioLib.Loaded then return end - 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." ) - 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." ) + return + end - return - end + 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 ) + return + end - local deleted = StreamRadioLib.DeleteFolder( StreamRadioLib.DataDirectory .. "/playlists/community" ) - if not deleted then - local msgstring = StreamRadioLib.AddonPrefix .. "Community playlists could not be rebuilt" + local msgstring = StreamRadioLib.AddonPrefix .. "Community playlists deleted" StreamRadioLib.Msg( ply, msgstring ) - return + Rebuild_CommunityPlaylists( ply, cmd, args ) end - local msgstring = StreamRadioLib.AddonPrefix .. "Community playlists deleted" - StreamRadioLib.Msg( ply, msgstring ) - Rebuild_CommunityPlaylists( ply, cmd, args ) + concommand.Add( "sv_streamradio_resetplaylists", Reset_Playlists ) + concommand.Add( "sv_streamradio_resetplaylists_community", Reset_CommunityPlaylists ) end -concommand.Add( "sv_streamradio_resetplaylists", Reset_Playlists ) -concommand.Add( "sv_streamradio_resetplaylists_community", Reset_CommunityPlaylists ) - StreamRadioLib.Timedcall( function() if not StreamRadioLib.Loaded then return end diff --git a/lua/streamradio_core/tool.lua b/lua/streamradio_core/tool.lua index ef29b2f..bd207a3 100644 --- a/lua/streamradio_core/tool.lua +++ b/lua/streamradio_core/tool.lua @@ -1,5 +1,7 @@ StreamRadioLib.Tool = StreamRadioLib.Tool or {} local LIB = StreamRadioLib.Tool +local LIBNetwork = StreamRadioLib.Network +local LIBNet = StreamRadioLib.Net function LIB.GetTool(ply) if not IsValid(ply) then @@ -172,13 +174,13 @@ function LIB.Setup(toolobj) function toolobj:GetFallbackTrace() local ply = self:GetOwner() - local tr = util.GetPlayerTrace(ply) - tr.mask = bit.bor( CONTENTS_SOLID, CONTENTS_MOVEABLE, CONTENTS_MONSTER, CONTENTS_WINDOW, CONTENTS_DEBRIS, CONTENTS_GRATE, CONTENTS_AUX ) + local trace = util.GetPlayerTrace(ply) + trace.mask = bit.bor( CONTENTS_SOLID, CONTENTS_MOVEABLE, CONTENTS_MONSTER, CONTENTS_WINDOW, CONTENTS_DEBRIS, CONTENTS_GRATE, CONTENTS_AUX ) - local trace = util.TraceLine(tr) - if not self:IsValidTrace(trace) then return nil end + local result = util.TraceLine(trace) + if not self:IsValidTrace(result) then return nil end - return trace + return result end function toolobj:SetClientInfo( name, var ) @@ -287,6 +289,16 @@ function LIB.Setup(toolobj) return checkbox end + function toolobj:AddComboBox( panel, command, descbool ) + local checkbox = panel:ComboBox(StreamRadioLib.Tool.GetLocale(self, command), self.Mode .. "_" .. command) + + if descbool then + checkbox:SetTooltip(StreamRadioLib.Tool.GetLocale(self, command .. ".desc")) + end + + return checkbox + end + function toolobj:AddTextEntry( panel, command, descbool ) local textentry = panel:TextEntry(StreamRadioLib.Tool.GetLocale(self, command), self.Mode .. "_" .. command) @@ -420,25 +432,20 @@ function LIB.Setup(toolobj) end local callback = nil -local nwlib = StreamRadioLib.Net - -function LIB.RegisterClientToolHook(tool, hook) - local toolname = tool.Mode - - nwlib.ToHash(toolname) - nwlib.ToHash(hook) +function LIB.RegisterClientToolHook(tool, toolhook) if SERVER then - util.AddNetworkString( "Streamradio_Tool" ) + LIBNetwork.AddNetworkString("ClientToolHook") return end if callback then return end + callback = function() local ply = LocalPlayer() - local nwtoolname = nwlib.ReceiveStringHash() - local bwhook = nwlib.ReceiveStringHash() + local nwtoolname = net.ReadString() + local bwhook = net.ReadString() local tool = ply:GetWeapon("gmod_tool") @@ -456,17 +463,17 @@ function LIB.RegisterClientToolHook(tool, hook) func(toolobj) end - net.Receive("Streamradio_Tool", callback) + LIBNet.Receive("ClientToolHook", callback) end -function LIB.CallClientToolHook(tool, hook) +function LIB.CallClientToolHook(tool, toolhook) if CLIENT then return end local owner = tool:GetOwner() local toolname = tool.Mode - net.Start("Streamradio_Tool") - nwlib.SendStringHash(toolname) - nwlib.SendStringHash(hook) + LIBNet.Start("ClientToolHook") + net.WriteString(toolname) + net.WriteString(toolhook) net.Send(owner) end diff --git a/lua/streamradio_core/vr.lua b/lua/streamradio_core/vr.lua index f363c8f..b70fd77 100644 --- a/lua/streamradio_core/vr.lua +++ b/lua/streamradio_core/vr.lua @@ -135,16 +135,16 @@ function LIB.GetRadioTouched() return false end - local tr = LIB.TraceHand() - if not tr then + local trace = LIB.TraceHand() + if not trace then return false end - if not tr.Hit then + if not trace.Hit then return false end - local ent = tr.Entity + local ent = trace.Entity if not IsValid(ent) then return false @@ -194,16 +194,22 @@ function LIB.TraceHand() g_PlayerHandTrace.endpos = end_pos local ply = LocalPlayer() + local plyVehicle = ply.GetVehicle and ply:GetVehicle() or false - g_PlayerHandTrace.filter = g_PlayerHandTrace.filter or (function(ent) - if not IsValid(ent) then return false end - if not IsValid(ply) then return false end + local tmp = {} - if ent == ply then return false end + tmp[ply] = ply + tmp[plyVehicle] = plyVehicle - if ply.GetVehicle and ent == ply:GetVehicle() then return false end - return true - end) + filter = {} + + for _, filterEnt in pairs(tmp) do + if not IsValid(filterEnt) then continue end + + filter[#filter + 1] = filterEnt + end + + g_PlayerHandTrace.filter = filter util.TraceLine(g_PlayerHandTrace) g_PlayerHandTraceCache = g_PlayerHandTrace.output diff --git a/lua/streamradio_core/wire.lua b/lua/streamradio_core/wire.lua new file mode 100644 index 0000000..799ca4f --- /dev/null +++ b/lua/streamradio_core/wire.lua @@ -0,0 +1,205 @@ +StreamRadioLib.Wire = StreamRadioLib.Wire or {} +local LIB = StreamRadioLib.Wire + +local g_HasWiremod = nil + +function LIB.HasWiremod() + if g_HasWiremod ~= nil then + return g_HasWiremod + end + + g_HasWiremod = false + + local wmod = _G.WireAddon or _G.WIRE_CLIENT_INSTALLED + if not wmod then return false end + if not _G.WireLib then return false end + + g_HasWiremod = true + return true +end + +local function findCallingWireUserEntityFunction() + for i = 1, 100 do + local data = debug.getinfo(i, "fS") + if not data then + break + end + + local func = data.func + if not func then + break + end + + local short_src = data.short_src + if not short_src then + break + end + + short_src = string.lower(short_src) + + if not string.find(short_src, "entities/gmod_wire_user.lua", 1, true) then + continue + end + + data.index = i + return data + end + + return nil +end + +local function findCallingWireUserEntityLocals(data) + if not data then + return nil + end + + local locals = {} + + local i = 1 + + while true do + local name, value = debug.getlocal(data.index, i) + if not name then + break + end + + locals[name] = value + i = i + 1 + end + + return locals +end + +function LIB.FindCallingWireUserEntityData() + if not LIB.HasWiremod() then + return nil + end + + local data = findCallingWireUserEntityFunction() + local locals = findCallingWireUserEntityLocals(data) + if not locals then + return nil + end + + local userEntity = locals["self"] + if not LIB.IsWireUser(userEntity) then + return nil + end + + local trace = locals["trace"] + if not trace then + return nil + end + + local ent = trace.Entity + if not IsValid( ent ) then + return nil + end + + if not ent.__IsRadio then + return nil + end + + local result = { + userEntity = userEntity, + trace = table.Copy(trace), + } + + return result +end + +function LIB.IsWireUser(ent) + if not LIB.HasWiremod() then + return false + end + + if not IsValid(ent) then + return false + end + + if not ent.IsWire then + return false + end + + if ent:GetClass() ~= "gmod_wire_user" then + return false + end + + return true +end + +function LIB.GetUserPos(ent) + if not LIB.IsWireUser(ent) then + return nil + end + + pos = ent:GetPos() +end + +function LIB.GetUserPosDir(ent) + if not LIB.IsWireUser(ent) then + return nil, nil + end + + local pos = ent:GetPos() + local dir = ent:GetUp() + + return pos, dir +end + +local g_WireUserTraceCache = {} +local g_WireUserTraceCacheCount = 0 +local g_WireUserTrace = {} + +function LIB.WireUserTrace(ent) + if not LIB.IsWireUser(ent) then + return nil + end + + local cacheID = tostring(ent or "") + local cacheItem = g_WireUserTraceCache[cacheID] + + if cacheItem and StreamRadioLib.IsSameFrame("StreamRadioLib.Wire.WireUserTrace_" .. cacheID) then + return cacheItem + end + + g_WireUserTraceCache[cacheID] = nil + + local pos, dir = LIB.GetUserPosDir(ent) + + if not pos then + return nil + end + + if not dir then + return nil + end + + local len = ent:GetBeamLength() + if not len then + return nil + end + + local start_pos = pos + local end_pos = pos + dir * len + + g_WireUserTrace.start = start_pos + g_WireUserTrace.endpos = end_pos + g_WireUserTrace.filter = ent + + local trace = util.TraceLine(g_WireUserTrace) + + -- prevent the cache from overflowing + if g_WireUserTraceCacheCount > 1024 then + g_WireUserTraceCache = {} + g_WireUserTraceCacheCount = 0 + end + + g_WireUserTraceCache[cacheID] = trace + + if not cacheItem then + g_WireUserTraceCacheCount = g_WireUserTraceCacheCount + 1 + end + + return g_WireUserTraceCache[cacheID] +end \ No newline at end of file diff --git a/lua/weapons/gmod_tool/stools/streamradio.lua b/lua/weapons/gmod_tool/stools/streamradio.lua index b679d73..1a60134 100644 --- a/lua/weapons/gmod_tool/stools/streamradio.lua +++ b/lua/weapons/gmod_tool/stools/streamradio.lua @@ -13,23 +13,27 @@ end cleanup.Register( TOOL.Mode ) -TOOL.ClientConVar["model"] = "models/sligwolf/grocel/radio/radio.mdl" -TOOL.ClientConVar["noadvwire"] = "1" -TOOL.ClientConVar["nodisplay"] = "0" -TOOL.ClientConVar["noinput"] = "0" -TOOL.ClientConVar["loop"] = "0" -TOOL.ClientConVar["playlistloop"] = "1" +TOOL.ClientConVar["model"] = StreamRadioLib.GetDefaultModel() +TOOL.ClientConVar["streamurl"] = "" +TOOL.ClientConVar["play"] = "1" TOOL.ClientConVar["3dsound"] = "1" TOOL.ClientConVar["volume"] = "1" TOOL.ClientConVar["radius"] = "1200" -TOOL.ClientConVar["play"] = "1" -TOOL.ClientConVar["streamurl"] = "" +TOOL.ClientConVar["playbackloopmode"] = tostring(StreamRadioLib.PLAYBACK_LOOP_MODE_PLAYLIST) + +TOOL.ClientConVar["nodisplay"] = "0" +TOOL.ClientConVar["noinput"] = "0" +TOOL.ClientConVar["noadvwire"] = "1" TOOL.ClientConVar["freeze"] = "1" TOOL.ClientConVar["weld"] = "1" TOOL.ClientConVar["worldweld"] = "0" TOOL.ClientConVar["nocollide"] = "1" +local g_icon_playbackloopmode_none = StreamRadioLib.GetPNGIconPath("arrow_not_refresh", true) +local g_icon_playbackloopmode_song = StreamRadioLib.GetPNGIconPath("arrow_refresh") +local g_icon_playbackloopmode_playlist = StreamRadioLib.GetPNGIconPath("table_refresh") + if StreamRadioLib and StreamRadioLib.Loaded then StreamRadioLib.Tool.AddLocale(TOOL, "name", "Radio Spawner") StreamRadioLib.Tool.AddLocale(TOOL, "desc", "Spawns a Stream Radio") @@ -55,9 +59,11 @@ if StreamRadioLib and StreamRadioLib.Loaded then 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, "loop", "Enable loop") - StreamRadioLib.Tool.AddLocale(TOOL, "playlistloop", "Enable playlist track switch") - StreamRadioLib.Tool.AddLocale(TOOL, "playlistloop.desc", "If set, the radio will play the next track in the playlist when the current one ends.") + 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, "volume", "Volume:") StreamRadioLib.Tool.AddLocale(TOOL, "radius", "Radius:") @@ -67,7 +73,6 @@ if StreamRadioLib and StreamRadioLib.Loaded then StreamRadioLib.Tool.AddLocale(TOOL, "weld", "Weld") StreamRadioLib.Tool.AddLocale(TOOL, "worldweld", "Weld to world") StreamRadioLib.Tool.AddLocale(TOOL, "nocollide", "Nocollide") - StreamRadioLib.Tool.AddLocale(TOOL, "radiosettings", "Radio settings:") StreamRadioLib.Tool.AddLocale(TOOL, "spawnsettings", "Spawn settings:") StreamRadioLib.Tool.Setup(TOOL) @@ -118,16 +123,25 @@ function TOOL:BuildToolPanel(CPanel) CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer(5)) - self:AddLabel( CPanel, "radiosettings", false ) self:AddURLTextEntry( CPanel, "streamurl", false ) self:AddCheckbox( CPanel, "play", true ) self:AddCheckbox( CPanel, "nodisplay", false ) self:AddCheckbox( CPanel, "noinput", true ) self:AddCheckbox( CPanel, "noadvwire", true ) - self:AddCheckbox( CPanel, "loop", false ) - self:AddCheckbox( CPanel, "playlistloop", true ) self:AddCheckbox( CPanel, "3dsound", false ) + CPanel:AddPanel(StreamRadioLib.Menu.GetSpacer(5)) + + local PlaybackLoopModeComboBox = self:AddComboBox(CPanel, "playbackloopmode", true) + PlaybackLoopModeComboBox:SetSortItems(false) + PlaybackLoopModeComboBox:AddChoice(StreamRadioLib.Tool.GetLocale(self, "playbackloopmode.option.none"), StreamRadioLib.PLAYBACK_LOOP_MODE_NONE, false, g_icon_playbackloopmode_none) + PlaybackLoopModeComboBox:AddSpacer() + 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)) + local VolumeNumSlider = self:AddNumSlider( CPanel, "volume", false ) VolumeNumSlider:SetMin( 0 ) VolumeNumSlider:SetMax( 1 ) @@ -256,10 +270,8 @@ function TOOL:GetSettings() 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.StreamLoop = self:GetClientBool("loop") - - settings.PlaylistLoop = self:GetClientBool("playlistloop") settings.DisableDisplay = self:GetClientBool("nodisplay") settings.DisableInput = self:GetClientBool("noinput") settings.DisableAdvancedOutputs = self:GetClientBool("noadvwire") @@ -278,11 +290,8 @@ function TOOL:SetSettings(settings) 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("loop", settings.StreamLoop) - - self:SetClientBool("playlistloop", settings.PlaylistLoop) self:SetClientBool("nodisplay", settings.DisableDisplay) self:SetClientBool("noinput", settings.DisableInput) self:SetClientBool("noadvwire", settings.DisableAdvancedOutputs) @@ -405,7 +414,6 @@ function TOOL:Reload( trace ) if not self.ToolLibLoaded then return false end if not self:IsValidTrace(trace) then return false end - local ply = self:GetOwner( ) local ent = trace.Entity if not IsValid(ent) then return false end @@ -414,9 +422,7 @@ function TOOL:Reload( trace ) if ent:GetPhysicsObjectCount() > 1 then return false end -- No ragdolls! local model = ent:GetModel() - if IsUselessModel(model) then return false end - if not util.IsValidModel(model) then return false end - if not util.IsValidProp(model) then return false end + if not StreamRadioLib.IsValidModel(model) then return false end if CLIENT then return true end @@ -455,9 +461,12 @@ function TOOL:Think( ) if not IsValid(self.GhostEntity) then self:MakeGhostEntity( Model(model), - Vector(), - Angle(0, 0, 0) + vector_origin, + angle_zero ) + end + + if not IsValid(self.GhostEntity) then return end @@ -470,11 +479,10 @@ function TOOL:Think( ) end function TOOL:GetModel( ) - local model = "models/sligwolf/grocel/radio/radio.mdl" - local modelcheck = self:GetClientInfo("model") + local model = self:GetClientInfo("model") - if util.IsValidModel(modelcheck) and util.IsValidProp(modelcheck) and not IsUselessModel(modelcheck) then - return modelcheck + if not StreamRadioLib.IsValidModel(model) then + return StreamRadioLib.GetDefaultModel() end return model diff --git a/lua/weapons/gmod_tool/stools/streamradio_gui_color_global.lua b/lua/weapons/gmod_tool/stools/streamradio_gui_color_global.lua index 58b1d97..90f2a28 100644 --- a/lua/weapons/gmod_tool/stools/streamradio_gui_color_global.lua +++ b/lua/weapons/gmod_tool/stools/streamradio_gui_color_global.lua @@ -396,7 +396,6 @@ function TOOL:AddModeList( panel ) col3:SetMaxWidth(70) col4:SetFixedWidth(40) - local olddatastring = "" local lines = {} listpanel.NextConVarCheck = 0 @@ -409,9 +408,9 @@ function TOOL:AddModeList( panel ) local changed = false if ( input.IsMouseDown( MOUSE_LEFT ) ) then return end - if ( listpanel.NextConVarCheck > SysTime() ) then return end + if ( listpanel.NextConVarCheck > RealTime() ) then return end - listpanel.NextConVarCheck = SysTime() + 0.2 + listpanel.NextConVarCheck = RealTime() + 0.2 for varname, line in pairs(lines) do if not self.SkinVars[varname] then continue end @@ -426,9 +425,6 @@ function TOOL:AddModeList( panel ) local color = data[varname] if not color then continue end - -- local checked = activecheckbox:GetChecked() - -- line:SetSortValue(line._activeindex, checked and 1 or 0) - local oldcolor = colortile:GetColor() if color == oldcolor then continue end diff --git a/lua/weapons/gmod_tool/stools/streamradio_gui_color_individual.lua b/lua/weapons/gmod_tool/stools/streamradio_gui_color_individual.lua index abf475f..50e3558 100644 --- a/lua/weapons/gmod_tool/stools/streamradio_gui_color_individual.lua +++ b/lua/weapons/gmod_tool/stools/streamradio_gui_color_individual.lua @@ -194,7 +194,6 @@ function TOOL:AddModeList( panel ) col3:SetMaxWidth(70) col4:SetFixedWidth(40) - local olddatastring = "" local lines = {} listpanel.NextConVarCheck = 0 @@ -207,9 +206,9 @@ function TOOL:AddModeList( panel ) local changed = false if ( input.IsMouseDown( MOUSE_LEFT ) ) then return end - if ( listpanel.NextConVarCheck > SysTime() ) then return end + if ( listpanel.NextConVarCheck > RealTime() ) then return end - listpanel.NextConVarCheck = SysTime() + 0.2 + listpanel.NextConVarCheck = RealTime() + 0.2 for varname, line in pairs(lines) do if not self.SkinVars[varname] then continue end @@ -224,9 +223,6 @@ function TOOL:AddModeList( panel ) local color = data[varname] if not color then continue end - -- local checked = activecheckbox:GetChecked() - -- line:SetSortValue(line._activeindex, checked and 1 or 0) - local oldcolor = colortile:GetColor() if color == oldcolor then continue end diff --git a/materials/3dstreamradio/_data/version.vmt b/materials/3dstreamradio/_data/version.vmt index 3faa4d5..820b4b3 100644 --- a/materials/3dstreamradio/_data/version.vmt +++ b/materials/3dstreamradio/_data/version.vmt @@ -1,2 +1,2 @@ -418 -1684107996 +419 +1686191666 diff --git a/materials/3dstreamradio/icon16/arrow_not_refresh.png b/materials/3dstreamradio/icon16/arrow_not_refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..5d43e56f0f6d93896af0de5638833f1107de3570 GIT binary patch literal 907 zcmV;619bd}P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D10YF6K~y+Tg;HB+ zR96&TXYOR;WX#*fG?SSOK3Wx9M2n{V$0e?{$gjAT0#=bs29=u*6M6JiRZ&K%x%msV8KYd?H zwcnrP59enWAjji_iwH_fLMoM6^?VZg{4h3+=_di{;@Oty5d;E365J8P$UmSWVkUA3KS&;T-U^U?-;E>Q59I zKLqaafJbY~3;dV`w0}~ECaT*-@KT+N*7^S!X}F<&!;@0uWaz#K=4d-k3_57LHL^vM znM7UHnyMf(TzK&K0nT0Q{*#T=)v=YQPGxQxY-re;F0@J2FCelgs hGc(0cW=>ukz)N1-#t8TwwPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qQF!XYv0004wNklY5KSS1g@pu>fC?If zXd&V+2tg1LG@$VdLawqAWeW>QP|tt{@gpdRC}O}Sh;X%q9D+p(k?m~nYMaYHc-8Xk7tD1o z_rZBq;I~1*J^~(wp)bV)p0J57SV{@5^SY~{TAOKxW6ZKTy=gJltE}%AfUhj@k^-KR zz|U>t+gJdOM_}fMV45ad+aw7PR3|uHPWwFv9K_i;`bU0H1}y~s5)z3xWbFjFSLrKN zC_?8n3Nb$l!!UwicakU*2EXQk(qvQhG1m_&jk*$0_Ku+tTaY(vz`06SsanM-_uH*G z6iof52_qo4Ypw_7%L{i9!<8wE!yVIPcHjMx#QyK+Zmp3QuTV$ZkgJW h*tQJ{|K)$y`4>K0vh4z4&2j($002ovPDHLkV1nKT;1~b^ diff --git a/materials/3dstreamradio/icon16/format_pplay.png b/materials/3dstreamradio/icon16/format_pplay.png deleted file mode 100644 index 7e1487714085493a3bedddce6a182f3c3e36a173..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 521 zcmV+k0`~ohP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E;js(W8VM(0eVS9K~y+Tol{Lq0#OuwNGrGXD_U3d1Nsfo zdZI;}phdI`!Idzxm;ym*;W{WCC30pKgIOT;gQ#D+cquB>gc)-2G`tU&ZmvSOU}8j8#hf8vMeJK zi3~(E8bv;z2RY>WvR^QfN~NHxDq1gPWV70c34Rt(f$KPqW3a@Yfm@XDaVpTxh0v~U!7A#A#bSX0xzv1%J*RM`Qt9-PGeXfmUh(Z#OEmOeQd)hFZZk8HN!g`VEG|K3YY+tRay|fEsFNGdNs9y_6m_`0EOd zU4f@Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0$NE#K~y+Tol;F` z6G0UIHre7&L@=}oHBtl-k+v#Uqs5aT9z-osZxuu(hl+Yo58}aF@hGU^p->D3i=vT$ z;zhy6*cO6^Vr-j$1{KU-n;%@d$ZuSm>F}_B|C=9(VsjRDo{P)tsDY;x!Ae$zPCW?yxhLiie zux!5Q5gxqGsjQFR7gU8OiM-+^JeyeQmujYS?ru^pn+g~y5Yll2&nJp-yIqJ*7NNNS z`V>Z9P9iruj;_P~*cn(4i!~LfbJ>EtRo$)ObgvIO?VdX5Ln!D&ADQC_6lUYF>IRPW z_|?_!t$vZA&3AAo`pp?Q+K%{471|$9D>D{>uIuo6y_5u){+vQEbRFIK6g*Ey!AjN3 zN&&M;0sYZJEgbJ@!&+TaECfeqXScjER4P?iRs*2}er$U60x{oyV4;3RC6KR9!tK)V zW@=8YCEGOVWHO0tHVeb}MTHo?U$_cxEa1cDU4U8N(M(|5%^8q)WOu6?reqmZt5uOo zeL^OaCIuEchVMZ)3>-?21Laz}*@7G*wb0eE0lBh?ZT@x0<_$C&4Vb2ZK%gBimy22w zNdKszeBuoHk};s(xUWWbZFo9iGrq^-!ox_mG){BT%z~2bnR|z}jV+j;FQdHmq71=M r^cK7y