From a9f6d1040a12915a89dd9716d71baeb39d989433 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 30 Jan 2026 10:40:28 -0300 Subject: [PATCH 01/59] Initial comment stuff, preparing for changes --- lua/acf/core/globals.lua | 3 +++ lua/acf/core/utilities/sounds/sounds_cl.lua | 4 +--- lua/acf/menu/operations/acf_menu.lua | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lua/acf/core/globals.lua b/lua/acf/core/globals.lua index c606db743..5f79446aa 100644 --- a/lua/acf/core/globals.lua +++ b/lua/acf/core/globals.lua @@ -195,6 +195,9 @@ do -- ACF global vars ACF.KwToHp = 1.341 -- Kilowatts to horsepower ACF.LToGal = 0.264172 -- Liters to gallons + -- Misc Sound Stuff + ACF.SpeedOfSound = 343 * ACF.MeterToInch + -- Fuzes ACF.MinFuzeCaliber = 20 -- Minimum caliber in millimeters that can be fuzed diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 2f2e03416..41d0b32d1 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -27,9 +27,6 @@ do -- Valid sound check end end --- MARCH/TODO: universal ACF constant for speed of sound (maybe it already exists and I don't know :P) -local SpeedOfSound = 343 * 39.37 - local function DistanceToOrigin(Origin) if isentity(Origin) and IsValid(Origin) then return LocalPlayer():EyePos():Distance(Origin:GetPos()) @@ -46,6 +43,7 @@ end local function DoDelayed(Origin, Call, Instant) if Instant then return Call() end + local SpeedOfSound = ACF.SpeedOfSound -- I dunno if making this variable is okay or just fuck it we ball it right below local Delay = DistanceToOrigin(Origin) / SpeedOfSound if Delay > 0.1 then timer.Simple(Delay, function() Call() end) diff --git a/lua/acf/menu/operations/acf_menu.lua b/lua/acf/menu/operations/acf_menu.lua index badac666b..5ca3f1a58 100644 --- a/lua/acf/menu/operations/acf_menu.lua +++ b/lua/acf/menu/operations/acf_menu.lua @@ -218,8 +218,11 @@ do -- Generic Spawner/Linker operation creator if Total > 1 then ReportMultiple(Player, Action, EntName, Failed, #Success, Total) else + -- FIXME(TMF): There's a bug here that prevents entities from being linked + -- Wether we accidentally hit the world in what's some quite obscure bug local Result = next(Success) and true or false - local Origin = table.remove(Result and Success or Failed) + local Origin = table.remove(Result and Success or Failed) -- Somehow this table becomes nil + -- print(Result, Origin) local Target = Origin.Name local Message = Origin.Message From 7d3f27945f479d7617614d918221094dc7c6e783 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 30 Jan 2026 10:45:01 -0300 Subject: [PATCH 02/59] Bump this number for the love of god. Does nothing but annoy anyone coming to do something in this god's forsaken addon --- lua/acf/core/globals.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/acf/core/globals.lua b/lua/acf/core/globals.lua index 5f79446aa..dd08321a0 100644 --- a/lua/acf/core/globals.lua +++ b/lua/acf/core/globals.lua @@ -173,7 +173,7 @@ do -- ACF global vars -- The deviation of the input direction from the shaft + the output direction from the shaft cannot exceed this ACF.DefineSetting("MaxDriveshaftAngle", 85, nil, ACF.FloatDataCallback(85, 180, 0)) - ACF.Year = 1945 + ACF.Year = 2026 -- Was 1945. Define this hardcoded for now, always the year in course unless more work on this gets done and does actually get used properly ACF.IllegalDisableTime = 30 -- Time in seconds for an entity to be disabled when it fails ACF.IsLegal ACF.Volume = 1 -- Global volume for ACF sounds ACF.MobilityLinkDistance = 650 -- Maximum distance, in inches, at which mobility-related components will remain linked with each other From cd0938e6bc6f5d36fd24953a285b3cd7868dc8c1 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 30 Jan 2026 10:47:42 -0300 Subject: [PATCH 03/59] Guess what, i see no harm in doing this anyway --- lua/acf/core/utilities/sounds/sounds_cl.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 41d0b32d1..60b96709c 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -43,8 +43,7 @@ end local function DoDelayed(Origin, Call, Instant) if Instant then return Call() end - local SpeedOfSound = ACF.SpeedOfSound -- I dunno if making this variable is okay or just fuck it we ball it right below - local Delay = DistanceToOrigin(Origin) / SpeedOfSound + local Delay = DistanceToOrigin(Origin) / ACF.SpeedOfSound if Delay > 0.1 then timer.Simple(Delay, function() Call() end) else From f4e7cd235ed29f425742c370a3c283c85842faff Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Tue, 3 Feb 2026 14:02:56 -0300 Subject: [PATCH 04/59] Initial work for networking interpolated sounds, WIP --- lua/acf/core/utilities/sounds/sounds_cl.lua | 62 +++++++++++++++++++ lua/acf/core/utilities/sounds/sounds_sv.lua | 27 +++++++- .../core/utilities/sounds/tool_support_sh.lua | 21 ++++++- 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 60b96709c..c0be19973 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -178,6 +178,68 @@ do -- Processing adjustable sounds (for example, engine noises) end) end +do -- Multiple Engine Sounds(ex. Interpolated sounds) + local IsValid = IsValid -- Should this stay as local to each scope? + + function Sounds.UpdateMultipleAdjustableSounds(Origin, PathTable) + if not IsValid(Origin) then return end + if not istable(PathTable) then return end + + local OldTable = Origin.SoundBank + local OldPath, OldPitch, OldVolume = OldTable[1], OldTable[2], OldTable[3] + + for _, soundTable in PathTable do + if soundTable[1] == OldPath then continue -- Check for any deltas, if not just move along + elseif soundTable[2] == OldPitch then continue + elseif soundTable[3] == OldVolume then continue end + + Sounds.UpdateAdjustableSound(Origin, soundTable[1], soundTable[2], soundTable[3]) + end + end + + function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable, _, _) + if not IsValid(Origin) then return end + if not istable(PathTable) then return end + local soundTable = PathTable + + -- I hope this works... + for _, pathTbl in soundTable do + Sounds.CreateAdjustableSound(Origin, + pathTbl[1], -- String path + pathTbl[2], -- Pitch + pathTbl[3] -- Volume + ) + end + end + + function Sounds.DeleteMultipleAdjustableSounds(Origin, _) + local currentSoundBank = Origin.SoundBank + if not currentSoundBank then return end + + -- I suppose this actually gets garbage collected? + for _, snd in currentSoundBank do + snd:Stop() + snd = nil + end + + currentSoundBank = nil + end + -- This might not work as it is... + net.Receive("ACF_Sounds_AdjustableCreate_Multi", function() + local Origin = net.ReadEntity() + local Path = net.ReadTable() + --local Pitch = net.ReadUInt(8) + --local Volume = net.Float() + local SoundTable = {} + + for rpm, soundPath in Path do + if not Sounds.IsValidSound(soundPath) then return end + SoundTable[rpm] = soundPath + end + + Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) + end) +end --- Returns a table of sound infomation depending on what the trace hit. --- @param Data table The effect data relating to the projectile --- @param Trace table The trace data relating to the projectile diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index 830504d57..fa1fa3021 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -3,6 +3,8 @@ local Sounds = ACF.Utilities.Sounds util.AddNetworkString("ACF_Sounds") util.AddNetworkString("ACF_Sounds_Adjustable") util.AddNetworkString("ACF_Sounds_AdjustableCreate") +util.AddNetworkString("ACF_Sounds_Adjustable_Multi") +util.AddNetworkString("ACF_Sounds_AdjustableCreate_Multi") --- Sends a single, non-looping sound to all clients in the PAS. --- @param Origin table | vector The source to play the sound from @@ -85,4 +87,27 @@ function Sounds.SendAdjustableSound(Origin, ShouldStop, Pitch, Volume) net.SendPAS(Origin:GetPos()) OriginTbl.SoundTimer = Time + 0.05 end -end \ No newline at end of file +end + +--- Creates a sound table to be broadcasted to all players within PAS. +--- Just like the CreateAdjustableSound, except meant to play multiple sounds on an entity that can be adjusted based on a variable. +--- This also allows us to modify the pitch/volume of multiple looping sounds (for an engine) with minimal network usage. +--- @param Origin table The entity to play the sound from +--- @param PathTable table The table with multiple sound paths, pitch and volume to be played at a defined RPM +function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable) + if not IsValid(Origin) then return end + if not istable(PathTable) then return end + + -- Literally the same as above but as a table instead, i dunno how bad this is yet + net.Start("ACF_Sounds_AdjustableCreate_Multi") + net.WriteEntity(Origin) + net.WriteTable(PathTable) + net.SendPAS(Origin:GetPos()) +end + +--[[function Sounds.SendMultipleAdjustableSounds(Origin, _, PathTable) + net.Start("ACF_Sounds_Adjustable_Multi") + net.WriteEntity(Origin) + net.WriteTable(PathTable) + net.SendPAS(Origin:GetPos()) +end]]-- \ No newline at end of file diff --git a/lua/acf/core/utilities/sounds/tool_support_sh.lua b/lua/acf/core/utilities/sounds/tool_support_sh.lua index 074f84757..1497de457 100644 --- a/lua/acf/core/utilities/sounds/tool_support_sh.lua +++ b/lua/acf/core/utilities/sounds/tool_support_sh.lua @@ -48,11 +48,26 @@ Sounds.acf_engine = { Ent:UpdateSound() end, ResetSound = function(Ent) - Ent.SoundPath = Ent.DefaultSound - Ent.SoundPitch = 1 - Ent.SoundVolume = 1 + Ent.SoundPath = Ent.DefaultSound + Ent.SoundBank["default"] = Ent.DefaultSound + Ent.SoundPitch = 1 + Ent.SoundVolume = 1 Ent:UpdateSound() + end, + GetSoundBank = function(Ent) + return { + SoundBank = Ent.SoundBank + } + end, + SetSoundBank = function(Ent, SoundBankData) + local soundTable = SoundBankData + for _, path in soundTable do + path[1].Sound:Trim():lower() + Ent:UpdateSound() + end + + Ent.SoundBank = soundTable end } From 145f8010f73a147db9e3cd320a8ccd46469ea56d Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 6 Feb 2026 20:37:21 -0300 Subject: [PATCH 05/59] Add initial implementation of interpolated sounds - Done using a very naive approach that consumes a lot of resources for no reason - Still delegates some calculations to the server to do them - Some things broken, such as single sound entities - Fallback to single sound is broken - I hate the state of this, rewrite coming soon enough! WIP --- lua/acf/core/globals.lua | 6 +- lua/acf/core/utilities/sounds/sounds_cl.lua | 112 +++++++---- lua/acf/core/utilities/sounds/sounds_sv.lua | 44 +++-- .../core/utilities/sounds/tool_support_sh.lua | 3 +- lua/entities/acf_engine/init.lua | 177 +++++++++++++----- 5 files changed, 238 insertions(+), 104 deletions(-) diff --git a/lua/acf/core/globals.lua b/lua/acf/core/globals.lua index dd08321a0..0d0dd683d 100644 --- a/lua/acf/core/globals.lua +++ b/lua/acf/core/globals.lua @@ -195,9 +195,9 @@ do -- ACF global vars ACF.KwToHp = 1.341 -- Kilowatts to horsepower ACF.LToGal = 0.264172 -- Liters to gallons - -- Misc Sound Stuff - ACF.SpeedOfSound = 343 * ACF.MeterToInch - + -- Miscellaneous Sound Stuff + ACF.SpeedOfSound = 343 * ACF.MeterToInch -- in Meters per Second. Source internally uses inches(or units) so we have to convert + -- Actually this would vary as a function of temperature and air pressure, but this should suffice for now -- Fuzes ACF.MinFuzeCaliber = 20 -- Minimum caliber in millimeters that can be fuzed diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index c0be19973..9a2b53e5e 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -96,6 +96,8 @@ do -- Playing regular sounds end) end +local SoundObjects = {} + do -- Processing adjustable sounds (for example, engine noises) local IsValid = IsValid @@ -106,10 +108,8 @@ do -- Processing adjustable sounds (for example, engine noises) --- @param Pitch integer The sound's pitch from 0-255 --- @param Volume number A float representing the sound's volume function Sounds.UpdateAdjustableSound(Origin, Pitch, Volume) - if not IsValid(Origin) then return end - - local Sound = Origin.Sound - if not Sound then return end + local Sound = Origin + if type(Sound) ~= "CSoundPatch" then return end Volume = Volume * ACF.Volume @@ -127,19 +127,20 @@ do -- Processing adjustable sounds (for example, engine noises) --- @param Path string The path to the sound to be played local to the game's sound folder --- @param Pitch integer The sound's pitch from 0-255 --- @param Volume number A float representing the sound's volume + --- @return Sound CSoundPatch The sound object function Sounds.CreateAdjustableSound(Origin, Path, Pitch, Volume) if not IsValid(Origin) then return end - if Origin.Sound then return end local Sound = CreateSound(Origin, Path) - Origin.Sound = Sound + table.insert(SoundObjects, Sound) -- Ensuring that the sound can't stick around if the server doesn't properly ask for it to be destroyed Origin:CallOnRemove("ACF_ForceStopAdjustableSound", function(Entity) Sounds.DestroyAdjustableSound(Entity, true) end) - Sounds.UpdateAdjustableSound(Origin, Pitch, Volume) + Sounds.UpdateAdjustableSound(Sound, Pitch, Volume) + return Sound end --- Stops an existing adjustable sound on the origin. @@ -180,64 +181,101 @@ end do -- Multiple Engine Sounds(ex. Interpolated sounds) local IsValid = IsValid -- Should this stay as local to each scope? + -- Convert the string path to a sound that's already created + local function ParseString(String) + if type(String) ~= "CSoundPatch" then + if isstring(String) then + for _, v in ipairs(SoundObjects) do + if table.IsEmpty(SoundObjects) then error("Tried to parse string with empty SoundObjects table!") end + local s = string.gsub(tostring(v), "CSoundPatch [-%A]", "") + s = string.gsub(s, "%]$", "") -- Pretty sure i don't need this matched twice just for the ']' at the end + if String == s then String = v + return String + end + end + error("Failed to parse string to CSoundPatch! (Sound not found)") + else + error("Cannot parse a non string value!") + end + return String end + end function Sounds.UpdateMultipleAdjustableSounds(Origin, PathTable) if not IsValid(Origin) then return end if not istable(PathTable) then return end - local OldTable = Origin.SoundBank - local OldPath, OldPitch, OldVolume = OldTable[1], OldTable[2], OldTable[3] + -- Potentially some mechanism here to check for any differences and only update those + for _, soundTable in pairs(PathTable) do + local Sound = soundTable[1] + local Pitch = soundTable[2] + local Volume = soundTable[3] - for _, soundTable in PathTable do - if soundTable[1] == OldPath then continue -- Check for any deltas, if not just move along - elseif soundTable[2] == OldPitch then continue - elseif soundTable[3] == OldVolume then continue end + local parsed = ParseString(Sound) - Sounds.UpdateAdjustableSound(Origin, soundTable[1], soundTable[2], soundTable[3]) + Sounds.UpdateAdjustableSound(parsed, Pitch, Volume) end end function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable, _, _) - if not IsValid(Origin) then return end - if not istable(PathTable) then return end - local soundTable = PathTable - - -- I hope this works... - for _, pathTbl in soundTable do - Sounds.CreateAdjustableSound(Origin, + for _, pathTbl in pairs(PathTable) do + local Sound = Sounds.CreateAdjustableSound(Origin, pathTbl[1], -- String path pathTbl[2], -- Pitch pathTbl[3] -- Volume ) + pathTbl[1] = Sound -- Replace string with a sound object end - end + -- Ensuring that the sounds can't stick around if the server doesn't properly ask for them to be destroyed + Origin:CallOnRemove("ACF_ForceStopMultipleAdjustableSounds", function(Entity) + Sounds.DeleteMultipleAdjustableSounds(Entity, true) + end) - function Sounds.DeleteMultipleAdjustableSounds(Origin, _) - local currentSoundBank = Origin.SoundBank - if not currentSoundBank then return end + Sounds.UpdateMultipleAdjustableSounds(Origin, PathTable) + end + function Sounds.DeleteMultipleAdjustableSounds(Origin) + local count = 0 + print("Deleting sounds!") -- I suppose this actually gets garbage collected? - for _, snd in currentSoundBank do + for idx, snd in ipairs(SoundObjects) do snd:Stop() snd = nil + count = idx end - currentSoundBank = nil + --count = 0 -- I dunno why it persists when this function ends lol + Origin.Sound = nil + print("Successfully deleted " .. count .. " sounds!") end - -- This might not work as it is... - net.Receive("ACF_Sounds_AdjustableCreate_Multi", function() + + net.Receive("ACF_Sounds_AdjustableCreate_Multi", function(len) + print("Received " .. len .. " bits from server for \"ACF_Sounds_AdjustableCreate_Multi\"") local Origin = net.ReadEntity() local Path = net.ReadTable() - --local Pitch = net.ReadUInt(8) - --local Volume = net.Float() - local SoundTable = {} - for rpm, soundPath in Path do - if not Sounds.IsValidSound(soundPath) then return end - SoundTable[rpm] = soundPath - end + if not IsValid(Origin) then return end + if not istable(Path) then return end + + -- Only one sound was found, we assume it's at -1 index + --if #Path < 1 then + -- Sounds.CreateAdjustableSound(Origin, Path[-1][1], 100, 1) -- Might want to network pitch and volume back later + --else + Sounds.CreateMultipleAdjustableSounds(Origin, Path) + --end + end) - Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) + net.Receive("ACF_Sounds_Adjustable_Multi", function(len) + print("Received " .. len .. " bits from server for \"ACF_Sounds_Adjustable_Multi\"") + local Origin = net.ReadEntity() + local ShouldStop = net.ReadBool() + local Table = net.ReadTable() + + if ShouldStop then + Sounds.DeleteMultipleAdjustableSounds(Origin) + else + if not istable(Table) then return end + Sounds.UpdateMultipleAdjustableSounds(Origin, Table) + end end) end --- Returns a table of sound infomation depending on what the trace hit. diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index fa1fa3021..dcb2a4eb8 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -93,21 +93,43 @@ end --- Just like the CreateAdjustableSound, except meant to play multiple sounds on an entity that can be adjusted based on a variable. --- This also allows us to modify the pitch/volume of multiple looping sounds (for an engine) with minimal network usage. --- @param Origin table The entity to play the sound from ---- @param PathTable table The table with multiple sound paths, pitch and volume to be played at a defined RPM -function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable) +--- @param SoundTable table The table whose keys are arbitrary RPM's and values containing a table with a sound path, pitch and volume, to be played at a defined RPM(Its keys). +function Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) if not IsValid(Origin) then return end - if not istable(PathTable) then return end + if not istable(SoundTable) then return end - -- Literally the same as above but as a table instead, i dunno how bad this is yet + -- Literally the same as CreateAdjustableSound but as a table instead net.Start("ACF_Sounds_AdjustableCreate_Multi") net.WriteEntity(Origin) - net.WriteTable(PathTable) + net.WriteTable(SoundTable) net.SendPAS(Origin:GetPos()) end ---[[function Sounds.SendMultipleAdjustableSounds(Origin, _, PathTable) - net.Start("ACF_Sounds_Adjustable_Multi") - net.WriteEntity(Origin) - net.WriteTable(PathTable) - net.SendPAS(Origin:GetPos()) -end]]-- \ No newline at end of file +--- Sends an update to multiple adjustable sounds to all clients within PAS. +--- If all the adjustable sounds were stopped by the client, it will begin playing again on the origin with the given parameters. +--- This function mirrors "SendAdjustableSound" and as such is rate limited +--- @param Origin table The entity to update the sound on +--- @param ShouldStop? boolean Whether the sound should be destroyed; defaults to false +--- @param PathTable table The table whose keys are arbitrary RPM's and values containing a table with a sound path, pitch and volume, to be played at a defined RPM. +function Sounds.SendMultipleAdjustableSounds(Origin, ShouldStop, SoundTable) + ShouldStop = ShouldStop or false + local Time = CurTime() + local OriginTbl = Origin.ACF + if not OriginTbl then + OriginTbl = {} + Origin.ACF = OriginTbl + end + OriginTbl.SoundTimer = OriginTbl.SoundTimer or Time + + -- Slowing down the rate of sending a bit + if OriginTbl.SoundTimer <= Time or ShouldStop then + net.Start("ACF_Sounds_Adjustable_Multi", true) + net.WriteEntity(Origin) + net.WriteBool(ShouldStop) + if not ShouldStop then + net.WriteTable(SoundTable) + end + net.SendPAS(Origin:GetPos()) + OriginTbl.SoundTimer = Time + 0.05 + end +end diff --git a/lua/acf/core/utilities/sounds/tool_support_sh.lua b/lua/acf/core/utilities/sounds/tool_support_sh.lua index 1497de457..ff23a3c5e 100644 --- a/lua/acf/core/utilities/sounds/tool_support_sh.lua +++ b/lua/acf/core/utilities/sounds/tool_support_sh.lua @@ -60,9 +60,10 @@ Sounds.acf_engine = { SoundBank = Ent.SoundBank } end, + -- This is dog... Change this! SetSoundBank = function(Ent, SoundBankData) local soundTable = SoundBankData - for _, path in soundTable do + for _, path in pairs(soundTable) do path[1].Sound:Trim():lower() Ent:UpdateSound() end diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 5e083ac69..278138be0 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -118,7 +118,28 @@ local TimerCreate = timer.Create local TimerRemove = timer.Remove local TickInterval = engine.TickInterval -local function GetPitchVolume(Engine) +-- Maps a value, X, from a range A-B, to a new range C-D +local function map(x, a, b, c, d) + return (x - a) / (b - a) * (d - c) + c +end + +-- Fade function taken from: +-- https://dsp.stackexchange.com/questions/37477/understanding-equal-power-crossfades +-- https://dsp.stackexchange.com/questions/14754/equal-power-crossfade +-- https://i.imgur.com/KaFmaMf.png +local function fade(n, min, mid, max) + local _PI = math.pi + + if n < min or n > max then return 0 end + + if n > mid then + min = mid - (max - mid) + end + + return math.cos((1 - ((n - min) / (mid - min))) * (_PI / 2)) +end + +--[[local function GetPitchVolume(Engine) local RPM = Engine.FlyRPM local Pitch = Clamp(20 + (RPM * Engine.SoundPitch) * 0.02, 1, 255) -- Rev limiter code disabled because it has issues with the volume delta time, but it's still here if we need it @@ -126,6 +147,37 @@ local function GetPitchVolume(Engine) local Volume = 0.25 + (0.1 + 0.9 * ((RPM / Engine.LimitRPM) ^ 1.5)) * Throttle * 0.666 return Pitch, Volume * Engine.SoundVolume +end]]-- + +-- Very naive approach to calculate and set interpolated engine sounds +local function CalcPitchVolume(Engine) + local SoundBank = Engine.SoundBank + local SoundRPMs = Engine.SoundRPMs + local RPM = Engine.FlyRPM + local Throttle = Engine.Throttle + local SmoothRPM = Engine.SmoothRPM + local SmoothThrottle = Engine.SmoothThrottle + local AdditionalCurveWidth = 2 or Engine.AddCurveWidth + SmoothRPM = SmoothRPM * (1 - 0.1) + RPM + SmoothThrottle = SmoothThrottle * (1 - 0.1) + Throttle + + -- Sound volumes when throttle is 0 and 100 respectively + local _OFFVOLUME = 0.25 + local _ONVOLUME = 1 + --PrintTable(SoundRPMs) + for idx, rpm in pairs(SoundRPMs) do + --print("Reached here! " .. idx .. " Times! " .. rpm .. " Rpms!") + --PrintTable(SoundBank[rpm]) + if not SoundRPMs[idx] then continue end + local min = idx == 1 and -100000 or SoundRPMs[idx - 1] + local mid = rpm + local max = idx == #SoundRPMs and 100000 or SoundRPMs[idx + 1] + local curve = fade(SmoothRPM, min - AdditionalCurveWidth, mid, max + AdditionalCurveWidth) + local Volume = curve * map(SmoothThrottle, 0, 100, _OFFVOLUME, _ONVOLUME) + local Pitch = (SmoothRPM / rpm) + SoundBank[rpm][2] = Pitch * 100 + SoundBank[rpm][3] = Volume * 100 + end end local function GetNextFuelTank(Engine) @@ -178,7 +230,6 @@ end local function SetActive(Entity, Value, EntTbl) EntTbl = EntTbl or Entity:GetTable() - local ActBool = tobool(Value) if EntTbl.Active == ActBool then return end -- Already in the desired state @@ -193,7 +244,7 @@ local function SetActive(Entity, Value, EntTbl) EntTbl.Torque = EntTbl.PeakTorque EntTbl.FlyRPM = EntTbl.IdleRPM * 1.5 - Entity:UpdateSound(EntTbl) + Entity:UpdateSoundBank(EntTbl) Entity:NextThink(Clock.CurTime + TickInterval()) @@ -206,11 +257,12 @@ local function SetActive(Entity, Value, EntTbl) Entity:CalcMassRatio(EntTbl) end) else -- Was on, turn off - EntTbl.Active = false - EntTbl.FlyRPM = 0 - EntTbl.Torque = 0 + EntTbl.Active = false + EntTbl.SmoothRPM = 0 + EntTbl.FlyRPM = 0 + EntTbl.Torque = 0 - Entity:DestroySound() + Entity:DestroyAllSounds() TimerRemove("ACF Engine Clock " .. Entity:EntIndex()) end @@ -300,8 +352,11 @@ do -- Spawn and Update functions Entity.EntType = Class.Name Entity.ClassData = Class Entity.DefaultSound = Engine.Sound + Entity.SoundBank = Entity.SoundBank or {[-1] = Entity.DefaultSound} + Entity.SoundRPMs = Entity.SoundRPMs Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 + Entity.AddCurveWidth = Entity.AddCurveWidth or 0 Entity.TorqueCurve = Engine.TorqueCurve Entity.PeakTorque = Engine.Torque Entity.PeakPower = Engine.PeakPower @@ -311,6 +366,8 @@ do -- Spawn and Update functions Entity.PeakMinRPM = Engine.RPM.PeakMin Entity.PeakMaxRPM = Engine.RPM.PeakMax Entity.LimitRPM = Engine.RPM.Limit + Entity.SmoothRPM = Entity.SmoothRPM or 0 + Entity.SmoothThrottle = Entity.SmoothThrottle or 0 Entity.RevLimited = false Entity.FlywheelOverride = Engine.RPM.Override Entity.FlywheelMass = Engine.FlywheelMass @@ -329,7 +386,7 @@ do -- Spawn and Update functions if Engine.IsTrans then Entity.Out = ACF.LocalPlane(vector_origin, Vector(0, 1, 0)) end - Entity.IsSpecial = Engines.IsSpecial(Engines.GetItem(Class.ID, Data.Engine)) + Entity.IsSpecial = Engines.IsSpecial(Engines.GetItem(Class.ID, Data.Engine)) WireIO.SetupInputs(Entity, Inputs, Data, Class, Engine, Type) WireIO.SetupOutputs(Entity, Outputs, Data, Class, Engine, Type) @@ -373,29 +430,57 @@ do -- Spawn and Update functions Player:AddCleanup("acf_engine", Entity) Player:AddCount(Limit, Entity) - Entity.Active = false - Entity.Gearboxes = {} - Entity.FuelTanks = {} - Entity.LastThink = 0 - Entity.MassRatio = 1 - Entity.FuelUsage = 0 - Entity.Throttle = 0 - Entity.FlyRPM = 0 - Entity.SoundPath = Engine.Sound - Entity.LastPitch = 0 - Entity.LastTorque = 0 - Entity.LastFuelUsage = 0 - Entity.LastPower = 0 - Entity.LastRPM = 0 - Entity.LastTotalMass = 0 - Entity.LastPhysMass = 0 - Entity.DataStore = Entities.GetArguments("acf_engine") + local SuperDuperHandyTable = { + [714] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", 100, 0}, + [967] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", 100, 0}, + [1538] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", 100, 0}, + [1978] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", 100, 0}, + [2571] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", 100, 0}, + [3450] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", 100, 0}, + [3889] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", 100, 0}, + [4482] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", 100, 0}, + [4922] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", 100, 0}, + [5295] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", 100, 0}, + [5823] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", 100, 0}, + [6350] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", 100, 0}, + [6833] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", 100, 0} + } + + Entity.Active = false + Entity.Gearboxes = {} + Entity.FuelTanks = {} + Entity.LastThink = 0 + Entity.MassRatio = 1 + Entity.FuelUsage = 0 + Entity.Throttle = 0 + Entity.FlyRPM = 0 + Entity.SmoothRPM = 0 + Entity.SmoothThrottle = 0 + Entity.SoundPath = Engine.Sound + Entity.SoundBank = SuperDuperHandyTable or {[-1] = Entity.SoundPath} -- i have no idea if this is a good idea + Entity.SoundRPMs = {} -- It only stores the rpms from above, probably not needed + Entity.AddCurveWidth = Entity.AddCurveWidth or 0 + Entity.LastPitch = 0 + Entity.LastTorque = 0 + Entity.LastFuelUsage = 0 + Entity.LastPower = 0 + Entity.LastRPM = 0 + Entity.LastTotalMass = 0 + Entity.LastPhysMass = 0 + Entity.DataStore = Entities.GetArguments("acf_engine") Entity.revLimiterEnabled = true duplicator.ClearEntityModifier(Entity, "mass") UpdateEngine(Entity, Data, Class, Engine, Type) + for k, _ in pairs(Entity.SoundBank) do + table.insert(Entity.SoundRPMs, k) + end + table.sort(Entity.SoundRPMs) + + --PrintTable(Entity.SoundRPMs) + if Class.OnSpawn then Class.OnSpawn(Entity, Data, Class, Engine) end @@ -603,37 +688,25 @@ function ENT:ACF_OnDamage(DmgResult, DmgInfo) return HitRes end -function ENT:UpdateSound(SelfTbl) +function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() + local SoundBank = SelfTbl.SoundBank - local Path = SelfTbl.SoundPath - local LastSound = SelfTbl.LastSound - - if Path ~= LastSound and LastSound ~= nil then - self:DestroySound() - - SelfTbl.LastSound = Path - end - - if Path == "" then return end - if not SelfTbl.Active then return end - - local Pitch, Volume = GetPitchVolume(SelfTbl) - - if math.abs(Pitch - SelfTbl.LastPitch) < 1 then return end -- Don't bother updating if the pitch difference is too small to notice - - SelfTbl.LastPitch = Pitch - - if SelfTbl.Sound then - Sounds.SendAdjustableSound(self, false, Pitch, Volume) + if SelfTbl.Sound then + CalcPitchVolume(SelfTbl) + Sounds.SendMultipleAdjustableSounds(self, false, SoundBank) + --PrintTable(SoundBank) else - Sounds.CreateAdjustableSound(self, Path, Pitch, Volume) - SelfTbl.Sound = true + Sounds.CreateMultipleAdjustableSounds(self, SoundBank) + SelfTbl.Sound = true -- Not really needed anymore i think? + print("Soundbank successfully created!") + --PrintTable(SoundBank) end end -function ENT:DestroySound() - Sounds.SendAdjustableSound(self, true) +function ENT:DestroyAllSounds() + + Sounds.SendMultipleAdjustableSounds(self, true, _) self.LastSound = nil self.LastPitch = 0 @@ -850,7 +923,7 @@ function ENT:CalcRPM(SelfTbl) SelfTbl.FlyRPM = FlyRPM - min(TorqueDiff, TotalReqTq) / Inertia SelfTbl.LastThink = ClockTime - self:UpdateSound(SelfTbl) + self:UpdateSoundBank(SelfTbl) self:UpdateOutputs(SelfTbl) end @@ -940,7 +1013,7 @@ function ENT:OnRemove() hook.Run("ACF_OnEntityLast", "acf_engine", self, Class) - self:DestroySound() + self:DestroyAllSounds() for Gearbox in pairs(self.Gearboxes) do self:Unlink(Gearbox) From f98d236f9ddade951610c7a2ddeae283ad0ed9c6 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 6 Feb 2026 22:24:36 -0300 Subject: [PATCH 06/59] Begin offloading pitch/volume calculations to clientside, also try to have engine registration to work with the sound table --- lua/acf/core/classes/engines/registration.lua | 5 ++ lua/acf/core/utilities/sounds/sounds_cl.lua | 52 ++++++++++++ lua/acf/entities/engines/special.lua | 36 ++++++++- lua/entities/acf_engine/init.lua | 80 ++----------------- 4 files changed, 99 insertions(+), 74 deletions(-) diff --git a/lua/acf/core/classes/engines/registration.lua b/lua/acf/core/classes/engines/registration.lua index f9fe0941f..3909832cb 100644 --- a/lua/acf/core/classes/engines/registration.lua +++ b/lua/acf/core/classes/engines/registration.lua @@ -45,6 +45,11 @@ function Engines.RegisterItem(ID, ClassID, Data) Class.Sound = "vehicles/junker/jnk_fourth_cruise_loop2.wav" end + -- Will this work though? + if not Class.SoundBank then + Class.SoundBank = {[-1] = Class.Sound} + end + if Loaded then AddPerformanceData(Class) end diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 9a2b53e5e..362b78d15 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -52,6 +52,58 @@ local function DoDelayed(Origin, Call, Instant) end DoDelayed = DoDelayed +-- Maps a value, X, from a range A-B, to a new range C-D +local function map(x, a, b, c, d) + return (x - a) / (b - a) * (d - c) + c +end + +-- Fade function taken from: +-- https://dsp.stackexchange.com/questions/37477/understanding-equal-power-crossfades +-- https://dsp.stackexchange.com/questions/14754/equal-power-crossfade +-- https://i.imgur.com/KaFmaMf.png +local function fade(n, min, mid, max) + local _PI = math.pi + + if n < min or n > max then return 0 end + + if n > mid then + min = mid - (max - mid) + end + + return math.cos((1 - ((n - min) / (mid - min))) * (_PI / 2)) +end + +-- Very naive approach to calculate and set interpolated engine sounds +local function CalcPitchVolume(Engine) + local SoundBank = Engine.SoundBank + local SoundRPMs = Engine.SoundRPMs + local RPM = Engine.FlyRPM + local Throttle = Engine.Throttle + local SmoothRPM = Engine.SmoothRPM + local SmoothThrottle = Engine.SmoothThrottle + local AdditionalCurveWidth = 2 or Engine.AddCurveWidth + SmoothRPM = SmoothRPM * (1 - 0.1) + RPM + SmoothThrottle = SmoothThrottle * (1 - 0.1) + Throttle + + -- Sound volumes when throttle is 0 and 100 respectively + local _OFFVOLUME = 0.25 + local _ONVOLUME = 1 + --PrintTable(SoundRPMs) + for idx, rpm in pairs(SoundRPMs) do + --print("Reached here! " .. idx .. " Times! " .. rpm .. " Rpms!") + --PrintTable(SoundBank[rpm]) + if not SoundRPMs[idx] then continue end + local min = idx == 1 and -100000 or SoundRPMs[idx - 1] + local mid = rpm + local max = idx == #SoundRPMs and 100000 or SoundRPMs[idx + 1] + local curve = fade(SmoothRPM, min - AdditionalCurveWidth, mid, max + AdditionalCurveWidth) + local Volume = curve * map(SmoothThrottle, 0, 100, _OFFVOLUME, _ONVOLUME) + local Pitch = (SmoothRPM / rpm) + SoundBank[rpm][2] = Pitch * 100 + SoundBank[rpm][3] = Volume * 100 + end +end + do -- Playing regular sounds --- Plays a single, non-looping sound at the given origin. --- @param Origin table | vector The source to play the sound from diff --git a/lua/acf/entities/engines/special.lua b/lua/acf/entities/engines/special.lua index f6ecf8b0b..48dc09bbf 100644 --- a/lua/acf/entities/engines/special.lua +++ b/lua/acf/entities/engines/special.lua @@ -71,7 +71,41 @@ do -- Special I4 Engines FOV = 120, }, }) - + Engines.RegisterItem("2.0L-I4", "SP", { + Name = "2.0L 4B1 I4 Petrol", + Description = "The Mitsubishi 4B1 engine is a range of all-alloy straight-4 piston engines built at Mitsubishi's Japanese" .. + "\"World Engine\" powertrain plant in Shiga on the basis of the Global Engine Manufacturing Alliance (GEMA).", + Model = "models/engines/inline4s.mdl", + Sound = "acf_extra/vehiclefx/engines/l4/mini_onhigh.wav", + SoundBank = { + [714] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", + [967] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", + [1538] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", + [1978] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", + [2571] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", + [3450] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", + [3889] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", + [4482] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", + [4922] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", + [5295] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", + [5823] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", + [6350] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", + [6833] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav" + }, + Fuel = { Petrol = true }, + Type = "GenericPetrol", + Mass = 138, + Torque = 199, + FlywheelMass = 0.083, + Pitch = 1, + RPM = { + Idle = 700, + Limit = 7500, + }, + Preview = { + FOV = 120, + }, + }) Engines.RegisterItem("1.9L-I4", "SP", { Name = "1.9L I4 Petrol", Description = "#acf.descs.engines.sp.1_9", diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 278138be0..d111cf1fc 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -118,68 +118,6 @@ local TimerCreate = timer.Create local TimerRemove = timer.Remove local TickInterval = engine.TickInterval --- Maps a value, X, from a range A-B, to a new range C-D -local function map(x, a, b, c, d) - return (x - a) / (b - a) * (d - c) + c -end - --- Fade function taken from: --- https://dsp.stackexchange.com/questions/37477/understanding-equal-power-crossfades --- https://dsp.stackexchange.com/questions/14754/equal-power-crossfade --- https://i.imgur.com/KaFmaMf.png -local function fade(n, min, mid, max) - local _PI = math.pi - - if n < min or n > max then return 0 end - - if n > mid then - min = mid - (max - mid) - end - - return math.cos((1 - ((n - min) / (mid - min))) * (_PI / 2)) -end - ---[[local function GetPitchVolume(Engine) - local RPM = Engine.FlyRPM - local Pitch = Clamp(20 + (RPM * Engine.SoundPitch) * 0.02, 1, 255) - -- Rev limiter code disabled because it has issues with the volume delta time, but it's still here if we need it - local Throttle = Engine.Throttle -- Engine.RevLimited and 0 or Engine.Throttle - local Volume = 0.25 + (0.1 + 0.9 * ((RPM / Engine.LimitRPM) ^ 1.5)) * Throttle * 0.666 - - return Pitch, Volume * Engine.SoundVolume -end]]-- - --- Very naive approach to calculate and set interpolated engine sounds -local function CalcPitchVolume(Engine) - local SoundBank = Engine.SoundBank - local SoundRPMs = Engine.SoundRPMs - local RPM = Engine.FlyRPM - local Throttle = Engine.Throttle - local SmoothRPM = Engine.SmoothRPM - local SmoothThrottle = Engine.SmoothThrottle - local AdditionalCurveWidth = 2 or Engine.AddCurveWidth - SmoothRPM = SmoothRPM * (1 - 0.1) + RPM - SmoothThrottle = SmoothThrottle * (1 - 0.1) + Throttle - - -- Sound volumes when throttle is 0 and 100 respectively - local _OFFVOLUME = 0.25 - local _ONVOLUME = 1 - --PrintTable(SoundRPMs) - for idx, rpm in pairs(SoundRPMs) do - --print("Reached here! " .. idx .. " Times! " .. rpm .. " Rpms!") - --PrintTable(SoundBank[rpm]) - if not SoundRPMs[idx] then continue end - local min = idx == 1 and -100000 or SoundRPMs[idx - 1] - local mid = rpm - local max = idx == #SoundRPMs and 100000 or SoundRPMs[idx + 1] - local curve = fade(SmoothRPM, min - AdditionalCurveWidth, mid, max + AdditionalCurveWidth) - local Volume = curve * map(SmoothThrottle, 0, 100, _OFFVOLUME, _ONVOLUME) - local Pitch = (SmoothRPM / rpm) - SoundBank[rpm][2] = Pitch * 100 - SoundBank[rpm][3] = Volume * 100 - end -end - local function GetNextFuelTank(Engine) local FuelTanks = Engine.FuelTanks if not next(FuelTanks) then return end @@ -332,6 +270,7 @@ do -- Spawn and Update functions end end + -- Engine update function local function UpdateEngine(Entity, Data, Class, Engine, Type) local Mass = Engine.Mass @@ -353,7 +292,6 @@ do -- Spawn and Update functions Entity.ClassData = Class Entity.DefaultSound = Engine.Sound Entity.SoundBank = Entity.SoundBank or {[-1] = Entity.DefaultSound} - Entity.SoundRPMs = Entity.SoundRPMs Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 Entity.AddCurveWidth = Entity.AddCurveWidth or 0 @@ -405,6 +343,7 @@ do -- Spawn and Update functions Contraption.SetMass(Entity, Mass) end + -- Engine creation function function ACF.MakeEngine(Player, Pos, Angle, Data) VerifyData(Data) @@ -430,7 +369,8 @@ do -- Spawn and Update functions Player:AddCleanup("acf_engine", Entity) Player:AddCount(Limit, Entity) - local SuperDuperHandyTable = { + -- Test constant table, remove this before PR! + local SuperDuperHandyTestTable = { [714] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", 100, 0}, [967] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", 100, 0}, [1538] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", 100, 0}, @@ -457,8 +397,7 @@ do -- Spawn and Update functions Entity.SmoothRPM = 0 Entity.SmoothThrottle = 0 Entity.SoundPath = Engine.Sound - Entity.SoundBank = SuperDuperHandyTable or {[-1] = Entity.SoundPath} -- i have no idea if this is a good idea - Entity.SoundRPMs = {} -- It only stores the rpms from above, probably not needed + Entity.SoundBank = SuperDuperHandyTestTable or {[-1] = Entity.SoundPath} -- i have no idea if this is a good idea Entity.AddCurveWidth = Entity.AddCurveWidth or 0 Entity.LastPitch = 0 Entity.LastTorque = 0 @@ -474,13 +413,6 @@ do -- Spawn and Update functions UpdateEngine(Entity, Data, Class, Engine, Type) - for k, _ in pairs(Entity.SoundBank) do - table.insert(Entity.SoundRPMs, k) - end - table.sort(Entity.SoundRPMs) - - --PrintTable(Entity.SoundRPMs) - if Class.OnSpawn then Class.OnSpawn(Entity, Data, Class, Engine) end @@ -688,6 +620,8 @@ function ENT:ACF_OnDamage(DmgResult, DmgInfo) return HitRes end +-- Change this +-- TODO(TMF): Optimize how much data is about to be sent to the client! function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() local SoundBank = SelfTbl.SoundBank From fe4d531e74b255bde740a11548f9971da3cc4a9c Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 7 Feb 2026 02:47:44 -0300 Subject: [PATCH 07/59] Drastically improve how sounds are networked and handled --- lua/acf/core/utilities/sounds/sounds_cl.lua | 134 ++++++++------------ lua/acf/core/utilities/sounds/sounds_sv.lua | 90 ++++++------- lua/entities/acf_engine/init.lua | 43 +++---- 3 files changed, 112 insertions(+), 155 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 362b78d15..f3f0d4932 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -73,34 +73,31 @@ local function fade(n, min, mid, max) return math.cos((1 - ((n - min) / (mid - min))) * (_PI / 2)) end --- Very naive approach to calculate and set interpolated engine sounds -local function CalcPitchVolume(Engine) - local SoundBank = Engine.SoundBank - local SoundRPMs = Engine.SoundRPMs - local RPM = Engine.FlyRPM - local Throttle = Engine.Throttle - local SmoothRPM = Engine.SmoothRPM - local SmoothThrottle = Engine.SmoothThrottle - local AdditionalCurveWidth = 2 or Engine.AddCurveWidth - SmoothRPM = SmoothRPM * (1 - 0.1) + RPM - SmoothThrottle = SmoothThrottle * (1 - 0.1) + Throttle +-- This is where we store our sound objects +local SoundObjects = {} +-- Consider if we actually want to do this too! (commented out for now) +--local SmoothRPM = 0 +--local SmoothThrottle = 0 + +local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) + local AdditionalCurveWidth = Origin.AddCurveWidth or 0 + --SmoothRPM = SmoothRPM * (1 - 0.1) + RPM * 0.1 + --SmoothThrottle = SmoothThrottle * (1 - 0.1) + Throttle * 10 -- Sound volumes when throttle is 0 and 100 respectively local _OFFVOLUME = 0.25 local _ONVOLUME = 1 - --PrintTable(SoundRPMs) - for idx, rpm in pairs(SoundRPMs) do - --print("Reached here! " .. idx .. " Times! " .. rpm .. " Rpms!") - --PrintTable(SoundBank[rpm]) - if not SoundRPMs[idx] then continue end - local min = idx == 1 and -100000 or SoundRPMs[idx - 1] - local mid = rpm - local max = idx == #SoundRPMs and 100000 or SoundRPMs[idx + 1] - local curve = fade(SmoothRPM, min - AdditionalCurveWidth, mid, max + AdditionalCurveWidth) - local Volume = curve * map(SmoothThrottle, 0, 100, _OFFVOLUME, _ONVOLUME) - local Pitch = (SmoothRPM / rpm) - SoundBank[rpm][2] = Pitch * 100 - SoundBank[rpm][3] = Volume * 100 + + -- TODO(TMF): Potentially some mechanism here to check for any differences and only update those + for idx, soundTable in ipairs(SoundObjects) do + if not SoundObjects[idx].rpm then continue end + local min = idx == 1 and 0 or SoundObjects[idx - 1].rpm + local mid = RPM + local max = idx == #SoundObjects and 16383 or SoundObjects[idx + 1].rpm + local curve = fade(RPM, min - AdditionalCurveWidth, mid, max + AdditionalCurveWidth) + local volume = curve * map(Throttle, 0, 1, _OFFVOLUME, _ONVOLUME) + local pitch = (RPM / soundTable.rpm) * 100 + Sounds.UpdateAdjustableSound(soundTable.sound, pitch, volume) end end @@ -148,8 +145,6 @@ do -- Playing regular sounds end) end -local SoundObjects = {} - do -- Processing adjustable sounds (for example, engine noises) local IsValid = IsValid @@ -184,7 +179,7 @@ do -- Processing adjustable sounds (for example, engine noises) if not IsValid(Origin) then return end local Sound = CreateSound(Origin, Path) - table.insert(SoundObjects, Sound) + Origin.Sound = Sound -- Ensuring that the sound can't stick around if the server doesn't properly ask for it to be destroyed Origin:CallOnRemove("ACF_ForceStopAdjustableSound", function(Entity) @@ -233,69 +228,36 @@ end do -- Multiple Engine Sounds(ex. Interpolated sounds) local IsValid = IsValid -- Should this stay as local to each scope? - -- Convert the string path to a sound that's already created - local function ParseString(String) - if type(String) ~= "CSoundPatch" then - if isstring(String) then - for _, v in ipairs(SoundObjects) do - if table.IsEmpty(SoundObjects) then error("Tried to parse string with empty SoundObjects table!") end - local s = string.gsub(tostring(v), "CSoundPatch [-%A]", "") - s = string.gsub(s, "%]$", "") -- Pretty sure i don't need this matched twice just for the ']' at the end - if String == s then String = v - return String - end - end - error("Failed to parse string to CSoundPatch! (Sound not found)") - else - error("Cannot parse a non string value!") - end - return String end - end - - function Sounds.UpdateMultipleAdjustableSounds(Origin, PathTable) - if not IsValid(Origin) then return end - if not istable(PathTable) then return end - - -- Potentially some mechanism here to check for any differences and only update those - for _, soundTable in pairs(PathTable) do - local Sound = soundTable[1] - local Pitch = soundTable[2] - local Volume = soundTable[3] - - local parsed = ParseString(Sound) - - Sounds.UpdateAdjustableSound(parsed, Pitch, Volume) - end - end - function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable, _, _) - for _, pathTbl in pairs(PathTable) do + function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable) + local count = 1 + for rpm, soundTable in pairs(PathTable) do local Sound = Sounds.CreateAdjustableSound(Origin, - pathTbl[1], -- String path - pathTbl[2], -- Pitch - pathTbl[3] -- Volume + soundTable.Path, + soundTable.Pitch, + soundTable.Volume ) - pathTbl[1] = Sound -- Replace string with a sound object + table.insert(SoundObjects, count, {["rpm"] = rpm, ["sound"] = Sound}) + count = count + 1 + + Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch, 0) end + table.sort(SoundObjects, function(a, b) return a.rpm < b.rpm end) -- Ensuring that the sounds can't stick around if the server doesn't properly ask for them to be destroyed Origin:CallOnRemove("ACF_ForceStopMultipleAdjustableSounds", function(Entity) Sounds.DeleteMultipleAdjustableSounds(Entity, true) end) - - Sounds.UpdateMultipleAdjustableSounds(Origin, PathTable) end - function Sounds.DeleteMultipleAdjustableSounds(Origin) + function Sounds.DeleteMultipleAdjustableSounds(Origin, _) local count = 0 print("Deleting sounds!") -- I suppose this actually gets garbage collected? for idx, snd in ipairs(SoundObjects) do - snd:Stop() - snd = nil + snd.sound:Stop() + SoundObjects[idx] = nil count = idx end - - --count = 0 -- I dunno why it persists when this function ends lol Origin.Sound = nil print("Successfully deleted " .. count .. " sounds!") end @@ -303,16 +265,19 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) net.Receive("ACF_Sounds_AdjustableCreate_Multi", function(len) print("Received " .. len .. " bits from server for \"ACF_Sounds_AdjustableCreate_Multi\"") local Origin = net.ReadEntity() - local Path = net.ReadTable() + local SoundTable = net.ReadTable() if not IsValid(Origin) then return end - if not istable(Path) then return end - - -- Only one sound was found, we assume it's at -1 index - --if #Path < 1 then - -- Sounds.CreateAdjustableSound(Origin, Path[-1][1], 100, 1) -- Might want to network pitch and volume back later + if not istable(SoundTable) then return end + + -- If only one sound was found, we assume it's at -1 index + print(#SoundTable) + --if #SoundTable < 1 then + --PrintTable(SoundTable) + -- local SoundTable = SoundTable[-1] + -- Sounds.CreateAdjustableSound(Origin, SoundTable.Path, SoundTable.Pitch, SoundTable.Volume) --else - Sounds.CreateMultipleAdjustableSounds(Origin, Path) + Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) --end end) @@ -320,13 +285,14 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) print("Received " .. len .. " bits from server for \"ACF_Sounds_Adjustable_Multi\"") local Origin = net.ReadEntity() local ShouldStop = net.ReadBool() - local Table = net.ReadTable() + local Throttle = net.ReadUInt(7) + local RPM = net.ReadUInt(14) + -- Do we really need to remove every existing sound when the engine just turns off? if ShouldStop then Sounds.DeleteMultipleAdjustableSounds(Origin) else - if not istable(Table) then return end - Sounds.UpdateMultipleAdjustableSounds(Origin, Table) + DoPitchVolumeAtRPM(Origin, Throttle, RPM) end end) end diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index dcb2a4eb8..9212fd2fe 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -6,12 +6,12 @@ util.AddNetworkString("ACF_Sounds_AdjustableCreate") util.AddNetworkString("ACF_Sounds_Adjustable_Multi") util.AddNetworkString("ACF_Sounds_AdjustableCreate_Multi") ---- Sends a single, non-looping sound to all clients in the PAS. ---- @param Origin table | vector The source to play the sound from ---- @param Path string The path to the sound to be played local to the game's sound folder ---- @param Level? integer The sound's level/attenuation from 0-127 ---- @param Pitch? integer The sound's pitch from 0-255 ---- @param Volume number A float representing the sound's volume. This is internally converted into an integer from 0-255 for network optimization + --- Sends a single, non-looping sound to all clients in the PAS. + --- @param Origin table | vector The source to play the sound from + --- @param Path string The path to the sound to be played local to the game's sound folder + --- @param Level? integer The sound's level/attenuation from 0-127 + --- @param Pitch? integer The sound's pitch from 0-255 + --- @param Volume number A float representing the sound's volume. This is internally converted into an integer from 0-255 for network optimization function Sounds.SendSound(Origin, Path, Level, Pitch, Volume) if not IsValid(Origin) then return end @@ -38,13 +38,13 @@ function Sounds.SendSound(Origin, Path, Level, Pitch, Volume) net.SendPAS(Pos) end ---- Creates a sound patch on all clients in the PAS. ---- This is intended to be used for self-looping sounds played on an entity that can be adjusted easily later. ---- This allows us to modify the pitch/volume of a looping sound (ex. engines) with minimal network usage. ---- @param Origin table The entity to play the sound from ---- @param Path string The path to the sound to be played local to the game's sound folder ---- @param Pitch integer The sound's pitch from 0-255 ---- @param Volume number A float representing the sound's volume + --- Creates a sound patch on all clients in the PAS. + --- This is intended to be used for self-looping sounds played on an entity that can be adjusted easily later. + --- This allows us to modify the pitch/volume of a looping sound (ex. engines) with minimal network usage. + --- @param Origin table The entity to play the sound from + --- @param Path string The path to the sound to be played local to the game's sound folder + --- @param Pitch integer The sound's pitch from 0-255 + --- @param Volume number A float representing the sound's volume function Sounds.CreateAdjustableSound(Origin, Path, Pitch, Volume) if not IsValid(Origin) then return end @@ -56,13 +56,13 @@ function Sounds.CreateAdjustableSound(Origin, Path, Pitch, Volume) net.SendPAS(Origin:GetPos()) end ---- Sends an update to an adjustable sound to all clients in the PAS. ---- If the adjustable sound was stopped on the client, it will begin playing again on the origin with the given parameters. ---- This function is ratelimited to reduce network consumption, and subsequent updates will be smoothed on the client with an equivalent delta time. ---- @param Origin table The entity to update the sound on ---- @param ShouldStop? boolean Whether the sound should be destroyed; defaults to false ---- @param Pitch integer The sound's pitch from 0-255 ---- @param Volume number A float representing the sound's volume. This is internally converted into an integer from 0-255 for network optimization + --- Sends an update to an adjustable sound to all clients in the PAS. + --- If the adjustable sound was stopped on the client, it will begin playing again on the origin with the given parameters. + --- This function is ratelimited to reduce network consumption, and subsequent updates will be smoothed on the client with an equivalent delta time. + --- @param Origin table The entity to update the sound on + --- @param ShouldStop? boolean Whether the sound should be destroyed; defaults to false + --- @param Pitch integer The sound's pitch from 0-255 + --- @param Volume number A float representing the sound's volume. This is internally converted into an integer from 0-255 for network optimization function Sounds.SendAdjustableSound(Origin, ShouldStop, Pitch, Volume) ShouldStop = ShouldStop or false local Time = CurTime() @@ -89,11 +89,11 @@ function Sounds.SendAdjustableSound(Origin, ShouldStop, Pitch, Volume) end end ---- Creates a sound table to be broadcasted to all players within PAS. ---- Just like the CreateAdjustableSound, except meant to play multiple sounds on an entity that can be adjusted based on a variable. ---- This also allows us to modify the pitch/volume of multiple looping sounds (for an engine) with minimal network usage. ---- @param Origin table The entity to play the sound from ---- @param SoundTable table The table whose keys are arbitrary RPM's and values containing a table with a sound path, pitch and volume, to be played at a defined RPM(Its keys). + --- Creates a sound table to be broadcasted to all players within PAS. + --- This allows us to then create multiple sounds attached to a single entity, fully clientsided. + --- For creating 13 sounds, the data required can ballon up to 1.537kb's of data at once. + --- @param Origin table The entity to play the sound from + --- @param SoundTable table The table whose keys are arbitrary RPM's and values containing a table with a sound path, pitch and volume, to be played at a defined RPM(Its keys). function Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) if not IsValid(Origin) then return end if not istable(SoundTable) then return end @@ -105,31 +105,23 @@ function Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) net.SendPAS(Origin:GetPos()) end ---- Sends an update to multiple adjustable sounds to all clients within PAS. ---- If all the adjustable sounds were stopped by the client, it will begin playing again on the origin with the given parameters. ---- This function mirrors "SendAdjustableSound" and as such is rate limited ---- @param Origin table The entity to update the sound on ---- @param ShouldStop? boolean Whether the sound should be destroyed; defaults to false ---- @param PathTable table The table whose keys are arbitrary RPM's and values containing a table with a sound path, pitch and volume, to be played at a defined RPM. -function Sounds.SendMultipleAdjustableSounds(Origin, ShouldStop, SoundTable) + --- Sends an update to the client regarding Throttle, RPM and if it should stop the sound, from an engine. + --- This also allows us to modify the pitch/volume of multiple looping sounds (for an engine) with minimal network usage. + --- The sound calculations are also performed entirely clientside and require minimum network usage, and require net unreliable for better sound composition. + --- @param Origin table The entity to update the sound from + --- @param ShouldStop? boolean Whether the sound should be destroyed; defaults to false + --- @param Throttle? int The entity's throttle + --- @param RPM? int The entity's RPM +function Sounds.SendMultipleAdjustableSounds(Origin, ShouldStop, Throttle, RPM) + if not IsValid(Origin) then return end ShouldStop = ShouldStop or false - local Time = CurTime() - local OriginTbl = Origin.ACF - if not OriginTbl then - OriginTbl = {} - Origin.ACF = OriginTbl - end - OriginTbl.SoundTimer = OriginTbl.SoundTimer or Time - -- Slowing down the rate of sending a bit - if OriginTbl.SoundTimer <= Time or ShouldStop then - net.Start("ACF_Sounds_Adjustable_Multi", true) - net.WriteEntity(Origin) - net.WriteBool(ShouldStop) - if not ShouldStop then - net.WriteTable(SoundTable) - end - net.SendPAS(Origin:GetPos()) - OriginTbl.SoundTimer = Time + 0.05 + net.Start("ACF_Sounds_Adjustable_Multi", true) + net.WriteEntity(Origin) + net.WriteBool(ShouldStop) + if not ShouldStop then + net.WriteUInt(Throttle or 0, 7) + net.WriteUInt(RPM or 0, 14) -- Theorically there are engines capable of reaching more than 20K RPM. If you do so, you can go off yourself... end + net.SendPAS(Origin:GetPos()) end diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index d111cf1fc..068e9f4c9 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -371,19 +371,19 @@ do -- Spawn and Update functions -- Test constant table, remove this before PR! local SuperDuperHandyTestTable = { - [714] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", 100, 0}, - [967] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", 100, 0}, - [1538] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", 100, 0}, - [1978] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", 100, 0}, - [2571] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", 100, 0}, - [3450] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", 100, 0}, - [3889] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", 100, 0}, - [4482] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", 100, 0}, - [4922] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", 100, 0}, - [5295] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", 100, 0}, - [5823] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", 100, 0}, - [6350] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", 100, 0}, - [6833] = {"acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", 100, 0} + [714] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", Pitch = 100, Volume = 0}, + [967] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", Pitch = 100, Volume = 0}, + [1538] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", Pitch = 100, Volume = 0}, + [1978] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", Pitch = 100, Volume = 0}, + [2571] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", Pitch = 100, Volume = 0}, + [3450] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", Pitch = 100, Volume = 0}, + [3889] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", Pitch = 100, Volume = 0}, + [4482] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", Pitch = 100, Volume = 0}, + [4922] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", Pitch = 100, Volume = 0}, + [5295] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", Pitch = 100, Volume = 0}, + [5823] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", Pitch = 100, Volume = 0}, + [6350] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", Pitch = 100, Volume = 0}, + [6833] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", Pitch = 100, Volume = 0} } Entity.Active = false @@ -620,27 +620,26 @@ function ENT:ACF_OnDamage(DmgResult, DmgInfo) return HitRes end --- Change this --- TODO(TMF): Optimize how much data is about to be sent to the client! function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() local SoundBank = SelfTbl.SoundBank - if SelfTbl.Sound then - CalcPitchVolume(SelfTbl) - Sounds.SendMultipleAdjustableSounds(self, false, SoundBank) - --PrintTable(SoundBank) + if SelfTbl.Sound then + local Throttle = Round(SelfTbl.Throttle) + local RPM = Round(SelfTbl.FlyRPM) + + Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) -- Should this be one thing, two...? else + -- TODO(TMF): Optimize how much data is about to be sent to the client! Sounds.CreateMultipleAdjustableSounds(self, SoundBank) - SelfTbl.Sound = true -- Not really needed anymore i think? + SelfTbl.Sound = true print("Soundbank successfully created!") - --PrintTable(SoundBank) end end function ENT:DestroyAllSounds() - Sounds.SendMultipleAdjustableSounds(self, true, _) + Sounds.SendMultipleAdjustableSounds(self, true, _, _) self.LastSound = nil self.LastPitch = 0 From 2d84e1613c460c9798ebc3bb74678f93836c65b3 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 7 Feb 2026 19:44:16 -0300 Subject: [PATCH 08/59] Fix single sounds not playing, move SuperDuperHandySoundBank back to its engine registration thing --- lua/acf/core/utilities/sounds/sounds_cl.lua | 38 ++++----- lua/acf/entities/engines/special.lua | 28 +++--- lua/entities/acf_engine/init.lua | 95 +++++++++++++-------- 3 files changed, 91 insertions(+), 70 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index f3f0d4932..2066d57e3 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -75,6 +75,7 @@ end -- This is where we store our sound objects local SoundObjects = {} +local SoundCount = 0 -- Consider if we actually want to do this too! (commented out for now) --local SmoothRPM = 0 --local SmoothThrottle = 0 @@ -155,8 +156,10 @@ do -- Processing adjustable sounds (for example, engine noises) --- @param Pitch integer The sound's pitch from 0-255 --- @param Volume number A float representing the sound's volume function Sounds.UpdateAdjustableSound(Origin, Pitch, Volume) - local Sound = Origin - if type(Sound) ~= "CSoundPatch" then return end + if not IsValid(Origin) then return end + + local Sound = type(Origin) ~= "CSoundPatch" and Origin.Sound or Origin + if not Sound then return end Volume = Volume * ACF.Volume @@ -230,19 +233,21 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) local IsValid = IsValid -- Should this stay as local to each scope? function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable) - local count = 1 for rpm, soundTable in pairs(PathTable) do + if not Sounds.IsValidSound(soundTable.Path) then return end local Sound = Sounds.CreateAdjustableSound(Origin, soundTable.Path, - soundTable.Pitch, - soundTable.Volume + soundTable.Pitch or 100, + soundTable.Volume or 0 ) + SoundCount = SoundCount + 1 table.insert(SoundObjects, count, {["rpm"] = rpm, ["sound"] = Sound}) - count = count + 1 - Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch, 0) + Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch, soundTable.Volume) + end + if SoundCount > 1 then + table.sort(SoundObjects, function(a, b) return a.rpm < b.rpm end) end - table.sort(SoundObjects, function(a, b) return a.rpm < b.rpm end) -- Ensuring that the sounds can't stick around if the server doesn't properly ask for them to be destroyed Origin:CallOnRemove("ACF_ForceStopMultipleAdjustableSounds", function(Entity) Sounds.DeleteMultipleAdjustableSounds(Entity, true) @@ -250,16 +255,13 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) end function Sounds.DeleteMultipleAdjustableSounds(Origin, _) - local count = 0 - print("Deleting sounds!") - -- I suppose this actually gets garbage collected? for idx, snd in ipairs(SoundObjects) do snd.sound:Stop() SoundObjects[idx] = nil count = idx end Origin.Sound = nil - print("Successfully deleted " .. count .. " sounds!") + SoundCount = 0 end net.Receive("ACF_Sounds_AdjustableCreate_Multi", function(len) @@ -270,15 +272,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) if not IsValid(Origin) then return end if not istable(SoundTable) then return end - -- If only one sound was found, we assume it's at -1 index - print(#SoundTable) - --if #SoundTable < 1 then - --PrintTable(SoundTable) - -- local SoundTable = SoundTable[-1] - -- Sounds.CreateAdjustableSound(Origin, SoundTable.Path, SoundTable.Pitch, SoundTable.Volume) - --else - Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) - --end + Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) end) net.Receive("ACF_Sounds_Adjustable_Multi", function(len) @@ -292,7 +286,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) if ShouldStop then Sounds.DeleteMultipleAdjustableSounds(Origin) else - DoPitchVolumeAtRPM(Origin, Throttle, RPM) + DoPitchVolume(Origin, Throttle, RPM) end end) end diff --git a/lua/acf/entities/engines/special.lua b/lua/acf/entities/engines/special.lua index 48dc09bbf..f6f26e9e2 100644 --- a/lua/acf/entities/engines/special.lua +++ b/lua/acf/entities/engines/special.lua @@ -78,20 +78,20 @@ do -- Special I4 Engines Model = "models/engines/inline4s.mdl", Sound = "acf_extra/vehiclefx/engines/l4/mini_onhigh.wav", SoundBank = { - [714] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", - [967] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", - [1538] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", - [1978] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", - [2571] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", - [3450] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", - [3889] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", - [4482] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", - [4922] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", - [5295] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", - [5823] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", - [6350] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", - [6833] = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav" - }, + [714] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", Pitch = 100, Volume = 0}, + [967] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", Pitch = 100, Volume = 0}, + [1538] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", Pitch = 100, Volume = 0}, + [1978] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", Pitch = 100, Volume = 0}, + [2571] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", Pitch = 100, Volume = 0}, + [3450] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", Pitch = 100, Volume = 0}, + [3889] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", Pitch = 100, Volume = 0}, + [4482] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", Pitch = 100, Volume = 0}, + [4922] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", Pitch = 100, Volume = 0}, + [5295] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", Pitch = 100, Volume = 0}, + [5823] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", Pitch = 100, Volume = 0}, + [6350] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", Pitch = 100, Volume = 0}, + [6833] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", Pitch = 100, Volume = 0} + }, Fuel = { Petrol = true }, Type = "GenericPetrol", Mass = 138, diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 068e9f4c9..70a9af534 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -118,6 +118,16 @@ local TimerCreate = timer.Create local TimerRemove = timer.Remove local TickInterval = engine.TickInterval +local function GetPitchVolume(Engine) + local RPM = Engine.FlyRPM + local Pitch = Clamp(20 + (RPM * Engine.SoundPitch) * 0.02, 1, 255) + -- Rev limiter code disabled because it has issues with the volume delta time, but it's still here if we need it + local Throttle = Engine.Throttle -- Engine.RevLimited and 0 or Engine.Throttle + local Volume = 0.25 + (0.1 + 0.9 * ((RPM / Engine.LimitRPM) ^ 1.5)) * Throttle * 0.666 + + return Pitch, Volume * Engine.SoundVolume +end + local function GetNextFuelTank(Engine) local FuelTanks = Engine.FuelTanks if not next(FuelTanks) then return end @@ -196,7 +206,6 @@ local function SetActive(Entity, Value, EntTbl) end) else -- Was on, turn off EntTbl.Active = false - EntTbl.SmoothRPM = 0 EntTbl.FlyRPM = 0 EntTbl.Torque = 0 @@ -273,6 +282,7 @@ do -- Spawn and Update functions -- Engine update function local function UpdateEngine(Entity, Data, Class, Engine, Type) local Mass = Engine.Mass + local SoundCount = 0 Entity.ACF = Entity.ACF or {} @@ -286,12 +296,17 @@ do -- Spawn and Update functions Entity[V] = Data[V] end + for _ in pairs(Entity.SoundBank) do + SoundCount = SoundCount + 1 + end + Entity.Name = Engine.Name Entity.ShortName = Engine.ID Entity.EntType = Class.Name Entity.ClassData = Class Entity.DefaultSound = Engine.Sound - Entity.SoundBank = Entity.SoundBank or {[-1] = Entity.DefaultSound} + Entity.SoundBank = Entity.SoundBank or {[-1] = {Path = Entity.DefaultSound}} + Entity.SoundCount = SoundCount or 1 Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 Entity.AddCurveWidth = Entity.AddCurveWidth or 0 @@ -304,8 +319,6 @@ do -- Spawn and Update functions Entity.PeakMinRPM = Engine.RPM.PeakMin Entity.PeakMaxRPM = Engine.RPM.PeakMax Entity.LimitRPM = Engine.RPM.Limit - Entity.SmoothRPM = Entity.SmoothRPM or 0 - Entity.SmoothThrottle = Entity.SmoothThrottle or 0 Entity.RevLimited = false Entity.FlywheelOverride = Engine.RPM.Override Entity.FlywheelMass = Engine.FlywheelMass @@ -369,23 +382,6 @@ do -- Spawn and Update functions Player:AddCleanup("acf_engine", Entity) Player:AddCount(Limit, Entity) - -- Test constant table, remove this before PR! - local SuperDuperHandyTestTable = { - [714] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", Pitch = 100, Volume = 0}, - [967] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", Pitch = 100, Volume = 0}, - [1538] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", Pitch = 100, Volume = 0}, - [1978] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", Pitch = 100, Volume = 0}, - [2571] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", Pitch = 100, Volume = 0}, - [3450] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", Pitch = 100, Volume = 0}, - [3889] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", Pitch = 100, Volume = 0}, - [4482] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", Pitch = 100, Volume = 0}, - [4922] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", Pitch = 100, Volume = 0}, - [5295] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", Pitch = 100, Volume = 0}, - [5823] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", Pitch = 100, Volume = 0}, - [6350] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", Pitch = 100, Volume = 0}, - [6833] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", Pitch = 100, Volume = 0} - } - Entity.Active = false Entity.Gearboxes = {} Entity.FuelTanks = {} @@ -394,10 +390,9 @@ do -- Spawn and Update functions Entity.FuelUsage = 0 Entity.Throttle = 0 Entity.FlyRPM = 0 - Entity.SmoothRPM = 0 - Entity.SmoothThrottle = 0 Entity.SoundPath = Engine.Sound - Entity.SoundBank = SuperDuperHandyTestTable or {[-1] = Entity.SoundPath} -- i have no idea if this is a good idea + Entity.SoundBank = Entity.SoundBank or {[-1] = {Path = Entity.SoundPath}} -- i have no idea if this is a good idea + Entity.SoundCount = 0 Entity.AddCurveWidth = Entity.AddCurveWidth or 0 Entity.LastPitch = 0 Entity.LastTorque = 0 @@ -623,23 +618,55 @@ end function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() local SoundBank = SelfTbl.SoundBank + local SoundCount = SelfTbl.SoundCount - if SelfTbl.Sound then - local Throttle = Round(SelfTbl.Throttle) - local RPM = Round(SelfTbl.FlyRPM) + -- If there's more than one sound, then play from the soundbank, otherwise not + if SoundCount > 1 then + if SelfTbl.Sound then + local Throttle = Round(SelfTbl.Throttle) + local RPM = Round(SelfTbl.FlyRPM) - Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) -- Should this be one thing, two...? + Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) -- Should this be one thing, two...? + else + -- TODO(TMF): Optimize how much data is about to be sent to the client! + Sounds.CreateMultipleAdjustableSounds(self, SoundBank) + SelfTbl.Sound = true + --print("Soundbank successfully created!") + end else - -- TODO(TMF): Optimize how much data is about to be sent to the client! - Sounds.CreateMultipleAdjustableSounds(self, SoundBank) - SelfTbl.Sound = true - print("Soundbank successfully created!") + local Path = SelfTbl.SoundPath + local LastSound = SelfTbl.LastSound + + if Path ~= LastSound and LastSound ~= nil then + self:DestroyAllSounds() + + SelfTbl.LastSound = Path + end + + if Path == "" then return end + if not SelfTbl.Active then return end + + local Pitch, Volume = GetPitchVolume(SelfTbl) + + if math.abs(Pitch - SelfTbl.LastPitch) < 1 then return end -- Don't bother updating if the pitch difference is too small to notice + + SelfTbl.LastPitch = Pitch + + if SelfTbl.Sound then + Sounds.SendAdjustableSound(self, false, Pitch, Volume) + else + Sounds.CreateAdjustableSound(self, Path, Pitch, Volume) + SelfTbl.Sound = true + end end end function ENT:DestroyAllSounds() - - Sounds.SendMultipleAdjustableSounds(self, true, _, _) + if self.SoundCount > 1 then + Sounds.SendMultipleAdjustableSounds(self, true, _, _) + else + Sounds.SendAdjustableSound(self, true, _, _) + end self.LastSound = nil self.LastPitch = 0 From d92e5f6e8d247d68f9a3669ffce5c2502aba38c3 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 7 Feb 2026 19:45:13 -0300 Subject: [PATCH 09/59] Whoopsy daisy --- lua/acf/core/utilities/sounds/sounds_cl.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 2066d57e3..ad7ed069a 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -286,7 +286,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) if ShouldStop then Sounds.DeleteMultipleAdjustableSounds(Origin) else - DoPitchVolume(Origin, Throttle, RPM) + DoPitchVolumeAtRPM(Origin, Throttle, RPM) end end) end From 3271767483048c9bb82b0913adf7c474a37e5fa6 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 7 Feb 2026 20:49:00 -0300 Subject: [PATCH 10/59] Shuffling things around, cleanup --- lua/acf/core/utilities/sounds/sounds_cl.lua | 130 +++++++++++--------- lua/acf/core/utilities/sounds/sounds_sv.lua | 2 +- lua/acf/entities/engines/special.lua | 26 ++-- lua/entities/acf_engine/init.lua | 7 +- 4 files changed, 86 insertions(+), 79 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index ad7ed069a..99478766c 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -52,56 +52,6 @@ local function DoDelayed(Origin, Call, Instant) end DoDelayed = DoDelayed --- Maps a value, X, from a range A-B, to a new range C-D -local function map(x, a, b, c, d) - return (x - a) / (b - a) * (d - c) + c -end - --- Fade function taken from: --- https://dsp.stackexchange.com/questions/37477/understanding-equal-power-crossfades --- https://dsp.stackexchange.com/questions/14754/equal-power-crossfade --- https://i.imgur.com/KaFmaMf.png -local function fade(n, min, mid, max) - local _PI = math.pi - - if n < min or n > max then return 0 end - - if n > mid then - min = mid - (max - mid) - end - - return math.cos((1 - ((n - min) / (mid - min))) * (_PI / 2)) -end - --- This is where we store our sound objects -local SoundObjects = {} -local SoundCount = 0 --- Consider if we actually want to do this too! (commented out for now) ---local SmoothRPM = 0 ---local SmoothThrottle = 0 - -local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) - local AdditionalCurveWidth = Origin.AddCurveWidth or 0 - --SmoothRPM = SmoothRPM * (1 - 0.1) + RPM * 0.1 - --SmoothThrottle = SmoothThrottle * (1 - 0.1) + Throttle * 10 - - -- Sound volumes when throttle is 0 and 100 respectively - local _OFFVOLUME = 0.25 - local _ONVOLUME = 1 - - -- TODO(TMF): Potentially some mechanism here to check for any differences and only update those - for idx, soundTable in ipairs(SoundObjects) do - if not SoundObjects[idx].rpm then continue end - local min = idx == 1 and 0 or SoundObjects[idx - 1].rpm - local mid = RPM - local max = idx == #SoundObjects and 16383 or SoundObjects[idx + 1].rpm - local curve = fade(RPM, min - AdditionalCurveWidth, mid, max + AdditionalCurveWidth) - local volume = curve * map(Throttle, 0, 1, _OFFVOLUME, _ONVOLUME) - local pitch = (RPM / soundTable.rpm) * 100 - Sounds.UpdateAdjustableSound(soundTable.sound, pitch, volume) - end -end - do -- Playing regular sounds --- Plays a single, non-looping sound at the given origin. --- @param Origin table | vector The source to play the sound from @@ -229,43 +179,104 @@ do -- Processing adjustable sounds (for example, engine noises) end) end +-- Maps a value, X, from a range A-B, to a new range C-D +local function map(x, a, b, c, d) + return (x - a) / (b - a) * (d - c) + c +end + +-- Fade function taken from: +-- https://dsp.stackexchange.com/questions/37477/understanding-equal-power-crossfades +-- https://dsp.stackexchange.com/questions/14754/equal-power-crossfade +-- https://i.imgur.com/KaFmaMf.png +local function fade(n, min, mid, max) + local _PI = math.pi + + if n < min or n > max then return 0 end + + if n > mid then + min = mid - (max - mid) + end + + return math.cos((1 - ((n - min) / (mid - min))) * (_PI / 2)) +end + +-- Consider if we actually want to do this too! (commented out for now) +--local SmoothRPM = 0 +--local SmoothThrottle = 0 + +local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) + local SoundObjects = Origin.SoundObjects + --SmoothRPM = SmoothRPM * (1 - 0.1) + RPM * 0.1 + --SmoothThrottle = SmoothThrottle * (1 - 0.1) + Throttle * 10 + + -- Sound volumes when throttle is 0 and 100 respectively + local _OFFVOLUME = 0.25 + local _ONVOLUME = 1 + + -- TODO(TMF): Potentially some mechanism here to check for any differences and only update those + for idx, soundTable in ipairs(SoundObjects) do + if not soundTable.rpm then continue end + local addCurveWidth = soundTable.AddCurveWidth or 0 + local min = idx == 1 and 0 or SoundObjects[idx - 1].rpm + local mid = RPM + local max = idx == #SoundObjects and 16383 or SoundObjects[idx + 1].rpm + local curve = fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) + local volume = curve * map(Throttle, 0, 1, _OFFVOLUME, _ONVOLUME) + local pitch = (RPM / soundTable.rpm) * 100 + Sounds.UpdateAdjustableSound(soundTable.sound, pitch, volume) + end +end + do -- Multiple Engine Sounds(ex. Interpolated sounds) local IsValid = IsValid -- Should this stay as local to each scope? + --- Creates many sounds from a table, and stores their entries in another table. + --- Reuses existing methods to create and update sounds but these are locally stored in SoundObjects table. + --- The networked table is then discarded. + --- @param Origin table The entity to play the sounds from + --- @param PathTable table The networked table with nested table(Key as RPM) containing sound path, pitch and volume function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable) + -- This is where we store our sound objects + local SoundObjects = {} + local SoundCount = 0 + for rpm, soundTable in pairs(PathTable) do if not Sounds.IsValidSound(soundTable.Path) then return end local Sound = Sounds.CreateAdjustableSound(Origin, soundTable.Path, - soundTable.Pitch or 100, - soundTable.Volume or 0 + soundTable.Pitch or 100, 0 -- Create the sound deafened ) SoundCount = SoundCount + 1 - table.insert(SoundObjects, count, {["rpm"] = rpm, ["sound"] = Sound}) + -- Insert the sound objects inside the SoundObjects table, indexed as the rpm to play the sound at + -- addCurveWidth allows the sound to play in a wider range of RPM's + table.insert(SoundObjects, count, {["rpm"] = rpm, ["addCurveWidth"] = soundTable.Width, ["sound"] = Sound}) - Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch, soundTable.Volume) + Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch, 0) end + -- Sort the table if its necessary before moving on, so it plays in sequential order if SoundCount > 1 then table.sort(SoundObjects, function(a, b) return a.rpm < b.rpm end) end + Origin.SoundObjects = SoundObjects -- Ensuring that the sounds can't stick around if the server doesn't properly ask for them to be destroyed Origin:CallOnRemove("ACF_ForceStopMultipleAdjustableSounds", function(Entity) Sounds.DeleteMultipleAdjustableSounds(Entity, true) end) end + --- Stops all the existing sounds from the entity + --- @param Origin table The entity to stop all the sounds from function Sounds.DeleteMultipleAdjustableSounds(Origin, _) - for idx, snd in ipairs(SoundObjects) do + for idx, snd in ipairs(Origin.SoundObjects) do snd.sound:Stop() - SoundObjects[idx] = nil + Origin.SoundObjects[idx] = nil count = idx end Origin.Sound = nil SoundCount = 0 end - net.Receive("ACF_Sounds_AdjustableCreate_Multi", function(len) - print("Received " .. len .. " bits from server for \"ACF_Sounds_AdjustableCreate_Multi\"") + net.Receive("ACF_Sounds_AdjustableCreate_Multi", function() local Origin = net.ReadEntity() local SoundTable = net.ReadTable() @@ -275,8 +286,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) end) - net.Receive("ACF_Sounds_Adjustable_Multi", function(len) - print("Received " .. len .. " bits from server for \"ACF_Sounds_Adjustable_Multi\"") + net.Receive("ACF_Sounds_Adjustable_Multi", function() local Origin = net.ReadEntity() local ShouldStop = net.ReadBool() local Throttle = net.ReadUInt(7) diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index 9212fd2fe..7a75fda7c 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -121,7 +121,7 @@ function Sounds.SendMultipleAdjustableSounds(Origin, ShouldStop, Throttle, RPM) net.WriteBool(ShouldStop) if not ShouldStop then net.WriteUInt(Throttle or 0, 7) - net.WriteUInt(RPM or 0, 14) -- Theorically there are engines capable of reaching more than 20K RPM. If you do so, you can go off yourself... + net.WriteUInt(RPM or 0, 14) -- Theorically there are engines capable of reaching more than 16K RPM. If you do so, you can go off yourself... end net.SendPAS(Origin:GetPos()) end diff --git a/lua/acf/entities/engines/special.lua b/lua/acf/entities/engines/special.lua index f6f26e9e2..55f29bc84 100644 --- a/lua/acf/entities/engines/special.lua +++ b/lua/acf/entities/engines/special.lua @@ -78,19 +78,19 @@ do -- Special I4 Engines Model = "models/engines/inline4s.mdl", Sound = "acf_extra/vehiclefx/engines/l4/mini_onhigh.wav", SoundBank = { - [714] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", Pitch = 100, Volume = 0}, - [967] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", Pitch = 100, Volume = 0}, - [1538] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", Pitch = 100, Volume = 0}, - [1978] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", Pitch = 100, Volume = 0}, - [2571] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", Pitch = 100, Volume = 0}, - [3450] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", Pitch = 100, Volume = 0}, - [3889] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", Pitch = 100, Volume = 0}, - [4482] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", Pitch = 100, Volume = 0}, - [4922] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", Pitch = 100, Volume = 0}, - [5295] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", Pitch = 100, Volume = 0}, - [5823] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", Pitch = 100, Volume = 0}, - [6350] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", Pitch = 100, Volume = 0}, - [6833] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", Pitch = 100, Volume = 0} + [714] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", Pitch = 100, Width = 0}, + [967] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", Pitch = 100, Width = 0}, + [1538] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", Pitch = 100, Width = 0}, + [1978] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", Pitch = 100, Width = 0}, + [2571] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", Pitch = 100, Width = 0}, + [3450] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", Pitch = 100, Width = 0}, + [3889] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", Pitch = 100, Width = 0}, + [4482] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", Pitch = 100, Width = 0}, + [4922] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", Pitch = 100, Width = 0}, + [5295] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", Pitch = 100, Width = 0}, + [5823] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", Pitch = 100, Width = 0}, + [6350] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", Pitch = 100, Width = 0}, + [6833] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", Pitch = 100, Width = 0} }, Fuel = { Petrol = true }, Type = "GenericPetrol", diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 70a9af534..5a034aa63 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -309,7 +309,6 @@ do -- Spawn and Update functions Entity.SoundCount = SoundCount or 1 Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 - Entity.AddCurveWidth = Entity.AddCurveWidth or 0 Entity.TorqueCurve = Engine.TorqueCurve Entity.PeakTorque = Engine.Torque Entity.PeakPower = Engine.PeakPower @@ -393,7 +392,6 @@ do -- Spawn and Update functions Entity.SoundPath = Engine.Sound Entity.SoundBank = Entity.SoundBank or {[-1] = {Path = Entity.SoundPath}} -- i have no idea if this is a good idea Entity.SoundCount = 0 - Entity.AddCurveWidth = Entity.AddCurveWidth or 0 Entity.LastPitch = 0 Entity.LastTorque = 0 Entity.LastFuelUsage = 0 @@ -617,7 +615,7 @@ end function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() - local SoundBank = SelfTbl.SoundBank + local SoundBank = SelfTbl.SoundBank local SoundCount = SelfTbl.SoundCount -- If there's more than one sound, then play from the soundbank, otherwise not @@ -626,12 +624,11 @@ function ENT:UpdateSoundBank(SelfTbl) local Throttle = Round(SelfTbl.Throttle) local RPM = Round(SelfTbl.FlyRPM) - Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) -- Should this be one thing, two...? + Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) else -- TODO(TMF): Optimize how much data is about to be sent to the client! Sounds.CreateMultipleAdjustableSounds(self, SoundBank) SelfTbl.Sound = true - --print("Soundbank successfully created!") end else local Path = SelfTbl.SoundPath From 1a5706846c896af7d5b43321e1b793274861ad17 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 7 Feb 2026 21:38:34 -0300 Subject: [PATCH 11/59] Actually no, nevermind this goes back to how it was for now --- lua/acf/entities/engines/special.lua | 1 + lua/entities/acf_engine/init.lua | 75 ++++++++++++++++------------ 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/lua/acf/entities/engines/special.lua b/lua/acf/entities/engines/special.lua index 55f29bc84..dcb37e119 100644 --- a/lua/acf/entities/engines/special.lua +++ b/lua/acf/entities/engines/special.lua @@ -71,6 +71,7 @@ do -- Special I4 Engines FOV = 120, }, }) + -- Test engine, remove before PR! Engines.RegisterItem("2.0L-I4", "SP", { Name = "2.0L 4B1 I4 Petrol", Description = "The Mitsubishi 4B1 engine is a range of all-alloy straight-4 piston engines built at Mitsubishi's Japanese" .. diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 5a034aa63..430257353 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -192,7 +192,11 @@ local function SetActive(Entity, Value, EntTbl) EntTbl.Torque = EntTbl.PeakTorque EntTbl.FlyRPM = EntTbl.IdleRPM * 1.5 - Entity:UpdateSoundBank(EntTbl) + if Entity.SoundCount > 1 then + Entity:UpdateSoundBank(EntTbl) + else + Entity:UpdateSound(EntTbl) + end Entity:NextThink(Clock.CurTime + TickInterval()) @@ -296,6 +300,7 @@ do -- Spawn and Update functions Entity[V] = Data[V] end + -- Coutn all the sounds that exist in the SoundBank for _ in pairs(Entity.SoundBank) do SoundCount = SoundCount + 1 end @@ -306,7 +311,7 @@ do -- Spawn and Update functions Entity.ClassData = Class Entity.DefaultSound = Engine.Sound Entity.SoundBank = Entity.SoundBank or {[-1] = {Path = Entity.DefaultSound}} - Entity.SoundCount = SoundCount or 1 + Entity.SoundCount = SoundCount or 0 Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 Entity.TorqueCurve = Engine.TorqueCurve @@ -353,6 +358,7 @@ do -- Spawn and Update functions ACF.Activate(Entity, true) Contraption.SetMass(Entity, Mass) + print("Finished updating engine! Found " .. SoundCount .. " sound" .. (SoundCount == 1 and "" or "s") .. " in SoundBank!") end -- Engine creation function @@ -616,45 +622,45 @@ end function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() local SoundBank = SelfTbl.SoundBank - local SoundCount = SelfTbl.SoundCount - -- If there's more than one sound, then play from the soundbank, otherwise not - if SoundCount > 1 then - if SelfTbl.Sound then - local Throttle = Round(SelfTbl.Throttle) - local RPM = Round(SelfTbl.FlyRPM) + if SelfTbl.Sound then + local Throttle = Round(SelfTbl.Throttle) + local RPM = Round(SelfTbl.FlyRPM) - Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) - else - -- TODO(TMF): Optimize how much data is about to be sent to the client! - Sounds.CreateMultipleAdjustableSounds(self, SoundBank) - SelfTbl.Sound = true - end + Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) else - local Path = SelfTbl.SoundPath - local LastSound = SelfTbl.LastSound + -- TODO(TMF): Optimize how much data is about to be sent to the client! + Sounds.CreateMultipleAdjustableSounds(self, SoundBank) + SelfTbl.Sound = true + end +end - if Path ~= LastSound and LastSound ~= nil then - self:DestroyAllSounds() +function ENT:UpdateSound(SelfTbl) + SelfTbl = SelfTbl or self:GetTable() - SelfTbl.LastSound = Path - end + local Path = SelfTbl.SoundPath + local LastSound = SelfTbl.LastSound - if Path == "" then return end - if not SelfTbl.Active then return end + if Path ~= LastSound and LastSound ~= nil then + self:DestroyAllSounds() - local Pitch, Volume = GetPitchVolume(SelfTbl) + SelfTbl.LastSound = Path + end - if math.abs(Pitch - SelfTbl.LastPitch) < 1 then return end -- Don't bother updating if the pitch difference is too small to notice + if Path == "" then return end + if not SelfTbl.Active then return end - SelfTbl.LastPitch = Pitch + local Pitch, Volume = GetPitchVolume(SelfTbl) - if SelfTbl.Sound then - Sounds.SendAdjustableSound(self, false, Pitch, Volume) - else - Sounds.CreateAdjustableSound(self, Path, Pitch, Volume) - SelfTbl.Sound = true - end + if math.abs(Pitch - SelfTbl.LastPitch) < 1 then return end -- Don't bother updating if the pitch difference is too small to notice + + SelfTbl.LastPitch = Pitch + + if SelfTbl.Sound then + Sounds.SendAdjustableSound(self, false, Pitch, Volume) + else + Sounds.CreateAdjustableSound(self, Path, Pitch, Volume) + SelfTbl.Sound = true end end @@ -880,7 +886,12 @@ function ENT:CalcRPM(SelfTbl) SelfTbl.FlyRPM = FlyRPM - min(TorqueDiff, TotalReqTq) / Inertia SelfTbl.LastThink = ClockTime - self:UpdateSoundBank(SelfTbl) + if self.SoundCount > 1 then + self:UpdateSoundBank(SelfTbl) + else + self:UpdateSound(SelfTbl) + end + self:UpdateOutputs(SelfTbl) end From 6a77f4a18cacaf4ca08efdfa3d9a1bdb9813c60d Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sun, 8 Feb 2026 17:13:22 -0300 Subject: [PATCH 12/59] Fix not getting any SoundBanks, improve clientside sound object storing and fetching --- lua/acf/core/classes/engines/registration.lua | 5 ----- lua/acf/core/utilities/sounds/sounds_cl.lua | 15 +++++++++------ lua/acf/menu/operations/acf_menu.lua | 5 +---- lua/entities/acf_engine/init.lua | 11 +++++------ 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/lua/acf/core/classes/engines/registration.lua b/lua/acf/core/classes/engines/registration.lua index 3909832cb..f9fe0941f 100644 --- a/lua/acf/core/classes/engines/registration.lua +++ b/lua/acf/core/classes/engines/registration.lua @@ -45,11 +45,6 @@ function Engines.RegisterItem(ID, ClassID, Data) Class.Sound = "vehicles/junker/jnk_fourth_cruise_loop2.wav" end - -- Will this work though? - if not Class.SoundBank then - Class.SoundBank = {[-1] = Class.Sound} - end - if Loaded then AddPerformanceData(Class) end diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 99478766c..015937f25 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -108,7 +108,7 @@ do -- Processing adjustable sounds (for example, engine noises) function Sounds.UpdateAdjustableSound(Origin, Pitch, Volume) if not IsValid(Origin) then return end - local Sound = type(Origin) ~= "CSoundPatch" and Origin.Sound or Origin + local Sound = Origin.Sound if not Sound then return end Volume = Volume * ACF.Volume @@ -216,6 +216,7 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) -- TODO(TMF): Potentially some mechanism here to check for any differences and only update those for idx, soundTable in ipairs(SoundObjects) do if not soundTable.rpm then continue end + Origin.Sound = soundTable.sound local addCurveWidth = soundTable.AddCurveWidth or 0 local min = idx == 1 and 0 or SoundObjects[idx - 1].rpm local mid = RPM @@ -223,7 +224,7 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local curve = fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) local volume = curve * map(Throttle, 0, 1, _OFFVOLUME, _ONVOLUME) local pitch = (RPM / soundTable.rpm) * 100 - Sounds.UpdateAdjustableSound(soundTable.sound, pitch, volume) + Sounds.UpdateAdjustableSound(Origin, pitch, volume) end end @@ -249,7 +250,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) SoundCount = SoundCount + 1 -- Insert the sound objects inside the SoundObjects table, indexed as the rpm to play the sound at -- addCurveWidth allows the sound to play in a wider range of RPM's - table.insert(SoundObjects, count, {["rpm"] = rpm, ["addCurveWidth"] = soundTable.Width, ["sound"] = Sound}) + table.insert(SoundObjects, SoundCount, {["rpm"] = rpm, ["addCurveWidth"] = soundTable.Width, ["sound"] = Sound}) Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch, 0) end @@ -257,7 +258,9 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) if SoundCount > 1 then table.sort(SoundObjects, function(a, b) return a.rpm < b.rpm end) end + Origin.SoundObjects = SoundObjects + Origin.SoundCount = SoundCount -- Ensuring that the sounds can't stick around if the server doesn't properly ask for them to be destroyed Origin:CallOnRemove("ACF_ForceStopMultipleAdjustableSounds", function(Entity) Sounds.DeleteMultipleAdjustableSounds(Entity, true) @@ -267,13 +270,13 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) --- Stops all the existing sounds from the entity --- @param Origin table The entity to stop all the sounds from function Sounds.DeleteMultipleAdjustableSounds(Origin, _) + if not IsValid(Origin) then return end for idx, snd in ipairs(Origin.SoundObjects) do snd.sound:Stop() Origin.SoundObjects[idx] = nil - count = idx end - Origin.Sound = nil - SoundCount = 0 + Origin.Sound = nil + Origin.SoundCount = 0 end net.Receive("ACF_Sounds_AdjustableCreate_Multi", function() diff --git a/lua/acf/menu/operations/acf_menu.lua b/lua/acf/menu/operations/acf_menu.lua index 5ca3f1a58..badac666b 100644 --- a/lua/acf/menu/operations/acf_menu.lua +++ b/lua/acf/menu/operations/acf_menu.lua @@ -218,11 +218,8 @@ do -- Generic Spawner/Linker operation creator if Total > 1 then ReportMultiple(Player, Action, EntName, Failed, #Success, Total) else - -- FIXME(TMF): There's a bug here that prevents entities from being linked - -- Wether we accidentally hit the world in what's some quite obscure bug local Result = next(Success) and true or false - local Origin = table.remove(Result and Success or Failed) -- Somehow this table becomes nil - -- print(Result, Origin) + local Origin = table.remove(Result and Success or Failed) local Target = Origin.Name local Message = Origin.Message diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 430257353..c47910893 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -300,8 +300,8 @@ do -- Spawn and Update functions Entity[V] = Data[V] end - -- Coutn all the sounds that exist in the SoundBank - for _ in pairs(Entity.SoundBank) do + -- Count all the existing sounds in SoundBank + for _ in pairs(Engine.SoundBank) do SoundCount = SoundCount + 1 end @@ -310,8 +310,8 @@ do -- Spawn and Update functions Entity.EntType = Class.Name Entity.ClassData = Class Entity.DefaultSound = Engine.Sound - Entity.SoundBank = Entity.SoundBank or {[-1] = {Path = Entity.DefaultSound}} - Entity.SoundCount = SoundCount or 0 + Entity.SoundBank = Engine.SoundBank + Entity.SoundCount = SoundCount or 1 Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 Entity.TorqueCurve = Engine.TorqueCurve @@ -358,7 +358,6 @@ do -- Spawn and Update functions ACF.Activate(Entity, true) Contraption.SetMass(Entity, Mass) - print("Finished updating engine! Found " .. SoundCount .. " sound" .. (SoundCount == 1 and "" or "s") .. " in SoundBank!") end -- Engine creation function @@ -396,7 +395,7 @@ do -- Spawn and Update functions Entity.Throttle = 0 Entity.FlyRPM = 0 Entity.SoundPath = Engine.Sound - Entity.SoundBank = Entity.SoundBank or {[-1] = {Path = Entity.SoundPath}} -- i have no idea if this is a good idea + Entity.SoundBank = Entity.SoundBank or {} Entity.SoundCount = 0 Entity.LastPitch = 0 Entity.LastTorque = 0 From 7da3fb6e0a7c0235ad59ff0202dd00271d95918d Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sun, 8 Feb 2026 21:15:00 -0300 Subject: [PATCH 13/59] Several more tweaks done - Move sound counting to its own local function - Throttle is now networked properly - Fix DoPitchVolumeAtRPM function to use pitch and width properly - Some other changes to documentation --- lua/acf/core/utilities/sounds/sounds_cl.lua | 37 ++++++++++++--------- lua/acf/core/utilities/sounds/sounds_sv.lua | 6 ++-- lua/entities/acf_engine/init.lua | 22 +++++++----- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 015937f25..30b983935 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -204,6 +204,7 @@ end --local SmoothRPM = 0 --local SmoothThrottle = 0 +-- This is where the magic to interpolate sounds happen. local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local SoundObjects = Origin.SoundObjects --SmoothRPM = SmoothRPM * (1 - 0.1) + RPM * 0.1 @@ -217,13 +218,16 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) for idx, soundTable in ipairs(SoundObjects) do if not soundTable.rpm then continue end Origin.Sound = soundTable.sound - local addCurveWidth = soundTable.AddCurveWidth or 0 + + local addCurveWidth = soundTable.width or 0 + local enginePitch = soundTable.pitch or 1 local min = idx == 1 and 0 or SoundObjects[idx - 1].rpm local mid = RPM local max = idx == #SoundObjects and 16383 or SoundObjects[idx + 1].rpm local curve = fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) - local volume = curve * map(Throttle, 0, 1, _OFFVOLUME, _ONVOLUME) - local pitch = (RPM / soundTable.rpm) * 100 + local volume = curve * map(Throttle, 0, 100, _OFFVOLUME, _ONVOLUME) + local pitch = (RPM / soundTable.rpm) * enginePitch + Sounds.UpdateAdjustableSound(Origin, pitch, volume) end end @@ -231,13 +235,12 @@ end do -- Multiple Engine Sounds(ex. Interpolated sounds) local IsValid = IsValid -- Should this stay as local to each scope? - --- Creates many sounds from a table, and stores their entries in another table. - --- Reuses existing methods to create and update sounds but these are locally stored in SoundObjects table. - --- The networked table is then discarded. + --- Creates many sounds from a table, and stores their entries in the Origin's entity. + --- Reuses existing methods to create and update sounds. --- @param Origin table The entity to play the sounds from - --- @param PathTable table The networked table with nested table(Key as RPM) containing sound path, pitch and volume + --- @param PathTable table The networked table with nested table(Key as RPM) containing sound path, pitch and width function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable) - -- This is where we store our sound objects + -- This is where we store our sound objects and keep count of them local SoundObjects = {} local SoundCount = 0 @@ -248,14 +251,16 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) soundTable.Pitch or 100, 0 -- Create the sound deafened ) SoundCount = SoundCount + 1 - -- Insert the sound objects inside the SoundObjects table, indexed as the rpm to play the sound at - -- addCurveWidth allows the sound to play in a wider range of RPM's - table.insert(SoundObjects, SoundCount, {["rpm"] = rpm, ["addCurveWidth"] = soundTable.Width, ["sound"] = Sound}) + + -- Insert the CSoundPatch type objects inside the SoundObjects table, alongside with the rpm it has be to play at the desired pitch + -- width allows the sound to play in a wider range of RPM's + table.insert(SoundObjects, SoundCount, {["rpm"] = rpm, ["width"] = soundTable.Width, ["pitch"] = soundTable.Pitch, ["sound"] = Sound}) Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch, 0) end - -- Sort the table if its necessary before moving on, so it plays in sequential order - if SoundCount > 1 then + + -- Sort the table before moving on, so it can be iterated in sequential order + if SoundCount > 1 then -- Potentially unnecessary conditional, will see... table.sort(SoundObjects, function(a, b) return a.rpm < b.rpm end) end @@ -275,7 +280,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) snd.sound:Stop() Origin.SoundObjects[idx] = nil end - Origin.Sound = nil + Origin.Sound = nil -- Just in case Origin.SoundCount = 0 end @@ -292,13 +297,13 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) net.Receive("ACF_Sounds_Adjustable_Multi", function() local Origin = net.ReadEntity() local ShouldStop = net.ReadBool() - local Throttle = net.ReadUInt(7) - local RPM = net.ReadUInt(14) -- Do we really need to remove every existing sound when the engine just turns off? if ShouldStop then Sounds.DeleteMultipleAdjustableSounds(Origin) else + local Throttle = net.ReadUInt(7) + local RPM = net.ReadUInt(14) DoPitchVolumeAtRPM(Origin, Throttle, RPM) end end) diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index 7a75fda7c..f1345390e 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -90,8 +90,8 @@ function Sounds.SendAdjustableSound(Origin, ShouldStop, Pitch, Volume) end --- Creates a sound table to be broadcasted to all players within PAS. - --- This allows us to then create multiple sounds attached to a single entity, fully clientsided. - --- For creating 13 sounds, the data required can ballon up to 1.537kb's of data at once. + --- This allows us to then create multiple sounds attached to a single entity, and be played fully clientside. + --- For creating 13 sounds, the data being sent can ballon up to 1.537kb's of data at once. --- @param Origin table The entity to play the sound from --- @param SoundTable table The table whose keys are arbitrary RPM's and values containing a table with a sound path, pitch and volume, to be played at a defined RPM(Its keys). function Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) @@ -107,7 +107,7 @@ end --- Sends an update to the client regarding Throttle, RPM and if it should stop the sound, from an engine. --- This also allows us to modify the pitch/volume of multiple looping sounds (for an engine) with minimal network usage. - --- The sound calculations are also performed entirely clientside and require minimum network usage, and require net unreliable for better sound composition. + --- The sound calculations are performed entirely clientside and require net unreliable for better sound composition. --- @param Origin table The entity to update the sound from --- @param ShouldStop? boolean Whether the sound should be destroyed; defaults to false --- @param Throttle? int The entity's throttle diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index c47910893..f8706209b 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -118,6 +118,17 @@ local TimerCreate = timer.Create local TimerRemove = timer.Remove local TickInterval = engine.TickInterval +-- Count all the existing sounds in a SoundBank +local function GetSoundCount(Engine) + if not Engine.SoundBank then return end + + local SoundCount = 0 + for _ in pairs(Engine.SoundBank) do + SoundCount = SoundCount + 1 + end + return SoundCount +end + local function GetPitchVolume(Engine) local RPM = Engine.FlyRPM local Pitch = Clamp(20 + (RPM * Engine.SoundPitch) * 0.02, 1, 255) @@ -286,7 +297,6 @@ do -- Spawn and Update functions -- Engine update function local function UpdateEngine(Entity, Data, Class, Engine, Type) local Mass = Engine.Mass - local SoundCount = 0 Entity.ACF = Entity.ACF or {} @@ -300,18 +310,13 @@ do -- Spawn and Update functions Entity[V] = Data[V] end - -- Count all the existing sounds in SoundBank - for _ in pairs(Engine.SoundBank) do - SoundCount = SoundCount + 1 - end - Entity.Name = Engine.Name Entity.ShortName = Engine.ID Entity.EntType = Class.Name Entity.ClassData = Class Entity.DefaultSound = Engine.Sound Entity.SoundBank = Engine.SoundBank - Entity.SoundCount = SoundCount or 1 + Entity.SoundCount = GetSoundCount(Engine) or 1 Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 Entity.TorqueCurve = Engine.TorqueCurve @@ -623,9 +628,8 @@ function ENT:UpdateSoundBank(SelfTbl) local SoundBank = SelfTbl.SoundBank if SelfTbl.Sound then - local Throttle = Round(SelfTbl.Throttle) + local Throttle = Round(SelfTbl.Throttle, 2) * 100 local RPM = Round(SelfTbl.FlyRPM) - Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) else -- TODO(TMF): Optimize how much data is about to be sent to the client! From db368aa64821ee222d1fc8e29cc6ab95e10caac1 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sun, 8 Feb 2026 22:57:11 -0300 Subject: [PATCH 14/59] Forgot to rate limit our SendMultipleAdjustableSounds function too, add some safeguards to sound creation --- lua/acf/core/utilities/sounds/sounds_cl.lua | 4 ++-- lua/acf/core/utilities/sounds/sounds_sv.lua | 26 +++++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 30b983935..10f897787 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -254,9 +254,9 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) -- Insert the CSoundPatch type objects inside the SoundObjects table, alongside with the rpm it has be to play at the desired pitch -- width allows the sound to play in a wider range of RPM's - table.insert(SoundObjects, SoundCount, {["rpm"] = rpm, ["width"] = soundTable.Width, ["pitch"] = soundTable.Pitch, ["sound"] = Sound}) + table.insert(SoundObjects, SoundCount, {["rpm"] = rpm, ["width"] = soundTable.Width or 0, ["pitch"] = soundTable.Pitch or 100, ["sound"] = Sound}) - Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch, 0) + Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch or 100, 0) end -- Sort the table before moving on, so it can be iterated in sequential order diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index f1345390e..49cfd5c9d 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -108,6 +108,7 @@ end --- Sends an update to the client regarding Throttle, RPM and if it should stop the sound, from an engine. --- This also allows us to modify the pitch/volume of multiple looping sounds (for an engine) with minimal network usage. --- The sound calculations are performed entirely clientside and require net unreliable for better sound composition. + --- This function is also rate limited to reduce network consumption, and subsequent updates will be smoothed on the client with an equivalent delta time. --- @param Origin table The entity to update the sound from --- @param ShouldStop? boolean Whether the sound should be destroyed; defaults to false --- @param Throttle? int The entity's throttle @@ -115,13 +116,24 @@ end function Sounds.SendMultipleAdjustableSounds(Origin, ShouldStop, Throttle, RPM) if not IsValid(Origin) then return end ShouldStop = ShouldStop or false + local Time = CurTime() + local OriginTbl = Origin.ACF + if not OriginTbl then + OriginTbl = {} + Origin.ACF = OriginTbl + end + OriginTbl.SoundTimer = OriginTbl.SoundTimer or Time - net.Start("ACF_Sounds_Adjustable_Multi", true) - net.WriteEntity(Origin) - net.WriteBool(ShouldStop) - if not ShouldStop then - net.WriteUInt(Throttle or 0, 7) - net.WriteUInt(RPM or 0, 14) -- Theorically there are engines capable of reaching more than 16K RPM. If you do so, you can go off yourself... + -- Slowing down the rate of sending a bit + if OriginTbl.SoundTimer <= Time or ShouldStop then + net.Start("ACF_Sounds_Adjustable_Multi", true) + net.WriteEntity(Origin) + net.WriteBool(ShouldStop) + if not ShouldStop then + net.WriteUInt(Throttle or 0, 7) + net.WriteUInt(RPM or 0, 14) -- Theorically there are engines capable of reaching more than 16K RPM. If you do so, you can go off yourself... + end + net.SendPAS(Origin:GetPos()) + OriginTbl.SoundTimer = Time + 0.05 end - net.SendPAS(Origin:GetPos()) end From 571a13625b7906db5ecc690efbcac7acf89e5ab7 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sun, 8 Feb 2026 22:58:41 -0300 Subject: [PATCH 15/59] Add some new lines --- lua/acf/core/utilities/sounds/sounds_cl.lua | 1 + lua/acf/core/utilities/sounds/sounds_sv.lua | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 10f897787..eac7354f8 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -304,6 +304,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) else local Throttle = net.ReadUInt(7) local RPM = net.ReadUInt(14) + DoPitchVolumeAtRPM(Origin, Throttle, RPM) end end) diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index 49cfd5c9d..14129f08b 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -65,8 +65,10 @@ end --- @param Volume number A float representing the sound's volume. This is internally converted into an integer from 0-255 for network optimization function Sounds.SendAdjustableSound(Origin, ShouldStop, Pitch, Volume) ShouldStop = ShouldStop or false + local Time = CurTime() local OriginTbl = Origin.ACF + if not OriginTbl then OriginTbl = {} Origin.ACF = OriginTbl @@ -116,8 +118,10 @@ end function Sounds.SendMultipleAdjustableSounds(Origin, ShouldStop, Throttle, RPM) if not IsValid(Origin) then return end ShouldStop = ShouldStop or false + local Time = CurTime() local OriginTbl = Origin.ACF + if not OriginTbl then OriginTbl = {} Origin.ACF = OriginTbl From f95dd1a09ed51fc3882ca3f9c50327aded8c1aac Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sun, 8 Feb 2026 22:59:45 -0300 Subject: [PATCH 16/59] Correct these two, they're not optional --- lua/acf/core/utilities/sounds/sounds_sv.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index 14129f08b..558649838 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -113,8 +113,8 @@ end --- This function is also rate limited to reduce network consumption, and subsequent updates will be smoothed on the client with an equivalent delta time. --- @param Origin table The entity to update the sound from --- @param ShouldStop? boolean Whether the sound should be destroyed; defaults to false - --- @param Throttle? int The entity's throttle - --- @param RPM? int The entity's RPM + --- @param Throttle int The entity's throttle + --- @param RPM int The entity's RPM function Sounds.SendMultipleAdjustableSounds(Origin, ShouldStop, Throttle, RPM) if not IsValid(Origin) then return end ShouldStop = ShouldStop or false From 89fe1f9c837b653dd7fa00e52633d6204c0b1365 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Thu, 12 Feb 2026 18:41:51 -0300 Subject: [PATCH 17/59] Initial menu overhaul, commented out the old code for now, many things broken! --- lua/acf/menu/items_cl/sound_replacer.lua | 204 ++++++++++++++++++++++- 1 file changed, 202 insertions(+), 2 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 97aa1f0a1..219ad4c22 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -1,12 +1,210 @@ +local panels = {} + +-- NOTICE WHEN REVIEWING THIS: i would have used the methods that March's provided to us to use instead of plain vgui, however i noticed +-- that it brings more positioning issues when parenting any element to another, so instead i just did the good old way, just to have this working! +local function addPanel(Menu, ParentPanel) + local id = #panels + 1 + + local Wide = Menu:GetWide() + local ButtonHeight = 20 + local parent = ParentPanel + parent = parent -- So the linter stops bitching, idk if i need this extra arg + + local panel = Menu:AddPanel("DPanel") + panel:SetWide(Wide) + panel:SetTall(54) + panel:SetText("") + panel:Dock(TOP) + panel.id = id + + local top_panel = vgui.Create("DPanel", panel) + top_panel.Paint = function() end + top_panel:Dock(TOP) + top_panel:DockMargin(0, 2, 0, 2) + + local bottom_panel = vgui.Create("DPanel", panel) + bottom_panel.Paint = function() end + bottom_panel:Dock(TOP) + + local num_panel = vgui.Create("DLabel", top_panel) + num_panel:SetText(panel.id .. " = ") + num_panel:Dock(LEFT) + num_panel:DockMargin(4, 0, -36, 0) + num_panel:SetColor(color_black) + + local rpmInput = vgui.Create("DNumberWang", top_panel) + rpmInput:SetMinMax(0, 16383) -- Maximum number it can be networked to the client, also a minmax... + rpmInput:SetTall(ButtonHeight) + rpmInput:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + rpmInput:Dock(LEFT) + rpmInput:DockMargin(0, 0, 2, 0) + + local soundPath = vgui.Create("DTextEntry", top_panel) + soundPath:SetText("") + soundPath:SetWide(Wide - 20) + soundPath:SetTall(ButtonHeight) + soundPath:SetMultiline(false) + soundPath:Dock(FILL) + soundPath:DockMargin(0, 0, 2, 0) + --soundPath:SetConVar("wire_soundemitter_sound") + + local removeButton = vgui.Create("DImageButton", top_panel) + removeButton:SetImage( "icon16/delete.png" ) + removeButton:SizeToContents() + --removeButton:SetPos( 0, 4 ) + removeButton:Dock(RIGHT) + removeButton:SetTooltip("Remove this sound") + removeButton.DoClick = function() + + local id = panel.id + panels[id]:Remove() + panels[id] = nil + table.remove(panels, id) + end + + local searchbutton = vgui.Create("DImageButton", top_panel) + searchbutton:SetImage("icon16/application_view_list.png") + searchbutton:SizeToContents() + searchbutton:Dock(RIGHT) + searchbutton:SetTooltip("Open sound browser") + searchbutton.DoClick = function() + RunConsoleCommand("wire_sound_browser_open") + end + + local pitchLabel = vgui.Create("DLabel", bottom_panel) + pitchLabel:SetTall(ButtonHeight) + pitchLabel:SetText("Pitch:") + pitchLabel:Dock(LEFT) + pitchLabel:DockMargin(4, 0, -28, 0) + pitchLabel:SetColor(color_black) + + local pitch = vgui.Create("DNumberWang", bottom_panel) + pitch:SetTall(ButtonHeight) + pitch:SetMinMax(0, 255) + pitch:Dock(LEFT) + pitch:SetTooltip("Set the pitch of your sound to play at this exact RPM") + pitch:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding + + local volumeLabel = vgui.Create("DLabel", bottom_panel) + volumeLabel:SetTall(ButtonHeight) + volumeLabel:SetText("Volume:") + volumeLabel:Dock(LEFT) + volumeLabel:DockMargin(4, 0, -20, 0) + volumeLabel:SetColor(color_black) + + local volume = vgui.Create("DNumberWang", bottom_panel) + volume:SetTall(ButtonHeight) + volume:SetMinMax(0, 1) + volume:SetDecimals(2) + volume:SetInterval(0.01) + volume:SetFraction(0.01) + volume:Dock(LEFT) + volume:SetTooltip("Set the volume of your sound to play at this exact RPM") + volume:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding + + local widthLabel = vgui.Create("DLabel", bottom_panel) + widthLabel:SetTall(ButtonHeight) + widthLabel:SetText("Width:") + widthLabel:Dock(LEFT) + widthLabel:DockMargin(4, 0, -24, 0) + widthLabel:SetColor(color_black) + + local width = vgui.Create("DNumberWang", bottom_panel) + width:SetTall(ButtonHeight) + width:SetMinMax(0, 16) + width:Dock(LEFT) + width:SetTooltip("Widens the curve of the sound, making it pitch up sooner/later in the curve") + width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding + + table.insert(panels, panel) + return panel +end + +local function do4thPanel(Menu) + local mainPanel = Menu:AddPanel("DPanel") + mainPanel:SizeToContents() + + local top_panel = Menu:AddPanel("DPanel") + top_panel:SetParent(mainPanel) + top_panel:SetText("") + top_panel:Dock(TOP) + top_panel.Paint = function() end + + local rpmLabel = Menu:AddPanel("DLabel") + rpmLabel:SetParent(top_panel) + rpmLabel:SetText("RPM") + rpmLabel:Dock(LEFT) + rpmLabel:DockMargin(20, 0, 0, 0) + rpmLabel:SetColor(color_black) + + local addbtn = Menu:AddPanel("DImageButton") + addbtn:SetParent(top_panel) + addbtn:SetImage("icon16/add.png") + addbtn:SetSize(16, 16) + addbtn:Dock(RIGHT) + addbtn:DockMargin(20, 0, 0, 0) + + addbtn:SetTooltip("Add a new value") + addbtn.DoClick = function() + addPanel(Menu, mainPanel) + PrintTable(panels) + end + + local pathLabel = Menu:AddPanel("DLabel") + pathLabel:SetParent(top_panel) + pathLabel:SetText("Sound Path") + pathLabel:Dock(FILL) + pathLabel:DockMargin(0, 0, 20, 0) + pathLabel:Center() + pathLabel:DockPadding(20, 0, 0, 0) + pathLabel:SetColor(color_black) + + addPanel(Menu, mainPanel) -- add the first +end + --- Generates the menu used in the Sound Replacer tool. --- @param Panel panel The base panel to build the menu off of. function ACF.CreateSoundMenu(Panel) local Menu = ACF.InitMenuBase(Panel, "SoundMenu", "acf_reload_sound_menu") - local Wide = Menu:GetWide() + Menu.OnRemove = function() + for panel in pairs(panels) do + panel = nil + table.remove(panels, panel) + end + end + --local Wide = Menu:GetWide() local ButtonHeight = 20 Menu:AddLabel("#tool.acfsound.help") - local SoundNameText = Menu:AddPanel("DTextEntry") + local optionSelectionBox = Menu:AddPanel("DComboBox") + optionSelectionBox:SetText("Select an Option...") + optionSelectionBox:Dock(TOP) + optionSelectionBox:SetTall(ButtonHeight) + optionSelectionBox:AddChoice("Generic - One sound. ") + optionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ") + optionSelectionBox:AddChoice("Engines - Simple interpolated. ") + optionSelectionBox:AddChoice("Engines - Custom interpolated. ") + optionSelectionBox.OnSelect = function(_, index, value) + -- Ideas for how i want this to look like, thinking about how to implement these... + -- Wether it makes sense to have it like this or not, we'll see... + print("This option should show... \n") + if index == 1 then + print(value .. "Old menu with text entry for a single sound") + + elseif index == 2 then + print(value .. "New menu with three text entries stylized as [Label] = [Sound Path]") + -- This one in particular is probably not really necessary, if i manage to consolidate this idea with the custom one... + elseif index == 3 then + print(value .. "New menu with a slider(min = 2, max = 5) that dynamically adds a label \ + (can be numeric like 00, 33, 66, 99 OR verylow, low, mid, high, veryhigh; we'd see) and the text entries for them sound paths; For simple sound interpolation") + + elseif index == 4 then + -- Creates an invisible generic panel(like a HTML div) + do4thPanel(Menu) + end + end + + --[[local SoundNameText = Menu:AddPanel("DTextEntry") SoundNameText:SetText("") SoundNameText:SetWide(Wide - 20) SoundNameText:SetTall(ButtonHeight) @@ -62,4 +260,6 @@ function ACF.CreateSoundMenu(Panel) VolumeSlider:SetConVar("acfsound_volume") local PitchSlider = Menu:AddSlider("#tool.acfsound.pitch", 0.1, 2, 2) PitchSlider:SetConVar("acfsound_pitch") + + --]] end \ No newline at end of file From d4c80fba76f44f40f86ebbf25cd2bf0a9d336ab3 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 13 Feb 2026 18:37:38 -0300 Subject: [PATCH 18/59] Prevent the last item from removing itself, some work towards relabeling the panels --- lua/acf/menu/items_cl/sound_replacer.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 219ad4c22..4b85e4be3 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -31,6 +31,7 @@ local function addPanel(Menu, ParentPanel) num_panel:Dock(LEFT) num_panel:DockMargin(4, 0, -36, 0) num_panel:SetColor(color_black) + num_panel.id = id local rpmInput = vgui.Create("DNumberWang", top_panel) rpmInput:SetMinMax(0, 16383) -- Maximum number it can be networked to the client, also a minmax... @@ -55,11 +56,23 @@ local function addPanel(Menu, ParentPanel) removeButton:Dock(RIGHT) removeButton:SetTooltip("Remove this sound") removeButton.DoClick = function() + -- Don't remove the last item + if #panels == 1 then + removeButton.DoClick = function() end + return + end local id = panel.id panels[id]:Remove() - panels[id] = nil table.remove(panels, id) + + for i = id, #panels do + panels[i].id = i + num_panel[i].id = i + --panels[i]:SetText(panel.id .. " = ") + num_panel[i].id:SetText(panel.id .. " = ") + print(i, num_panel[i], num_panel[i].id) + end end local searchbutton = vgui.Create("DImageButton", top_panel) From 10c5416cfbe96d17564463f28e95c12c5a1c9ce7 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 13 Feb 2026 20:42:45 -0300 Subject: [PATCH 19/59] Redo panels to use ACF panels instead, tweak panel margins, fix error, labels still not updating --- lua/acf/menu/items_cl/sound_replacer.lua | 79 +++++++++++++++--------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 4b85e4be3..68cdeedfb 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -12,35 +12,43 @@ local function addPanel(Menu, ParentPanel) local panel = Menu:AddPanel("DPanel") panel:SetWide(Wide) - panel:SetTall(54) + panel:SetTall(60) panel:SetText("") panel:Dock(TOP) + panel:DockMargin(0, -5, 0, 10) panel.id = id - local top_panel = vgui.Create("DPanel", panel) + local top_panel = Menu:AddPanel("DPanel") + top_panel:SetParent(panel) top_panel.Paint = function() end top_panel:Dock(TOP) - top_panel:DockMargin(0, 2, 0, 2) + top_panel:DockMargin(0, 2, 0, 0) - local bottom_panel = vgui.Create("DPanel", panel) + local bottom_panel = Menu:AddPanel("DPanel") + bottom_panel:SetParent(panel) bottom_panel.Paint = function() end - bottom_panel:Dock(TOP) + bottom_panel:Dock(BOTTOM) + bottom_panel:DockMargin(0, 0, 0, -6) + bottom_panel:SetTall(34) -- Why is it setting the tall of its children as well :sob: - local num_panel = vgui.Create("DLabel", top_panel) + local num_panel = Menu:AddPanel("DLabel") + num_panel:SetParent(top_panel) num_panel:SetText(panel.id .. " = ") num_panel:Dock(LEFT) num_panel:DockMargin(4, 0, -36, 0) num_panel:SetColor(color_black) num_panel.id = id - local rpmInput = vgui.Create("DNumberWang", top_panel) + local rpmInput = Menu:AddPanel("DNumberWang") + rpmInput:SetParent(top_panel) rpmInput:SetMinMax(0, 16383) -- Maximum number it can be networked to the client, also a minmax... rpmInput:SetTall(ButtonHeight) rpmInput:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 rpmInput:Dock(LEFT) rpmInput:DockMargin(0, 0, 2, 0) - local soundPath = vgui.Create("DTextEntry", top_panel) + local soundPath = Menu:AddPanel("DTextEntry") + soundPath:SetParent(top_panel) soundPath:SetText("") soundPath:SetWide(Wide - 20) soundPath:SetTall(ButtonHeight) @@ -49,7 +57,8 @@ local function addPanel(Menu, ParentPanel) soundPath:DockMargin(0, 0, 2, 0) --soundPath:SetConVar("wire_soundemitter_sound") - local removeButton = vgui.Create("DImageButton", top_panel) + local removeButton = Menu:AddPanel("DImageButton") + removeButton:SetParent(top_panel) removeButton:SetImage( "icon16/delete.png" ) removeButton:SizeToContents() --removeButton:SetPos( 0, 4 ) @@ -68,44 +77,48 @@ local function addPanel(Menu, ParentPanel) for i = id, #panels do panels[i].id = i - num_panel[i].id = i + num_panel.id = i --panels[i]:SetText(panel.id .. " = ") - num_panel[i].id:SetText(panel.id .. " = ") - print(i, num_panel[i], num_panel[i].id) + num_panel:SetText(panel.id .. " = ") end end - local searchbutton = vgui.Create("DImageButton", top_panel) - searchbutton:SetImage("icon16/application_view_list.png") - searchbutton:SizeToContents() - searchbutton:Dock(RIGHT) - searchbutton:SetTooltip("Open sound browser") - searchbutton.DoClick = function() + local searchButton = Menu:AddPanel("DImageButton") + searchButton:SetParent(top_panel) + searchButton:SetImage("icon16/application_view_list.png") + searchButton:SizeToContents() + searchButton:Dock(RIGHT) + searchButton:SetTooltip("Open sound browser") + searchButton.DoClick = function() RunConsoleCommand("wire_sound_browser_open") end - local pitchLabel = vgui.Create("DLabel", bottom_panel) + local pitchLabel = Menu:AddPanel("DLabel") + pitchLabel:SetParent(bottom_panel) pitchLabel:SetTall(ButtonHeight) pitchLabel:SetText("Pitch:") pitchLabel:Dock(LEFT) pitchLabel:DockMargin(4, 0, -28, 0) pitchLabel:SetColor(color_black) - local pitch = vgui.Create("DNumberWang", bottom_panel) + local pitch = Menu:AddPanel("DNumberWang") + pitch:SetParent(bottom_panel) pitch:SetTall(ButtonHeight) pitch:SetMinMax(0, 255) pitch:Dock(LEFT) pitch:SetTooltip("Set the pitch of your sound to play at this exact RPM") pitch:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding - local volumeLabel = vgui.Create("DLabel", bottom_panel) + local volumeLabel = Menu:AddPanel("DLabel") + volumeLabel:SetParent(bottom_panel) volumeLabel:SetTall(ButtonHeight) volumeLabel:SetText("Volume:") volumeLabel:Dock(LEFT) volumeLabel:DockMargin(4, 0, -20, 0) volumeLabel:SetColor(color_black) - local volume = vgui.Create("DNumberWang", bottom_panel) + local volume = Menu:AddPanel("DNumberWang") + volume:SetParent(bottom_panel) volume:SetTall(ButtonHeight) volume:SetMinMax(0, 1) volume:SetDecimals(2) @@ -115,14 +128,16 @@ local function addPanel(Menu, ParentPanel) volume:SetTooltip("Set the volume of your sound to play at this exact RPM") volume:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding - local widthLabel = vgui.Create("DLabel", bottom_panel) + local widthLabel = Menu:AddPanel("DLabel") + widthLabel:SetParent(bottom_panel) widthLabel:SetTall(ButtonHeight) widthLabel:SetText("Width:") widthLabel:Dock(LEFT) widthLabel:DockMargin(4, 0, -24, 0) widthLabel:SetColor(color_black) - local width = vgui.Create("DNumberWang", bottom_panel) + local width = Menu:AddPanel("DNumberWang") + width:SetParent(bottom_panel) width:SetTall(ButtonHeight) width:SetMinMax(0, 16) width:Dock(LEFT) @@ -143,24 +158,29 @@ local function do4thPanel(Menu) top_panel:Dock(TOP) top_panel.Paint = function() end + local numLabel = Menu:AddPanel("DLabel") + numLabel:SetParent(top_panel) + numLabel:SetText("N°") + numLabel:Dock(LEFT) + numLabel:DockMargin(4, 0, 0, 0) + numLabel:SetColor(color_black) + local rpmLabel = Menu:AddPanel("DLabel") rpmLabel:SetParent(top_panel) rpmLabel:SetText("RPM") rpmLabel:Dock(LEFT) - rpmLabel:DockMargin(20, 0, 0, 0) + rpmLabel:DockMargin(-36, 0, 0, 0) rpmLabel:SetColor(color_black) local addbtn = Menu:AddPanel("DImageButton") addbtn:SetParent(top_panel) addbtn:SetImage("icon16/add.png") - addbtn:SetSize(16, 16) + addbtn:SizeToContents() addbtn:Dock(RIGHT) - addbtn:DockMargin(20, 0, 0, 0) - + addbtn:DockMargin(0, 0, 0, 0) addbtn:SetTooltip("Add a new value") addbtn.DoClick = function() addPanel(Menu, mainPanel) - PrintTable(panels) end local pathLabel = Menu:AddPanel("DLabel") @@ -169,7 +189,6 @@ local function do4thPanel(Menu) pathLabel:Dock(FILL) pathLabel:DockMargin(0, 0, 20, 0) pathLabel:Center() - pathLabel:DockPadding(20, 0, 0, 0) pathLabel:SetColor(color_black) addPanel(Menu, mainPanel) -- add the first From 26ea37a201eaad66d5dd7f403d38279580d628cb Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 13 Feb 2026 20:43:06 -0300 Subject: [PATCH 20/59] Remove notice --- lua/acf/menu/items_cl/sound_replacer.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 68cdeedfb..23cd8d538 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -1,7 +1,5 @@ local panels = {} --- NOTICE WHEN REVIEWING THIS: i would have used the methods that March's provided to us to use instead of plain vgui, however i noticed --- that it brings more positioning issues when parenting any element to another, so instead i just did the good old way, just to have this working! local function addPanel(Menu, ParentPanel) local id = #panels + 1 From 15dad9eeadb74547d101bf582fb9dd18291c0046 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 13 Feb 2026 21:38:47 -0300 Subject: [PATCH 21/59] Fix sound indices not updating upon removing any sound --- lua/acf/menu/items_cl/sound_replacer.lua | 32 +++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 23cd8d538..8d64e343e 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -20,7 +20,7 @@ local function addPanel(Menu, ParentPanel) top_panel:SetParent(panel) top_panel.Paint = function() end top_panel:Dock(TOP) - top_panel:DockMargin(0, 2, 0, 0) + top_panel:DockMargin(0, 4, 0, 0) local bottom_panel = Menu:AddPanel("DPanel") bottom_panel:SetParent(panel) @@ -41,7 +41,7 @@ local function addPanel(Menu, ParentPanel) rpmInput:SetParent(top_panel) rpmInput:SetMinMax(0, 16383) -- Maximum number it can be networked to the client, also a minmax... rpmInput:SetTall(ButtonHeight) - rpmInput:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + rpmInput:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding rpmInput:Dock(LEFT) rpmInput:DockMargin(0, 0, 2, 0) @@ -61,7 +61,9 @@ local function addPanel(Menu, ParentPanel) removeButton:SizeToContents() --removeButton:SetPos( 0, 4 ) removeButton:Dock(RIGHT) - removeButton:SetTooltip("Remove this sound") + removeButton:DockMargin(2, 2, 2, 2) + removeButton:Center() + removeButton:SetTooltip("Remove this sound.") removeButton.DoClick = function() -- Don't remove the last item if #panels == 1 then @@ -69,16 +71,16 @@ local function addPanel(Menu, ParentPanel) return end + for k, _ in pairs(panels) do + panel.id = k + num_panel.id = id + num_panel:SetText(num_panel.id .. " = ") + print(k, id, num_panel.id) + end + local id = panel.id panels[id]:Remove() table.remove(panels, id) - - for i = id, #panels do - panels[i].id = i - num_panel.id = i - --panels[i]:SetText(panel.id .. " = ") - num_panel:SetText(panel.id .. " = ") - end end local searchButton = Menu:AddPanel("DImageButton") @@ -86,7 +88,9 @@ local function addPanel(Menu, ParentPanel) searchButton:SetImage("icon16/application_view_list.png") searchButton:SizeToContents() searchButton:Dock(RIGHT) - searchButton:SetTooltip("Open sound browser") + searchButton:DockMargin(2, 2, 2, 2) + searchButton:Center() + searchButton:SetTooltip("Open sound browser.") searchButton.DoClick = function() RunConsoleCommand("wire_sound_browser_open") end @@ -175,8 +179,8 @@ local function do4thPanel(Menu) addbtn:SetImage("icon16/add.png") addbtn:SizeToContents() addbtn:Dock(RIGHT) - addbtn:DockMargin(0, 0, 0, 0) - addbtn:SetTooltip("Add a new value") + addbtn:DockMargin(2, 2, 2, 2) + addbtn:SetTooltip("Add a new sound.") addbtn.DoClick = function() addPanel(Menu, mainPanel) end @@ -185,7 +189,7 @@ local function do4thPanel(Menu) pathLabel:SetParent(top_panel) pathLabel:SetText("Sound Path") pathLabel:Dock(FILL) - pathLabel:DockMargin(0, 0, 20, 0) + pathLabel:DockMargin(-12, 0, 0, 0) pathLabel:Center() pathLabel:SetColor(color_black) From 41724ce9248e98fe574bd92562d5df05120b24a9 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 13 Feb 2026 22:38:22 -0300 Subject: [PATCH 22/59] Allow panels to refresh by making them temporary, that was easy... --- lua/acf/menu/items_cl/sound_replacer.lua | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 8d64e343e..9d7d7f392 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -53,13 +53,11 @@ local function addPanel(Menu, ParentPanel) soundPath:SetMultiline(false) soundPath:Dock(FILL) soundPath:DockMargin(0, 0, 2, 0) - --soundPath:SetConVar("wire_soundemitter_sound") local removeButton = Menu:AddPanel("DImageButton") removeButton:SetParent(top_panel) removeButton:SetImage( "icon16/delete.png" ) removeButton:SizeToContents() - --removeButton:SetPos( 0, 4 ) removeButton:Dock(RIGHT) removeButton:DockMargin(2, 2, 2, 2) removeButton:Center() @@ -71,11 +69,10 @@ local function addPanel(Menu, ParentPanel) return end - for k, _ in pairs(panels) do + for k in ipairs(panels) do panel.id = k num_panel.id = id num_panel:SetText(num_panel.id .. " = ") - print(k, id, num_panel.id) end local id = panel.id @@ -107,6 +104,7 @@ local function addPanel(Menu, ParentPanel) pitch:SetParent(bottom_panel) pitch:SetTall(ButtonHeight) pitch:SetMinMax(0, 255) + pitch:SetValue(100) pitch:Dock(LEFT) pitch:SetTooltip("Set the pitch of your sound to play at this exact RPM") pitch:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding @@ -126,6 +124,7 @@ local function addPanel(Menu, ParentPanel) volume:SetDecimals(2) volume:SetInterval(0.01) volume:SetFraction(0.01) + volume:SetValue(1) volume:Dock(LEFT) volume:SetTooltip("Set the volume of your sound to play at this exact RPM") volume:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding @@ -147,11 +146,15 @@ local function addPanel(Menu, ParentPanel) width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding table.insert(panels, panel) + PrintTable(panels) return panel end local function do4thPanel(Menu) local mainPanel = Menu:AddPanel("DPanel") + -- Clear the panels table + panels = nil + panels = {} mainPanel:SizeToContents() local top_panel = Menu:AddPanel("DPanel") @@ -200,12 +203,6 @@ end --- @param Panel panel The base panel to build the menu off of. function ACF.CreateSoundMenu(Panel) local Menu = ACF.InitMenuBase(Panel, "SoundMenu", "acf_reload_sound_menu") - Menu.OnRemove = function() - for panel in pairs(panels) do - panel = nil - table.remove(panels, panel) - end - end --local Wide = Menu:GetWide() local ButtonHeight = 20 Menu:AddLabel("#tool.acfsound.help") @@ -219,6 +216,8 @@ function ACF.CreateSoundMenu(Panel) optionSelectionBox:AddChoice("Engines - Simple interpolated. ") optionSelectionBox:AddChoice("Engines - Custom interpolated. ") optionSelectionBox.OnSelect = function(_, index, value) + Menu:StartTemporal(Panel) + Menu:ClearTemporal(Panel) -- Ideas for how i want this to look like, thinking about how to implement these... -- Wether it makes sense to have it like this or not, we'll see... print("This option should show... \n") @@ -236,6 +235,8 @@ function ACF.CreateSoundMenu(Panel) -- Creates an invisible generic panel(like a HTML div) do4thPanel(Menu) end + + Menu:EndTemporal(Panel) end --[[local SoundNameText = Menu:AddPanel("DTextEntry") From 30114bc54b855d2a88d6de4f6db72bdbba87dcd0 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 14 Feb 2026 13:45:46 -0300 Subject: [PATCH 23/59] Refactor the menu a bit, add a wip text to describe what the panel does --- lua/acf/menu/items_cl/sound_replacer.lua | 518 ++++++++++++----------- 1 file changed, 267 insertions(+), 251 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 9d7d7f392..eb0d33689 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -1,209 +1,300 @@ local panels = {} +local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! +local addBtn -- Dumb glocal cause i can't pattern! -local function addPanel(Menu, ParentPanel) +local function addComplexPanel(Menu) local id = #panels + 1 local Wide = Menu:GetWide() local ButtonHeight = 20 - local parent = ParentPanel - parent = parent -- So the linter stops bitching, idk if i need this extra arg - - local panel = Menu:AddPanel("DPanel") - panel:SetWide(Wide) - panel:SetTall(60) - panel:SetText("") - panel:Dock(TOP) - panel:DockMargin(0, -5, 0, 10) - panel.id = id - - local top_panel = Menu:AddPanel("DPanel") - top_panel:SetParent(panel) - top_panel.Paint = function() end - top_panel:Dock(TOP) - top_panel:DockMargin(0, 4, 0, 0) - - local bottom_panel = Menu:AddPanel("DPanel") - bottom_panel:SetParent(panel) - bottom_panel.Paint = function() end - bottom_panel:Dock(BOTTOM) - bottom_panel:DockMargin(0, 0, 0, -6) - bottom_panel:SetTall(34) -- Why is it setting the tall of its children as well :sob: - - local num_panel = Menu:AddPanel("DLabel") - num_panel:SetParent(top_panel) - num_panel:SetText(panel.id .. " = ") - num_panel:Dock(LEFT) - num_panel:DockMargin(4, 0, -36, 0) - num_panel:SetColor(color_black) - num_panel.id = id - - local rpmInput = Menu:AddPanel("DNumberWang") - rpmInput:SetParent(top_panel) - rpmInput:SetMinMax(0, 16383) -- Maximum number it can be networked to the client, also a minmax... - rpmInput:SetTall(ButtonHeight) - rpmInput:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding - rpmInput:Dock(LEFT) - rpmInput:DockMargin(0, 0, 2, 0) + + local mainPanel = Menu:AddPanel("DPanel") + mainPanel:SetWide(Wide) + mainPanel:SetTall(60) + mainPanel:SetText("") + mainPanel:Dock(TOP) + mainPanel:DockMargin(0, -5, 0, 10) + mainPanel.id = id + + local top_panel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to. + top_panel:SetParent(mainPanel) + top_panel:Dock(TOP) + top_panel:DockMargin(0, 4, 0, 0) + top_panel.Paint = function() end + + local bottom_panel = Menu:AddPanel("DPanel") -- Same as above + bottom_panel:SetParent(mainPanel) + bottom_panel:Dock(BOTTOM) + bottom_panel:DockMargin(0, 0, 0, -6) + bottom_panel:SetTall(34) -- Why is it setting the tall of its children as well :sob: + bottom_panel.Paint = function() end + + local numLabel = Menu:AddPanel("DLabel") + numLabel:SetParent(top_panel) + numLabel:SetFont("ACF_Control") + numLabel:SetText(mainPanel.id .. " = ") + numLabel:Dock(LEFT) + numLabel:DockMargin(4, 0, -36, 0) + numLabel:SetColor(color_black) + numLabel.id = id + + local rpm = Menu:AddPanel("DNumberWang") + rpm:SetParent(top_panel) + rpm:SetMinMax(0, 16383) -- Maximum number it can be networked to the client, also a minmax... + rpm:SetTall(ButtonHeight) + rpm:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding + rpm:SetValue(1000 * id) + rpm:Dock(LEFT) + rpm:DockMargin(0, 0, 2, 0) local soundPath = Menu:AddPanel("DTextEntry") - soundPath:SetParent(top_panel) - soundPath:SetText("") - soundPath:SetWide(Wide - 20) - soundPath:SetTall(ButtonHeight) - soundPath:SetMultiline(false) - soundPath:Dock(FILL) - soundPath:DockMargin(0, 0, 2, 0) - - local removeButton = Menu:AddPanel("DImageButton") - removeButton:SetParent(top_panel) - removeButton:SetImage( "icon16/delete.png" ) - removeButton:SizeToContents() - removeButton:Dock(RIGHT) - removeButton:DockMargin(2, 2, 2, 2) - removeButton:Center() - removeButton:SetTooltip("Remove this sound.") - removeButton.DoClick = function() - -- Don't remove the last item - if #panels == 1 then - removeButton.DoClick = function() end - return + soundPath:SetParent(top_panel) + soundPath:SetText("") + soundPath:SetWide(Wide - 20) + soundPath:SetTall(ButtonHeight) + soundPath:SetMultiline(false) + soundPath:Dock(FILL) + soundPath:DockMargin(0, 0, 2, 0) + + local removeBtn = Menu:AddPanel("DImageButton") + removeBtn:SetParent(top_panel) + removeBtn:SetImage( "icon16/delete.png" ) + removeBtn:SizeToContents() + removeBtn:Dock(RIGHT) + removeBtn:DockMargin(2, 2, 2, 2) + removeBtn:Center() + removeBtn:SetTooltip("Remove this sound.") + removeBtn.DoClick = function() + -- TODO(TMF): Have it do a popup modal prompting for removal before executing this function! + -- Don't remove the last item + if #panels == 1 then + removeBtn.DoClick = function() end + return + end + + -- Move the label number of the other panels up to compensate + for k in ipairs(panels) do + mainPanel.id = k + numLabel.id = id + numLabel:SetText(numLabel.id .. " = ") + end + + local id = mainPanel.id + panels[id]:Remove() + table.remove(panels, id) + + addBtn:SetEnabled(true) -- Reenable our button end - for k in ipairs(panels) do - panel.id = k - num_panel.id = id - num_panel:SetText(num_panel.id .. " = ") + local searchBtn = Menu:AddPanel("DImageButton") + searchBtn:SetParent(top_panel) + searchBtn:SetImage("icon16/application_view_list.png") + searchBtn:SizeToContents() + searchBtn:Dock(RIGHT) + searchBtn:DockMargin(2, 2, 2, 2) + searchBtn:Center() + searchBtn:SetTooltip("Open sound browser.") + searchBtn.DoClick = function() + RunConsoleCommand("wire_sound_browser_open") end - local id = panel.id - panels[id]:Remove() - table.remove(panels, id) - end - - local searchButton = Menu:AddPanel("DImageButton") - searchButton:SetParent(top_panel) - searchButton:SetImage("icon16/application_view_list.png") - searchButton:SizeToContents() - searchButton:Dock(RIGHT) - searchButton:DockMargin(2, 2, 2, 2) - searchButton:Center() - searchButton:SetTooltip("Open sound browser.") - searchButton.DoClick = function() - RunConsoleCommand("wire_sound_browser_open") - end - local pitchLabel = Menu:AddPanel("DLabel") - pitchLabel:SetParent(bottom_panel) - pitchLabel:SetTall(ButtonHeight) - pitchLabel:SetText("Pitch:") - pitchLabel:Dock(LEFT) - pitchLabel:DockMargin(4, 0, -28, 0) - pitchLabel:SetColor(color_black) + pitchLabel:SetParent(bottom_panel) + pitchLabel:SetFont("ACF_Control") + pitchLabel:SetText("Pitch:") + pitchLabel:Dock(LEFT) + pitchLabel:DockMargin(4, 0, -28, 0) + pitchLabel:SetColor(color_black) local pitch = Menu:AddPanel("DNumberWang") - pitch:SetParent(bottom_panel) - pitch:SetTall(ButtonHeight) - pitch:SetMinMax(0, 255) - pitch:SetValue(100) - pitch:Dock(LEFT) - pitch:SetTooltip("Set the pitch of your sound to play at this exact RPM") - pitch:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding + pitch:SetParent(bottom_panel) + pitch:SetTall(ButtonHeight) + pitch:SetMinMax(0, 255) + pitch:SetValue(100) + pitch:Dock(LEFT) + pitch:SetTooltip("Set the pitch of your sound to play at this exact RPM") + pitch:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding local volumeLabel = Menu:AddPanel("DLabel") - volumeLabel:SetParent(bottom_panel) - volumeLabel:SetTall(ButtonHeight) - volumeLabel:SetText("Volume:") - volumeLabel:Dock(LEFT) - volumeLabel:DockMargin(4, 0, -20, 0) - volumeLabel:SetColor(color_black) + volumeLabel:SetParent(bottom_panel) + volumeLabel:SetFont("ACF_Control") + volumeLabel:SetText("Volume:") + volumeLabel:Dock(LEFT) + volumeLabel:DockMargin(4, 0, -16, 0) + volumeLabel:SetColor(color_black) local volume = Menu:AddPanel("DNumberWang") - volume:SetParent(bottom_panel) - volume:SetTall(ButtonHeight) - volume:SetMinMax(0, 1) - volume:SetDecimals(2) - volume:SetInterval(0.01) - volume:SetFraction(0.01) - volume:SetValue(1) - volume:Dock(LEFT) - volume:SetTooltip("Set the volume of your sound to play at this exact RPM") - volume:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding + volume:SetParent(bottom_panel) + volume:SetTall(ButtonHeight) + volume:SetMinMax(0, 1) + volume:SetDecimals(2) + volume:SetInterval(0.01) + volume:SetFraction(0.01) + volume:SetValue(1) + volume:Dock(LEFT) + volume:SetTooltip("Set the volume of your sound to play at this exact RPM") + volume:SetWide(40) -- Equivalent to 000 w/ decimal point + up/down buttons at font size = 16 + padding local widthLabel = Menu:AddPanel("DLabel") - widthLabel:SetParent(bottom_panel) - widthLabel:SetTall(ButtonHeight) - widthLabel:SetText("Width:") - widthLabel:Dock(LEFT) - widthLabel:DockMargin(4, 0, -24, 0) - widthLabel:SetColor(color_black) + widthLabel:SetParent(bottom_panel) + widthLabel:SetFont("ACF_Control") + widthLabel:SetText("Width:") + widthLabel:Dock(LEFT) + widthLabel:DockMargin(4, 0, -24, 0) + widthLabel:SetColor(color_black) local width = Menu:AddPanel("DNumberWang") - width:SetParent(bottom_panel) - width:SetTall(ButtonHeight) - width:SetMinMax(0, 16) - width:Dock(LEFT) - width:SetTooltip("Widens the curve of the sound, making it pitch up sooner/later in the curve") - width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding - - table.insert(panels, panel) - PrintTable(panels) + width:SetParent(bottom_panel) + width:SetTall(ButtonHeight) + width:SetMinMax(0, 16) + width:Dock(LEFT) + width:SetTooltip("Widens the curve of the sound, making it pitch up sooner/later in the curve") -- Better description for this! + width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding + + table.insert(panels, mainPanel) return panel end -local function do4thPanel(Menu) - local mainPanel = Menu:AddPanel("DPanel") - -- Clear the panels table - panels = nil - panels = {} - mainPanel:SizeToContents() - - local top_panel = Menu:AddPanel("DPanel") - top_panel:SetParent(mainPanel) - top_panel:SetText("") - top_panel:Dock(TOP) - top_panel.Paint = function() end - - local numLabel = Menu:AddPanel("DLabel") - numLabel:SetParent(top_panel) - numLabel:SetText("N°") - numLabel:Dock(LEFT) - numLabel:DockMargin(4, 0, 0, 0) - numLabel:SetColor(color_black) - - local rpmLabel = Menu:AddPanel("DLabel") - rpmLabel:SetParent(top_panel) - rpmLabel:SetText("RPM") - rpmLabel:Dock(LEFT) - rpmLabel:DockMargin(-36, 0, 0, 0) - rpmLabel:SetColor(color_black) - - local addbtn = Menu:AddPanel("DImageButton") - addbtn:SetParent(top_panel) - addbtn:SetImage("icon16/add.png") - addbtn:SizeToContents() - addbtn:Dock(RIGHT) - addbtn:DockMargin(2, 2, 2, 2) - addbtn:SetTooltip("Add a new sound.") - addbtn.DoClick = function() - addPanel(Menu, mainPanel) - end - - local pathLabel = Menu:AddPanel("DLabel") - pathLabel:SetParent(top_panel) - pathLabel:SetText("Sound Path") - pathLabel:Dock(FILL) - pathLabel:DockMargin(-12, 0, 0, 0) - pathLabel:Center() - pathLabel:SetColor(color_black) +-- Build the panels according to our selection +local function doPanel(Num, Menu) + local case = { + -- I explictly gave these their numeric keys so its easier to infer which panel we're working with + -- First panel, Generic - One sound. Old menu with text entry for a single sound + [1] = function () + local Wide = Menu:GetWide() + local ButtonHeight = 20 + + Menu:AddLabel("This is the first panel, I don't know what to add here yet but you can imagine its gonna be something good, so stay tuned!") + + local SoundNameText = Menu:AddPanel("DTextEntry") + SoundNameText:SetText("") + SoundNameText:SetWide(Wide - 20) + SoundNameText:SetTall(ButtonHeight) + SoundNameText:SetMultiline(false) + SoundNameText:SetConVar("wire_soundemitter_sound") + + local SoundBrowserButton = Menu:AddButton("#tool.acfsound.open_browser", "wire_sound_browser_open", SoundNameText:GetValue(), "1") + SoundBrowserButton:SetWide(Wide) + SoundBrowserButton:SetTall(ButtonHeight) + SoundBrowserButton:SetIcon("icon16/application_view_list.png") + + local SoundPre = Menu:AddPanel("ACF_Panel") + SoundPre:SetWide(Wide) + SoundPre:SetTall(ButtonHeight) + + local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") + SoundPrePlay:SetIcon("icon16/sound.png") + SoundPrePlay.DoClick = function() + RunConsoleCommand("play", SoundNameText:GetValue()) + end + + -- Playing a silent sound will mute the preview but not the sound emitters. + local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") + SoundPreStop:SetIcon("icon16/sound_mute.png") + + -- Set the Play/Stop button positions here + SoundPre:InvalidateLayout(true) + SoundPre.PerformLayout = function() + local HWide = SoundPre:GetWide() / 2 + SoundPrePlay:SetSize(HWide, ButtonHeight) + SoundPrePlay:Dock(LEFT) + SoundPreStop:Dock(FILL) -- FILL will cover the remaining space which the previous button didn't + end + + local CopyButton = Menu:AddButton("#tool.acfsound.copy") + CopyButton:SetWide(Wide) + CopyButton:SetTall(ButtonHeight) + CopyButton:SetIcon("icon16/page_copy.png") + CopyButton.DoClick = function() + SetClipboardText(SoundNameText:GetValue()) + end + + local ClearButton = Menu:AddButton("#tool.acfsound.clear") + ClearButton:SetWide(Wide) + ClearButton:SetTall(ButtonHeight) + ClearButton:SetIcon("icon16/cancel.png") + ClearButton.DoClick = function() + SoundNameText:SetValue("") + RunConsoleCommand("wire_soundemitter_sound", "") + end + + local VolumeSlider = Menu:AddSlider("#tool.acfsound.volume", 0.1, 1, 2) + VolumeSlider:SetConVar("acfsound_volume") + local PitchSlider = Menu:AddSlider("#tool.acfsound.pitch", 0.1, 2, 2) + PitchSlider:SetConVar("acfsound_pitch") + end, + -- Second panel, Weapons - Start/Loop/Stop. New menu with three text entries labeled as "Start", "Loop", "End" respectively, to put the sound paths + -- Layout is similar to the first option + [2] = function() + Menu:AddLabel("This is the second panel, I don't know what to add here yet but you can imagine its gonna be something nice, so stay tuned!") + + end, + -- Third panel, Engines - Simple interpolated. New menu with a slider that creates N amount of text entries to put the sound paths + -- Layout is similar to the first option + [3] = function() + Menu:AddLabel("This is the third panel, I don't know what to add here yet but you can imagine its gonna be something fantastic, so stay tuned!") + + end, + -- Fourth panel, Engines - Custom interpolated. New menu with a button to add up to 16 sound paths, with configurable pitch, volume and width for each sound + -- Has a graph at the bottom of the list with a graph to better visualise how they play at a determined engine RPM + [4] = function() + Menu:AddLabel("This is the fourth panel, I don't know what to add here yet but you can imagine its gonna be something mindblowing, so stay tuned!") + local mainPanel = Menu:AddPanel("DPanel") + -- TODO(TMF): Allow this panel to save and load the values that the user has placed! + panels = nil + panels = {} -- Reset the panels table + mainPanel:SizeToContents() + + local top_panel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to + top_panel:SetParent(mainPanel) + top_panel:SetText("") + top_panel:Dock(TOP) + top_panel.Paint = function() end + + local numLabel = Menu:AddLabel("N°") + numLabel:SetParent(top_panel) + numLabel:Dock(LEFT) + numLabel:DockMargin(4, 0, 0, 0) + numLabel:SetColor(color_black) + + local rpmLabel = Menu:AddLabel("RPM") + rpmLabel:SetParent(top_panel) + rpmLabel:Dock(LEFT) + rpmLabel:DockMargin(-36, 0, 0, 0) + rpmLabel:SetColor(color_black) + + local pathLabel = Menu:AddLabel("Sound Path") + pathLabel:SetParent(top_panel) + pathLabel:Dock(LEFT) + pathLabel:DockMargin(-12, 5, 0, 0) -- The top margin is fucking bullshit, why wont this align by itself??? :sob: :sob: :sob: + pathLabel:SetColor(color_black) + + -- Made a global for now, this is dumb + addBtn = Menu:AddPanel("DImageButton") + addBtn:SetParent(top_panel) + addBtn:SetImage("icon16/add.png") + addBtn:SizeToContents() + addBtn:Dock(RIGHT) + addBtn:DockMargin(2, 2, 2, 2) + addBtn:SetTooltip("Add a new sound.") + addBtn.DoClick = function() + if #panels >= _MAXSOUNDS then addBtn:SetEnabled(false) return end -- Disable the button if enough panels exist already + addComplexPanel(Menu) + end + + -- Add the first panel if none exists + if #panels == 0 then addComplexPanel(Menu) end + end + } - addPanel(Menu, mainPanel) -- add the first + local switch = case[Num] + switch() end --- Generates the menu used in the Sound Replacer tool. --- @param Panel panel The base panel to build the menu off of. function ACF.CreateSoundMenu(Panel) local Menu = ACF.InitMenuBase(Panel, "SoundMenu", "acf_reload_sound_menu") - --local Wide = Menu:GetWide() local ButtonHeight = 20 Menu:AddLabel("#tool.acfsound.help") @@ -215,86 +306,11 @@ function ACF.CreateSoundMenu(Panel) optionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ") optionSelectionBox:AddChoice("Engines - Simple interpolated. ") optionSelectionBox:AddChoice("Engines - Custom interpolated. ") - optionSelectionBox.OnSelect = function(_, index, value) + optionSelectionBox.OnSelect = function(_, index, _) Menu:StartTemporal(Panel) Menu:ClearTemporal(Panel) - -- Ideas for how i want this to look like, thinking about how to implement these... - -- Wether it makes sense to have it like this or not, we'll see... - print("This option should show... \n") - if index == 1 then - print(value .. "Old menu with text entry for a single sound") - - elseif index == 2 then - print(value .. "New menu with three text entries stylized as [Label] = [Sound Path]") - -- This one in particular is probably not really necessary, if i manage to consolidate this idea with the custom one... - elseif index == 3 then - print(value .. "New menu with a slider(min = 2, max = 5) that dynamically adds a label \ - (can be numeric like 00, 33, 66, 99 OR verylow, low, mid, high, veryhigh; we'd see) and the text entries for them sound paths; For simple sound interpolation") - - elseif index == 4 then - -- Creates an invisible generic panel(like a HTML div) - do4thPanel(Menu) - end - + -- Build the panels according to our selection + doPanel(index, Menu) Menu:EndTemporal(Panel) end - - --[[local SoundNameText = Menu:AddPanel("DTextEntry") - SoundNameText:SetText("") - SoundNameText:SetWide(Wide - 20) - SoundNameText:SetTall(ButtonHeight) - SoundNameText:SetMultiline(false) - SoundNameText:SetConVar("wire_soundemitter_sound") - - local SoundBrowserButton = Menu:AddButton("#tool.acfsound.open_browser", "wire_sound_browser_open", SoundNameText:GetValue(), "1") - SoundBrowserButton:SetWide(Wide) - SoundBrowserButton:SetTall(ButtonHeight) - SoundBrowserButton:SetIcon("icon16/application_view_list.png") - - local SoundPre = Menu:AddPanel("ACF_Panel") - SoundPre:SetWide(Wide) - SoundPre:SetTall(ButtonHeight) - - local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") - SoundPrePlay:SetIcon("icon16/sound.png") - SoundPrePlay.DoClick = function() - RunConsoleCommand("play", SoundNameText:GetValue()) - end - - -- Playing a silent sound will mute the preview but not the sound emitters. - local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") - SoundPreStop:SetIcon("icon16/sound_mute.png") - - -- Set the Play/Stop button positions here - SoundPre:InvalidateLayout(true) - SoundPre.PerformLayout = function() - local HWide = SoundPre:GetWide() / 2 - SoundPrePlay:SetSize(HWide, ButtonHeight) - SoundPrePlay:Dock(LEFT) - SoundPreStop:Dock(FILL) -- FILL will cover the remaining space which the previous button didn't. - end - - local CopyButton = Menu:AddButton("#tool.acfsound.copy") - CopyButton:SetWide(Wide) - CopyButton:SetTall(ButtonHeight) - CopyButton:SetIcon("icon16/page_copy.png") - CopyButton.DoClick = function() - SetClipboardText(SoundNameText:GetValue()) - end - - local ClearButton = Menu:AddButton("#tool.acfsound.clear") - ClearButton:SetWide(Wide) - ClearButton:SetTall(ButtonHeight) - ClearButton:SetIcon("icon16/cancel.png") - ClearButton.DoClick = function() - SoundNameText:SetValue("") - RunConsoleCommand("wire_soundemitter_sound", "") - end - - local VolumeSlider = Menu:AddSlider("#tool.acfsound.volume", 0.1, 1, 2) - VolumeSlider:SetConVar("acfsound_volume") - local PitchSlider = Menu:AddSlider("#tool.acfsound.pitch", 0.1, 2, 2) - PitchSlider:SetConVar("acfsound_pitch") - - --]] end \ No newline at end of file From 97e4ab2867e5e2773fa9b2dc8fd18bb62540af26 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 14 Feb 2026 15:28:07 -0300 Subject: [PATCH 24/59] Add a graph, some misc changes --- lua/acf/menu/items_cl/sound_replacer.lua | 34 ++++++++++++++++-------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index eb0d33689..da8afa7cf 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -1,10 +1,10 @@ local panels = {} local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! -local addBtn -- Dumb glocal cause i can't pattern! +local addBtn -- Dumb glocals cause i can't pattern! +local graphPanel local function addComplexPanel(Menu) local id = #panels + 1 - local Wide = Menu:GetWide() local ButtonHeight = 20 @@ -151,6 +151,18 @@ local function addComplexPanel(Menu) width:SetTooltip("Widens the curve of the sound, making it pitch up sooner/later in the curve") -- Better description for this! width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding + -- I'm so fucking done with this retarded panel + if not IsValid(graphPanel) then graphPanel = Menu:AddGraph() end + graphPanel:SetParent(Menu) + graphPanel:SetSize(Wide, 160) + graphPanel:SetPos(graphPanel:GetX(), graphPanel:GetY() + (32 * #panels)) + graphPanel:SetXLabel("RPM") + graphPanel:SetYLabel("Pitch") + graphPanel:SetXSpacing(1000) + graphPanel:SetYSpacing(100) + graphPanel:SetFidelity(10) + graphPanel:Dock(BOTTOM) + graphPanel:DockMargin(0, 0, 0, 0) table.insert(panels, mainPanel) return panel end @@ -245,33 +257,33 @@ local function doPanel(Num, Menu) panels = {} -- Reset the panels table mainPanel:SizeToContents() - local top_panel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to - top_panel:SetParent(mainPanel) - top_panel:SetText("") - top_panel:Dock(TOP) - top_panel.Paint = function() end + local _ = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to + _:SetParent(mainPanel) + _:SetText("") + _:Dock(TOP) + _.Paint = function() end local numLabel = Menu:AddLabel("N°") - numLabel:SetParent(top_panel) + numLabel:SetParent(_) numLabel:Dock(LEFT) numLabel:DockMargin(4, 0, 0, 0) numLabel:SetColor(color_black) local rpmLabel = Menu:AddLabel("RPM") - rpmLabel:SetParent(top_panel) + rpmLabel:SetParent(_) rpmLabel:Dock(LEFT) rpmLabel:DockMargin(-36, 0, 0, 0) rpmLabel:SetColor(color_black) local pathLabel = Menu:AddLabel("Sound Path") - pathLabel:SetParent(top_panel) + pathLabel:SetParent(_) pathLabel:Dock(LEFT) pathLabel:DockMargin(-12, 5, 0, 0) -- The top margin is fucking bullshit, why wont this align by itself??? :sob: :sob: :sob: pathLabel:SetColor(color_black) -- Made a global for now, this is dumb addBtn = Menu:AddPanel("DImageButton") - addBtn:SetParent(top_panel) + addBtn:SetParent(_) addBtn:SetImage("icon16/add.png") addBtn:SizeToContents() addBtn:Dock(RIGHT) From e18f5879f5b7d761c59709a075a043646b02b53d Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 14 Feb 2026 18:00:48 -0300 Subject: [PATCH 25/59] Removed redundant function from sounds_cl; Fixed graph panel not showing correctly, added a sound path validation icon --- lua/acf/core/utilities/sounds/sounds_cl.lua | 7 +- lua/acf/menu/items_cl/sound_replacer.lua | 106 +++++++++++++------- 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index eac7354f8..8814d4b22 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -1,4 +1,5 @@ local Sounds = ACF.Utilities.Sounds +local map = math.Remap do -- Valid sound check local file = file @@ -180,9 +181,9 @@ do -- Processing adjustable sounds (for example, engine noises) end -- Maps a value, X, from a range A-B, to a new range C-D -local function map(x, a, b, c, d) - return (x - a) / (b - a) * (d - c) + c -end +--local function map(x, a, b, c, d) +-- return (x - a) / (b - a) * (d - c) + c +--end -- Fade function taken from: -- https://dsp.stackexchange.com/questions/37477/understanding-equal-power-crossfades diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index da8afa7cf..7d579536c 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -1,3 +1,5 @@ +local Sounds = ACF.Utilities.Sounds + local panels = {} local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! local addBtn -- Dumb glocals cause i can't pattern! @@ -8,22 +10,22 @@ local function addComplexPanel(Menu) local Wide = Menu:GetWide() local ButtonHeight = 20 - local mainPanel = Menu:AddPanel("DPanel") - mainPanel:SetWide(Wide) - mainPanel:SetTall(60) - mainPanel:SetText("") - mainPanel:Dock(TOP) - mainPanel:DockMargin(0, -5, 0, 10) - mainPanel.id = id + local panel = Menu:AddPanel("DPanel") + panel:SetWide(Wide) + panel:SetTall(60) + panel:SetText("") + panel:Dock(TOP) + panel:DockMargin(0, -5, 0, 10) + panel.id = id local top_panel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to. - top_panel:SetParent(mainPanel) + top_panel:SetParent(panel) top_panel:Dock(TOP) top_panel:DockMargin(0, 4, 0, 0) top_panel.Paint = function() end local bottom_panel = Menu:AddPanel("DPanel") -- Same as above - bottom_panel:SetParent(mainPanel) + bottom_panel:SetParent(panel) bottom_panel:Dock(BOTTOM) bottom_panel:DockMargin(0, 0, 0, -6) bottom_panel:SetTall(34) -- Why is it setting the tall of its children as well :sob: @@ -32,7 +34,7 @@ local function addComplexPanel(Menu) local numLabel = Menu:AddPanel("DLabel") numLabel:SetParent(top_panel) numLabel:SetFont("ACF_Control") - numLabel:SetText(mainPanel.id .. " = ") + numLabel:SetText(panel.id .. " = ") numLabel:Dock(LEFT) numLabel:DockMargin(4, 0, -36, 0) numLabel:SetColor(color_black) @@ -55,6 +57,27 @@ local function addComplexPanel(Menu) soundPath:SetMultiline(false) soundPath:Dock(FILL) soundPath:DockMargin(0, 0, 2, 0) + panel.soundPath = soundPath + soundPath.OnChange = function() + local isValid = Sounds.IsValidSound + local value = soundPath:GetValue() + + if isValid(value) then + panel.soundPath:SetTooltip() + panel.parseIcon:SetImage("icon16/accept.png") + else + panel.soundPath:SetTooltip("Invalid sound: File does not exist") + panel.parseIcon:SetImage("icon16/cancel.png") + end + end + + local parseIcon = Menu:AddPanel("DImage") + panel.parseIcon = parseIcon + parseIcon:SetParent(soundPath) + parseIcon:Dock(RIGHT) + parseIcon:DockMargin(2, 2, 2, 2) + parseIcon:SetImage("icon16/accept.png") + parseIcon:SizeToContents() local removeBtn = Menu:AddPanel("DImageButton") removeBtn:SetParent(top_panel) @@ -74,12 +97,12 @@ local function addComplexPanel(Menu) -- Move the label number of the other panels up to compensate for k in ipairs(panels) do - mainPanel.id = k + panel.id = k numLabel.id = id numLabel:SetText(numLabel.id .. " = ") end - local id = mainPanel.id + local id = panel.id panels[id]:Remove() table.remove(panels, id) @@ -151,19 +174,7 @@ local function addComplexPanel(Menu) width:SetTooltip("Widens the curve of the sound, making it pitch up sooner/later in the curve") -- Better description for this! width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding - -- I'm so fucking done with this retarded panel - if not IsValid(graphPanel) then graphPanel = Menu:AddGraph() end - graphPanel:SetParent(Menu) - graphPanel:SetSize(Wide, 160) - graphPanel:SetPos(graphPanel:GetX(), graphPanel:GetY() + (32 * #panels)) - graphPanel:SetXLabel("RPM") - graphPanel:SetYLabel("Pitch") - graphPanel:SetXSpacing(1000) - graphPanel:SetYSpacing(100) - graphPanel:SetFidelity(10) - graphPanel:Dock(BOTTOM) - graphPanel:DockMargin(0, 0, 0, 0) - table.insert(panels, mainPanel) + table.insert(panels, panel) return panel end @@ -256,34 +267,57 @@ local function doPanel(Num, Menu) panels = nil panels = {} -- Reset the panels table mainPanel:SizeToContents() - - local _ = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to - _:SetParent(mainPanel) - _:SetText("") - _:Dock(TOP) - _.Paint = function() end + mainPanel:SetTall(160) + + -- I am unable to have the graph to accomodate itself to the bottom of this list dynamically, so instead i put it to be at the top + local top_panel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to + top_panel:SetParent(mainPanel) + top_panel:SetText("") + top_panel:Dock(TOP) + top_panel:Dock(FILL) + top_panel:DockMargin(0, 0, 0, 0) + top_panel.Paint = function() end + + local bottom_panel = Menu:AddPanel("DPanel") -- Same here... + bottom_panel:SetParent(mainPanel) + bottom_panel:SetText("") + bottom_panel:Dock(BOTTOM) + bottom_panel:DockMargin(0, 0, 0, 0) + bottom_panel.Paint = function() end + + -- Made it global for now, this is dumb + graphPanel = Menu:AddGraph() + graphPanel:SetParent(top_panel) + graphPanel:SetPos(graphPanel:GetX(), graphPanel:GetY() + (32 * #panels)) + graphPanel:SetXLabel("RPM") + graphPanel:SetYLabel("Pitch/Volume") + graphPanel:SetXSpacing(1000) + graphPanel:SetYSpacing(100) + graphPanel:SetFidelity(10) + graphPanel:Dock(FILL) + graphPanel:DockMargin(4, 4, 4, 2) local numLabel = Menu:AddLabel("N°") - numLabel:SetParent(_) + numLabel:SetParent(bottom_panel) numLabel:Dock(LEFT) numLabel:DockMargin(4, 0, 0, 0) numLabel:SetColor(color_black) local rpmLabel = Menu:AddLabel("RPM") - rpmLabel:SetParent(_) + rpmLabel:SetParent(bottom_panel) rpmLabel:Dock(LEFT) rpmLabel:DockMargin(-36, 0, 0, 0) rpmLabel:SetColor(color_black) local pathLabel = Menu:AddLabel("Sound Path") - pathLabel:SetParent(_) + pathLabel:SetParent(bottom_panel) pathLabel:Dock(LEFT) pathLabel:DockMargin(-12, 5, 0, 0) -- The top margin is fucking bullshit, why wont this align by itself??? :sob: :sob: :sob: pathLabel:SetColor(color_black) - -- Made a global for now, this is dumb + -- Made it global for now, this is dumb addBtn = Menu:AddPanel("DImageButton") - addBtn:SetParent(_) + addBtn:SetParent(bottom_panel) addBtn:SetImage("icon16/add.png") addBtn:SizeToContents() addBtn:Dock(RIGHT) From bba5d91c86be1703bc9dd1afe98d650492b08330 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 14 Feb 2026 20:54:32 -0300 Subject: [PATCH 26/59] Alright i think im done with stylizing these panels, clean up sounds_cl file --- lua/acf/core/utilities/sounds/sounds_cl.lua | 7 +- lua/acf/menu/items_cl/sound_replacer.lua | 85 +++++++++++++++++++-- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 8814d4b22..9057e39ea 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -180,11 +180,6 @@ do -- Processing adjustable sounds (for example, engine noises) end) end --- Maps a value, X, from a range A-B, to a new range C-D ---local function map(x, a, b, c, d) --- return (x - a) / (b - a) * (d - c) + c ---end - -- Fade function taken from: -- https://dsp.stackexchange.com/questions/37477/understanding-equal-power-crossfades -- https://dsp.stackexchange.com/questions/14754/equal-power-crossfade @@ -215,7 +210,7 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local _OFFVOLUME = 0.25 local _ONVOLUME = 1 - -- TODO(TMF): Potentially some mechanism here to check for any differences and only update those + -- TODO(TMF): Potentially add some mechanism here to check for any differences and only update those for idx, soundTable in ipairs(SoundObjects) do if not soundTable.rpm then continue end Origin.Sound = soundTable.sound diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 7d579536c..79a2d7655 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -16,7 +16,7 @@ local function addComplexPanel(Menu) panel:SetText("") panel:Dock(TOP) panel:DockMargin(0, -5, 0, 10) - panel.id = id + panel.id = id local top_panel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to. top_panel:SetParent(panel) @@ -38,7 +38,7 @@ local function addComplexPanel(Menu) numLabel:Dock(LEFT) numLabel:DockMargin(4, 0, -36, 0) numLabel:SetColor(color_black) - numLabel.id = id + numLabel.id = id local rpm = Menu:AddPanel("DNumberWang") rpm:SetParent(top_panel) @@ -48,6 +48,7 @@ local function addComplexPanel(Menu) rpm:SetValue(1000 * id) rpm:Dock(LEFT) rpm:DockMargin(0, 0, 2, 0) + panel.rpm = rpm local soundPath = Menu:AddPanel("DTextEntry") soundPath:SetParent(top_panel) @@ -130,6 +131,7 @@ local function addComplexPanel(Menu) pitchLabel:SetColor(color_black) local pitch = Menu:AddPanel("DNumberWang") + panel.pitch = pitch pitch:SetParent(bottom_panel) pitch:SetTall(ButtonHeight) pitch:SetMinMax(0, 255) @@ -147,6 +149,7 @@ local function addComplexPanel(Menu) volumeLabel:SetColor(color_black) local volume = Menu:AddPanel("DNumberWang") + panel.volume = volume volume:SetParent(bottom_panel) volume:SetTall(ButtonHeight) volume:SetMinMax(0, 1) @@ -167,6 +170,7 @@ local function addComplexPanel(Menu) widthLabel:SetColor(color_black) local width = Menu:AddPanel("DNumberWang") + panel.width = width width:SetParent(bottom_panel) width:SetTall(ButtonHeight) width:SetMinMax(0, 16) @@ -259,25 +263,59 @@ local function doPanel(Num, Menu) end, -- Fourth panel, Engines - Custom interpolated. New menu with a button to add up to 16 sound paths, with configurable pitch, volume and width for each sound - -- Has a graph at the bottom of the list with a graph to better visualise how they play at a determined engine RPM + -- Has a graph at the top of the list to better visualise how they play at a determined engine RPM [4] = function() Menu:AddLabel("This is the fourth panel, I don't know what to add here yet but you can imagine its gonna be something mindblowing, so stay tuned!") + + -- Adding these before the main panel shit happens + local SoundPre = Menu:AddPanel("ACF_Panel") + SoundPre:SetWide(Wide) + SoundPre:SetTall(ButtonHeight) + + local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") + SoundPrePlay:SetIcon("icon16/sound.png") + SoundPrePlay.DoClick = function() + -- Do something here to play them sounds! + end + + -- Playing a silent sound will mute the preview but not the sound emitters. + local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") + SoundPreStop:SetIcon("icon16/sound_mute.png") + + -- Set the Play/Stop button positions here + SoundPre:InvalidateLayout(true) + SoundPre.PerformLayout = function() + local HWide = SoundPre:GetWide() / 2 + SoundPrePlay:SetSize(HWide, ButtonHeight) + SoundPrePlay:Dock(LEFT) + SoundPreStop:Dock(FILL) -- FILL will cover the remaining space which the previous button didn't + end + + -- The panel for the rest local mainPanel = Menu:AddPanel("DPanel") -- TODO(TMF): Allow this panel to save and load the values that the user has placed! panels = nil panels = {} -- Reset the panels table mainPanel:SizeToContents() - mainPanel:SetTall(160) + mainPanel:SetTall(200) -- I am unable to have the graph to accomodate itself to the bottom of this list dynamically, so instead i put it to be at the top local top_panel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to top_panel:SetParent(mainPanel) top_panel:SetText("") - top_panel:Dock(TOP) top_panel:Dock(FILL) top_panel:DockMargin(0, 0, 0, 0) top_panel.Paint = function() end + local _ = Menu:AddPanel("DPanel") + _:SetParent(top_panel) + _:SetText("") + _:SetTall(24) + _:Dock(TOP) + _:DockMargin(4, 4, 4, 4) + _:SetWide(Wide) + _.Paint = function() end + local bottom_panel = Menu:AddPanel("DPanel") -- Same here... bottom_panel:SetParent(mainPanel) bottom_panel:SetText("") @@ -285,6 +323,36 @@ local function doPanel(Num, Menu) bottom_panel:DockMargin(0, 0, 0, 0) bottom_panel.Paint = function() end + local idleLabel = Menu:AddPanel("DLabel") + idleLabel:SetParent(_) + idleLabel:SetText("Idle:") + idleLabel:Dock(LEFT) + idleLabel:DockMargin(4, 0, 0, 0) + idleLabel:SetColor(color_black) + + local idle = Menu:AddPanel("DNumberWang") + idle:SetParent(_) + idle:SetMinMax(100, 2000) + idle:SetValue(idle:GetMin()) + idle:Dock(LEFT) + idle:DockMargin(-40, 0, 0, 0) + idle:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding + + local redlineLabel = Menu:AddPanel("DLabel") + redlineLabel:SetParent(_) + redlineLabel:SetText("Redline:") + redlineLabel:Dock(LEFT) + redlineLabel:DockMargin(4, 0, 0, 0) + redlineLabel:SetColor(color_black) + + local redline = Menu:AddPanel("DNumberWang") + redline:SetParent(_) + redline:SetMinMax(idle:GetValue(), 16383) + redline:SetValue(idle:GetValue() + 1000) + redline:Dock(LEFT) + redline:DockMargin(-24, 0, 0, 0) + redline:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding + -- Made it global for now, this is dumb graphPanel = Menu:AddGraph() graphPanel:SetParent(top_panel) @@ -295,7 +363,12 @@ local function doPanel(Num, Menu) graphPanel:SetYSpacing(100) graphPanel:SetFidelity(10) graphPanel:Dock(FILL) - graphPanel:DockMargin(4, 4, 4, 2) + graphPanel:DockMargin(4, 0, 4, 2) + + local slider = Menu:AddSlider("RPM", idle:GetValue(), redline:GetValue()) + slider:SetParent(top_panel) + slider:Dock(BOTTOM) + slider:DockMargin(4, 0, 4, 0) local numLabel = Menu:AddLabel("N°") numLabel:SetParent(bottom_panel) From 531fccaa9fe5bc4cf895ee8bd32f1c04064af112 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sun, 15 Feb 2026 13:15:10 -0300 Subject: [PATCH 27/59] Optimize the amount of data being sent to the client upon multiple sound creation, include the volume on pitchvolume mixing instead of just fallback --- lua/acf/core/utilities/sounds/sounds_cl.lua | 63 ++++++++++++++++----- lua/acf/core/utilities/sounds/sounds_sv.lua | 23 +++++++- lua/acf/entities/engines/special.lua | 26 ++++----- lua/entities/acf_engine/init.lua | 4 +- 4 files changed, 83 insertions(+), 33 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 9057e39ea..62f682d7d 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -221,7 +221,7 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local mid = RPM local max = idx == #SoundObjects and 16383 or SoundObjects[idx + 1].rpm local curve = fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) - local volume = curve * map(Throttle, 0, 100, _OFFVOLUME, _ONVOLUME) + local volume = curve * map(Throttle, 0, 100, _OFFVOLUME, _ONVOLUME) * (soundTable.volume or 1) local pitch = (RPM / soundTable.rpm) * enginePitch Sounds.UpdateAdjustableSound(Origin, pitch, volume) @@ -240,7 +240,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) local SoundObjects = {} local SoundCount = 0 - for rpm, soundTable in pairs(PathTable) do + for _, soundTable in ipairs(PathTable) do if not Sounds.IsValidSound(soundTable.Path) then return end local Sound = Sounds.CreateAdjustableSound(Origin, soundTable.Path, @@ -248,17 +248,19 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) ) SoundCount = SoundCount + 1 - -- Insert the CSoundPatch type objects inside the SoundObjects table, alongside with the rpm it has be to play at the desired pitch - -- width allows the sound to play in a wider range of RPM's - table.insert(SoundObjects, SoundCount, {["rpm"] = rpm, ["width"] = soundTable.Width or 0, ["pitch"] = soundTable.Pitch or 100, ["sound"] = Sound}) + -- Insert the CSoundPatch type objects inside the SoundObjects table, alongside with the rpm it has be to play at the desired pitch, + -- the volume and the width which allows the sound to play in a wider range of RPM's + table.insert(SoundObjects, SoundCount, {["rpm"] = soundTable.RPM, + ["width"] = soundTable.Width or 0, + ["pitch"] = soundTable.Pitch or 100, + ["volume"] = soundTable.Volume or 1, + ["sound"] = Sound}) Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch or 100, 0) end - -- Sort the table before moving on, so it can be iterated in sequential order - if SoundCount > 1 then -- Potentially unnecessary conditional, will see... - table.sort(SoundObjects, function(a, b) return a.rpm < b.rpm end) - end + -- Sort the table by the rpm before moving on, so it can be iterated in sequential order + table.sort(SoundObjects, function(a, b) return a.rpm < b.rpm end) Origin.SoundObjects = SoundObjects Origin.SoundCount = SoundCount @@ -280,17 +282,48 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) Origin.SoundCount = 0 end - net.Receive("ACF_Sounds_AdjustableCreate_Multi", function() + net.Receive("ACF_Sounds_AdjustableCreate_Multi", function(len) + print("Received " .. len .. " bits from \"ACF_Sounds_AdjustableCreate_Multi\" for sound creation!") + local SoundTable = {} + local Origin = net.ReadEntity() - local SoundTable = net.ReadTable() + local Count = net.ReadUInt(4) + local CountTable = function (Table) + if not istable(Table) then return end - if not IsValid(Origin) then return end - if not istable(SoundTable) then return end + local Count = 0 + for _ in pairs(Table) do + Count = Count + 1 + end + return Count + end + local I = 0 + + while (I < Count) do + local Key = net.ReadUInt(14) + local StringPath = net.ReadString() + local Pitch = net.ReadUInt(8) + local Volume = net.ReadUInt(7) + local Width = net.ReadUInt(4) + + Volume = Volume * 0.01 -- Reduce the received value down to a float + table.insert(SoundTable, { RPM = Key, + Path = StringPath, + Pitch = Pitch, + Volume = Volume, + Width = Width }) + I = I + 1 + end - Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) + if CountTable(SoundTable) == Count then + Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) + else + print("Got " .. CountTable(SoundTable) .. " out of a total of " .. Count .. " sounds!") + end end) - net.Receive("ACF_Sounds_Adjustable_Multi", function() + net.Receive("ACF_Sounds_Adjustable_Multi", function(len) + print("Received " .. len .. " bits from \"ACF_Sounds_Adjustable_Multi\" for sound updates!") local Origin = net.ReadEntity() local ShouldStop = net.ReadBool() diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index 558649838..b327096db 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -96,14 +96,31 @@ end --- For creating 13 sounds, the data being sent can ballon up to 1.537kb's of data at once. --- @param Origin table The entity to play the sound from --- @param SoundTable table The table whose keys are arbitrary RPM's and values containing a table with a sound path, pitch and volume, to be played at a defined RPM(Its keys). -function Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) +function Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable, SoundCount) if not IsValid(Origin) then return end if not istable(SoundTable) then return end - -- Literally the same as CreateAdjustableSound but as a table instead + -- Separate our table in chunks to be sent instead of all at once + -- This saves about 40% in data size vs. sending the whole table net.Start("ACF_Sounds_AdjustableCreate_Multi") net.WriteEntity(Origin) - net.WriteTable(SoundTable) + net.WriteUInt(SoundCount, 4) + + for k, v in pairs(SoundTable) do + local key = k + local stringPath = v.Path + local pitch = v.Pitch + local volume = v.Volume + local width = v.Width + + net.WriteUInt(key, 14) + net.WriteString(stringPath) + net.WriteUInt(pitch, 8) + + volume = volume * 100 -- Sending the approximate volume as an int to reduce message size + net.WriteUInt(volume, 7) + net.WriteUInt(width, 4) + end net.SendPAS(Origin:GetPos()) end diff --git a/lua/acf/entities/engines/special.lua b/lua/acf/entities/engines/special.lua index dcb37e119..590a82dd0 100644 --- a/lua/acf/entities/engines/special.lua +++ b/lua/acf/entities/engines/special.lua @@ -79,19 +79,19 @@ do -- Special I4 Engines Model = "models/engines/inline4s.mdl", Sound = "acf_extra/vehiclefx/engines/l4/mini_onhigh.wav", SoundBank = { - [714] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", Pitch = 100, Width = 0}, - [967] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", Pitch = 100, Width = 0}, - [1538] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", Pitch = 100, Width = 0}, - [1978] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", Pitch = 100, Width = 0}, - [2571] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", Pitch = 100, Width = 0}, - [3450] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", Pitch = 100, Width = 0}, - [3889] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", Pitch = 100, Width = 0}, - [4482] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", Pitch = 100, Width = 0}, - [4922] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", Pitch = 100, Width = 0}, - [5295] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", Pitch = 100, Width = 0}, - [5823] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", Pitch = 100, Width = 0}, - [6350] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", Pitch = 100, Width = 0}, - [6833] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", Pitch = 100, Width = 0} + [714] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", Pitch = 100, Volume = 1, Width = 0}, + [967] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", Pitch = 100, Volume = 1, Width = 0}, + [1538] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", Pitch = 100, Volume = 1, Width = 0}, + [1978] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", Pitch = 100, Volume = 1, Width = 0}, + [2571] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", Pitch = 100, Volume = 1, Width = 0}, + [3450] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", Pitch = 100, Volume = 1, Width = 0}, + [3889] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", Pitch = 100, Volume = 1, Width = 0}, + [4482] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", Pitch = 100, Volume = 1, Width = 0}, + [4922] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", Pitch = 100, Volume = 1, Width = 0}, + [5295] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", Pitch = 100, Volume = 1, Width = 0}, + [5823] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", Pitch = 100, Volume = 1, Width = 0}, + [6350] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", Pitch = 100, Volume = 1, Width = 0}, + [6833] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", Pitch = 100, Volume = 1, Width = 0} }, Fuel = { Petrol = true }, Type = "GenericPetrol", diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 8d6b67efd..e18d455ab 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -630,14 +630,14 @@ end function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() local SoundBank = SelfTbl.SoundBank + local SoundCount = SelfTbl.SoundCount if SelfTbl.Sound then local Throttle = Round(SelfTbl.Throttle, 2) * 100 local RPM = Round(SelfTbl.FlyRPM) Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) else - -- TODO(TMF): Optimize how much data is about to be sent to the client! - Sounds.CreateMultipleAdjustableSounds(self, SoundBank) + Sounds.CreateMultipleAdjustableSounds(self, SoundBank, SoundCount) SelfTbl.Sound = true end end From bd3963a0471240cffff691bdf0dee0bdeb5ea551 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Mon, 16 Feb 2026 14:00:17 -0300 Subject: [PATCH 28/59] Refactor variable naming, switch from camelCase to PascalCase on most instances, set idle NumWang minimum to 0 --- lua/acf/menu/items_cl/sound_replacer.lua | 528 +++++++++++------------ 1 file changed, 264 insertions(+), 264 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 79a2d7655..57b3f706a 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -1,190 +1,190 @@ local Sounds = ACF.Utilities.Sounds -local panels = {} +local Panels = {} local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! -local addBtn -- Dumb glocals cause i can't pattern! -local graphPanel +local AddBtn -- Dumb glocals cause i can't pattern! +local GraphPanel -local function addComplexPanel(Menu) - local id = #panels + 1 +local function AddComplexPanel(Menu) + local ID = #Panels + 1 local Wide = Menu:GetWide() local ButtonHeight = 20 - local panel = Menu:AddPanel("DPanel") - panel:SetWide(Wide) - panel:SetTall(60) - panel:SetText("") - panel:Dock(TOP) - panel:DockMargin(0, -5, 0, 10) - panel.id = id - - local top_panel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to. - top_panel:SetParent(panel) - top_panel:Dock(TOP) - top_panel:DockMargin(0, 4, 0, 0) - top_panel.Paint = function() end - - local bottom_panel = Menu:AddPanel("DPanel") -- Same as above - bottom_panel:SetParent(panel) - bottom_panel:Dock(BOTTOM) - bottom_panel:DockMargin(0, 0, 0, -6) - bottom_panel:SetTall(34) -- Why is it setting the tall of its children as well :sob: - bottom_panel.Paint = function() end - - local numLabel = Menu:AddPanel("DLabel") - numLabel:SetParent(top_panel) - numLabel:SetFont("ACF_Control") - numLabel:SetText(panel.id .. " = ") - numLabel:Dock(LEFT) - numLabel:DockMargin(4, 0, -36, 0) - numLabel:SetColor(color_black) - numLabel.id = id - - local rpm = Menu:AddPanel("DNumberWang") - rpm:SetParent(top_panel) - rpm:SetMinMax(0, 16383) -- Maximum number it can be networked to the client, also a minmax... - rpm:SetTall(ButtonHeight) - rpm:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding - rpm:SetValue(1000 * id) - rpm:Dock(LEFT) - rpm:DockMargin(0, 0, 2, 0) - panel.rpm = rpm - - local soundPath = Menu:AddPanel("DTextEntry") - soundPath:SetParent(top_panel) - soundPath:SetText("") - soundPath:SetWide(Wide - 20) - soundPath:SetTall(ButtonHeight) - soundPath:SetMultiline(false) - soundPath:Dock(FILL) - soundPath:DockMargin(0, 0, 2, 0) - panel.soundPath = soundPath - soundPath.OnChange = function() + local Panel = Menu:AddPanel("DPanel") + Panel:SetWide(Wide) + Panel:SetTall(60) + Panel:SetText("") + Panel:Dock(TOP) + Panel:DockMargin(0, -5, 0, 10) + Panel.ID = ID + + local TopPanel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to. + TopPanel:SetParent(Panel) + TopPanel:Dock(TOP) + TopPanel:DockMargin(0, 4, 0, 0) + TopPanel.Paint = function() end + + local BottomPanel = Menu:AddPanel("DPanel") -- Same as above + BottomPanel:SetParent(Panel) + BottomPanel:Dock(BOTTOM) + BottomPanel:DockMargin(0, 0, 0, -6) + BottomPanel:SetTall(34) -- Why is it setting the tall of its children as well :sob: + BottomPanel.Paint = function() end + + local NumLabel = Menu:AddPanel("DLabel") + NumLabel:SetParent(TopPanel) + NumLabel:SetFont("ACF_Control") + NumLabel:SetText(Panel.ID .. " = ") + NumLabel:Dock(LEFT) + NumLabel:DockMargin(4, 0, -36, 0) + NumLabel:SetColor(color_black) + NumLabel.ID = ID + + local Rpm = Menu:AddPanel("DNumberWang") + Rpm:SetParent(TopPanel) + Rpm:SetMinMax(0, 16383) -- Maximum number it can be networked to the client, also a minmax... + Rpm:SetTall(ButtonHeight) + Rpm:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding + Rpm:SetValue(1000 * ID) + Rpm:Dock(LEFT) + Rpm:DockMargin(0, 0, 2, 0) + Panel.RPM = Rpm + + local SoundPath = Menu:AddPanel("DTextEntry") + SoundPath:SetParent(TopPanel) + SoundPath:SetText("") + SoundPath:SetWide(Wide - 20) + SoundPath:SetTall(ButtonHeight) + SoundPath:SetMultiline(false) + SoundPath:Dock(FILL) + SoundPath:DockMargin(0, 0, 2, 0) + Panel.SoundPath = SoundPath + SoundPath.OnChange = function() local isValid = Sounds.IsValidSound - local value = soundPath:GetValue() + local value = SoundPath:GetValue() if isValid(value) then - panel.soundPath:SetTooltip() - panel.parseIcon:SetImage("icon16/accept.png") + Panel.SoundPath:SetTooltip() + Panel.ParseIcon:SetImage("icon16/accept.png") else - panel.soundPath:SetTooltip("Invalid sound: File does not exist") - panel.parseIcon:SetImage("icon16/cancel.png") + Panel.SoundPath:SetTooltip("Invalid sound: File does not exist") + Panel.ParseIcon:SetImage("icon16/cancel.png") end end - local parseIcon = Menu:AddPanel("DImage") - panel.parseIcon = parseIcon - parseIcon:SetParent(soundPath) - parseIcon:Dock(RIGHT) - parseIcon:DockMargin(2, 2, 2, 2) - parseIcon:SetImage("icon16/accept.png") - parseIcon:SizeToContents() - - local removeBtn = Menu:AddPanel("DImageButton") - removeBtn:SetParent(top_panel) - removeBtn:SetImage( "icon16/delete.png" ) - removeBtn:SizeToContents() - removeBtn:Dock(RIGHT) - removeBtn:DockMargin(2, 2, 2, 2) - removeBtn:Center() - removeBtn:SetTooltip("Remove this sound.") - removeBtn.DoClick = function() + local ParseIcon = Menu:AddPanel("DImage") + Panel.ParseIcon = ParseIcon + ParseIcon:SetParent(SoundPath) + ParseIcon:Dock(RIGHT) + ParseIcon:DockMargin(2, 2, 2, 2) + ParseIcon:SetImage("icon16/accept.png") + ParseIcon:SizeToContents() + + local RemoveBtn = Menu:AddPanel("DImageButton") + RemoveBtn:SetParent(TopPanel) + RemoveBtn:SetImage( "icon16/delete.png" ) + RemoveBtn:SizeToContents() + RemoveBtn:Dock(RIGHT) + RemoveBtn:DockMargin(2, 2, 2, 2) + RemoveBtn:Center() + RemoveBtn:SetTooltip("Remove this sound.") + RemoveBtn.DoClick = function() -- TODO(TMF): Have it do a popup modal prompting for removal before executing this function! -- Don't remove the last item - if #panels == 1 then - removeBtn.DoClick = function() end + if #Panels == 1 then + RemoveBtn.DoClick = function() end return end - -- Move the label number of the other panels up to compensate - for k in ipairs(panels) do - panel.id = k - numLabel.id = id - numLabel:SetText(numLabel.id .. " = ") + -- Move the label number of the other Panels up to compensate + for k in ipairs(Panels) do + Panel.ID = k + NumLabel.ID = ID + NumLabel:SetText(NumLabel.ID .. " = ") end - local id = panel.id - panels[id]:Remove() - table.remove(panels, id) + local ID = Panel.ID + Panels[ID]:Remove() + table.remove(Panels, ID) - addBtn:SetEnabled(true) -- Reenable our button + AddBtn:SetEnabled(true) -- Reenable our button end - local searchBtn = Menu:AddPanel("DImageButton") - searchBtn:SetParent(top_panel) - searchBtn:SetImage("icon16/application_view_list.png") - searchBtn:SizeToContents() - searchBtn:Dock(RIGHT) - searchBtn:DockMargin(2, 2, 2, 2) - searchBtn:Center() - searchBtn:SetTooltip("Open sound browser.") - searchBtn.DoClick = function() + local SearchBtn = Menu:AddPanel("DImageButton") + SearchBtn:SetParent(TopPanel) + SearchBtn:SetImage("icon16/application_view_list.png") + SearchBtn:SizeToContents() + SearchBtn:Dock(RIGHT) + SearchBtn:DockMargin(2, 2, 2, 2) + SearchBtn:Center() + SearchBtn:SetTooltip("Open sound browser.") + SearchBtn.DoClick = function() RunConsoleCommand("wire_sound_browser_open") end - local pitchLabel = Menu:AddPanel("DLabel") - pitchLabel:SetParent(bottom_panel) - pitchLabel:SetFont("ACF_Control") - pitchLabel:SetText("Pitch:") - pitchLabel:Dock(LEFT) - pitchLabel:DockMargin(4, 0, -28, 0) - pitchLabel:SetColor(color_black) - - local pitch = Menu:AddPanel("DNumberWang") - panel.pitch = pitch - pitch:SetParent(bottom_panel) - pitch:SetTall(ButtonHeight) - pitch:SetMinMax(0, 255) - pitch:SetValue(100) - pitch:Dock(LEFT) - pitch:SetTooltip("Set the pitch of your sound to play at this exact RPM") - pitch:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding - - local volumeLabel = Menu:AddPanel("DLabel") - volumeLabel:SetParent(bottom_panel) - volumeLabel:SetFont("ACF_Control") - volumeLabel:SetText("Volume:") - volumeLabel:Dock(LEFT) - volumeLabel:DockMargin(4, 0, -16, 0) - volumeLabel:SetColor(color_black) - - local volume = Menu:AddPanel("DNumberWang") - panel.volume = volume - volume:SetParent(bottom_panel) - volume:SetTall(ButtonHeight) - volume:SetMinMax(0, 1) - volume:SetDecimals(2) - volume:SetInterval(0.01) - volume:SetFraction(0.01) - volume:SetValue(1) - volume:Dock(LEFT) - volume:SetTooltip("Set the volume of your sound to play at this exact RPM") - volume:SetWide(40) -- Equivalent to 000 w/ decimal point + up/down buttons at font size = 16 + padding - - local widthLabel = Menu:AddPanel("DLabel") - widthLabel:SetParent(bottom_panel) - widthLabel:SetFont("ACF_Control") - widthLabel:SetText("Width:") - widthLabel:Dock(LEFT) - widthLabel:DockMargin(4, 0, -24, 0) - widthLabel:SetColor(color_black) - - local width = Menu:AddPanel("DNumberWang") - panel.width = width - width:SetParent(bottom_panel) - width:SetTall(ButtonHeight) - width:SetMinMax(0, 16) - width:Dock(LEFT) - width:SetTooltip("Widens the curve of the sound, making it pitch up sooner/later in the curve") -- Better description for this! - width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding - - table.insert(panels, panel) - return panel + local PitchLabel = Menu:AddPanel("DLabel") + PitchLabel:SetParent(BottomPanel) + PitchLabel:SetFont("ACF_Control") + PitchLabel:SetText("Pitch:") + PitchLabel:Dock(LEFT) + PitchLabel:DockMargin(4, 0, -28, 0) + PitchLabel:SetColor(color_black) + + local Pitch = Menu:AddPanel("DNumberWang") + Panel.Pitch = Pitch + Pitch:SetParent(BottomPanel) + Pitch:SetTall(ButtonHeight) + Pitch:SetMinMax(0, 255) + Pitch:SetValue(100) + Pitch:Dock(LEFT) + Pitch:SetTooltip("Set the pitch of your sound to play at this exact RPM") + Pitch:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding + + local VolumeLabel = Menu:AddPanel("DLabel") + VolumeLabel:SetParent(BottomPanel) + VolumeLabel:SetFont("ACF_Control") + VolumeLabel:SetText("Volume:") + VolumeLabel:Dock(LEFT) + VolumeLabel:DockMargin(4, 0, -16, 0) + VolumeLabel:SetColor(color_black) + + local Volume = Menu:AddPanel("DNumberWang") + Panel.Volume = Volume + Volume:SetParent(BottomPanel) + Volume:SetTall(ButtonHeight) + Volume:SetMinMax(0, 1) + Volume:SetDecimals(2) + Volume:SetInterval(0.01) + Volume:SetFraction(0.01) + Volume:SetValue(1) + Volume:Dock(LEFT) + Volume:SetTooltip("Set the volume of your sound to play at this exact RPM") + Volume:SetWide(40) -- Equivalent to 000 w/ decimal point + up/down buttons at font size = 16 + padding + + local WidthLabel = Menu:AddPanel("DLabel") + WidthLabel:SetParent(BottomPanel) + WidthLabel:SetFont("ACF_Control") + WidthLabel:SetText("Width:") + WidthLabel:Dock(LEFT) + WidthLabel:DockMargin(4, 0, -24, 0) + WidthLabel:SetColor(color_black) + + local Width = Menu:AddPanel("DNumberWang") + Panel.Width = Width + Width:SetParent(BottomPanel) + Width:SetTall(ButtonHeight) + Width:SetMinMax(0, 16) + Width:Dock(LEFT) + Width:SetTooltip("Widens the curve of the sound, making it pitch up sooner/later in the curve") -- Better description for this! + Width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding + + table.insert(Panels, Panel) + return Panel end -- Build the panels according to our selection -local function doPanel(Num, Menu) - local case = { +local function BuildPanelsFromSelection(Num, Menu) + local Case = { -- I explictly gave these their numeric keys so its easier to infer which panel we're working with -- First panel, Generic - One sound. Old menu with text entry for a single sound [1] = function () @@ -256,7 +256,7 @@ local function doPanel(Num, Menu) Menu:AddLabel("This is the second panel, I don't know what to add here yet but you can imagine its gonna be something nice, so stay tuned!") end, - -- Third panel, Engines - Simple interpolated. New menu with a slider that creates N amount of text entries to put the sound paths + -- Third panel, Engines - Simple interpolated. New menu with a Slider that creates N amount of text entries to put the sound paths -- Layout is similar to the first option [3] = function() Menu:AddLabel("This is the third panel, I don't know what to add here yet but you can imagine its gonna be something fantastic, so stay tuned!") @@ -292,23 +292,23 @@ local function doPanel(Num, Menu) end -- The panel for the rest - local mainPanel = Menu:AddPanel("DPanel") + local MainPanel = Menu:AddPanel("DPanel") -- TODO(TMF): Allow this panel to save and load the values that the user has placed! - panels = nil - panels = {} -- Reset the panels table - mainPanel:SizeToContents() - mainPanel:SetTall(200) + Panels = nil + Panels = {} -- Reset the panels table + MainPanel:SizeToContents() + MainPanel:SetTall(200) -- I am unable to have the graph to accomodate itself to the bottom of this list dynamically, so instead i put it to be at the top - local top_panel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to - top_panel:SetParent(mainPanel) - top_panel:SetText("") - top_panel:Dock(FILL) - top_panel:DockMargin(0, 0, 0, 0) - top_panel.Paint = function() end - - local _ = Menu:AddPanel("DPanel") - _:SetParent(top_panel) + local TopPanel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to + TopPanel:SetParent(MainPanel) + TopPanel:SetText("") + TopPanel:Dock(FILL) + TopPanel:DockMargin(0, 0, 0, 0) + TopPanel.Paint = function() end + + local _ = Menu:AddPanel("DPanel") -- Nameless panel just so it i can properly fit these fucking panels + _:SetParent(TopPanel) _:SetText("") _:SetTall(24) _:Dock(TOP) @@ -316,98 +316,98 @@ local function doPanel(Num, Menu) _:SetWide(Wide) _.Paint = function() end - local bottom_panel = Menu:AddPanel("DPanel") -- Same here... - bottom_panel:SetParent(mainPanel) - bottom_panel:SetText("") - bottom_panel:Dock(BOTTOM) - bottom_panel:DockMargin(0, 0, 0, 0) - bottom_panel.Paint = function() end - - local idleLabel = Menu:AddPanel("DLabel") - idleLabel:SetParent(_) - idleLabel:SetText("Idle:") - idleLabel:Dock(LEFT) - idleLabel:DockMargin(4, 0, 0, 0) - idleLabel:SetColor(color_black) - - local idle = Menu:AddPanel("DNumberWang") - idle:SetParent(_) - idle:SetMinMax(100, 2000) - idle:SetValue(idle:GetMin()) - idle:Dock(LEFT) - idle:DockMargin(-40, 0, 0, 0) - idle:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding - - local redlineLabel = Menu:AddPanel("DLabel") - redlineLabel:SetParent(_) - redlineLabel:SetText("Redline:") - redlineLabel:Dock(LEFT) - redlineLabel:DockMargin(4, 0, 0, 0) - redlineLabel:SetColor(color_black) - - local redline = Menu:AddPanel("DNumberWang") - redline:SetParent(_) - redline:SetMinMax(idle:GetValue(), 16383) - redline:SetValue(idle:GetValue() + 1000) - redline:Dock(LEFT) - redline:DockMargin(-24, 0, 0, 0) - redline:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding + local BottomPanel = Menu:AddPanel("DPanel") -- Same here... + BottomPanel:SetParent(MainPanel) + BottomPanel:SetText("") + BottomPanel:Dock(BOTTOM) + BottomPanel:DockMargin(0, 0, 0, 0) + BottomPanel.Paint = function() end + + local IdleLabel = Menu:AddPanel("DLabel") + IdleLabel:SetParent(_) + IdleLabel:SetText("Idle:") + IdleLabel:Dock(LEFT) + IdleLabel:DockMargin(4, 0, 0, 0) + IdleLabel:SetColor(color_black) + + local Idle = Menu:AddPanel("DNumberWang") + Idle:SetParent(_) + Idle:SetMinMax(0, 2000) + Idle:SetValue(Idle:GetMin()) + Idle:Dock(LEFT) + Idle:DockMargin(-40, 0, 0, 0) + Idle:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding + + local RedlineLabel = Menu:AddPanel("DLabel") + RedlineLabel:SetParent(_) + RedlineLabel:SetText("Redline:") + RedlineLabel:Dock(LEFT) + RedlineLabel:DockMargin(4, 0, 0, 0) + RedlineLabel:SetColor(color_black) + + local Redline = Menu:AddPanel("DNumberWang") + Redline:SetParent(_) + Redline:SetMinMax(Idle:GetValue(), 16383) + Redline:SetValue(Idle:GetValue() + 1000) + Redline:Dock(LEFT) + Redline:DockMargin(-24, 0, 0, 0) + Redline:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding -- Made it global for now, this is dumb - graphPanel = Menu:AddGraph() - graphPanel:SetParent(top_panel) - graphPanel:SetPos(graphPanel:GetX(), graphPanel:GetY() + (32 * #panels)) - graphPanel:SetXLabel("RPM") - graphPanel:SetYLabel("Pitch/Volume") - graphPanel:SetXSpacing(1000) - graphPanel:SetYSpacing(100) - graphPanel:SetFidelity(10) - graphPanel:Dock(FILL) - graphPanel:DockMargin(4, 0, 4, 2) - - local slider = Menu:AddSlider("RPM", idle:GetValue(), redline:GetValue()) - slider:SetParent(top_panel) - slider:Dock(BOTTOM) - slider:DockMargin(4, 0, 4, 0) - - local numLabel = Menu:AddLabel("N°") - numLabel:SetParent(bottom_panel) - numLabel:Dock(LEFT) - numLabel:DockMargin(4, 0, 0, 0) - numLabel:SetColor(color_black) - - local rpmLabel = Menu:AddLabel("RPM") - rpmLabel:SetParent(bottom_panel) - rpmLabel:Dock(LEFT) - rpmLabel:DockMargin(-36, 0, 0, 0) - rpmLabel:SetColor(color_black) - - local pathLabel = Menu:AddLabel("Sound Path") - pathLabel:SetParent(bottom_panel) - pathLabel:Dock(LEFT) - pathLabel:DockMargin(-12, 5, 0, 0) -- The top margin is fucking bullshit, why wont this align by itself??? :sob: :sob: :sob: - pathLabel:SetColor(color_black) + GraphPanel = Menu:AddGraph() + GraphPanel:SetParent(TopPanel) + GraphPanel:SetPos(GraphPanel:GetX(), GraphPanel:GetY() + (32 * #Panels)) + GraphPanel:SetXLabel("RPM") + GraphPanel:SetYLabel("Pitch/Volume") + GraphPanel:SetXSpacing(1000) + GraphPanel:SetYSpacing(100) + GraphPanel:SetFidelity(10) + GraphPanel:Dock(FILL) + GraphPanel:DockMargin(4, 0, 4, 2) + + local RPMSlider = Menu:AddSlider("RPM", Idle:GetValue(), Redline:GetValue()) + RPMSlider:SetParent(TopPanel) + RPMSlider:Dock(BOTTOM) + RPMSlider:DockMargin(4, 0, 4, 0) + + local NumLabel = Menu:AddLabel("N°") + NumLabel:SetParent(BottomPanel) + NumLabel:Dock(LEFT) + NumLabel:DockMargin(4, 0, 0, 0) + NumLabel:SetColor(color_black) + + local RpmLabel = Menu:AddLabel("RPM") + RpmLabel:SetParent(BottomPanel) + RpmLabel:Dock(LEFT) + RpmLabel:DockMargin(-36, 0, 0, 0) + RpmLabel:SetColor(color_black) + + local PathLabel = Menu:AddLabel("Sound Path") + PathLabel:SetParent(BottomPanel) + PathLabel:Dock(LEFT) + PathLabel:DockMargin(-12, 5, 0, 0) -- The top margin is fucking bullshit, why wont this align by itself??? :sob: :sob: :sob: + PathLabel:SetColor(color_black) -- Made it global for now, this is dumb - addBtn = Menu:AddPanel("DImageButton") - addBtn:SetParent(bottom_panel) - addBtn:SetImage("icon16/add.png") - addBtn:SizeToContents() - addBtn:Dock(RIGHT) - addBtn:DockMargin(2, 2, 2, 2) - addBtn:SetTooltip("Add a new sound.") - addBtn.DoClick = function() - if #panels >= _MAXSOUNDS then addBtn:SetEnabled(false) return end -- Disable the button if enough panels exist already - addComplexPanel(Menu) + AddBtn = Menu:AddPanel("DImageButton") + AddBtn:SetParent(BottomPanel) + AddBtn:SetImage("icon16/add.png") + AddBtn:SizeToContents() + AddBtn:Dock(RIGHT) + AddBtn:DockMargin(2, 2, 2, 2) + AddBtn:SetTooltip("Add a new sound.") + AddBtn.DoClick = function() + if #Panels >= _MAXSOUNDS then AddBtn:SetEnabled(false) return end -- Disable the button if enough panels exist already + AddComplexPanel(Menu) end -- Add the first panel if none exists - if #panels == 0 then addComplexPanel(Menu) end + if #Panels == 0 then AddComplexPanel(Menu) end end } - local switch = case[Num] - switch() + local Switch = Case[Num] + Switch() end --- Generates the menu used in the Sound Replacer tool. @@ -417,19 +417,19 @@ function ACF.CreateSoundMenu(Panel) local ButtonHeight = 20 Menu:AddLabel("#tool.acfsound.help") - local optionSelectionBox = Menu:AddPanel("DComboBox") - optionSelectionBox:SetText("Select an Option...") - optionSelectionBox:Dock(TOP) - optionSelectionBox:SetTall(ButtonHeight) - optionSelectionBox:AddChoice("Generic - One sound. ") - optionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ") - optionSelectionBox:AddChoice("Engines - Simple interpolated. ") - optionSelectionBox:AddChoice("Engines - Custom interpolated. ") - optionSelectionBox.OnSelect = function(_, index, _) + local OptionSelectionBox = Menu:AddPanel("DComboBox") + OptionSelectionBox:SetText("Select an Option...") + OptionSelectionBox:Dock(TOP) + OptionSelectionBox:SetTall(ButtonHeight) + OptionSelectionBox:AddChoice("Generic - One sound. ") + OptionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ") + OptionSelectionBox:AddChoice("Engines - Simple interpolated. ") + OptionSelectionBox:AddChoice("Engines - Custom interpolated. ") + OptionSelectionBox.OnSelect = function(_, Index, _) Menu:StartTemporal(Panel) Menu:ClearTemporal(Panel) -- Build the panels according to our selection - doPanel(index, Menu) + BuildPanelsFromSelection(Index, Menu) Menu:EndTemporal(Panel) end end \ No newline at end of file From ea29260e30965212e31c6e67f35f90a8ad68c331 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Mon, 16 Feb 2026 14:05:15 -0300 Subject: [PATCH 29/59] it*-less --- lua/acf/menu/items_cl/sound_replacer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 57b3f706a..3c4ec88b6 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -307,7 +307,7 @@ local function BuildPanelsFromSelection(Num, Menu) TopPanel:DockMargin(0, 0, 0, 0) TopPanel.Paint = function() end - local _ = Menu:AddPanel("DPanel") -- Nameless panel just so it i can properly fit these fucking panels + local _ = Menu:AddPanel("DPanel") -- Nameless panel just so i can properly fit these fucking panels _:SetParent(TopPanel) _:SetText("") _:SetTall(24) From 64f2f1aa44a59a3510e6dadb80ca3079bd5b0fa3 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Mon, 16 Feb 2026 14:53:04 -0300 Subject: [PATCH 30/59] Replace OptionSelectionBox be an ACF combobox instead of a panel, unimplement SetSoundBank in tool_support_sh --- lua/acf/core/utilities/sounds/tool_support_sh.lua | 11 ++++++----- lua/acf/menu/items_cl/sound_replacer.lua | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lua/acf/core/utilities/sounds/tool_support_sh.lua b/lua/acf/core/utilities/sounds/tool_support_sh.lua index ff23a3c5e..584fee323 100644 --- a/lua/acf/core/utilities/sounds/tool_support_sh.lua +++ b/lua/acf/core/utilities/sounds/tool_support_sh.lua @@ -49,7 +49,6 @@ Sounds.acf_engine = { end, ResetSound = function(Ent) Ent.SoundPath = Ent.DefaultSound - Ent.SoundBank["default"] = Ent.DefaultSound Ent.SoundPitch = 1 Ent.SoundVolume = 1 @@ -63,12 +62,14 @@ Sounds.acf_engine = { -- This is dog... Change this! SetSoundBank = function(Ent, SoundBankData) local soundTable = SoundBankData - for _, path in pairs(soundTable) do - path[1].Sound:Trim():lower() - Ent:UpdateSound() + + for K, V in ipairs(soundTable) do + print("Called \"SetSoundBank\" from \"tool_support_sh.lua\" but no implementation was made!") + print(K, V) end - Ent.SoundBank = soundTable + Ent.SoundBank = SoundBankData + end } diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 3c4ec88b6..e5832b8da 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -417,14 +417,14 @@ function ACF.CreateSoundMenu(Panel) local ButtonHeight = 20 Menu:AddLabel("#tool.acfsound.help") - local OptionSelectionBox = Menu:AddPanel("DComboBox") + local OptionSelectionBox = Menu:AddComboBox() OptionSelectionBox:SetText("Select an Option...") OptionSelectionBox:Dock(TOP) OptionSelectionBox:SetTall(ButtonHeight) - OptionSelectionBox:AddChoice("Generic - One sound. ") - OptionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ") - OptionSelectionBox:AddChoice("Engines - Simple interpolated. ") - OptionSelectionBox:AddChoice("Engines - Custom interpolated. ") + OptionSelectionBox:AddChoice("Generic - One sound. ", 1) + OptionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ", 2) + OptionSelectionBox:AddChoice("Engines - Simple interpolated. ", 3) + OptionSelectionBox:AddChoice("Engines - Custom interpolated. ", 4) OptionSelectionBox.OnSelect = function(_, Index, _) Menu:StartTemporal(Panel) Menu:ClearTemporal(Panel) From bedf71867e5d5550755febed7fcf46346305d3a6 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Mon, 16 Feb 2026 22:10:19 -0300 Subject: [PATCH 31/59] Refactor 4th menu again; Idle, Redline and RPM can communicate with eachother now, initial work towards sending object info back to the engines --- lua/acf/menu/items_cl/sound_replacer.lua | 168 ++++++++++++++++++----- 1 file changed, 132 insertions(+), 36 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index e5832b8da..3136124e2 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -1,12 +1,11 @@ +local ACF = ACF local Sounds = ACF.Utilities.Sounds -local Panels = {} +local Current = {Panels = {}, } local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! -local AddBtn -- Dumb glocals cause i can't pattern! -local GraphPanel local function AddComplexPanel(Menu) - local ID = #Panels + 1 + local ID = #Current.Panels + 1 local Wide = Menu:GetWide() local ButtonHeight = 20 @@ -38,7 +37,6 @@ local function AddComplexPanel(Menu) NumLabel:Dock(LEFT) NumLabel:DockMargin(4, 0, -36, 0) NumLabel:SetColor(color_black) - NumLabel.ID = ID local Rpm = Menu:AddPanel("DNumberWang") Rpm:SetParent(TopPanel) @@ -91,23 +89,22 @@ local function AddComplexPanel(Menu) RemoveBtn.DoClick = function() -- TODO(TMF): Have it do a popup modal prompting for removal before executing this function! -- Don't remove the last item - if #Panels == 1 then + if #Current.Panels == 1 then RemoveBtn.DoClick = function() end return end -- Move the label number of the other Panels up to compensate - for k in ipairs(Panels) do + for k in ipairs(Current.Panels) do Panel.ID = k - NumLabel.ID = ID - NumLabel:SetText(NumLabel.ID .. " = ") + NumLabel:SetText(Panel.ID .. " = ") end local ID = Panel.ID - Panels[ID]:Remove() - table.remove(Panels, ID) + Current.Panels[ID]:Remove() + table.remove(Current.Panels, ID) - AddBtn:SetEnabled(true) -- Reenable our button + --AddBtn:SetEnabled(true) -- Reenable our button end local SearchBtn = Menu:AddPanel("DImageButton") @@ -178,19 +175,18 @@ local function AddComplexPanel(Menu) Width:SetTooltip("Widens the curve of the sound, making it pitch up sooner/later in the curve") -- Better description for this! Width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding - table.insert(Panels, Panel) + table.insert(Current.Panels, Panel) return Panel end -- Build the panels according to our selection local function BuildPanelsFromSelection(Num, Menu) + local Wide = Menu:GetWide() + local ButtonHeight = 20 local Case = { -- I explictly gave these their numeric keys so its easier to infer which panel we're working with -- First panel, Generic - One sound. Old menu with text entry for a single sound [1] = function () - local Wide = Menu:GetWide() - local ButtonHeight = 20 - Menu:AddLabel("This is the first panel, I don't know what to add here yet but you can imagine its gonna be something good, so stay tuned!") local SoundNameText = Menu:AddPanel("DTextEntry") @@ -255,6 +251,14 @@ local function BuildPanelsFromSelection(Num, Menu) [2] = function() Menu:AddLabel("This is the second panel, I don't know what to add here yet but you can imagine its gonna be something nice, so stay tuned!") + local KoolWackyClientPanelThang = Menu:AddPanel("DPanel") + + local SliderX = Menu:AddSlider() + SliderX:SetParent(KoolWackyClientPanelThang) + + --local Min = 0 + --local Max = 16383 + end, -- Third panel, Engines - Simple interpolated. New menu with a Slider that creates N amount of text entries to put the sound paths -- Layout is similar to the first option @@ -267,31 +271,122 @@ local function BuildPanelsFromSelection(Num, Menu) [4] = function() Menu:AddLabel("This is the fourth panel, I don't know what to add here yet but you can imagine its gonna be something mindblowing, so stay tuned!") - -- Adding these before the main panel shit happens + -- The menu is divided in two groups + -- The top group where the graph lies + local GraphGroup = Menu:AddCollapsible("Graph", nil, "icon16/chart_curve_edit.png") + local GraphPanel = Menu:AddPanel("DPanel") + local LabelTop = Menu:AddLabel() + local SoundGraph = Menu:AddGraph() + local PanelBottom = Menu:AddPanel("ACF_Panel") + local IdleLabel = Menu:AddLabel("Idle:") + local IdleWang = Menu:AddPanel("DNumberWang", 0, 2000) + local RedlineLabel = Menu:AddLabel("Redline:") + local RedlineWang = Menu:AddPanel("DNumberWang", 0, 16383) + local RPMSlider = Menu:AddSlider("RPM", 0, 16383) local SoundPre = Menu:AddPanel("ACF_Panel") - SoundPre:SetWide(Wide) - SoundPre:SetTall(ButtonHeight) - local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") - SoundPrePlay:SetIcon("icon16/sound.png") - SoundPrePlay.DoClick = function() - -- Do something here to play them sounds! - end + local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") -- Playing a silent sound will mute the preview but not the sound emitters + + -- The properties + GraphGroup:DockMargin(0, 0, 0, 0) + GraphPanel:SetParent(GraphGroup) + GraphPanel:DockPadding(4, 4, 4, 8) + GraphPanel:Dock(TOP) + GraphPanel:SetTall(368) -- Why can't this grow dynamically + LabelTop:SetParent(GraphPanel) + LabelTop:Dock(TOP) + SoundGraph:SetParent(GraphPanel) + SoundGraph:Dock(TOP) + SoundGraph:SetTall(192) + PanelBottom:SetParent(GraphPanel) + PanelBottom:Dock(TOP) + PanelBottom:DockPadding(0, 4, 4, -4) + PanelBottom:SetTall(34) + + IdleLabel:SetParent(PanelBottom) + IdleLabel:Dock(LEFT) + IdleWang:SetParent(PanelBottom) + IdleWang:Dock(LEFT) + IdleWang:SetClientData("Idle", "OnValueChanged") + IdleWang:DefineSetter(function(Panel, _, _, Value) + Panel:SetMinMax(0, 2000) -- I shouldn't even need to do this! + Panel:SetValue(Value) + Current.Idle = Value + + return Value + end) + + RedlineLabel:SetParent(PanelBottom) + RedlineLabel:Dock(LEFT) + RedlineLabel:DockMargin(8, 4, 0, 0) -- Fucking retarded + RedlineWang:SetParent(PanelBottom) + RedlineWang:Dock(LEFT) + RedlineWang:SetClientData("Redline", "OnValueChanged") + RedlineWang:DefineSetter(function(Panel, _, _, Value) + Panel:SetMin(Current.Idle or 1) + Panel:SetMax(16383) + Panel:SetValue(Value) + Current.Redline = Value + + return Value + end) + + RPMSlider:SetParent(GraphPanel) + RPMSlider:Dock(TOP) + RPMSlider:SetWide(Wide) + RPMSlider:SetClientData("RPMSlider", "OnValueChanged") + RPMSlider:DefineSetter(function(Panel, _, _, Value) + local Min = Current.Idle or 0 + local Max = Current.Redline or 1 + + Panel:SetMin(Min) + Panel:SetMax(Max) + Panel:SetValue(Value) + Current.RPM = Value + + return Value + end) + + SoundPre:SetParent(GraphPanel) + SoundPre:SetWide(Wide) + SoundPre:SetTall(ButtonHeight) + SoundPrePlay:SetIcon("icon16/sound.png") + SoundPrePlay.DoClick = function() + -- Do something here to play them sounds! + end + SoundPreStop:SetIcon("icon16/sound_mute.png") + + -- Set the Play/Stop button positions here + SoundPre:InvalidateLayout(true) + SoundPre.PerformLayout = function() + local HWide = SoundPre:GetWide() / 2 + SoundPrePlay:SetSize(HWide, ButtonHeight) + SoundPrePlay:Dock(LEFT) + SoundPreStop:Dock(FILL) -- FILL will cover the remaining space which the previous button didn't + end - -- Playing a silent sound will mute the preview but not the sound emitters. - local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") - SoundPreStop:SetIcon("icon16/sound_mute.png") + -- The bottom group where the panels are added and removed dynamically + local ValuesGroup = Menu:AddCollapsible("Values", nil, "icon16/application_double.png") + --ValuesGroup:SetTall(96) + + local AddValue = Menu:AddPanel("DImageButton") + AddValue:SetParent(ValuesGroup) + AddValue:SetImage("icon16/add.png") + AddValue:SetSize(ButtonHeight, ButtonHeight) + AddValue:Dock(BOTTOM) + AddValue:DockMargin(2, 2, 2, 2) + AddValue:SetTooltip("Add a new sound.") + AddValue.DoClick = function() + if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end -- Disable the button if enough panels exist already + AddComplexPanel(ValuesGroup) + end - -- Set the Play/Stop button positions here - SoundPre:InvalidateLayout(true) - SoundPre.PerformLayout = function() - local HWide = SoundPre:GetWide() / 2 - SoundPrePlay:SetSize(HWide, ButtonHeight) - SoundPrePlay:Dock(LEFT) - SoundPreStop:Dock(FILL) -- FILL will cover the remaining space which the previous button didn't - end + function ValuesGroup.PerformLayout() + AddValue:Center() + AddValue:SetPos(0, 92 * #Current.Panels or 1) + end - -- The panel for the rest + --[[ local MainPanel = Menu:AddPanel("DPanel") -- TODO(TMF): Allow this panel to save and load the values that the user has placed! Panels = nil @@ -403,6 +498,7 @@ local function BuildPanelsFromSelection(Num, Menu) -- Add the first panel if none exists if #Panels == 0 then AddComplexPanel(Menu) end + ]]-- end } From 08ad85a323e675656e7f2cd30788e2db4a8c742b Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Tue, 17 Feb 2026 02:06:34 -0300 Subject: [PATCH 32/59] Refactor value panel creation, renamed method, improve style --- lua/acf/menu/items_cl/sound_replacer.lua | 318 +++++++++++++---------- 1 file changed, 185 insertions(+), 133 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 3136124e2..4acd5da2a 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -4,7 +4,7 @@ local Sounds = ACF.Utilities.Sounds local Current = {Panels = {}, } local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! -local function AddComplexPanel(Menu) +--[[local function AddValuePanel(Menu) local ID = #Current.Panels + 1 local Wide = Menu:GetWide() local ButtonHeight = 20 @@ -15,7 +15,8 @@ local function AddComplexPanel(Menu) Panel:SetText("") Panel:Dock(TOP) Panel:DockMargin(0, -5, 0, 10) - Panel.ID = ID + Menu:Dock(FILL) + Current.Panels.ID = ID local TopPanel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to. TopPanel:SetParent(Panel) @@ -33,7 +34,7 @@ local function AddComplexPanel(Menu) local NumLabel = Menu:AddPanel("DLabel") NumLabel:SetParent(TopPanel) NumLabel:SetFont("ACF_Control") - NumLabel:SetText(Panel.ID .. " = ") + NumLabel:SetText(Current.Panels.ID .. " = ") NumLabel:Dock(LEFT) NumLabel:DockMargin(4, 0, -36, 0) NumLabel:SetColor(color_black) @@ -46,7 +47,7 @@ local function AddComplexPanel(Menu) Rpm:SetValue(1000 * ID) Rpm:Dock(LEFT) Rpm:DockMargin(0, 0, 2, 0) - Panel.RPM = Rpm + Current.Panels.RPM = Rpm local SoundPath = Menu:AddPanel("DTextEntry") SoundPath:SetParent(TopPanel) @@ -56,7 +57,7 @@ local function AddComplexPanel(Menu) SoundPath:SetMultiline(false) SoundPath:Dock(FILL) SoundPath:DockMargin(0, 0, 2, 0) - Panel.SoundPath = SoundPath + Current.Panels.SoundPath = SoundPath SoundPath.OnChange = function() local isValid = Sounds.IsValidSound local value = SoundPath:GetValue() @@ -96,11 +97,11 @@ local function AddComplexPanel(Menu) -- Move the label number of the other Panels up to compensate for k in ipairs(Current.Panels) do - Panel.ID = k - NumLabel:SetText(Panel.ID .. " = ") + Current.Panels.ID = k + NumLabel:SetText(Current.Panels.ID .. " = ") end - local ID = Panel.ID + local ID = Current.Panels.ID Current.Panels[ID]:Remove() table.remove(Current.Panels, ID) @@ -128,7 +129,7 @@ local function AddComplexPanel(Menu) PitchLabel:SetColor(color_black) local Pitch = Menu:AddPanel("DNumberWang") - Panel.Pitch = Pitch + Current.Panels.Pitch = Pitch Pitch:SetParent(BottomPanel) Pitch:SetTall(ButtonHeight) Pitch:SetMinMax(0, 255) @@ -146,7 +147,7 @@ local function AddComplexPanel(Menu) VolumeLabel:SetColor(color_black) local Volume = Menu:AddPanel("DNumberWang") - Panel.Volume = Volume + Current.Panels.Volume = Volume Volume:SetParent(BottomPanel) Volume:SetTall(ButtonHeight) Volume:SetMinMax(0, 1) @@ -167,7 +168,7 @@ local function AddComplexPanel(Menu) WidthLabel:SetColor(color_black) local Width = Menu:AddPanel("DNumberWang") - Panel.Width = Width + Current.Panels.Width = Width Width:SetParent(BottomPanel) Width:SetTall(ButtonHeight) Width:SetMinMax(0, 16) @@ -176,11 +177,167 @@ local function AddComplexPanel(Menu) Width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding table.insert(Current.Panels, Panel) + PrintTable(Current) + return Panel +end]]-- + +local function AddValuePanel(Menu) + local ID = #Current.Panels + 1 + --local Wide = Menu:GetWide() + local ButtonHeight = 20 + + local _, ValueGroup = Menu:AddCollapsible() + local Panel = Menu:AddPanel("DPanel") + local TopDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML's Div, just here to parent other children to. + local BotDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML's Div, just here to parent other children to. + local RPMWang, RPMLabel = Menu:AddNumberWang("RPM:", 0, 16383, 0) + local _, PathLabel, PathText = Menu:AddTextEntry("Path:") + local ParseIcon = Menu:AddPanel("DImage") + local RemoveButton = Menu:AddPanel("DImageButton") + local SearchButton = Menu:AddPanel("DImageButton") + + local PitchWang, PitchLabel = Menu:AddNumberWang("Pitch:", 0, 255, 0) + local VolumeWang, VolumeLabel = Menu:AddNumberWang("Volume:", 0, 1, 2) + local WidthWang, WidthLabel = Menu:AddNumberWang("Width:", 0, 15, 0) + + ValueGroup:DockMargin(0, 0, 0, 0) + ValueGroup:SetLabel("Value " .. ID) + + Panel:SetParent(ValueGroup) + Panel:SetTall(72) + Panel:DockPadding(4, 6, 4, 0) + Panel:DockMargin(0, 0, 0, 0) + + TopDiv:SetParent(Panel) + TopDiv:Dock(TOP) + + BotDiv:SetParent(Panel) + BotDiv:Dock(BOTTOM) + + RPMLabel:SetParent(TopDiv) + RPMLabel:DockMargin(0, 0, 0, 0) + RPMLabel:Dock(LEFT) + + RPMWang:SetParent(TopDiv) + RPMWang:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding + RPMWang:DockMargin(-30, 0, 0, 0) + RPMWang:Dock(LEFT) + RPMWang:SetClientData("RPM " .. ID, "OnValueChanged") + RPMWang:DefineSetter(function(Panel, _, _, Value) + Current.Panels[ID].RPM = Value + return Value, Panel + end) + + PathLabel:SetParent(TopDiv) + PathLabel:Dock(LEFT) + + PathText:SetParent(TopDiv) + PathText:Dock(FILL) + PathText:DockMargin(-25, 0, 0, 0) + PathText:SetTall(ButtonHeight) + PathText:SetClientData("Path " .. ID, "OnValueChanged") + PathText:DefineSetter(function(Panel, _, _, Value) + local isValid = Sounds.IsValidSound + + if isValid(Value) then + Panel:SetTooltip() + ParseIcon:SetImage("icon16/accept.png") + + Current.Panels[ID].Path = Value + Current.Panels[ID].LastPath = Value + return Value, Panel + else + Panel:SetTooltip("Invalid sound: File does not exist") + ParseIcon:SetImage("icon16/cancel.png") + Current.Panels[ID].Path = Current.Panels[ID].LastPath + end + return false, Panel + end) + + ParseIcon:SetParent(PathText) + ParseIcon:Dock(RIGHT) + ParseIcon:DockMargin(3, 3, 3, 3) + ParseIcon:SetImage("icon16/accept.png") + ParseIcon:SizeToContents() + + RemoveButton:SetParent(TopDiv) + RemoveButton:Center() + RemoveButton:Dock(RIGHT) + RemoveButton:DockMargin(3, 3, 3, 3) + RemoveButton:SetImage("icon16/delete.png") + RemoveButton:SetTooltip("Remove this sound.") + RemoveButton:SizeToContents() + --[[RemoveButton.DoClick = function() + -- TODO(TMF): Have it do a popup modal prompting for removal before executing this function! + -- Don't remove the last item + if #Current.Panels == 1 then + RemoveButton.DoClick = function() end + return + end + + -- Move the label number of the other Panels up to compensate + for k, v in ipairs(Current.Panels) do + v.ID = k + ValueGroup:SetLabel("Value " .. v.ID) + end + + local panel = Panel.ID + panel[ID]:Remove() + table.remove(Current.Panels, ID) + + --AddBtn:SetEnabled(true) -- Reenable our button + end]]-- + + SearchButton:SetParent(TopDiv) + SearchButton:Center() + SearchButton:Dock(RIGHT) + SearchButton:DockMargin(3, 3, 3, 3) + SearchButton:SetImage("icon16/application_view_list.png") + SearchButton:SetTooltip("Open sound browser.") + SearchButton:SizeToContents() + SearchButton.DoClick = function() + RunConsoleCommand("wire_sound_browser_open") + end + + PitchLabel:SetParent(BotDiv) + PitchLabel:Dock(LEFT) + + PitchWang:SetParent(BotDiv) + PitchWang:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding + PitchWang:DockMargin(-30, 0, 4, 0) + PitchWang:Dock(LEFT) + + VolumeLabel:SetParent(BotDiv) + VolumeLabel:Dock(LEFT) + + VolumeWang:SetParent(BotDiv) + VolumeWang:SetWide(40) -- Equivalent to 0.00 + up/down buttons at font size = 16 + padding + VolumeWang:DockMargin(-16, 0, 4, 0) + VolumeWang:Dock(LEFT) + + WidthLabel:SetParent(BotDiv) + WidthLabel:Dock(LEFT) + + WidthWang:SetParent(BotDiv) + WidthWang:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding + WidthWang:DockMargin(-24, 0, 4, 0) + WidthWang:Dock(LEFT) + + Panel.ID = ID + Panel.RPM = RPMWang:GetValue() + Panel.Path = PathText:GetValue() + Panel.LastPath = Panel.Path + + table.insert(Current.Panels, {ID = Panel.ID, + RPM = Panel.RPM, + Path = Panel.Path, + LastPath = Path + }) return Panel end -- Build the panels according to our selection -local function BuildPanelsFromSelection(Num, Menu) +local function CreateSubMenu(Num, Menu) local Wide = Menu:GetWide() local ButtonHeight = 20 local Case = { @@ -287,6 +444,13 @@ local function BuildPanelsFromSelection(Num, Menu) local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") -- Playing a silent sound will mute the preview but not the sound emitters + -- Set defaults + local DefaultIdle = ACF.GetClientData("Idle", 800) + local DefaultRedline = ACF.GetClientData("Redline", 8000) + ACF.SetClientData("Idle", DefaultIdle, true) + ACF.SetClientData("Redline", DefaultRedline, true) + ACF.SetClientData("RPMSlider", (DefaultIdle + DefaultRedline) / 2, true) + -- The properties GraphGroup:DockMargin(0, 0, 0, 0) GraphPanel:SetParent(GraphGroup) @@ -367,138 +531,26 @@ local function BuildPanelsFromSelection(Num, Menu) -- The bottom group where the panels are added and removed dynamically local ValuesGroup = Menu:AddCollapsible("Values", nil, "icon16/application_double.png") - --ValuesGroup:SetTall(96) + ValuesGroup:DockMargin(0, 4, 0, 4) + local ValuesPanel = Menu:AddPanel("ACF_Panel") + ValuesPanel:SetParent(ValuesGroup) + ValuesPanel:Dock(TOP) local AddValue = Menu:AddPanel("DImageButton") - AddValue:SetParent(ValuesGroup) AddValue:SetImage("icon16/add.png") - AddValue:SetSize(ButtonHeight, ButtonHeight) - AddValue:Dock(BOTTOM) - AddValue:DockMargin(2, 2, 2, 2) AddValue:SetTooltip("Add a new sound.") AddValue.DoClick = function() if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end -- Disable the button if enough panels exist already - AddComplexPanel(ValuesGroup) + AddValuePanel(ValuesPanel) end function ValuesGroup.PerformLayout() AddValue:Center() - AddValue:SetPos(0, 92 * #Current.Panels or 1) + AddValue:SetSize(16, 16) end - --[[ - local MainPanel = Menu:AddPanel("DPanel") - -- TODO(TMF): Allow this panel to save and load the values that the user has placed! - Panels = nil - Panels = {} -- Reset the panels table - MainPanel:SizeToContents() - MainPanel:SetTall(200) - - -- I am unable to have the graph to accomodate itself to the bottom of this list dynamically, so instead i put it to be at the top - local TopPanel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to - TopPanel:SetParent(MainPanel) - TopPanel:SetText("") - TopPanel:Dock(FILL) - TopPanel:DockMargin(0, 0, 0, 0) - TopPanel.Paint = function() end - - local _ = Menu:AddPanel("DPanel") -- Nameless panel just so i can properly fit these fucking panels - _:SetParent(TopPanel) - _:SetText("") - _:SetTall(24) - _:Dock(TOP) - _:DockMargin(4, 4, 4, 4) - _:SetWide(Wide) - _.Paint = function() end - - local BottomPanel = Menu:AddPanel("DPanel") -- Same here... - BottomPanel:SetParent(MainPanel) - BottomPanel:SetText("") - BottomPanel:Dock(BOTTOM) - BottomPanel:DockMargin(0, 0, 0, 0) - BottomPanel.Paint = function() end - - local IdleLabel = Menu:AddPanel("DLabel") - IdleLabel:SetParent(_) - IdleLabel:SetText("Idle:") - IdleLabel:Dock(LEFT) - IdleLabel:DockMargin(4, 0, 0, 0) - IdleLabel:SetColor(color_black) - - local Idle = Menu:AddPanel("DNumberWang") - Idle:SetParent(_) - Idle:SetMinMax(0, 2000) - Idle:SetValue(Idle:GetMin()) - Idle:Dock(LEFT) - Idle:DockMargin(-40, 0, 0, 0) - Idle:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding - - local RedlineLabel = Menu:AddPanel("DLabel") - RedlineLabel:SetParent(_) - RedlineLabel:SetText("Redline:") - RedlineLabel:Dock(LEFT) - RedlineLabel:DockMargin(4, 0, 0, 0) - RedlineLabel:SetColor(color_black) - - local Redline = Menu:AddPanel("DNumberWang") - Redline:SetParent(_) - Redline:SetMinMax(Idle:GetValue(), 16383) - Redline:SetValue(Idle:GetValue() + 1000) - Redline:Dock(LEFT) - Redline:DockMargin(-24, 0, 0, 0) - Redline:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding - - -- Made it global for now, this is dumb - GraphPanel = Menu:AddGraph() - GraphPanel:SetParent(TopPanel) - GraphPanel:SetPos(GraphPanel:GetX(), GraphPanel:GetY() + (32 * #Panels)) - GraphPanel:SetXLabel("RPM") - GraphPanel:SetYLabel("Pitch/Volume") - GraphPanel:SetXSpacing(1000) - GraphPanel:SetYSpacing(100) - GraphPanel:SetFidelity(10) - GraphPanel:Dock(FILL) - GraphPanel:DockMargin(4, 0, 4, 2) - - local RPMSlider = Menu:AddSlider("RPM", Idle:GetValue(), Redline:GetValue()) - RPMSlider:SetParent(TopPanel) - RPMSlider:Dock(BOTTOM) - RPMSlider:DockMargin(4, 0, 4, 0) - - local NumLabel = Menu:AddLabel("N°") - NumLabel:SetParent(BottomPanel) - NumLabel:Dock(LEFT) - NumLabel:DockMargin(4, 0, 0, 0) - NumLabel:SetColor(color_black) - - local RpmLabel = Menu:AddLabel("RPM") - RpmLabel:SetParent(BottomPanel) - RpmLabel:Dock(LEFT) - RpmLabel:DockMargin(-36, 0, 0, 0) - RpmLabel:SetColor(color_black) - - local PathLabel = Menu:AddLabel("Sound Path") - PathLabel:SetParent(BottomPanel) - PathLabel:Dock(LEFT) - PathLabel:DockMargin(-12, 5, 0, 0) -- The top margin is fucking bullshit, why wont this align by itself??? :sob: :sob: :sob: - PathLabel:SetColor(color_black) - - -- Made it global for now, this is dumb - AddBtn = Menu:AddPanel("DImageButton") - AddBtn:SetParent(BottomPanel) - AddBtn:SetImage("icon16/add.png") - AddBtn:SizeToContents() - AddBtn:Dock(RIGHT) - AddBtn:DockMargin(2, 2, 2, 2) - AddBtn:SetTooltip("Add a new sound.") - AddBtn.DoClick = function() - if #Panels >= _MAXSOUNDS then AddBtn:SetEnabled(false) return end -- Disable the button if enough panels exist already - AddComplexPanel(Menu) - end - - -- Add the first panel if none exists - if #Panels == 0 then AddComplexPanel(Menu) end - ]]-- + -- Add the first panel if it none exists + if #Current.Panels == 0 then AddValuePanel(ValuesPanel) end end } @@ -525,7 +577,7 @@ function ACF.CreateSoundMenu(Panel) Menu:StartTemporal(Panel) Menu:ClearTemporal(Panel) -- Build the panels according to our selection - BuildPanelsFromSelection(Index, Menu) + CreateSubMenu(Index, Menu) Menu:EndTemporal(Panel) end end \ No newline at end of file From e17d69d4e4417843e147cbdb9da9d09683bd3147 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Wed, 18 Feb 2026 23:56:50 -0300 Subject: [PATCH 33/59] Several changes and improvements to the menu, graph can plot lines now, made fade function a part of the Sounds object --- lua/acf/core/utilities/sounds/sounds_cl.lua | 4 +- lua/acf/menu/items_cl/sound_replacer.lua | 321 ++++++-------------- 2 files changed, 94 insertions(+), 231 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 62f682d7d..76a888496 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -184,7 +184,7 @@ end -- https://dsp.stackexchange.com/questions/37477/understanding-equal-power-crossfades -- https://dsp.stackexchange.com/questions/14754/equal-power-crossfade -- https://i.imgur.com/KaFmaMf.png -local function fade(n, min, mid, max) +function Sounds.Fade(n, min, mid, max) local _PI = math.pi if n < min or n > max then return 0 end @@ -220,7 +220,7 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local min = idx == 1 and 0 or SoundObjects[idx - 1].rpm local mid = RPM local max = idx == #SoundObjects and 16383 or SoundObjects[idx + 1].rpm - local curve = fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) + local curve = Sounds.Fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) local volume = curve * map(Throttle, 0, 100, _OFFVOLUME, _ONVOLUME) * (soundTable.volume or 1) local pitch = (RPM / soundTable.rpm) * enginePitch diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 4acd5da2a..b83efff7a 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -1,195 +1,37 @@ local ACF = ACF local Sounds = ACF.Utilities.Sounds - -local Current = {Panels = {}, } +local AddValue +local Current = {Panels = {}, Graph = {Idle = 0, Redline = 1, RPMSlider = 0}} local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! ---[[local function AddValuePanel(Menu) - local ID = #Current.Panels + 1 - local Wide = Menu:GetWide() - local ButtonHeight = 20 - - local Panel = Menu:AddPanel("DPanel") - Panel:SetWide(Wide) - Panel:SetTall(60) - Panel:SetText("") - Panel:Dock(TOP) - Panel:DockMargin(0, -5, 0, 10) - Menu:Dock(FILL) - Current.Panels.ID = ID - - local TopPanel = Menu:AddPanel("DPanel") -- This is equivalent to a HTML's Div, just here to parent other children to. - TopPanel:SetParent(Panel) - TopPanel:Dock(TOP) - TopPanel:DockMargin(0, 4, 0, 0) - TopPanel.Paint = function() end - - local BottomPanel = Menu:AddPanel("DPanel") -- Same as above - BottomPanel:SetParent(Panel) - BottomPanel:Dock(BOTTOM) - BottomPanel:DockMargin(0, 0, 0, -6) - BottomPanel:SetTall(34) -- Why is it setting the tall of its children as well :sob: - BottomPanel.Paint = function() end - - local NumLabel = Menu:AddPanel("DLabel") - NumLabel:SetParent(TopPanel) - NumLabel:SetFont("ACF_Control") - NumLabel:SetText(Current.Panels.ID .. " = ") - NumLabel:Dock(LEFT) - NumLabel:DockMargin(4, 0, -36, 0) - NumLabel:SetColor(color_black) - - local Rpm = Menu:AddPanel("DNumberWang") - Rpm:SetParent(TopPanel) - Rpm:SetMinMax(0, 16383) -- Maximum number it can be networked to the client, also a minmax... - Rpm:SetTall(ButtonHeight) - Rpm:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding - Rpm:SetValue(1000 * ID) - Rpm:Dock(LEFT) - Rpm:DockMargin(0, 0, 2, 0) - Current.Panels.RPM = Rpm - - local SoundPath = Menu:AddPanel("DTextEntry") - SoundPath:SetParent(TopPanel) - SoundPath:SetText("") - SoundPath:SetWide(Wide - 20) - SoundPath:SetTall(ButtonHeight) - SoundPath:SetMultiline(false) - SoundPath:Dock(FILL) - SoundPath:DockMargin(0, 0, 2, 0) - Current.Panels.SoundPath = SoundPath - SoundPath.OnChange = function() - local isValid = Sounds.IsValidSound - local value = SoundPath:GetValue() - - if isValid(value) then - Panel.SoundPath:SetTooltip() - Panel.ParseIcon:SetImage("icon16/accept.png") - else - Panel.SoundPath:SetTooltip("Invalid sound: File does not exist") - Panel.ParseIcon:SetImage("icon16/cancel.png") - end - end - - local ParseIcon = Menu:AddPanel("DImage") - Panel.ParseIcon = ParseIcon - ParseIcon:SetParent(SoundPath) - ParseIcon:Dock(RIGHT) - ParseIcon:DockMargin(2, 2, 2, 2) - ParseIcon:SetImage("icon16/accept.png") - ParseIcon:SizeToContents() - - local RemoveBtn = Menu:AddPanel("DImageButton") - RemoveBtn:SetParent(TopPanel) - RemoveBtn:SetImage( "icon16/delete.png" ) - RemoveBtn:SizeToContents() - RemoveBtn:Dock(RIGHT) - RemoveBtn:DockMargin(2, 2, 2, 2) - RemoveBtn:Center() - RemoveBtn:SetTooltip("Remove this sound.") - RemoveBtn.DoClick = function() - -- TODO(TMF): Have it do a popup modal prompting for removal before executing this function! - -- Don't remove the last item - if #Current.Panels == 1 then - RemoveBtn.DoClick = function() end - return - end - - -- Move the label number of the other Panels up to compensate - for k in ipairs(Current.Panels) do - Current.Panels.ID = k - NumLabel:SetText(Current.Panels.ID .. " = ") - end - - local ID = Current.Panels.ID - Current.Panels[ID]:Remove() - table.remove(Current.Panels, ID) - - --AddBtn:SetEnabled(true) -- Reenable our button - end - - local SearchBtn = Menu:AddPanel("DImageButton") - SearchBtn:SetParent(TopPanel) - SearchBtn:SetImage("icon16/application_view_list.png") - SearchBtn:SizeToContents() - SearchBtn:Dock(RIGHT) - SearchBtn:DockMargin(2, 2, 2, 2) - SearchBtn:Center() - SearchBtn:SetTooltip("Open sound browser.") - SearchBtn.DoClick = function() - RunConsoleCommand("wire_sound_browser_open") - end - - local PitchLabel = Menu:AddPanel("DLabel") - PitchLabel:SetParent(BottomPanel) - PitchLabel:SetFont("ACF_Control") - PitchLabel:SetText("Pitch:") - PitchLabel:Dock(LEFT) - PitchLabel:DockMargin(4, 0, -28, 0) - PitchLabel:SetColor(color_black) - - local Pitch = Menu:AddPanel("DNumberWang") - Current.Panels.Pitch = Pitch - Pitch:SetParent(BottomPanel) - Pitch:SetTall(ButtonHeight) - Pitch:SetMinMax(0, 255) - Pitch:SetValue(100) - Pitch:Dock(LEFT) - Pitch:SetTooltip("Set the pitch of your sound to play at this exact RPM") - Pitch:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding - - local VolumeLabel = Menu:AddPanel("DLabel") - VolumeLabel:SetParent(BottomPanel) - VolumeLabel:SetFont("ACF_Control") - VolumeLabel:SetText("Volume:") - VolumeLabel:Dock(LEFT) - VolumeLabel:DockMargin(4, 0, -16, 0) - VolumeLabel:SetColor(color_black) - - local Volume = Menu:AddPanel("DNumberWang") - Current.Panels.Volume = Volume - Volume:SetParent(BottomPanel) - Volume:SetTall(ButtonHeight) - Volume:SetMinMax(0, 1) - Volume:SetDecimals(2) - Volume:SetInterval(0.01) - Volume:SetFraction(0.01) - Volume:SetValue(1) - Volume:Dock(LEFT) - Volume:SetTooltip("Set the volume of your sound to play at this exact RPM") - Volume:SetWide(40) -- Equivalent to 000 w/ decimal point + up/down buttons at font size = 16 + padding - - local WidthLabel = Menu:AddPanel("DLabel") - WidthLabel:SetParent(BottomPanel) - WidthLabel:SetFont("ACF_Control") - WidthLabel:SetText("Width:") - WidthLabel:Dock(LEFT) - WidthLabel:DockMargin(4, 0, -24, 0) - WidthLabel:SetColor(color_black) - - local Width = Menu:AddPanel("DNumberWang") - Current.Panels.Width = Width - Width:SetParent(BottomPanel) - Width:SetTall(ButtonHeight) - Width:SetMinMax(0, 16) - Width:Dock(LEFT) - Width:SetTooltip("Widens the curve of the sound, making it pitch up sooner/later in the curve") -- Better description for this! - Width:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding - - table.insert(Current.Panels, Panel) - PrintTable(Current) - return Panel -end]]-- +local function UpdateGraph(Panel) + local Panels = Current.Panels + + for I = 1, #Panels do + local min = I == 1 and 0 or Panels[I - 1].RPM + local mid = Panels[I].RPM + -- TODO(TMF): The max value below is hardcoded, this should be a global! + local max = I == #Panels and 16383 or Panels[I + 1].RPM + local pitch = Current.Panels[I].Pitch + --local volume = Current.Panels[I].Volume * 100 -- Idk if we want to plot volume as a function + local addCurveWidth = Current.Panels[I].Width + + Panel:PlotFunction("Sound " .. I, nil, function(X) + return (Sounds.Fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * pitch + end) + end +end local function AddValuePanel(Menu) local ID = #Current.Panels + 1 --local Wide = Menu:GetWide() local ButtonHeight = 20 - local _, ValueGroup = Menu:AddCollapsible() - local Panel = Menu:AddPanel("DPanel") + local Panel, ValueGroup = Menu:AddCollapsible() + local Pnl = Menu:AddPanel("DPanel") -- Override our previous collapsible base with this panel local TopDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML's Div, just here to parent other children to. local BotDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML's Div, just here to parent other children to. + -- TODO(TMF): The max value below is hardcoded, this should be a global! local RPMWang, RPMLabel = Menu:AddNumberWang("RPM:", 0, 16383, 0) local _, PathLabel, PathText = Menu:AddTextEntry("Path:") local ParseIcon = Menu:AddPanel("DImage") @@ -203,6 +45,7 @@ local function AddValuePanel(Menu) ValueGroup:DockMargin(0, 0, 0, 0) ValueGroup:SetLabel("Value " .. ID) + Panel = Pnl Panel:SetParent(ValueGroup) Panel:SetTall(72) Panel:DockPadding(4, 6, 4, 0) @@ -222,8 +65,15 @@ local function AddValuePanel(Menu) RPMWang:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding RPMWang:DockMargin(-30, 0, 0, 0) RPMWang:Dock(LEFT) + RPMWang:SetValue(1000 * (1 + #Current.Panels)) RPMWang:SetClientData("RPM " .. ID, "OnValueChanged") RPMWang:DefineSetter(function(Panel, _, _, Value) + -- TODO(TMF): The max value below is hardcoded, this should be a global! + local min = ID == 1 and 0 or Current.Panels[ID - 1].RPM + local max = ID == #Current.Panels and 16383 or Current.Panels[ID + 1].RPM + + Panel:SetMinMax(min, max) -- YEA, I MINMAX MY NUMBERS, SO What!? + Panel:SetValue(Value) Current.Panels[ID].RPM = Value return Value, Panel end) @@ -236,7 +86,7 @@ local function AddValuePanel(Menu) PathText:DockMargin(-25, 0, 0, 0) PathText:SetTall(ButtonHeight) PathText:SetClientData("Path " .. ID, "OnValueChanged") - PathText:DefineSetter(function(Panel, _, _, Value) + PathText.OnChange = function(Value) local isValid = Sounds.IsValidSound if isValid(Value) then @@ -244,15 +94,14 @@ local function AddValuePanel(Menu) ParseIcon:SetImage("icon16/accept.png") Current.Panels[ID].Path = Value - Current.Panels[ID].LastPath = Value - return Value, Panel else Panel:SetTooltip("Invalid sound: File does not exist") ParseIcon:SetImage("icon16/cancel.png") - Current.Panels[ID].Path = Current.Panels[ID].LastPath + + Current.Panels[ID].Path = "" end - return false, Panel - end) + return Value, Panel + end ParseIcon:SetParent(PathText) ParseIcon:Dock(RIGHT) @@ -267,9 +116,9 @@ local function AddValuePanel(Menu) RemoveButton:SetImage("icon16/delete.png") RemoveButton:SetTooltip("Remove this sound.") RemoveButton:SizeToContents() - --[[RemoveButton.DoClick = function() + RemoveButton.DoClick = function() -- TODO(TMF): Have it do a popup modal prompting for removal before executing this function! - -- Don't remove the last item + -- Just recreate the first item if #Current.Panels == 1 then RemoveButton.DoClick = function() end return @@ -278,15 +127,15 @@ local function AddValuePanel(Menu) -- Move the label number of the other Panels up to compensate for k, v in ipairs(Current.Panels) do v.ID = k - ValueGroup:SetLabel("Value " .. v.ID) + ValueGroup:SetLabel("Value " .. k) end - local panel = Panel.ID - panel[ID]:Remove() + -- Finally remove the panel from the menu and in the table + ValueGroup:Remove() table.remove(Current.Panels, ID) - --AddBtn:SetEnabled(true) -- Reenable our button - end]]-- + AddValue:SetEnabled(true) -- Reenable our button + end SearchButton:SetParent(TopDiv) SearchButton:Center() @@ -306,6 +155,8 @@ local function AddValuePanel(Menu) PitchWang:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding PitchWang:DockMargin(-30, 0, 4, 0) PitchWang:Dock(LEFT) + PitchWang:SetValue(100) + PitchWang:SetClientData("Pitch " .. ID, "OnValueChanged") VolumeLabel:SetParent(BotDiv) VolumeLabel:Dock(LEFT) @@ -314,6 +165,8 @@ local function AddValuePanel(Menu) VolumeWang:SetWide(40) -- Equivalent to 0.00 + up/down buttons at font size = 16 + padding VolumeWang:DockMargin(-16, 0, 4, 0) VolumeWang:Dock(LEFT) + VolumeWang:SetValue(1) + VolumeWang:SetClientData("Volume " .. ID, "OnValueChanged") WidthLabel:SetParent(BotDiv) WidthLabel:Dock(LEFT) @@ -322,16 +175,21 @@ local function AddValuePanel(Menu) WidthWang:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding WidthWang:DockMargin(-24, 0, 4, 0) WidthWang:Dock(LEFT) + WidthWang:SetClientData("Width " .. ID, "OnValueChanged") Panel.ID = ID Panel.RPM = RPMWang:GetValue() Panel.Path = PathText:GetValue() - Panel.LastPath = Panel.Path - - table.insert(Current.Panels, {ID = Panel.ID, - RPM = Panel.RPM, - Path = Panel.Path, - LastPath = Path + Panel.Pitch = PitchWang:GetValue() + Panel.Volume = VolumeWang:GetValue() + Panel.Width = WidthWang:GetValue() + + table.insert(Current.Panels, {ID = Panel.ID, + RPM = Panel.RPM, + Path = Panel.Path, + Pitch = Panel.Pitch, + Volume = Panel.Volume, + Width = Panel.Width }) return Panel end @@ -408,14 +266,6 @@ local function CreateSubMenu(Num, Menu) [2] = function() Menu:AddLabel("This is the second panel, I don't know what to add here yet but you can imagine its gonna be something nice, so stay tuned!") - local KoolWackyClientPanelThang = Menu:AddPanel("DPanel") - - local SliderX = Menu:AddSlider() - SliderX:SetParent(KoolWackyClientPanelThang) - - --local Min = 0 - --local Max = 16383 - end, -- Third panel, Engines - Simple interpolated. New menu with a Slider that creates N amount of text entries to put the sound paths -- Layout is similar to the first option @@ -427,7 +277,9 @@ local function CreateSubMenu(Num, Menu) -- Has a graph at the top of the list to better visualise how they play at a determined engine RPM [4] = function() Menu:AddLabel("This is the fourth panel, I don't know what to add here yet but you can imagine its gonna be something mindblowing, so stay tuned!") - + -- Reset them panels + Current.Panels = nil + Current.Panels = {} -- The menu is divided in two groups -- The top group where the graph lies local GraphGroup = Menu:AddCollapsible("Graph", nil, "icon16/chart_curve_edit.png") @@ -438,6 +290,7 @@ local function CreateSubMenu(Num, Menu) local IdleLabel = Menu:AddLabel("Idle:") local IdleWang = Menu:AddPanel("DNumberWang", 0, 2000) local RedlineLabel = Menu:AddLabel("Redline:") + -- TODO(TMF): The max values below are hardcoded, this should be a global! local RedlineWang = Menu:AddPanel("DNumberWang", 0, 16383) local RPMSlider = Menu:AddSlider("RPM", 0, 16383) local SoundPre = Menu:AddPanel("ACF_Panel") @@ -453,15 +306,23 @@ local function CreateSubMenu(Num, Menu) -- The properties GraphGroup:DockMargin(0, 0, 0, 0) + GraphPanel:SetParent(GraphGroup) GraphPanel:DockPadding(4, 4, 4, 8) GraphPanel:Dock(TOP) GraphPanel:SetTall(368) -- Why can't this grow dynamically + LabelTop:SetParent(GraphPanel) LabelTop:Dock(TOP) + SoundGraph:SetParent(GraphPanel) SoundGraph:Dock(TOP) SoundGraph:SetTall(192) + SoundGraph:SetYRange(0, 255) + SoundGraph:SetFidelity(10) + SoundGraph:SetXSpacing(1000) + SoundGraph:SetYSpacing(100) + PanelBottom:SetParent(GraphPanel) PanelBottom:Dock(TOP) PanelBottom:DockPadding(0, 4, 4, -4) @@ -469,13 +330,14 @@ local function CreateSubMenu(Num, Menu) IdleLabel:SetParent(PanelBottom) IdleLabel:Dock(LEFT) + IdleWang:SetParent(PanelBottom) IdleWang:Dock(LEFT) IdleWang:SetClientData("Idle", "OnValueChanged") IdleWang:DefineSetter(function(Panel, _, _, Value) Panel:SetMinMax(0, 2000) -- I shouldn't even need to do this! Panel:SetValue(Value) - Current.Idle = Value + Current.Graph["Idle"] = Value return Value end) @@ -483,15 +345,19 @@ local function CreateSubMenu(Num, Menu) RedlineLabel:SetParent(PanelBottom) RedlineLabel:Dock(LEFT) RedlineLabel:DockMargin(8, 4, 0, 0) -- Fucking retarded + RedlineWang:SetParent(PanelBottom) RedlineWang:Dock(LEFT) RedlineWang:SetClientData("Redline", "OnValueChanged") RedlineWang:DefineSetter(function(Panel, _, _, Value) - Panel:SetMin(Current.Idle or 1) + -- TODO(TMF): The max value below is hardcoded, this should be a global! + Panel:SetMin(Current.Graph["Idle"] or 1) Panel:SetMax(16383) Panel:SetValue(Value) - Current.Redline = Value + Current.Graph["Redline"] = Value + SoundGraph:SetXRange(0, Value + Current.Graph["Idle"]) + SoundGraph:SetXSpacing(Value < 1000 and 100 or 1000) return Value end) @@ -500,26 +366,27 @@ local function CreateSubMenu(Num, Menu) RPMSlider:SetWide(Wide) RPMSlider:SetClientData("RPMSlider", "OnValueChanged") RPMSlider:DefineSetter(function(Panel, _, _, Value) - local Min = Current.Idle or 0 - local Max = Current.Redline or 1 + -- TODO(TMF): The max value below is hardcoded, this should be a global! + local Min = Current.Graph["Idle"] or 0 + local Max = Current.Graph["Redline"] or 16383 - Panel:SetMin(Min) - Panel:SetMax(Max) + Panel:SetMinMax(Min, Max) Panel:SetValue(Value) - Current.RPM = Value + Current.Graph["RPM"] = Value + SoundGraph:PlotLimitLine("RPM", false, Value, color_black) return Value end) SoundPre:SetParent(GraphPanel) SoundPre:SetWide(Wide) SoundPre:SetTall(ButtonHeight) + SoundPrePlay:SetIcon("icon16/sound.png") SoundPrePlay.DoClick = function() -- Do something here to play them sounds! end SoundPreStop:SetIcon("icon16/sound_mute.png") - -- Set the Play/Stop button positions here SoundPre:InvalidateLayout(true) SoundPre.PerformLayout = function() @@ -532,25 +399,21 @@ local function CreateSubMenu(Num, Menu) -- The bottom group where the panels are added and removed dynamically local ValuesGroup = Menu:AddCollapsible("Values", nil, "icon16/application_double.png") ValuesGroup:DockMargin(0, 4, 0, 4) - local ValuesPanel = Menu:AddPanel("ACF_Panel") - ValuesPanel:SetParent(ValuesGroup) - ValuesPanel:Dock(TOP) - local AddValue = Menu:AddPanel("DImageButton") + AddValue = Menu:AddPanel("DImageButton") + AddValue:SetParent(ValuesGroup) + AddValue:Dock(BOTTOM) AddValue:SetImage("icon16/add.png") AddValue:SetTooltip("Add a new sound.") + AddValue:SetStretchToFit(false) AddValue.DoClick = function() + AddValuePanel(ValuesGroup) if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end -- Disable the button if enough panels exist already - AddValuePanel(ValuesPanel) + UpdateGraph(SoundGraph) end - - function ValuesGroup.PerformLayout() - AddValue:Center() - AddValue:SetSize(16, 16) - end - -- Add the first panel if it none exists - if #Current.Panels == 0 then AddValuePanel(ValuesPanel) end + if #Current.Panels == 0 then AddValuePanel(ValuesGroup) end + UpdateGraph(SoundGraph) end } From 77155f137bb2bd0e92303c297cc32f0e15629542 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 21 Feb 2026 01:42:42 -0300 Subject: [PATCH 34/59] Improve the menu some more - Fixed additional, non deleteable margin when adding new values - Improved value removal logic, still fragile and finicky - Improved GetClientData defaults - Fixed graph can now properly visualize additional curve widths --- lua/acf/menu/items_cl/sound_replacer.lua | 204 ++++++++++++++--------- 1 file changed, 125 insertions(+), 79 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index b83efff7a..31d6a1966 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -1,20 +1,33 @@ local ACF = ACF local Sounds = ACF.Utilities.Sounds +local GetClientData, SetClientData = ACF.GetClientData, ACF.SetClientData +local GetClientNumber, GetClientString = ACF.GetClientNumber, ACF.GetClientString + local AddValue -local Current = {Panels = {}, Graph = {Idle = 0, Redline = 1, RPMSlider = 0}} +local Current = {Panels = {}, + Count = 0, + Graph = { + Idle = 0, + Redline = 1, + RPMSlider = 2} + } local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! +-- The graphing function, this is a mirror of the function found in sounds_cl.lua local function UpdateGraph(Panel) - local Panels = Current.Panels + local Count = #Current.Panels + if not Count then return end - for I = 1, #Panels do - local min = I == 1 and 0 or Panels[I - 1].RPM - local mid = Panels[I].RPM - -- TODO(TMF): The max value below is hardcoded, this should be a global! - local max = I == #Panels and 16383 or Panels[I + 1].RPM - local pitch = Current.Panels[I].Pitch + Panel:Clear() + + for I = 1, Count do + local addCurveWidth = GetClientNumber("Width " .. I, 0) -- Current.Panels[I].Width + local pitch = GetClientNumber("Pitch " .. I, 0) -- Current.Panels[I].Pitch --local volume = Current.Panels[I].Volume * 100 -- Idk if we want to plot volume as a function - local addCurveWidth = Current.Panels[I].Width + local min = I == 1 and 0 or GetClientNumber("RPM " .. math.Clamp(I - 1 - addCurveWidth, 0, 16383)) + local mid = GetClientNumber("RPM " .. I, 0) + -- TODO(TMF): The max value below is hardcoded, this should be a global! + local max = I == Count and 16383 or GetClientNumber("RPM " .. math.Clamp(I + 1 + addCurveWidth, 0, 16383)) Panel:PlotFunction("Sound " .. I, nil, function(X) return (Sounds.Fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * pitch @@ -22,15 +35,16 @@ local function UpdateGraph(Panel) end end -local function AddValuePanel(Menu) - local ID = #Current.Panels + 1 - --local Wide = Menu:GetWide() +local function AddValuePanel(Menu, Graph) + Current.Count = Current.Count + 1 + local ID = math.max(Current.Count, #Current.Panels) local ButtonHeight = 20 - local Panel, ValueGroup = Menu:AddCollapsible() - local Pnl = Menu:AddPanel("DPanel") -- Override our previous collapsible base with this panel + local _, MPanel = Menu:AddCollapsible() + local Base = Menu:AddPanel("DPanel") + _ = Base -- Override ACF's basic Base with this local TopDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML's Div, just here to parent other children to. - local BotDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML's Div, just here to parent other children to. + local BotDiv = Menu:AddPanel("ACF_Panel") -- Same as above. -- TODO(TMF): The max value below is hardcoded, this should be a global! local RPMWang, RPMLabel = Menu:AddNumberWang("RPM:", 0, 16383, 0) local _, PathLabel, PathText = Menu:AddTextEntry("Path:") @@ -42,19 +56,25 @@ local function AddValuePanel(Menu) local VolumeWang, VolumeLabel = Menu:AddNumberWang("Volume:", 0, 1, 2) local WidthWang, WidthLabel = Menu:AddNumberWang("Width:", 0, 15, 0) - ValueGroup:DockMargin(0, 0, 0, 0) - ValueGroup:SetLabel("Value " .. ID) + -- Defaults + local DefaultPath = "" + local DefaultRPM = 1000 * ID + local DefaultPitch = 100 + local DefaultVolume = 1 + local DefaultWidth = 0 + + MPanel:DockMargin(0, 0, 0, 0) + MPanel:SetLabel("Value " .. ID) - Panel = Pnl - Panel:SetParent(ValueGroup) - Panel:SetTall(72) - Panel:DockPadding(4, 6, 4, 0) - Panel:DockMargin(0, 0, 0, 0) + Base:SetParent(MPanel) + Base:SetTall(72) + Base:DockPadding(4, 6, 4, 0) + Base:DockMargin(0, 0, 0, 0) - TopDiv:SetParent(Panel) + TopDiv:SetParent(Base) TopDiv:Dock(TOP) - BotDiv:SetParent(Panel) + BotDiv:SetParent(Base) BotDiv:Dock(BOTTOM) RPMLabel:SetParent(TopDiv) @@ -65,16 +85,16 @@ local function AddValuePanel(Menu) RPMWang:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding RPMWang:DockMargin(-30, 0, 0, 0) RPMWang:Dock(LEFT) - RPMWang:SetValue(1000 * (1 + #Current.Panels)) + RPMWang:SetValue(GetClientNumber("RPM " .. ID, DefaultRPM)) RPMWang:SetClientData("RPM " .. ID, "OnValueChanged") RPMWang:DefineSetter(function(Panel, _, _, Value) -- TODO(TMF): The max value below is hardcoded, this should be a global! - local min = ID == 1 and 0 or Current.Panels[ID - 1].RPM - local max = ID == #Current.Panels and 16383 or Current.Panels[ID + 1].RPM + local min = ID == 1 and 0 or GetClientNumber("RPM " .. ID - 1) -- Current.Panels[ID - 1].RPM + local max = ID == #Current.Panels and 16383 or GetClientNumber("RPM " .. ID + 1) -- Current.Panels[ID + 1].RPM Panel:SetMinMax(min, max) -- YEA, I MINMAX MY NUMBERS, SO What!? Panel:SetValue(Value) - Current.Panels[ID].RPM = Value + return Value, Panel end) @@ -85,56 +105,67 @@ local function AddValuePanel(Menu) PathText:Dock(FILL) PathText:DockMargin(-25, 0, 0, 0) PathText:SetTall(ButtonHeight) - PathText:SetClientData("Path " .. ID, "OnValueChanged") - PathText.OnChange = function(Value) + PathText:SetValue(GetClientString("Path " .. ID, DefaultPath)) + PathText:SetClientData("Path " .. ID, "OnChange") + -- Bitch this aint working! :sob: :sob: :sob: + PathText:DefineSetter(function(Panel, _, _, Value) local isValid = Sounds.IsValidSound if isValid(Value) then - Panel:SetTooltip() + ParseIcon:SetTooltip() ParseIcon:SetImage("icon16/accept.png") - Current.Panels[ID].Path = Value + SetClientData("Path " .. ID, Value) else - Panel:SetTooltip("Invalid sound: File does not exist") + ParseIcon:SetTooltip("Invalid sound: File does not exist") ParseIcon:SetImage("icon16/cancel.png") - Current.Panels[ID].Path = "" + SetClientData("Path " .. ID, DefaultPath) end return Value, Panel - end + end) ParseIcon:SetParent(PathText) ParseIcon:Dock(RIGHT) ParseIcon:DockMargin(3, 3, 3, 3) ParseIcon:SetImage("icon16/accept.png") - ParseIcon:SizeToContents() + ParseIcon:SetSize(16, 16) RemoveButton:SetParent(TopDiv) - RemoveButton:Center() RemoveButton:Dock(RIGHT) RemoveButton:DockMargin(3, 3, 3, 3) RemoveButton:SetImage("icon16/delete.png") RemoveButton:SetTooltip("Remove this sound.") - RemoveButton:SizeToContents() + RemoveButton:SetStretchToFit(false) + RemoveButton:SetSize(16, 16) RemoveButton.DoClick = function() - -- TODO(TMF): Have it do a popup modal prompting for removal before executing this function! - -- Just recreate the first item - if #Current.Panels == 1 then + -- Don't remove the last panel + if Current.Count == 1 then RemoveButton.DoClick = function() end return end - -- Move the label number of the other Panels up to compensate - for k, v in ipairs(Current.Panels) do - v.ID = k - ValueGroup:SetLabel("Value " .. k) - end + -- Reset our client data + SetClientData("RPM " .. ID, DefaultRPM, true) + SetClientData("Path " .. ID, DefaultPath, true) + SetClientData("Pitch " .. ID, DefaultPitch, true) + SetClientData("Volume " .. ID, DefaultVolume, true) + SetClientData("Width " .. ID, DefaultWidth, true) - -- Finally remove the panel from the menu and in the table - ValueGroup:Remove() + -- Remove the panel in question + MPanel:Remove() table.remove(Current.Panels, ID) - AddValue:SetEnabled(true) -- Reenable our button + -- Set the label of the remaining panels up + for k, v in pairs(Current.Panels) do + if not IsValid(v) then continue end + v:SetLabel("Value " .. k) + end + + AddValue:SetEnabled(true) -- Re-enable our add button + Current.Count = Current.Count - 1 + + UpdateGraph(Graph) end SearchButton:SetParent(TopDiv) @@ -143,7 +174,8 @@ local function AddValuePanel(Menu) SearchButton:DockMargin(3, 3, 3, 3) SearchButton:SetImage("icon16/application_view_list.png") SearchButton:SetTooltip("Open sound browser.") - SearchButton:SizeToContents() + SearchButton:SetStretchToFit(false) + SearchButton:SetSize(16, 16) SearchButton.DoClick = function() RunConsoleCommand("wire_sound_browser_open") end @@ -155,8 +187,11 @@ local function AddValuePanel(Menu) PitchWang:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding PitchWang:DockMargin(-30, 0, 4, 0) PitchWang:Dock(LEFT) - PitchWang:SetValue(100) + PitchWang:SetValue(GetClientNumber("Pitch " .. ID, DefaultPitch)) PitchWang:SetClientData("Pitch " .. ID, "OnValueChanged") + PitchWang:DefineSetter(function(_, _, _, Value) + SetClientData("Pitch " .. ID, Value) + end) VolumeLabel:SetParent(BotDiv) VolumeLabel:Dock(LEFT) @@ -165,8 +200,11 @@ local function AddValuePanel(Menu) VolumeWang:SetWide(40) -- Equivalent to 0.00 + up/down buttons at font size = 16 + padding VolumeWang:DockMargin(-16, 0, 4, 0) VolumeWang:Dock(LEFT) - VolumeWang:SetValue(1) + VolumeWang:SetValue(GetClientNumber("Volume " .. ID, DefaultVolume)) VolumeWang:SetClientData("Volume " .. ID, "OnValueChanged") + VolumeWang:DefineSetter(function(_, _, _, Value) + SetClientData("Volume " .. ID, Value) + end) WidthLabel:SetParent(BotDiv) WidthLabel:Dock(LEFT) @@ -175,23 +213,15 @@ local function AddValuePanel(Menu) WidthWang:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding WidthWang:DockMargin(-24, 0, 4, 0) WidthWang:Dock(LEFT) + WidthWang:SetValue(GetClientNumber("Width " .. ID, DefaultWidth)) WidthWang:SetClientData("Width " .. ID, "OnValueChanged") + WidthWang:DefineSetter(function(_, _, _, Value) + SetClientData("Width " .. ID, Value) + end) - Panel.ID = ID - Panel.RPM = RPMWang:GetValue() - Panel.Path = PathText:GetValue() - Panel.Pitch = PitchWang:GetValue() - Panel.Volume = VolumeWang:GetValue() - Panel.Width = WidthWang:GetValue() - - table.insert(Current.Panels, {ID = Panel.ID, - RPM = Panel.RPM, - Path = Panel.Path, - Pitch = Panel.Pitch, - Volume = Panel.Volume, - Width = Panel.Width - }) - return Panel + table.insert(Current.Panels, MPanel) + UpdateGraph(Graph) + return MPanel end -- Build the panels according to our selection @@ -280,6 +310,7 @@ local function CreateSubMenu(Num, Menu) -- Reset them panels Current.Panels = nil Current.Panels = {} + Current.Count = 0 -- The menu is divided in two groups -- The top group where the graph lies local GraphGroup = Menu:AddCollapsible("Graph", nil, "icon16/chart_curve_edit.png") @@ -298,11 +329,11 @@ local function CreateSubMenu(Num, Menu) local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") -- Playing a silent sound will mute the preview but not the sound emitters -- Set defaults - local DefaultIdle = ACF.GetClientData("Idle", 800) - local DefaultRedline = ACF.GetClientData("Redline", 8000) - ACF.SetClientData("Idle", DefaultIdle, true) - ACF.SetClientData("Redline", DefaultRedline, true) - ACF.SetClientData("RPMSlider", (DefaultIdle + DefaultRedline) / 2, true) + local DefaultIdle = GetClientData("Idle", 800) + local DefaultRedline = GetClientData("Redline", 8000) + SetClientData("Idle", DefaultIdle, true) + SetClientData("Redline", DefaultRedline, true) + SetClientData("RPMSlider", (DefaultIdle + DefaultRedline) / 2, true) -- The properties GraphGroup:DockMargin(0, 0, 0, 0) @@ -333,6 +364,7 @@ local function CreateSubMenu(Num, Menu) IdleWang:SetParent(PanelBottom) IdleWang:Dock(LEFT) + IdleWang:SetValue(DefaultIdle) -- I shouldn't need to do this but oh well, here we go... IdleWang:SetClientData("Idle", "OnValueChanged") IdleWang:DefineSetter(function(Panel, _, _, Value) Panel:SetMinMax(0, 2000) -- I shouldn't even need to do this! @@ -348,6 +380,7 @@ local function CreateSubMenu(Num, Menu) RedlineWang:SetParent(PanelBottom) RedlineWang:Dock(LEFT) + RedlineWang:SetValue(DefaultRedline) RedlineWang:SetClientData("Redline", "OnValueChanged") RedlineWang:DefineSetter(function(Panel, _, _, Value) -- TODO(TMF): The max value below is hardcoded, this should be a global! @@ -364,6 +397,7 @@ local function CreateSubMenu(Num, Menu) RPMSlider:SetParent(GraphPanel) RPMSlider:Dock(TOP) RPMSlider:SetWide(Wide) + RPMSlider:SetValue(GetClientNumber("RPMSlider", 4400)) RPMSlider:SetClientData("RPMSlider", "OnValueChanged") RPMSlider:DefineSetter(function(Panel, _, _, Value) -- TODO(TMF): The max value below is hardcoded, this should be a global! @@ -399,21 +433,33 @@ local function CreateSubMenu(Num, Menu) -- The bottom group where the panels are added and removed dynamically local ValuesGroup = Menu:AddCollapsible("Values", nil, "icon16/application_double.png") ValuesGroup:DockMargin(0, 4, 0, 4) + -- I don't know if this makes sense, but somehow it gives me less trouble to later remove any arbitrary panels + Menu:StartTemporal(ValuesGroup) + Menu:ClearTemporal(ValuesGroup) + + local ListPanel = Menu:AddPanel("DListLayout") + ListPanel:SetParent(ValuesGroup) + ListPanel:Dock(TOP) + + Menu:EndTemporal(ValuesGroup) AddValue = Menu:AddPanel("DImageButton") AddValue:SetParent(ValuesGroup) - AddValue:Dock(BOTTOM) + AddValue:Dock(TOP) AddValue:SetImage("icon16/add.png") AddValue:SetTooltip("Add a new sound.") AddValue:SetStretchToFit(false) + AddValue:SetSize(16, 16) AddValue.DoClick = function() - AddValuePanel(ValuesGroup) - if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end -- Disable the button if enough panels exist already - UpdateGraph(SoundGraph) + ListPanel:Add(AddValuePanel(Menu, SoundGraph)) + -- Disable the button if enough panels exist already + if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end + end - -- Add the first panel if it none exists - if #Current.Panels == 0 then AddValuePanel(ValuesGroup) end - UpdateGraph(SoundGraph) + -- Add the first panel if none exists + if #Current.Panels == 0 then + ListPanel:Add(AddValuePanel(Menu, SoundGraph)) + end end } From cae82a1ed685c6a23736fb91de5ade2cbd71bb5c Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Mon, 23 Feb 2026 00:47:29 -0300 Subject: [PATCH 35/59] Refactor how sound tables are networked - Change Engine SoundBank RPM keys to be an atribute of the table instead - Allow soundcounts in init.lua to be 0, tables can be nil now - Due to the above changes, we network RPM's as values instead of as keys - This allows me to reuse the networked table instead of discarding it and removes a table.insert operation on sounds creation --- lua/acf/core/utilities/sounds/sounds_cl.lua | 60 ++++++++----------- lua/acf/core/utilities/sounds/sounds_sv.lua | 8 +-- .../core/utilities/sounds/tool_support_sh.lua | 15 ++--- lua/acf/entities/engines/special.lua | 26 ++++---- lua/entities/acf_engine/init.lua | 4 +- 5 files changed, 49 insertions(+), 64 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 76a888496..e041d00d1 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -212,17 +212,17 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) -- TODO(TMF): Potentially add some mechanism here to check for any differences and only update those for idx, soundTable in ipairs(SoundObjects) do - if not soundTable.rpm then continue end - Origin.Sound = soundTable.sound + if not soundTable.RPM then continue end + Origin.Sound = soundTable.Sound - local addCurveWidth = soundTable.width or 0 - local enginePitch = soundTable.pitch or 1 - local min = idx == 1 and 0 or SoundObjects[idx - 1].rpm + local addCurveWidth = soundTable.Width or 0 + local enginePitch = soundTable.Pitch or 1 + local min = idx == 1 and 0 or SoundObjects[idx - 1].RPM local mid = RPM - local max = idx == #SoundObjects and 16383 or SoundObjects[idx + 1].rpm + local max = idx == #SoundObjects and 16383 or SoundObjects[idx + 1].RPM local curve = Sounds.Fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) - local volume = curve * map(Throttle, 0, 100, _OFFVOLUME, _ONVOLUME) * (soundTable.volume or 1) - local pitch = (RPM / soundTable.rpm) * enginePitch + local volume = curve * map(Throttle, 0, 100, _OFFVOLUME, _ONVOLUME) * (soundTable.Volume or 1) + local pitch = (RPM / soundTable.RPM) * enginePitch Sounds.UpdateAdjustableSound(Origin, pitch, volume) end @@ -234,35 +234,26 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) --- Creates many sounds from a table, and stores their entries in the Origin's entity. --- Reuses existing methods to create and update sounds. --- @param Origin table The entity to play the sounds from - --- @param PathTable table The networked table with nested table(Key as RPM) containing sound path, pitch and width - function Sounds.CreateMultipleAdjustableSounds(Origin, PathTable) - -- This is where we store our sound objects and keep count of them - local SoundObjects = {} + --- @param SoundTable table The networked table with nested table containing rpm, sound path, pitch, volume, width and empty sound + function Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) local SoundCount = 0 - for _, soundTable in ipairs(PathTable) do - if not Sounds.IsValidSound(soundTable.Path) then return end + for _, sndTable in ipairs(SoundTable) do + if not Sounds.IsValidSound(sndTable.Path) then return end local Sound = Sounds.CreateAdjustableSound(Origin, - soundTable.Path, - soundTable.Pitch or 100, 0 -- Create the sound deafened + sndTable.Path, + sndTable.Pitch or 100, 0 -- Create the sound deafened ) + sndTable.Sound = Sound SoundCount = SoundCount + 1 - -- Insert the CSoundPatch type objects inside the SoundObjects table, alongside with the rpm it has be to play at the desired pitch, - -- the volume and the width which allows the sound to play in a wider range of RPM's - table.insert(SoundObjects, SoundCount, {["rpm"] = soundTable.RPM, - ["width"] = soundTable.Width or 0, - ["pitch"] = soundTable.Pitch or 100, - ["volume"] = soundTable.Volume or 1, - ["sound"] = Sound}) - - Sounds.UpdateAdjustableSound(Origin, soundTable.Pitch or 100, 0) + Sounds.UpdateAdjustableSound(Origin, sndTable.Pitch or 100, 0) end -- Sort the table by the rpm before moving on, so it can be iterated in sequential order - table.sort(SoundObjects, function(a, b) return a.rpm < b.rpm end) + table.sort(SoundTable, function(a, b) return a.RPM < b.RPM end) - Origin.SoundObjects = SoundObjects + Origin.SoundObjects = SoundTable Origin.SoundCount = SoundCount -- Ensuring that the sounds can't stick around if the server doesn't properly ask for them to be destroyed Origin:CallOnRemove("ACF_ForceStopMultipleAdjustableSounds", function(Entity) @@ -275,7 +266,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) function Sounds.DeleteMultipleAdjustableSounds(Origin, _) if not IsValid(Origin) then return end for idx, snd in ipairs(Origin.SoundObjects) do - snd.sound:Stop() + snd.Sound:Stop() Origin.SoundObjects[idx] = nil end Origin.Sound = nil -- Just in case @@ -300,18 +291,19 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) local I = 0 while (I < Count) do - local Key = net.ReadUInt(14) + local RPM = net.ReadUInt(14) local StringPath = net.ReadString() local Pitch = net.ReadUInt(8) - local Volume = net.ReadUInt(7) + local Volume = net.ReadUInt(8) local Width = net.ReadUInt(4) Volume = Volume * 0.01 -- Reduce the received value down to a float - table.insert(SoundTable, { RPM = Key, + table.insert(SoundTable, { RPM = RPM, Path = StringPath, - Pitch = Pitch, - Volume = Volume, - Width = Width }) + Pitch = Pitch or 100, + Volume = Volume or 1, + Width = Width or 0, + Sound = nil }) -- Fuck it we ball I = I + 1 end diff --git a/lua/acf/core/utilities/sounds/sounds_sv.lua b/lua/acf/core/utilities/sounds/sounds_sv.lua index b327096db..49d5414cc 100644 --- a/lua/acf/core/utilities/sounds/sounds_sv.lua +++ b/lua/acf/core/utilities/sounds/sounds_sv.lua @@ -106,19 +106,19 @@ function Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable, SoundCount) net.WriteEntity(Origin) net.WriteUInt(SoundCount, 4) - for k, v in pairs(SoundTable) do - local key = k + for _, v in ipairs(SoundTable) do + local rpm = v.RPM local stringPath = v.Path local pitch = v.Pitch local volume = v.Volume local width = v.Width - net.WriteUInt(key, 14) + net.WriteUInt(rpm, 14) net.WriteString(stringPath) net.WriteUInt(pitch, 8) volume = volume * 100 -- Sending the approximate volume as an int to reduce message size - net.WriteUInt(volume, 7) + net.WriteUInt(volume, 8) net.WriteUInt(width, 4) end net.SendPAS(Origin:GetPos()) diff --git a/lua/acf/core/utilities/sounds/tool_support_sh.lua b/lua/acf/core/utilities/sounds/tool_support_sh.lua index 584fee323..538bec4ea 100644 --- a/lua/acf/core/utilities/sounds/tool_support_sh.lua +++ b/lua/acf/core/utilities/sounds/tool_support_sh.lua @@ -48,9 +48,9 @@ Sounds.acf_engine = { Ent:UpdateSound() end, ResetSound = function(Ent) - Ent.SoundPath = Ent.DefaultSound - Ent.SoundPitch = 1 - Ent.SoundVolume = 1 + Ent.SoundPath = Ent.DefaultSound + Ent.SoundPitch = 1 + Ent.SoundVolume = 1 Ent:UpdateSound() end, @@ -59,17 +59,10 @@ Sounds.acf_engine = { SoundBank = Ent.SoundBank } end, - -- This is dog... Change this! SetSoundBank = function(Ent, SoundBankData) - local soundTable = SoundBankData - - for K, V in ipairs(soundTable) do - print("Called \"SetSoundBank\" from \"tool_support_sh.lua\" but no implementation was made!") - print(K, V) - end - Ent.SoundBank = SoundBankData + Ent:UpdateSoundBank() end } diff --git a/lua/acf/entities/engines/special.lua b/lua/acf/entities/engines/special.lua index 590a82dd0..4bae17116 100644 --- a/lua/acf/entities/engines/special.lua +++ b/lua/acf/entities/engines/special.lua @@ -79,19 +79,19 @@ do -- Special I4 Engines Model = "models/engines/inline4s.mdl", Sound = "acf_extra/vehiclefx/engines/l4/mini_onhigh.wav", SoundBank = { - [714] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", Pitch = 100, Volume = 1, Width = 0}, - [967] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", Pitch = 100, Volume = 1, Width = 0}, - [1538] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", Pitch = 100, Volume = 1, Width = 0}, - [1978] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", Pitch = 100, Volume = 1, Width = 0}, - [2571] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", Pitch = 100, Volume = 1, Width = 0}, - [3450] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", Pitch = 100, Volume = 1, Width = 0}, - [3889] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", Pitch = 100, Volume = 1, Width = 0}, - [4482] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", Pitch = 100, Volume = 1, Width = 0}, - [4922] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", Pitch = 100, Volume = 1, Width = 0}, - [5295] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", Pitch = 100, Volume = 1, Width = 0}, - [5823] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", Pitch = 100, Volume = 1, Width = 0}, - [6350] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", Pitch = 100, Volume = 1, Width = 0}, - [6833] = {Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", Pitch = 100, Volume = 1, Width = 0} + {RPM = 714, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00714.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 967, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_00967.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 1538, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01538.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 1978, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_01978.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 2571, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_02571.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 3450, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03450.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 3889, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_03889.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 4482, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04482.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 4922, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_04922.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 5295, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05295.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 5823, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_05823.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 6350, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06350.wav", Pitch = 100, Volume = 1, Width = 0}, + {RPM = 6833, Path = "acf_forza6apex/mitsubishi/mitsubishilancerevoxgsr/engine_06833.wav", Pitch = 100, Volume = 1, Width = 0} }, Fuel = { Petrol = true }, Type = "GenericPetrol", diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index e18d455ab..e7be29a49 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -320,7 +320,7 @@ do -- Spawn and Update functions Entity.ClassData = Class Entity.DefaultSound = Engine.Sound Entity.SoundBank = Engine.SoundBank - Entity.SoundCount = GetSoundCount(Engine) or 1 + Entity.SoundCount = GetSoundCount(Engine) Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 Entity.TorqueCurve = Engine.TorqueCurve @@ -404,7 +404,7 @@ do -- Spawn and Update functions Entity.Throttle = 0 Entity.FlyRPM = 0 Entity.SoundPath = Engine.Sound - Entity.SoundBank = Entity.SoundBank or {} + Entity.SoundBank = Engine.SoundBank Entity.SoundCount = 0 Entity.LastPitch = 0 Entity.LastTorque = 0 From 272e73b86116fd4205697dc32d002aa23d61f5bf Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Mon, 23 Feb 2026 20:51:54 -0300 Subject: [PATCH 36/59] Here i reach a dead-end --- lua/weapons/gmod_tool/stools/acfsound.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index fc02fa288..1daf673a5 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -75,8 +75,12 @@ function TOOL:RightClick(trace) local class = trace.Entity:GetClass() local support = ACF.SoundToolSupport[class] + if not support then return false end + local soundData = support.GetSound(trace.Entity) + local soundTable = support.GetSoundBank(trace.Entity).SoundBank + owner:ConCommand("wire_soundemitter_sound " .. soundData.Sound) if soundData.Pitch then From 3a7338561ad76f6938b59fc1089132d61e1f7bf4 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Wed, 25 Feb 2026 20:45:08 -0300 Subject: [PATCH 37/59] Right clicking on an engine with a soundbank can now populate the sound menu, fix regression with engines with no soundbank --- lua/acf/menu/items_cl/sound_replacer.lua | 88 +++++++++++++++++++---- lua/entities/acf_engine/init.lua | 2 +- lua/weapons/gmod_tool/stools/acfsound.lua | 67 ++++++++++++++++- 3 files changed, 140 insertions(+), 17 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 31d6a1966..e5986c315 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -3,7 +3,7 @@ local Sounds = ACF.Utilities.Sounds local GetClientData, SetClientData = ACF.GetClientData, ACF.SetClientData local GetClientNumber, GetClientString = ACF.GetClientNumber, ACF.GetClientString -local AddValue +local AddValue, ListPanel, ValuesGroup, SoundGraph local Current = {Panels = {}, Count = 0, Graph = { @@ -35,7 +35,7 @@ local function UpdateGraph(Panel) end end -local function AddValuePanel(Menu, Graph) +local function AddValuePanel(Menu, Data) Current.Count = Current.Count + 1 local ID = math.max(Current.Count, #Current.Panels) local ButtonHeight = 20 @@ -63,6 +63,12 @@ local function AddValuePanel(Menu, Graph) local DefaultVolume = 1 local DefaultWidth = 0 + Data = istable(Data) and Data or { RPM = GetClientNumber("RPM " .. ID, DefaultRPM), + Path = GetClientString("Path " .. ID, DefaultPath), + Pitch = GetClientNumber("Pitch " .. ID, DefaultPitch), + Volume = GetClientNumber("Volume " .. ID, DefaultVolume), + Width = GetClientNumber("Width " .. ID, DefaultWidth)} + MPanel:DockMargin(0, 0, 0, 0) MPanel:SetLabel("Value " .. ID) @@ -85,7 +91,7 @@ local function AddValuePanel(Menu, Graph) RPMWang:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding RPMWang:DockMargin(-30, 0, 0, 0) RPMWang:Dock(LEFT) - RPMWang:SetValue(GetClientNumber("RPM " .. ID, DefaultRPM)) + RPMWang:SetValue(Data.RPM) RPMWang:SetClientData("RPM " .. ID, "OnValueChanged") RPMWang:DefineSetter(function(Panel, _, _, Value) -- TODO(TMF): The max value below is hardcoded, this should be a global! @@ -105,7 +111,7 @@ local function AddValuePanel(Menu, Graph) PathText:Dock(FILL) PathText:DockMargin(-25, 0, 0, 0) PathText:SetTall(ButtonHeight) - PathText:SetValue(GetClientString("Path " .. ID, DefaultPath)) + PathText:SetValue(Data.Path) PathText:SetClientData("Path " .. ID, "OnChange") -- Bitch this aint working! :sob: :sob: :sob: PathText:DefineSetter(function(Panel, _, _, Value) @@ -165,7 +171,7 @@ local function AddValuePanel(Menu, Graph) AddValue:SetEnabled(true) -- Re-enable our add button Current.Count = Current.Count - 1 - UpdateGraph(Graph) + UpdateGraph(SoundGraph) end SearchButton:SetParent(TopDiv) @@ -187,7 +193,7 @@ local function AddValuePanel(Menu, Graph) PitchWang:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding PitchWang:DockMargin(-30, 0, 4, 0) PitchWang:Dock(LEFT) - PitchWang:SetValue(GetClientNumber("Pitch " .. ID, DefaultPitch)) + PitchWang:SetValue(Data.Pitch) PitchWang:SetClientData("Pitch " .. ID, "OnValueChanged") PitchWang:DefineSetter(function(_, _, _, Value) SetClientData("Pitch " .. ID, Value) @@ -200,7 +206,7 @@ local function AddValuePanel(Menu, Graph) VolumeWang:SetWide(40) -- Equivalent to 0.00 + up/down buttons at font size = 16 + padding VolumeWang:DockMargin(-16, 0, 4, 0) VolumeWang:Dock(LEFT) - VolumeWang:SetValue(GetClientNumber("Volume " .. ID, DefaultVolume)) + VolumeWang:SetValue(Data.Volume) VolumeWang:SetClientData("Volume " .. ID, "OnValueChanged") VolumeWang:DefineSetter(function(_, _, _, Value) SetClientData("Volume " .. ID, Value) @@ -213,14 +219,14 @@ local function AddValuePanel(Menu, Graph) WidthWang:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding WidthWang:DockMargin(-24, 0, 4, 0) WidthWang:Dock(LEFT) - WidthWang:SetValue(GetClientNumber("Width " .. ID, DefaultWidth)) + WidthWang:SetValue(Data.Width) WidthWang:SetClientData("Width " .. ID, "OnValueChanged") WidthWang:DefineSetter(function(_, _, _, Value) SetClientData("Width " .. ID, Value) end) table.insert(Current.Panels, MPanel) - UpdateGraph(Graph) + UpdateGraph(SoundGraph) return MPanel end @@ -316,7 +322,7 @@ local function CreateSubMenu(Num, Menu) local GraphGroup = Menu:AddCollapsible("Graph", nil, "icon16/chart_curve_edit.png") local GraphPanel = Menu:AddPanel("DPanel") local LabelTop = Menu:AddLabel() - local SoundGraph = Menu:AddGraph() + SoundGraph = Menu:AddGraph() -- This one is glocal local PanelBottom = Menu:AddPanel("ACF_Panel") local IdleLabel = Menu:AddLabel("Idle:") local IdleWang = Menu:AddPanel("DNumberWang", 0, 2000) @@ -431,13 +437,13 @@ local function CreateSubMenu(Num, Menu) end -- The bottom group where the panels are added and removed dynamically - local ValuesGroup = Menu:AddCollapsible("Values", nil, "icon16/application_double.png") + ValuesGroup = Menu:AddCollapsible("Values", nil, "icon16/application_double.png") ValuesGroup:DockMargin(0, 4, 0, 4) -- I don't know if this makes sense, but somehow it gives me less trouble to later remove any arbitrary panels Menu:StartTemporal(ValuesGroup) Menu:ClearTemporal(ValuesGroup) - local ListPanel = Menu:AddPanel("DListLayout") + ListPanel = Menu:AddPanel("DListLayout") ListPanel:SetParent(ValuesGroup) ListPanel:Dock(TOP) @@ -451,15 +457,15 @@ local function CreateSubMenu(Num, Menu) AddValue:SetStretchToFit(false) AddValue:SetSize(16, 16) AddValue.DoClick = function() - ListPanel:Add(AddValuePanel(Menu, SoundGraph)) + ListPanel:Add(AddValuePanel(Menu, _)) -- Disable the button if enough panels exist already if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end end -- Add the first panel if none exists if #Current.Panels == 0 then - ListPanel:Add(AddValuePanel(Menu, SoundGraph)) - end + ListPanel:Add(AddValuePanel(Menu, _)) + end end } @@ -489,4 +495,56 @@ function ACF.CreateSoundMenu(Panel) CreateSubMenu(Index, Menu) Menu:EndTemporal(Panel) end + + do -- SoundBank entity data reception and menu population + local function PopulateMenu(Table, Count) + -- We set it to option 4 since that's where the values are located at + OptionSelectionBox:ChooseOption(OptionSelectionBox:GetOptionText(4), 4) + + -- Reset them panels + Current.Panels = nil + Current.Panels = {} + Current.Count = 0 + + -- Wipe the clients values list + Menu:ClearTemporal(ListPanel) + + for I = 1, Count do + local Data = Table[I] + + ListPanel:Add(AddValuePanel(Menu, Data)) + end + end + + net.Receive("ACF_SoundMenu_Get_Multi", function(len) + print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Get_Multi\"") + + local Origin = net.ReadEntity() + --local Bool = net.ReadBool() + if not Origin then return end + + local Count = net.ReadUInt(4) + local SoundTable = {} + + for _ = 1, Count do + local RPM = net.ReadUInt(14) + local StringPath = net.ReadString() + local Pitch = net.ReadUInt(8) + local Volume = net.ReadUInt(8) + local Width = net.ReadUInt(4) + + Volume = Volume * 0.01 -- Reduce the received value down to a float + + table.insert(SoundTable, { RPM = RPM, + Path = StringPath, + Pitch = Pitch or 100, + Volume = Volume or 1, + Width = Width or 0 }) + end + -- Sort the table before calling the function below + table.sort(SoundTable, function(a, b) return a.RPM < b.RPM end) + + PopulateMenu(SoundTable, Count) + end) + end end \ No newline at end of file diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index e7be29a49..80ad6fc92 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -320,7 +320,7 @@ do -- Spawn and Update functions Entity.ClassData = Class Entity.DefaultSound = Engine.Sound Entity.SoundBank = Engine.SoundBank - Entity.SoundCount = GetSoundCount(Engine) + Entity.SoundCount = GetSoundCount(Engine) or 1 Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 Entity.TorqueCurve = Engine.TorqueCurve diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index 1daf673a5..94c3e031a 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -12,8 +12,46 @@ TOOL.Information = { { name = "info" } } +-- NOTE: I would have used concommands just to set clients data, however i didn't feel like using them here since i don't know how to use them lol +-- So instead i went the dumb, hard and convoluted way and network the data needed back and forth +if SERVER then + util.AddNetworkString("ACF_SoundMenu_Get_Multi") -- Server to Client + util.AddNetworkString("ACF_SoundMenu_Set_Multi") -- Client to Server + util.AddNetworkString("ACF_SoundMenu_GetSoundBank") --???? +end + local Sounds = ACF.SoundToolSupport +local function GetSoundBankData(Player, Entity, Data, Loopback) + local soundTable = Data + local count = #soundTable + + net.Start("ACF_SoundMenu_Get_Multi") + net.WriteEntity(Entity) + if not Loopback then + net.WriteUInt(count, 4) + + for _, v in ipairs(soundTable) do + local rpm = v.RPM + local stringPath = v.Path + local pitch = v.Pitch + local volume = v.Volume + local width = v.Width + + net.WriteUInt(rpm, 14) + net.WriteString(stringPath) + net.WriteUInt(pitch, 8) + + volume = volume * 100 -- Sending the approximate volume as an int to reduce message size + net.WriteUInt(volume, 8) + net.WriteUInt(width, 4) + end + else + net.WriteBool(true) + end + net.Send(Player) +end + local function ReplaceSound(_, Entity, Data) if not IsValid(Entity) then return end @@ -52,6 +90,11 @@ local function IsReallyValid(trace, ply) return true end +local function ReplaceSounds(Player, Entity, Data) + ErrorNoHaltWithStack("A call to \"ReplaceSounds\" was made but no implementation was done!") + print("Received: Player: " .. Player .. ", Entity: " .. Entity .. ", Data: " .. Data) +end + function TOOL:LeftClick(trace) local owner = self:GetOwner() @@ -64,6 +107,20 @@ function TOOL:LeftClick(trace) ReplaceSound(owner, trace.Entity, { sound, pitch, volume }) + do + net.Receive("ACF_SoundMenu_Set_Multi", function (len, ply) + print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Set_Multi\" from player " .. ply) + + local Origin = net.ReadEntity() + local Table = net.ReadTable() + + if not Origin then return end + if not istable(Table) then return end + + ReplaceSounds(_, Entity, Table) + end) + end + return true end @@ -79,7 +136,6 @@ function TOOL:RightClick(trace) if not support then return false end local soundData = support.GetSound(trace.Entity) - local soundTable = support.GetSoundBank(trace.Entity).SoundBank owner:ConCommand("wire_soundemitter_sound " .. soundData.Sound) @@ -91,6 +147,15 @@ function TOOL:RightClick(trace) owner:ConCommand("acfsound_volume " .. soundData.Volume) end + -- Soundbank stuff, if it gets found, we switch to that instead + if not trace.Entity.SoundBank then return true end + local soundTable = support.GetSoundBank(trace.Entity).SoundBank + + -- Send the found soundbank table from the entity to the client for sound menu population + if soundTable then + GetSoundBankData(owner, trace.Entity, soundTable) + end + return true end From 2e535771973acd2d58e6c73a10769a137c7c124e Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Thu, 26 Feb 2026 20:02:35 -0300 Subject: [PATCH 38/59] More work towards setting a soundbank to any engine, the final function is not implemented yet --- lua/acf/menu/items_cl/sound_replacer.lua | 97 ++++++++++++++++------- lua/weapons/gmod_tool/stools/acfsound.lua | 20 +++-- 2 files changed, 79 insertions(+), 38 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index e5986c315..191255875 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -101,6 +101,8 @@ local function AddValuePanel(Menu, Data) Panel:SetMinMax(min, max) -- YEA, I MINMAX MY NUMBERS, SO What!? Panel:SetValue(Value) + Current.Panels[ID].RPM = Value + return Value, Panel end) @@ -112,21 +114,23 @@ local function AddValuePanel(Menu, Data) PathText:DockMargin(-25, 0, 0, 0) PathText:SetTall(ButtonHeight) PathText:SetValue(Data.Path) - PathText:SetClientData("Path " .. ID, "OnChange") - -- Bitch this aint working! :sob: :sob: :sob: + PathText:SetClientData("Path " .. ID, "OnValueChange") PathText:DefineSetter(function(Panel, _, _, Value) local isValid = Sounds.IsValidSound + print(Value) if isValid(Value) then ParseIcon:SetTooltip() ParseIcon:SetImage("icon16/accept.png") SetClientData("Path " .. ID, Value) + Current.Panels[ID].Path = Value else ParseIcon:SetTooltip("Invalid sound: File does not exist") ParseIcon:SetImage("icon16/cancel.png") - SetClientData("Path " .. ID, DefaultPath) + SetClientData("Path " .. ID, "") + Current.Panels[ID].Path = "" end return Value, Panel end) @@ -152,11 +156,11 @@ local function AddValuePanel(Menu, Data) end -- Reset our client data - SetClientData("RPM " .. ID, DefaultRPM, true) - SetClientData("Path " .. ID, DefaultPath, true) - SetClientData("Pitch " .. ID, DefaultPitch, true) - SetClientData("Volume " .. ID, DefaultVolume, true) - SetClientData("Width " .. ID, DefaultWidth, true) + SetClientData("RPM " .. ID, nil, true) + SetClientData("Path " .. ID, nil, true) + SetClientData("Pitch " .. ID, nil, true) + SetClientData("Volume " .. ID, nil, true) + SetClientData("Width " .. ID, nil, true) -- Remove the panel in question MPanel:Remove() @@ -197,6 +201,7 @@ local function AddValuePanel(Menu, Data) PitchWang:SetClientData("Pitch " .. ID, "OnValueChanged") PitchWang:DefineSetter(function(_, _, _, Value) SetClientData("Pitch " .. ID, Value) + Current.Panels[ID].Pitch = Value end) VolumeLabel:SetParent(BotDiv) @@ -210,6 +215,7 @@ local function AddValuePanel(Menu, Data) VolumeWang:SetClientData("Volume " .. ID, "OnValueChanged") VolumeWang:DefineSetter(function(_, _, _, Value) SetClientData("Volume " .. ID, Value) + Current.Panels[ID].Volume = Value end) WidthLabel:SetParent(BotDiv) @@ -223,6 +229,7 @@ local function AddValuePanel(Menu, Data) WidthWang:SetClientData("Width " .. ID, "OnValueChanged") WidthWang:DefineSetter(function(_, _, _, Value) SetClientData("Width " .. ID, Value) + Current.Panels[ID].Width = Value end) table.insert(Current.Panels, MPanel) @@ -480,6 +487,14 @@ function ACF.CreateSoundMenu(Panel) local ButtonHeight = 20 Menu:AddLabel("#tool.acfsound.help") + --[[for ID = 1, 16 do + SetClientData("RPM " .. ID, nil, true) + SetClientData("Path " .. ID, nil, true) + SetClientData("Pitch " .. ID, nil, true) + SetClientData("Volume " .. ID, nil, true) + SetClientData("Width " .. ID, nil, true) + end]]-- + local OptionSelectionBox = Menu:AddComboBox() OptionSelectionBox:SetText("Select an Option...") OptionSelectionBox:Dock(TOP) @@ -520,31 +535,53 @@ function ACF.CreateSoundMenu(Panel) print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Get_Multi\"") local Origin = net.ReadEntity() - --local Bool = net.ReadBool() if not Origin then return end - local Count = net.ReadUInt(4) - local SoundTable = {} - - for _ = 1, Count do - local RPM = net.ReadUInt(14) - local StringPath = net.ReadString() - local Pitch = net.ReadUInt(8) - local Volume = net.ReadUInt(8) - local Width = net.ReadUInt(4) - - Volume = Volume * 0.01 -- Reduce the received value down to a float - - table.insert(SoundTable, { RPM = RPM, - Path = StringPath, - Pitch = Pitch or 100, - Volume = Volume or 1, - Width = Width or 0 }) + local Feedback = net.ReadBool() + if not Feedback then + local Count = net.ReadUInt(4) + local SoundTable = {} + + for _ = 1, Count do + local RPM = net.ReadUInt(14) + local StringPath = net.ReadString() + local Pitch = net.ReadUInt(8) + local Volume = net.ReadUInt(8) + local Width = net.ReadUInt(4) + + Volume = Volume * 0.01 -- Reduce the received value down to a float + + table.insert(SoundTable, { RPM = RPM, + Path = StringPath, + Pitch = Pitch or 100, + Volume = Volume or 1, + Width = Width or 0 }) + end + -- Sort the table before calling the function below + table.sort(SoundTable, function(a, b) return a.RPM < b.RPM end) + + PopulateMenu(SoundTable, Count) + else + net.Start("ACF_SoundMenu_Set_Multi") + net.WriteEntity(Origin) + + local Table = {} + for I = 1, Current.Count do + local RPM = GetClientNumber("RPM " .. I) + local Path = GetClientString("Path " .. I) + local Pitch = GetClientNumber("Pitch " .. I) + local Volume = GetClientNumber("Volume " .. I) + local Width = GetClientNumber("Width " .. I) + + table.insert(Table, {RPM = RPM, + Path = Path, + Pitch = Pitch, + Volume = Volume, + Width = Width}) + end + net.WriteTable(Table) + net.SendToServer() end - -- Sort the table before calling the function below - table.sort(SoundTable, function(a, b) return a.RPM < b.RPM end) - - PopulateMenu(SoundTable, Count) end) end end \ No newline at end of file diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index 94c3e031a..8377ace79 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -23,12 +23,13 @@ end local Sounds = ACF.SoundToolSupport local function GetSoundBankData(Player, Entity, Data, Loopback) - local soundTable = Data - local count = #soundTable - net.Start("ACF_SoundMenu_Get_Multi") net.WriteEntity(Entity) if not Loopback then + local soundTable = Data + local count = #soundTable + + net.WriteBool(false) -- Just in case net.WriteUInt(count, 4) for _, v in ipairs(soundTable) do @@ -92,7 +93,8 @@ end local function ReplaceSounds(Player, Entity, Data) ErrorNoHaltWithStack("A call to \"ReplaceSounds\" was made but no implementation was done!") - print("Received: Player: " .. Player .. ", Entity: " .. Entity .. ", Data: " .. Data) + print("Received: Player: " .. Player:Nick() .. ", Entity: " .. tostring(Entity) .. ", Data: ") + PrintTable(Data) end function TOOL:LeftClick(trace) @@ -107,9 +109,11 @@ function TOOL:LeftClick(trace) ReplaceSound(owner, trace.Entity, { sound, pitch, volume }) - do + -- Simple call just to get the client's sound table menu data + GetSoundBankData(owner, trace.Entity, _, true) + do -- Sound Table from client reception, this is the same as the one displayed on the client's menu net.Receive("ACF_SoundMenu_Set_Multi", function (len, ply) - print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Set_Multi\" from player " .. ply) + print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Set_Multi\" from player " .. ply:Nick()) local Origin = net.ReadEntity() local Table = net.ReadTable() @@ -117,7 +121,7 @@ function TOOL:LeftClick(trace) if not Origin then return end if not istable(Table) then return end - ReplaceSounds(_, Entity, Table) + ReplaceSounds(ply, Entity, Table) end) end @@ -153,7 +157,7 @@ function TOOL:RightClick(trace) -- Send the found soundbank table from the entity to the client for sound menu population if soundTable then - GetSoundBankData(owner, trace.Entity, soundTable) + GetSoundBankData(owner, trace.Entity, soundTable, false) end return true From 9a25fdba8bebbf1c0c1795e98319b0a7669b19e0 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Thu, 26 Feb 2026 20:58:21 -0300 Subject: [PATCH 39/59] Add the missing function implementation, it worksgit add .! --- lua/acf/menu/items_cl/sound_replacer.lua | 8 -------- lua/weapons/gmod_tool/stools/acfsound.lua | 16 ++++++++++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 191255875..e71f56b40 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -487,14 +487,6 @@ function ACF.CreateSoundMenu(Panel) local ButtonHeight = 20 Menu:AddLabel("#tool.acfsound.help") - --[[for ID = 1, 16 do - SetClientData("RPM " .. ID, nil, true) - SetClientData("Path " .. ID, nil, true) - SetClientData("Pitch " .. ID, nil, true) - SetClientData("Volume " .. ID, nil, true) - SetClientData("Width " .. ID, nil, true) - end]]-- - local OptionSelectionBox = Menu:AddComboBox() OptionSelectionBox:SetText("Select an Option...") OptionSelectionBox:Dock(TOP) diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index 8377ace79..38c2215a7 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -91,10 +91,13 @@ local function IsReallyValid(trace, ply) return true end -local function ReplaceSounds(Player, Entity, Data) - ErrorNoHaltWithStack("A call to \"ReplaceSounds\" was made but no implementation was done!") - print("Received: Player: " .. Player:Nick() .. ", Entity: " .. tostring(Entity) .. ", Data: ") - PrintTable(Data) +local function ReplaceSounds(_, Entity, Data) + if not IsValid(Entity) then return end + + local Support = Sounds[Entity:GetClass()] + if not Support then return end + + Support.SetSoundBank(Entity, Data) end function TOOL:LeftClick(trace) @@ -109,7 +112,7 @@ function TOOL:LeftClick(trace) ReplaceSound(owner, trace.Entity, { sound, pitch, volume }) - -- Simple call just to get the client's sound table menu data + -- Simple call just to get the client's sound menu data GetSoundBankData(owner, trace.Entity, _, true) do -- Sound Table from client reception, this is the same as the one displayed on the client's menu net.Receive("ACF_SoundMenu_Set_Multi", function (len, ply) @@ -121,7 +124,8 @@ function TOOL:LeftClick(trace) if not Origin then return end if not istable(Table) then return end - ReplaceSounds(ply, Entity, Table) + PrintTable(Table) + ReplaceSounds(ply, Origin, Table) end) end From 1d3471e54e95f28b180ab9af78e756311a2c1f3b Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Thu, 26 Feb 2026 21:14:23 -0300 Subject: [PATCH 40/59] Remove goober --- lua/weapons/gmod_tool/stools/acfsound.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index 38c2215a7..0e8f5dac7 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -17,7 +17,6 @@ TOOL.Information = { if SERVER then util.AddNetworkString("ACF_SoundMenu_Get_Multi") -- Server to Client util.AddNetworkString("ACF_SoundMenu_Set_Multi") -- Client to Server - util.AddNetworkString("ACF_SoundMenu_GetSoundBank") --???? end local Sounds = ACF.SoundToolSupport From c443bf876ee6e76926872bfdfb87563737f9e5a1 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Thu, 26 Feb 2026 23:05:51 -0300 Subject: [PATCH 41/59] A round of documentation --- lua/acf/core/utilities/sounds/sounds_cl.lua | 8 ++--- lua/acf/menu/items_cl/sound_replacer.lua | 37 +++++++++++---------- lua/weapons/gmod_tool/stools/acfsound.lua | 22 +++++++++--- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index e041d00d1..4474bf969 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -274,12 +274,12 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) end net.Receive("ACF_Sounds_AdjustableCreate_Multi", function(len) - print("Received " .. len .. " bits from \"ACF_Sounds_AdjustableCreate_Multi\" for sound creation!") + print("Received " .. len .. " bits from \"ACF_Sounds_AdjustableCreate_Multi\" for sound creation!") -- Debug print local SoundTable = {} local Origin = net.ReadEntity() local Count = net.ReadUInt(4) - local CountTable = function (Table) + local CountTable = function (Table) -- This function might not be needed, its for debugging purposes if not istable(Table) then return end local Count = 0 @@ -310,12 +310,12 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) if CountTable(SoundTable) == Count then Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) else - print("Got " .. CountTable(SoundTable) .. " out of a total of " .. Count .. " sounds!") + print("Got " .. CountTable(SoundTable) .. " out of a total of " .. Count .. " sounds!") -- Debug print end end) net.Receive("ACF_Sounds_Adjustable_Multi", function(len) - print("Received " .. len .. " bits from \"ACF_Sounds_Adjustable_Multi\" for sound updates!") + print("Received " .. len .. " bits from \"ACF_Sounds_Adjustable_Multi\" for sound updates!") -- Debug print local Origin = net.ReadEntity() local ShouldStop = net.ReadBool() diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index e71f56b40..73841a595 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -524,12 +524,13 @@ function ACF.CreateSoundMenu(Panel) end net.Receive("ACF_SoundMenu_Get_Multi", function(len) - print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Get_Multi\"") + print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Get_Multi\"") -- Debug print local Origin = net.ReadEntity() if not Origin then return end local Feedback = net.ReadBool() + -- Get and populate menu if not Feedback then local Count = net.ReadUInt(4) local SoundTable = {} @@ -553,25 +554,25 @@ function ACF.CreateSoundMenu(Panel) table.sort(SoundTable, function(a, b) return a.RPM < b.RPM end) PopulateMenu(SoundTable, Count) - else + else -- Get and Set entities' soundbank net.Start("ACF_SoundMenu_Set_Multi") net.WriteEntity(Origin) - - local Table = {} - for I = 1, Current.Count do - local RPM = GetClientNumber("RPM " .. I) - local Path = GetClientString("Path " .. I) - local Pitch = GetClientNumber("Pitch " .. I) - local Volume = GetClientNumber("Volume " .. I) - local Width = GetClientNumber("Width " .. I) - - table.insert(Table, {RPM = RPM, - Path = Path, - Pitch = Pitch, - Volume = Volume, - Width = Width}) - end - net.WriteTable(Table) + net.WriteUInt(Current.Count, 4) + for I = 1, I <= Current.Count do + local RPM = GetClientNumber("RPM " .. I) + local Path = GetClientString("Path " .. I) + local Pitch = GetClientNumber("Pitch " .. I) + local Volume = GetClientNumber("Volume " .. I) + local Width = GetClientNumber("Width " .. I) + + Volume = Volume * 100 -- Increase the value up to an int + net.WriteUInt(RPM, 14) + net.WriteString(Path) + net.WriteUInt(Pitch, 8) + net.WriteUInt(Volume, 8) + net.WriteUInt(Width, 4) + end + -- We're taking the supposition here that the values being sent are already sorted net.SendToServer() end end) diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index 0e8f5dac7..b085d393b 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -115,15 +115,27 @@ function TOOL:LeftClick(trace) GetSoundBankData(owner, trace.Entity, _, true) do -- Sound Table from client reception, this is the same as the one displayed on the client's menu net.Receive("ACF_SoundMenu_Set_Multi", function (len, ply) - print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Set_Multi\" from player " .. ply:Nick()) + print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Set_Multi\" from player " .. ply:Nick()) -- Debug print + local SoundTable = {} local Origin = net.ReadEntity() - local Table = net.ReadTable() + local Count = net.ReadUInt(4) if not Origin then return end - if not istable(Table) then return end - - PrintTable(Table) + for _ = 1, Count do + local RPM = net.ReadUInt(14) + local StringPath = net.ReadString() + local Pitch = net.ReadUInt(8) + local Volume = net.ReadUInt(8) + local Width = net.ReadUInt(4) + + Volume = Volume * 0.01 -- Reduce the received value down to a float + table.insert(SoundTable, { RPM = RPM, + Path = StringPath, + Pitch = Pitch or 100, + Volume = Volume or 1, + Width = Width or 0}) + end ReplaceSounds(ply, Origin, Table) end) end From 676dee715154070c76c8edfbf532ba079d571f2c Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 27 Feb 2026 11:01:45 -0300 Subject: [PATCH 42/59] Shuffle things around --- lua/acf/menu/items_cl/sound_replacer.lua | 933 +++++++++++------------ 1 file changed, 466 insertions(+), 467 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 73841a595..96bc6923d 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -3,504 +3,503 @@ local Sounds = ACF.Utilities.Sounds local GetClientData, SetClientData = ACF.GetClientData, ACF.SetClientData local GetClientNumber, GetClientString = ACF.GetClientNumber, ACF.GetClientString -local AddValue, ListPanel, ValuesGroup, SoundGraph -local Current = {Panels = {}, - Count = 0, - Graph = { +local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! +local Current = {Panels = {}, -- Contains the panel objects + Count = 0, -- Keeps count of them + Graph = { -- This only relates to the graph Idle = 0, Redline = 1, RPMSlider = 2} } -local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! +--- Generates the menu used in the Sound Replacer tool. +--- @param Panel panel The base panel to build the menu off of. +function ACF.CreateSoundMenu(Panel) + local AddValue, ListPanel, SoundGraph -- Glocals + -- The graphing function, this is a mirror of the function found in sounds_cl.lua and is redundant + -- TODO(TMF): This should be a single function pulled from ACF.Sounds object + local function UpdateGraph(Panel) + local Count = #Current.Panels + if not Count then return end + + Panel:Clear() + + for I = 1, Count do + local addCurveWidth = GetClientNumber("Width " .. I, 0) -- Current.Panels[I].Width + local pitch = GetClientNumber("Pitch " .. I, 0) -- Current.Panels[I].Pitch + --local volume = Current.Panels[I].Volume * 100 -- Idk if we want to plot volume as a function + local min = I == 1 and 0 or GetClientNumber("RPM " .. math.Clamp(I - 1 - addCurveWidth, 0, 16383)) + local mid = GetClientNumber("RPM " .. I, 0) + -- TODO(TMF): The max value below is hardcoded, this should be a global! + local max = I == Count and 16383 or GetClientNumber("RPM " .. math.Clamp(I + 1 + addCurveWidth, 0, 16383)) + + Panel:PlotFunction("Sound " .. I, nil, function(X) + return (Sounds.Fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * pitch + end) + end + end + -- The function that adds the panels to the menu + local function AddValuePanel(Menu, Data) + Current.Count = Current.Count + 1 + local ID = math.max(Current.Count, #Current.Panels) + local ButtonHeight = 20 + + local _, MPanel = Menu:AddCollapsible() + local Base = Menu:AddPanel("DPanel") + _ = Base -- Override ACF's basic Base with this + local TopDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML's Div, just here to parent other children to. + local BotDiv = Menu:AddPanel("ACF_Panel") -- Same as above. + -- TODO(TMF): The max value below is hardcoded, this should be a global! + local RPMWang, RPMLabel = Menu:AddNumberWang("RPM:", 0, 16383, 0) + local _, PathLabel, PathText = Menu:AddTextEntry("Path:") + local ParseIcon = Menu:AddPanel("DImage") + local RemoveButton = Menu:AddPanel("DImageButton") + local SearchButton = Menu:AddPanel("DImageButton") + + local PitchWang, PitchLabel = Menu:AddNumberWang("Pitch:", 0, 255, 0) + local VolumeWang, VolumeLabel = Menu:AddNumberWang("Volume:", 0, 1, 2) + local WidthWang, WidthLabel = Menu:AddNumberWang("Width:", 0, 15, 0) + + -- Defaults + local DefaultPath = "" + local DefaultRPM = 1000 * ID + local DefaultPitch = 100 + local DefaultVolume = 1 + local DefaultWidth = 0 + + Data = istable(Data) and Data or { RPM = GetClientNumber("RPM " .. ID, DefaultRPM), + Path = GetClientString("Path " .. ID, DefaultPath), + Pitch = GetClientNumber("Pitch " .. ID, DefaultPitch), + Volume = GetClientNumber("Volume " .. ID, DefaultVolume), + Width = GetClientNumber("Width " .. ID, DefaultWidth)} + + MPanel:DockMargin(0, 0, 0, 0) + MPanel:SetLabel("Value " .. ID) + + Base:SetParent(MPanel) + Base:SetTall(72) + Base:DockPadding(4, 6, 4, 0) + Base:DockMargin(0, 0, 0, 0) + + TopDiv:SetParent(Base) + TopDiv:Dock(TOP) + + BotDiv:SetParent(Base) + BotDiv:Dock(BOTTOM) + + RPMLabel:SetParent(TopDiv) + RPMLabel:DockMargin(0, 0, 0, 0) + RPMLabel:Dock(LEFT) + + RPMWang:SetParent(TopDiv) + RPMWang:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding + RPMWang:DockMargin(-30, 0, 0, 0) + RPMWang:Dock(LEFT) + RPMWang:SetValue(Data.RPM) + RPMWang:SetClientData("RPM " .. ID, "OnValueChanged") + RPMWang:DefineSetter(function(Panel, _, _, Value) + -- TODO(TMF): The max value below is hardcoded, this should be a global! + local min = ID == 1 and 0 or GetClientNumber("RPM " .. ID - 1) -- Current.Panels[ID - 1].RPM + local max = ID == #Current.Panels and 16383 or GetClientNumber("RPM " .. ID + 1) -- Current.Panels[ID + 1].RPM + + Panel:SetMinMax(min, max) -- YEA, I MINMAX MY NUMBERS, SO What!? + Panel:SetValue(Value) + + Current.Panels[ID].RPM = Value + + return Value, Panel + end) --- The graphing function, this is a mirror of the function found in sounds_cl.lua -local function UpdateGraph(Panel) - local Count = #Current.Panels - if not Count then return end + PathLabel:SetParent(TopDiv) + PathLabel:Dock(LEFT) + + PathText:SetParent(TopDiv) + PathText:Dock(FILL) + PathText:DockMargin(-25, 0, 0, 0) + PathText:SetTall(ButtonHeight) + PathText:SetValue(Data.Path) + PathText:SetClientData("Path " .. ID, "OnValueChange") + PathText:DefineSetter(function(Panel, _, _, Value) + local isValid = Sounds.IsValidSound + + print(Value) + if isValid(Value) then + ParseIcon:SetTooltip() + ParseIcon:SetImage("icon16/accept.png") + + SetClientData("Path " .. ID, Value) + Current.Panels[ID].Path = Value + else + ParseIcon:SetTooltip("Invalid sound: File does not exist") + ParseIcon:SetImage("icon16/cancel.png") + + SetClientData("Path " .. ID, "") + Current.Panels[ID].Path = "" + end + return Value, Panel + end) - Panel:Clear() + ParseIcon:SetParent(PathText) + ParseIcon:Dock(RIGHT) + ParseIcon:DockMargin(3, 3, 3, 3) + ParseIcon:SetImage("icon16/accept.png") + ParseIcon:SetSize(16, 16) + + RemoveButton:SetParent(TopDiv) + RemoveButton:Dock(RIGHT) + RemoveButton:DockMargin(3, 3, 3, 3) + RemoveButton:SetImage("icon16/delete.png") + RemoveButton:SetTooltip("Remove this sound.") + RemoveButton:SetStretchToFit(false) + RemoveButton:SetSize(16, 16) + RemoveButton.DoClick = function() + -- Don't remove the last panel + if Current.Count == 1 then + RemoveButton.DoClick = function() end + return + end - for I = 1, Count do - local addCurveWidth = GetClientNumber("Width " .. I, 0) -- Current.Panels[I].Width - local pitch = GetClientNumber("Pitch " .. I, 0) -- Current.Panels[I].Pitch - --local volume = Current.Panels[I].Volume * 100 -- Idk if we want to plot volume as a function - local min = I == 1 and 0 or GetClientNumber("RPM " .. math.Clamp(I - 1 - addCurveWidth, 0, 16383)) - local mid = GetClientNumber("RPM " .. I, 0) - -- TODO(TMF): The max value below is hardcoded, this should be a global! - local max = I == Count and 16383 or GetClientNumber("RPM " .. math.Clamp(I + 1 + addCurveWidth, 0, 16383)) + -- Reset our client data + SetClientData("RPM " .. ID, nil, true) + SetClientData("Path " .. ID, nil, true) + SetClientData("Pitch " .. ID, nil, true) + SetClientData("Volume " .. ID, nil, true) + SetClientData("Width " .. ID, nil, true) + + -- Remove the panel in question + MPanel:Remove() + table.remove(Current.Panels, ID) + + -- Set the label of the remaining panels up + for k, v in pairs(Current.Panels) do + if not IsValid(v) then continue end + v:SetLabel("Value " .. k) + end - Panel:PlotFunction("Sound " .. I, nil, function(X) - return (Sounds.Fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * pitch - end) - end -end - -local function AddValuePanel(Menu, Data) - Current.Count = Current.Count + 1 - local ID = math.max(Current.Count, #Current.Panels) - local ButtonHeight = 20 - - local _, MPanel = Menu:AddCollapsible() - local Base = Menu:AddPanel("DPanel") - _ = Base -- Override ACF's basic Base with this - local TopDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML's Div, just here to parent other children to. - local BotDiv = Menu:AddPanel("ACF_Panel") -- Same as above. - -- TODO(TMF): The max value below is hardcoded, this should be a global! - local RPMWang, RPMLabel = Menu:AddNumberWang("RPM:", 0, 16383, 0) - local _, PathLabel, PathText = Menu:AddTextEntry("Path:") - local ParseIcon = Menu:AddPanel("DImage") - local RemoveButton = Menu:AddPanel("DImageButton") - local SearchButton = Menu:AddPanel("DImageButton") - - local PitchWang, PitchLabel = Menu:AddNumberWang("Pitch:", 0, 255, 0) - local VolumeWang, VolumeLabel = Menu:AddNumberWang("Volume:", 0, 1, 2) - local WidthWang, WidthLabel = Menu:AddNumberWang("Width:", 0, 15, 0) - - -- Defaults - local DefaultPath = "" - local DefaultRPM = 1000 * ID - local DefaultPitch = 100 - local DefaultVolume = 1 - local DefaultWidth = 0 - - Data = istable(Data) and Data or { RPM = GetClientNumber("RPM " .. ID, DefaultRPM), - Path = GetClientString("Path " .. ID, DefaultPath), - Pitch = GetClientNumber("Pitch " .. ID, DefaultPitch), - Volume = GetClientNumber("Volume " .. ID, DefaultVolume), - Width = GetClientNumber("Width " .. ID, DefaultWidth)} - - MPanel:DockMargin(0, 0, 0, 0) - MPanel:SetLabel("Value " .. ID) - - Base:SetParent(MPanel) - Base:SetTall(72) - Base:DockPadding(4, 6, 4, 0) - Base:DockMargin(0, 0, 0, 0) - - TopDiv:SetParent(Base) - TopDiv:Dock(TOP) - - BotDiv:SetParent(Base) - BotDiv:Dock(BOTTOM) - - RPMLabel:SetParent(TopDiv) - RPMLabel:DockMargin(0, 0, 0, 0) - RPMLabel:Dock(LEFT) - - RPMWang:SetParent(TopDiv) - RPMWang:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding - RPMWang:DockMargin(-30, 0, 0, 0) - RPMWang:Dock(LEFT) - RPMWang:SetValue(Data.RPM) - RPMWang:SetClientData("RPM " .. ID, "OnValueChanged") - RPMWang:DefineSetter(function(Panel, _, _, Value) - -- TODO(TMF): The max value below is hardcoded, this should be a global! - local min = ID == 1 and 0 or GetClientNumber("RPM " .. ID - 1) -- Current.Panels[ID - 1].RPM - local max = ID == #Current.Panels and 16383 or GetClientNumber("RPM " .. ID + 1) -- Current.Panels[ID + 1].RPM - - Panel:SetMinMax(min, max) -- YEA, I MINMAX MY NUMBERS, SO What!? - Panel:SetValue(Value) - - Current.Panels[ID].RPM = Value - - return Value, Panel - end) - - PathLabel:SetParent(TopDiv) - PathLabel:Dock(LEFT) - - PathText:SetParent(TopDiv) - PathText:Dock(FILL) - PathText:DockMargin(-25, 0, 0, 0) - PathText:SetTall(ButtonHeight) - PathText:SetValue(Data.Path) - PathText:SetClientData("Path " .. ID, "OnValueChange") - PathText:DefineSetter(function(Panel, _, _, Value) - local isValid = Sounds.IsValidSound - - print(Value) - if isValid(Value) then - ParseIcon:SetTooltip() - ParseIcon:SetImage("icon16/accept.png") - - SetClientData("Path " .. ID, Value) - Current.Panels[ID].Path = Value - else - ParseIcon:SetTooltip("Invalid sound: File does not exist") - ParseIcon:SetImage("icon16/cancel.png") - - SetClientData("Path " .. ID, "") - Current.Panels[ID].Path = "" - end - return Value, Panel - end) - - ParseIcon:SetParent(PathText) - ParseIcon:Dock(RIGHT) - ParseIcon:DockMargin(3, 3, 3, 3) - ParseIcon:SetImage("icon16/accept.png") - ParseIcon:SetSize(16, 16) - - RemoveButton:SetParent(TopDiv) - RemoveButton:Dock(RIGHT) - RemoveButton:DockMargin(3, 3, 3, 3) - RemoveButton:SetImage("icon16/delete.png") - RemoveButton:SetTooltip("Remove this sound.") - RemoveButton:SetStretchToFit(false) - RemoveButton:SetSize(16, 16) - RemoveButton.DoClick = function() - -- Don't remove the last panel - if Current.Count == 1 then - RemoveButton.DoClick = function() end - return + AddValue:SetEnabled(true) -- Re-enable our add button + Current.Count = Current.Count - 1 + + UpdateGraph(SoundGraph) end - -- Reset our client data - SetClientData("RPM " .. ID, nil, true) - SetClientData("Path " .. ID, nil, true) - SetClientData("Pitch " .. ID, nil, true) - SetClientData("Volume " .. ID, nil, true) - SetClientData("Width " .. ID, nil, true) - - -- Remove the panel in question - MPanel:Remove() - table.remove(Current.Panels, ID) - - -- Set the label of the remaining panels up - for k, v in pairs(Current.Panels) do - if not IsValid(v) then continue end - v:SetLabel("Value " .. k) + SearchButton:SetParent(TopDiv) + SearchButton:Center() + SearchButton:Dock(RIGHT) + SearchButton:DockMargin(3, 3, 3, 3) + SearchButton:SetImage("icon16/application_view_list.png") + SearchButton:SetTooltip("Open sound browser.") + SearchButton:SetStretchToFit(false) + SearchButton:SetSize(16, 16) + SearchButton.DoClick = function() + RunConsoleCommand("wire_sound_browser_open") end - AddValue:SetEnabled(true) -- Re-enable our add button - Current.Count = Current.Count - 1 + PitchLabel:SetParent(BotDiv) + PitchLabel:Dock(LEFT) + + PitchWang:SetParent(BotDiv) + PitchWang:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding + PitchWang:DockMargin(-30, 0, 4, 0) + PitchWang:Dock(LEFT) + PitchWang:SetValue(Data.Pitch) + PitchWang:SetClientData("Pitch " .. ID, "OnValueChanged") + PitchWang:DefineSetter(function(_, _, _, Value) + SetClientData("Pitch " .. ID, Value) + Current.Panels[ID].Pitch = Value + end) + VolumeLabel:SetParent(BotDiv) + VolumeLabel:Dock(LEFT) + + VolumeWang:SetParent(BotDiv) + VolumeWang:SetWide(40) -- Equivalent to 0.00 + up/down buttons at font size = 16 + padding + VolumeWang:DockMargin(-16, 0, 4, 0) + VolumeWang:Dock(LEFT) + VolumeWang:SetValue(Data.Volume) + VolumeWang:SetClientData("Volume " .. ID, "OnValueChanged") + VolumeWang:DefineSetter(function(_, _, _, Value) + SetClientData("Volume " .. ID, Value) + Current.Panels[ID].Volume = Value + end) + + WidthLabel:SetParent(BotDiv) + WidthLabel:Dock(LEFT) + + WidthWang:SetParent(BotDiv) + WidthWang:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding + WidthWang:DockMargin(-24, 0, 4, 0) + WidthWang:Dock(LEFT) + WidthWang:SetValue(Data.Width) + WidthWang:SetClientData("Width " .. ID, "OnValueChanged") + WidthWang:DefineSetter(function(_, _, _, Value) + SetClientData("Width " .. ID, Value) + Current.Panels[ID].Width = Value + end) + + table.insert(Current.Panels, MPanel) UpdateGraph(SoundGraph) + return MPanel end - SearchButton:SetParent(TopDiv) - SearchButton:Center() - SearchButton:Dock(RIGHT) - SearchButton:DockMargin(3, 3, 3, 3) - SearchButton:SetImage("icon16/application_view_list.png") - SearchButton:SetTooltip("Open sound browser.") - SearchButton:SetStretchToFit(false) - SearchButton:SetSize(16, 16) - SearchButton.DoClick = function() - RunConsoleCommand("wire_sound_browser_open") + -- Actual menu stuff + local Menu = ACF.InitMenuBase(Panel, "SoundMenu", "acf_reload_sound_menu") + Menu.ButtonHeight = 20 + Menu.Wide = Menu:GetWide() + Menu:AddLabel("#tool.acfsound.help") + + local OptionSelectionBox = Menu:AddComboBox() + OptionSelectionBox:SetText("Select an Option...") + OptionSelectionBox:Dock(TOP) + OptionSelectionBox:SetTall(Menu.ButtonHeight) + OptionSelectionBox:AddChoice("Generic - One sound. ", 1) + OptionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ", 2) + OptionSelectionBox:AddChoice("Engines - Simple interpolated. ", 3) + OptionSelectionBox:AddChoice("Engines - Custom interpolated. ", 4) + OptionSelectionBox.OnSelect = function(_, Index, _) + Menu:StartTemporal(Panel) + Menu:ClearTemporal(Panel) + Menu:CreateSubMenu(Index) -- Build the sub menu + Menu:EndTemporal(Panel) end - PitchLabel:SetParent(BotDiv) - PitchLabel:Dock(LEFT) - - PitchWang:SetParent(BotDiv) - PitchWang:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding - PitchWang:DockMargin(-30, 0, 4, 0) - PitchWang:Dock(LEFT) - PitchWang:SetValue(Data.Pitch) - PitchWang:SetClientData("Pitch " .. ID, "OnValueChanged") - PitchWang:DefineSetter(function(_, _, _, Value) - SetClientData("Pitch " .. ID, Value) - Current.Panels[ID].Pitch = Value - end) - - VolumeLabel:SetParent(BotDiv) - VolumeLabel:Dock(LEFT) - - VolumeWang:SetParent(BotDiv) - VolumeWang:SetWide(40) -- Equivalent to 0.00 + up/down buttons at font size = 16 + padding - VolumeWang:DockMargin(-16, 0, 4, 0) - VolumeWang:Dock(LEFT) - VolumeWang:SetValue(Data.Volume) - VolumeWang:SetClientData("Volume " .. ID, "OnValueChanged") - VolumeWang:DefineSetter(function(_, _, _, Value) - SetClientData("Volume " .. ID, Value) - Current.Panels[ID].Volume = Value - end) - - WidthLabel:SetParent(BotDiv) - WidthLabel:Dock(LEFT) - - WidthWang:SetParent(BotDiv) - WidthWang:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding - WidthWang:DockMargin(-24, 0, 4, 0) - WidthWang:Dock(LEFT) - WidthWang:SetValue(Data.Width) - WidthWang:SetClientData("Width " .. ID, "OnValueChanged") - WidthWang:DefineSetter(function(_, _, _, Value) - SetClientData("Width " .. ID, Value) - Current.Panels[ID].Width = Value - end) - - table.insert(Current.Panels, MPanel) - UpdateGraph(SoundGraph) - return MPanel -end - --- Build the panels according to our selection -local function CreateSubMenu(Num, Menu) - local Wide = Menu:GetWide() - local ButtonHeight = 20 - local Case = { - -- I explictly gave these their numeric keys so its easier to infer which panel we're working with - -- First panel, Generic - One sound. Old menu with text entry for a single sound - [1] = function () - Menu:AddLabel("This is the first panel, I don't know what to add here yet but you can imagine its gonna be something good, so stay tuned!") - - local SoundNameText = Menu:AddPanel("DTextEntry") - SoundNameText:SetText("") - SoundNameText:SetWide(Wide - 20) - SoundNameText:SetTall(ButtonHeight) - SoundNameText:SetMultiline(false) - SoundNameText:SetConVar("wire_soundemitter_sound") - - local SoundBrowserButton = Menu:AddButton("#tool.acfsound.open_browser", "wire_sound_browser_open", SoundNameText:GetValue(), "1") - SoundBrowserButton:SetWide(Wide) - SoundBrowserButton:SetTall(ButtonHeight) - SoundBrowserButton:SetIcon("icon16/application_view_list.png") - - local SoundPre = Menu:AddPanel("ACF_Panel") - SoundPre:SetWide(Wide) - SoundPre:SetTall(ButtonHeight) - - local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") + --- Build the rest of the menu according to our selection + --- @param Num int The sub menu selected at the index + function Menu:CreateSubMenu(Num) + local Case = { + -- I explictly gave these their numeric keys so its easier to infer which panel we're working with + -- First panel, Generic - One sound. Old menu with text entry for a single sound + [1] = function () + self:AddLabel("This is the first panel, I don't know what to add here yet but you can imagine its gonna be something good, so stay tuned!") + + local SoundNameText = self:AddPanel("DTextEntry") + SoundNameText:SetText("") + SoundNameText:SetWide(Menu.Wide - 20) + SoundNameText:SetTall(Menu.ButtonHeight) + SoundNameText:SetMultiline(false) + SoundNameText:SetConVar("wire_soundemitter_sound") + + local SoundBrowserButton = self:AddButton("#tool.acfsound.open_browser", "wire_sound_browser_open", SoundNameText:GetValue(), "1") + SoundBrowserButton:SetWide(Menu.Wide) + SoundBrowserButton:SetTall(Menu.ButtonHeight) + SoundBrowserButton:SetIcon("icon16/application_view_list.png") + + local SoundPre = self:AddPanel("ACF_Panel") + SoundPre:SetWide(Menu.Wide) + SoundPre:SetTall(Menu.ButtonHeight) + + local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") + SoundPrePlay:SetIcon("icon16/sound.png") + SoundPrePlay.DoClick = function() + RunConsoleCommand("play", SoundNameText:GetValue()) + end + + -- Playing a silent sound will mute the preview but not the sound emitters. + local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") + SoundPreStop:SetIcon("icon16/sound_mute.png") + + -- Set the Play/Stop button positions here + SoundPre:InvalidateLayout(true) + SoundPre.PerformLayout = function() + local HWide = SoundPre:GetWide() / 2 + SoundPrePlay:SetSize(HWide, Menu.ButtonHeight) + SoundPrePlay:Dock(LEFT) + SoundPreStop:Dock(FILL) -- FILL will cover the remaining space which the previous button didn't + end + + local CopyButton = self:AddButton("#tool.acfsound.copy") + CopyButton:SetWide(Menu.Wide) + CopyButton:SetTall(Menu.ButtonHeight) + CopyButton:SetIcon("icon16/page_copy.png") + CopyButton.DoClick = function() + SetClipboardText(SoundNameText:GetValue()) + end + + local ClearButton = self:AddButton("#tool.acfsound.clear") + ClearButton:SetWide(Menu.Wide) + ClearButton:SetTall(Menu.ButtonHeight) + ClearButton:SetIcon("icon16/cancel.png") + ClearButton.DoClick = function() + SoundNameText:SetValue("") + RunConsoleCommand("wire_soundemitter_sound", "") + end + + local VolumeSlider = self:AddSlider("#tool.acfsound.volume", 0.1, 1, 2) + VolumeSlider:SetConVar("acfsound_volume") + local PitchSlider = self:AddSlider("#tool.acfsound.pitch", 0.1, 2, 2) + PitchSlider:SetConVar("acfsound_pitch") + end, + -- Second panel, Weapons - Start/Loop/Stop. New menu with three text entries labeled as "Start", "Loop", "End" respectively, to put the sound paths + -- Layout is similar to the first option + [2] = function() + self:AddLabel("This is the second panel, I don't know what to add here yet but you can imagine its gonna be something nice, so stay tuned!") + + end, + -- Third panel, Engines - Simple interpolated. New menu with a Slider that creates N amount of text entries to put the sound paths + -- Layout is similar to the first option + [3] = function() + self:AddLabel("This is the third panel, I don't know what to add here yet but you can imagine its gonna be something fantastic, so stay tuned!") + + end, + -- Fourth panel, Engines - Custom interpolated. New menu with a button to add up to 16 sound paths, with configurable pitch, volume and width for each sound + -- Has a graph at the top of the list to better visualise how they play at a determined engine RPM + [4] = function() + self:AddLabel("This is the fourth panel, I don't know what to add here yet but you can imagine its gonna be something mindblowing, so stay tuned!") + -- Reset them panels + Current.Panels = nil + Current.Panels = {} + Current.Count = 0 + -- The menu is divided in two groups + -- The top group where the graph lies + local GraphGroup = self:AddCollapsible("Graph", nil, "icon16/chart_curve_edit.png") + local GraphPanel = self:AddPanel("DPanel") + local LabelTop = self:AddLabel() + SoundGraph = self:AddGraph() -- A Glocal so we can feed the other functions + local PanelBottom = self:AddPanel("ACF_Panel") + local IdleLabel = self:AddLabel("Idle:") + local IdleWang = self:AddPanel("DNumberWang", 0, 2000) + local RedlineLabel = self:AddLabel("Redline:") + -- TODO(TMF): The max values below are hardcoded, this should be a global! + local RedlineWang = self:AddPanel("DNumberWang", 0, 16383) + local RPMSlider = self:AddSlider("RPM", 0, 16383) + local SoundPre = self:AddPanel("ACF_Panel") + local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") + local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") -- Playing a silent sound will mute the preview but not the sound emitters + + -- Set defaults + local DefaultIdle = GetClientData("Idle", 800) + local DefaultRedline = GetClientData("Redline", 8000) + SetClientData("Idle", DefaultIdle, true) + SetClientData("Redline", DefaultRedline, true) + SetClientData("RPMSlider", (DefaultIdle + DefaultRedline) / 2, true) + + -- The properties + GraphGroup:DockMargin(0, 0, 0, 0) + + GraphPanel:SetParent(GraphGroup) + GraphPanel:DockPadding(4, 4, 4, 8) + GraphPanel:Dock(TOP) + GraphPanel:SetTall(368) -- Why can't this grow dynamically + + LabelTop:SetParent(GraphPanel) + LabelTop:Dock(TOP) + + SoundGraph:SetParent(GraphPanel) + SoundGraph:Dock(TOP) + SoundGraph:SetTall(192) + SoundGraph:SetYRange(0, 255) + SoundGraph:SetFidelity(10) + SoundGraph:SetXSpacing(1000) + SoundGraph:SetYSpacing(100) + + PanelBottom:SetParent(GraphPanel) + PanelBottom:Dock(TOP) + PanelBottom:DockPadding(0, 4, 4, -4) + PanelBottom:SetTall(34) + + IdleLabel:SetParent(PanelBottom) + IdleLabel:Dock(LEFT) + + IdleWang:SetParent(PanelBottom) + IdleWang:Dock(LEFT) + IdleWang:SetValue(DefaultIdle) -- I shouldn't need to do this but oh well, here we go... + IdleWang:SetClientData("Idle", "OnValueChanged") + IdleWang:DefineSetter(function(Panel, _, _, Value) + Panel:SetMinMax(0, 2000) -- I shouldn't even need to do this! + Panel:SetValue(Value) + Current.Graph["Idle"] = Value + + return Value + end) + + RedlineLabel:SetParent(PanelBottom) + RedlineLabel:Dock(LEFT) + RedlineLabel:DockMargin(8, 4, 0, 0) -- Fucking retarded + + RedlineWang:SetParent(PanelBottom) + RedlineWang:Dock(LEFT) + RedlineWang:SetValue(DefaultRedline) + RedlineWang:SetClientData("Redline", "OnValueChanged") + RedlineWang:DefineSetter(function(Panel, _, _, Value) + -- TODO(TMF): The max value below is hardcoded, this should be a global! + Panel:SetMin(Current.Graph["Idle"] or 1) + Panel:SetMax(16383) + Panel:SetValue(Value) + Current.Graph["Redline"] = Value + + SoundGraph:SetXRange(0, Value + Current.Graph["Idle"]) + SoundGraph:SetXSpacing(Value < 1000 and 100 or 1000) + return Value + end) + + RPMSlider:SetParent(GraphPanel) + RPMSlider:Dock(TOP) + RPMSlider:SetWide(Menu.Wide) + RPMSlider:SetValue(GetClientNumber("RPMSlider", 4400)) + RPMSlider:SetClientData("RPMSlider", "OnValueChanged") + RPMSlider:DefineSetter(function(Panel, _, _, Value) + -- TODO(TMF): The max value below is hardcoded, this should be a global! + local Min = Current.Graph["Idle"] or 0 + local Max = Current.Graph["Redline"] or 16383 + + Panel:SetMinMax(Min, Max) + Panel:SetValue(Value) + Current.Graph["RPM"] = Value + + SoundGraph:PlotLimitLine("RPM", false, Value, color_black) + return Value + end) + + SoundPre:SetParent(GraphPanel) + SoundPre:SetWide(Menu.Wide) + SoundPre:SetTall(Menu.ButtonHeight) + SoundPrePlay:SetIcon("icon16/sound.png") SoundPrePlay.DoClick = function() - RunConsoleCommand("play", SoundNameText:GetValue()) + -- Do something here to play them sounds! end - - -- Playing a silent sound will mute the preview but not the sound emitters. - local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") SoundPreStop:SetIcon("icon16/sound_mute.png") - -- Set the Play/Stop button positions here SoundPre:InvalidateLayout(true) SoundPre.PerformLayout = function() local HWide = SoundPre:GetWide() / 2 - SoundPrePlay:SetSize(HWide, ButtonHeight) + SoundPrePlay:SetSize(HWide, Menu.ButtonHeight) SoundPrePlay:Dock(LEFT) SoundPreStop:Dock(FILL) -- FILL will cover the remaining space which the previous button didn't end - local CopyButton = Menu:AddButton("#tool.acfsound.copy") - CopyButton:SetWide(Wide) - CopyButton:SetTall(ButtonHeight) - CopyButton:SetIcon("icon16/page_copy.png") - CopyButton.DoClick = function() - SetClipboardText(SoundNameText:GetValue()) - end + -- The bottom group where the panels are added and removed dynamically + local ValuesGroup = self:AddCollapsible("Values", nil, "icon16/application_double.png") + ValuesGroup:DockMargin(0, 4, 0, 4) + -- I don't know if this makes sense, but somehow it gives me less trouble to later remove any arbitrary panels + self:StartTemporal(ValuesGroup) + self:ClearTemporal(ValuesGroup) + + ListPanel = self:AddPanel("DListLayout") + ListPanel:SetParent(ValuesGroup) + ListPanel:Dock(TOP) + + self:EndTemporal(ValuesGroup) + + AddValue = self:AddPanel("DImageButton") + AddValue:SetParent(ValuesGroup) + AddValue:Dock(TOP) + AddValue:SetImage("icon16/add.png") + AddValue:SetTooltip("Add a new sound.") + AddValue:SetStretchToFit(false) + AddValue:SetSize(16, 16) + AddValue.DoClick = function() + ListPanel:Add(AddValuePanel(self, _)) + -- Disable the button if enough panels exist already + if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end - local ClearButton = Menu:AddButton("#tool.acfsound.clear") - ClearButton:SetWide(Wide) - ClearButton:SetTall(ButtonHeight) - ClearButton:SetIcon("icon16/cancel.png") - ClearButton.DoClick = function() - SoundNameText:SetValue("") - RunConsoleCommand("wire_soundemitter_sound", "") end - - local VolumeSlider = Menu:AddSlider("#tool.acfsound.volume", 0.1, 1, 2) - VolumeSlider:SetConVar("acfsound_volume") - local PitchSlider = Menu:AddSlider("#tool.acfsound.pitch", 0.1, 2, 2) - PitchSlider:SetConVar("acfsound_pitch") - end, - -- Second panel, Weapons - Start/Loop/Stop. New menu with three text entries labeled as "Start", "Loop", "End" respectively, to put the sound paths - -- Layout is similar to the first option - [2] = function() - Menu:AddLabel("This is the second panel, I don't know what to add here yet but you can imagine its gonna be something nice, so stay tuned!") - - end, - -- Third panel, Engines - Simple interpolated. New menu with a Slider that creates N amount of text entries to put the sound paths - -- Layout is similar to the first option - [3] = function() - Menu:AddLabel("This is the third panel, I don't know what to add here yet but you can imagine its gonna be something fantastic, so stay tuned!") - - end, - -- Fourth panel, Engines - Custom interpolated. New menu with a button to add up to 16 sound paths, with configurable pitch, volume and width for each sound - -- Has a graph at the top of the list to better visualise how they play at a determined engine RPM - [4] = function() - Menu:AddLabel("This is the fourth panel, I don't know what to add here yet but you can imagine its gonna be something mindblowing, so stay tuned!") - -- Reset them panels - Current.Panels = nil - Current.Panels = {} - Current.Count = 0 - -- The menu is divided in two groups - -- The top group where the graph lies - local GraphGroup = Menu:AddCollapsible("Graph", nil, "icon16/chart_curve_edit.png") - local GraphPanel = Menu:AddPanel("DPanel") - local LabelTop = Menu:AddLabel() - SoundGraph = Menu:AddGraph() -- This one is glocal - local PanelBottom = Menu:AddPanel("ACF_Panel") - local IdleLabel = Menu:AddLabel("Idle:") - local IdleWang = Menu:AddPanel("DNumberWang", 0, 2000) - local RedlineLabel = Menu:AddLabel("Redline:") - -- TODO(TMF): The max values below are hardcoded, this should be a global! - local RedlineWang = Menu:AddPanel("DNumberWang", 0, 16383) - local RPMSlider = Menu:AddSlider("RPM", 0, 16383) - local SoundPre = Menu:AddPanel("ACF_Panel") - local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") - local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") -- Playing a silent sound will mute the preview but not the sound emitters - - -- Set defaults - local DefaultIdle = GetClientData("Idle", 800) - local DefaultRedline = GetClientData("Redline", 8000) - SetClientData("Idle", DefaultIdle, true) - SetClientData("Redline", DefaultRedline, true) - SetClientData("RPMSlider", (DefaultIdle + DefaultRedline) / 2, true) - - -- The properties - GraphGroup:DockMargin(0, 0, 0, 0) - - GraphPanel:SetParent(GraphGroup) - GraphPanel:DockPadding(4, 4, 4, 8) - GraphPanel:Dock(TOP) - GraphPanel:SetTall(368) -- Why can't this grow dynamically - - LabelTop:SetParent(GraphPanel) - LabelTop:Dock(TOP) - - SoundGraph:SetParent(GraphPanel) - SoundGraph:Dock(TOP) - SoundGraph:SetTall(192) - SoundGraph:SetYRange(0, 255) - SoundGraph:SetFidelity(10) - SoundGraph:SetXSpacing(1000) - SoundGraph:SetYSpacing(100) - - PanelBottom:SetParent(GraphPanel) - PanelBottom:Dock(TOP) - PanelBottom:DockPadding(0, 4, 4, -4) - PanelBottom:SetTall(34) - - IdleLabel:SetParent(PanelBottom) - IdleLabel:Dock(LEFT) - - IdleWang:SetParent(PanelBottom) - IdleWang:Dock(LEFT) - IdleWang:SetValue(DefaultIdle) -- I shouldn't need to do this but oh well, here we go... - IdleWang:SetClientData("Idle", "OnValueChanged") - IdleWang:DefineSetter(function(Panel, _, _, Value) - Panel:SetMinMax(0, 2000) -- I shouldn't even need to do this! - Panel:SetValue(Value) - Current.Graph["Idle"] = Value - - return Value - end) - - RedlineLabel:SetParent(PanelBottom) - RedlineLabel:Dock(LEFT) - RedlineLabel:DockMargin(8, 4, 0, 0) -- Fucking retarded - - RedlineWang:SetParent(PanelBottom) - RedlineWang:Dock(LEFT) - RedlineWang:SetValue(DefaultRedline) - RedlineWang:SetClientData("Redline", "OnValueChanged") - RedlineWang:DefineSetter(function(Panel, _, _, Value) - -- TODO(TMF): The max value below is hardcoded, this should be a global! - Panel:SetMin(Current.Graph["Idle"] or 1) - Panel:SetMax(16383) - Panel:SetValue(Value) - Current.Graph["Redline"] = Value - - SoundGraph:SetXRange(0, Value + Current.Graph["Idle"]) - SoundGraph:SetXSpacing(Value < 1000 and 100 or 1000) - return Value - end) - - RPMSlider:SetParent(GraphPanel) - RPMSlider:Dock(TOP) - RPMSlider:SetWide(Wide) - RPMSlider:SetValue(GetClientNumber("RPMSlider", 4400)) - RPMSlider:SetClientData("RPMSlider", "OnValueChanged") - RPMSlider:DefineSetter(function(Panel, _, _, Value) - -- TODO(TMF): The max value below is hardcoded, this should be a global! - local Min = Current.Graph["Idle"] or 0 - local Max = Current.Graph["Redline"] or 16383 - - Panel:SetMinMax(Min, Max) - Panel:SetValue(Value) - Current.Graph["RPM"] = Value - - SoundGraph:PlotLimitLine("RPM", false, Value, color_black) - return Value - end) - - SoundPre:SetParent(GraphPanel) - SoundPre:SetWide(Wide) - SoundPre:SetTall(ButtonHeight) - - SoundPrePlay:SetIcon("icon16/sound.png") - SoundPrePlay.DoClick = function() - -- Do something here to play them sounds! - end - SoundPreStop:SetIcon("icon16/sound_mute.png") - -- Set the Play/Stop button positions here - SoundPre:InvalidateLayout(true) - SoundPre.PerformLayout = function() - local HWide = SoundPre:GetWide() / 2 - SoundPrePlay:SetSize(HWide, ButtonHeight) - SoundPrePlay:Dock(LEFT) - SoundPreStop:Dock(FILL) -- FILL will cover the remaining space which the previous button didn't - end - - -- The bottom group where the panels are added and removed dynamically - ValuesGroup = Menu:AddCollapsible("Values", nil, "icon16/application_double.png") - ValuesGroup:DockMargin(0, 4, 0, 4) - -- I don't know if this makes sense, but somehow it gives me less trouble to later remove any arbitrary panels - Menu:StartTemporal(ValuesGroup) - Menu:ClearTemporal(ValuesGroup) - - ListPanel = Menu:AddPanel("DListLayout") - ListPanel:SetParent(ValuesGroup) - ListPanel:Dock(TOP) - - Menu:EndTemporal(ValuesGroup) - - AddValue = Menu:AddPanel("DImageButton") - AddValue:SetParent(ValuesGroup) - AddValue:Dock(TOP) - AddValue:SetImage("icon16/add.png") - AddValue:SetTooltip("Add a new sound.") - AddValue:SetStretchToFit(false) - AddValue:SetSize(16, 16) - AddValue.DoClick = function() - ListPanel:Add(AddValuePanel(Menu, _)) - -- Disable the button if enough panels exist already - if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end - - end - -- Add the first panel if none exists - if #Current.Panels == 0 then - ListPanel:Add(AddValuePanel(Menu, _)) + -- Add the first panel if none exists + if #Current.Panels == 0 then + ListPanel:Add(AddValuePanel(self, _)) + end end - end - } - - local Switch = Case[Num] - Switch() -end - ---- Generates the menu used in the Sound Replacer tool. ---- @param Panel panel The base panel to build the menu off of. -function ACF.CreateSoundMenu(Panel) - local Menu = ACF.InitMenuBase(Panel, "SoundMenu", "acf_reload_sound_menu") - local ButtonHeight = 20 - Menu:AddLabel("#tool.acfsound.help") - - local OptionSelectionBox = Menu:AddComboBox() - OptionSelectionBox:SetText("Select an Option...") - OptionSelectionBox:Dock(TOP) - OptionSelectionBox:SetTall(ButtonHeight) - OptionSelectionBox:AddChoice("Generic - One sound. ", 1) - OptionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ", 2) - OptionSelectionBox:AddChoice("Engines - Simple interpolated. ", 3) - OptionSelectionBox:AddChoice("Engines - Custom interpolated. ", 4) - OptionSelectionBox.OnSelect = function(_, Index, _) - Menu:StartTemporal(Panel) - Menu:ClearTemporal(Panel) - -- Build the panels according to our selection - CreateSubMenu(Index, Menu) - Menu:EndTemporal(Panel) + } + local Switch = Case[Num] + Switch() end do -- SoundBank entity data reception and menu population @@ -522,7 +521,7 @@ function ACF.CreateSoundMenu(Panel) ListPanel:Add(AddValuePanel(Menu, Data)) end end - + -- A net string to receive and populate the menu or to send back to server the client's datavars net.Receive("ACF_SoundMenu_Get_Multi", function(len) print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Get_Multi\"") -- Debug print @@ -558,7 +557,7 @@ function ACF.CreateSoundMenu(Panel) net.Start("ACF_SoundMenu_Set_Multi") net.WriteEntity(Origin) net.WriteUInt(Current.Count, 4) - for I = 1, I <= Current.Count do + for I = 1, Current.Count do local RPM = GetClientNumber("RPM " .. I) local Path = GetClientString("Path " .. I) local Pitch = GetClientNumber("Pitch " .. I) From c01ae97ff1fce25b817d914163ffda61936caea8 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 28 Feb 2026 19:57:48 -0300 Subject: [PATCH 43/59] Rework how values are added and removed --- lua/acf/menu/items_cl/sound_replacer.lua | 168 +++++++++++----------- lua/entities/acf_engine/init.lua | 5 +- lua/weapons/gmod_tool/stools/acfsound.lua | 6 +- 3 files changed, 90 insertions(+), 89 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 96bc6923d..e7ac744f4 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -14,7 +14,7 @@ local Current = {Panels = {}, -- Contains the panel objects --- Generates the menu used in the Sound Replacer tool. --- @param Panel panel The base panel to build the menu off of. function ACF.CreateSoundMenu(Panel) - local AddValue, ListPanel, SoundGraph -- Glocals + local AddValue, ListSlider, ListPanel, SoundGraph -- Glocals -- The graphing function, this is a mirror of the function found in sounds_cl.lua and is redundant -- TODO(TMF): This should be a single function pulled from ACF.Sounds object local function UpdateGraph(Panel) @@ -38,40 +38,33 @@ function ACF.CreateSoundMenu(Panel) end end -- The function that adds the panels to the menu - local function AddValuePanel(Menu, Data) - Current.Count = Current.Count + 1 - local ID = math.max(Current.Count, #Current.Panels) + local function AddValuePanel(Menu) + local ID = #Current.Panels == 0 and 1 or #Current.Panels + 1 local ButtonHeight = 20 + -- Defaults + local DefaultPath = "" + local DefaultRPM = 1000 * ID + local DefaultPitch = 100 + local DefaultVolume = 1 + local DefaultWidth = 0 + + -- VGUI panels local _, MPanel = Menu:AddCollapsible() local Base = Menu:AddPanel("DPanel") _ = Base -- Override ACF's basic Base with this - local TopDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML's Div, just here to parent other children to. + local TopDiv = Menu:AddPanel("ACF_Panel") -- This is equivalent to a HTML Div, generic panel to parent other children to. local BotDiv = Menu:AddPanel("ACF_Panel") -- Same as above. -- TODO(TMF): The max value below is hardcoded, this should be a global! local RPMWang, RPMLabel = Menu:AddNumberWang("RPM:", 0, 16383, 0) local _, PathLabel, PathText = Menu:AddTextEntry("Path:") local ParseIcon = Menu:AddPanel("DImage") - local RemoveButton = Menu:AddPanel("DImageButton") local SearchButton = Menu:AddPanel("DImageButton") - + local RemoveButton = Menu:AddPanel("DImageButton") local PitchWang, PitchLabel = Menu:AddNumberWang("Pitch:", 0, 255, 0) local VolumeWang, VolumeLabel = Menu:AddNumberWang("Volume:", 0, 1, 2) local WidthWang, WidthLabel = Menu:AddNumberWang("Width:", 0, 15, 0) - -- Defaults - local DefaultPath = "" - local DefaultRPM = 1000 * ID - local DefaultPitch = 100 - local DefaultVolume = 1 - local DefaultWidth = 0 - - Data = istable(Data) and Data or { RPM = GetClientNumber("RPM " .. ID, DefaultRPM), - Path = GetClientString("Path " .. ID, DefaultPath), - Pitch = GetClientNumber("Pitch " .. ID, DefaultPitch), - Volume = GetClientNumber("Volume " .. ID, DefaultVolume), - Width = GetClientNumber("Width " .. ID, DefaultWidth)} - MPanel:DockMargin(0, 0, 0, 0) MPanel:SetLabel("Value " .. ID) @@ -94,18 +87,16 @@ function ACF.CreateSoundMenu(Panel) RPMWang:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding RPMWang:DockMargin(-30, 0, 0, 0) RPMWang:Dock(LEFT) - RPMWang:SetValue(Data.RPM) + RPMWang:SetValue(GetClientNumber("RPM " .. ID, DefaultRPM)) RPMWang:SetClientData("RPM " .. ID, "OnValueChanged") RPMWang:DefineSetter(function(Panel, _, _, Value) -- TODO(TMF): The max value below is hardcoded, this should be a global! - local min = ID == 1 and 0 or GetClientNumber("RPM " .. ID - 1) -- Current.Panels[ID - 1].RPM - local max = ID == #Current.Panels and 16383 or GetClientNumber("RPM " .. ID + 1) -- Current.Panels[ID + 1].RPM + local min = ID == 1 and 0 or GetClientNumber("RPM " .. ID - 1) + local max = ID == #Current.Panels and 16383 or GetClientNumber("RPM " .. ID + 1) Panel:SetMinMax(min, max) -- YEA, I MINMAX MY NUMBERS, SO What!? Panel:SetValue(Value) - Current.Panels[ID].RPM = Value - return Value, Panel end) @@ -116,24 +107,21 @@ function ACF.CreateSoundMenu(Panel) PathText:Dock(FILL) PathText:DockMargin(-25, 0, 0, 0) PathText:SetTall(ButtonHeight) - PathText:SetValue(Data.Path) + PathText:SetValue(GetClientString("Path " .. ID, DefaultPath)) PathText:SetClientData("Path " .. ID, "OnValueChange") PathText:DefineSetter(function(Panel, _, _, Value) local isValid = Sounds.IsValidSound - print(Value) if isValid(Value) then ParseIcon:SetTooltip() ParseIcon:SetImage("icon16/accept.png") SetClientData("Path " .. ID, Value) - Current.Panels[ID].Path = Value else ParseIcon:SetTooltip("Invalid sound: File does not exist") ParseIcon:SetImage("icon16/cancel.png") SetClientData("Path " .. ID, "") - Current.Panels[ID].Path = "" end return Value, Panel end) @@ -158,27 +146,16 @@ function ACF.CreateSoundMenu(Panel) return end - -- Reset our client data - SetClientData("RPM " .. ID, nil, true) - SetClientData("Path " .. ID, nil, true) - SetClientData("Pitch " .. ID, nil, true) - SetClientData("Volume " .. ID, nil, true) - SetClientData("Width " .. ID, nil, true) - -- Remove the panel in question MPanel:Remove() table.remove(Current.Panels, ID) + ListSlider:SetValue(math.max(ListSlider:GetValue() - 1, 1)) -- Set the label of the remaining panels up - for k, v in pairs(Current.Panels) do - if not IsValid(v) then continue end - v:SetLabel("Value " .. k) + for i = ID, Current.Count do + if not Current.Panels[i] then continue end + Current.Panels[i]:SetLabel("Value " .. i) end - - AddValue:SetEnabled(true) -- Re-enable our add button - Current.Count = Current.Count - 1 - - UpdateGraph(SoundGraph) end SearchButton:SetParent(TopDiv) @@ -200,11 +177,10 @@ function ACF.CreateSoundMenu(Panel) PitchWang:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding PitchWang:DockMargin(-30, 0, 4, 0) PitchWang:Dock(LEFT) - PitchWang:SetValue(Data.Pitch) + PitchWang:SetValue(GetClientNumber("Pitch " .. ID, DefaultPitch)) PitchWang:SetClientData("Pitch " .. ID, "OnValueChanged") PitchWang:DefineSetter(function(_, _, _, Value) SetClientData("Pitch " .. ID, Value) - Current.Panels[ID].Pitch = Value end) VolumeLabel:SetParent(BotDiv) @@ -214,11 +190,10 @@ function ACF.CreateSoundMenu(Panel) VolumeWang:SetWide(40) -- Equivalent to 0.00 + up/down buttons at font size = 16 + padding VolumeWang:DockMargin(-16, 0, 4, 0) VolumeWang:Dock(LEFT) - VolumeWang:SetValue(Data.Volume) + VolumeWang:SetValue(GetClientNumber("Volume " .. ID, DefaultVolume)) VolumeWang:SetClientData("Volume " .. ID, "OnValueChanged") VolumeWang:DefineSetter(function(_, _, _, Value) SetClientData("Volume " .. ID, Value) - Current.Panels[ID].Volume = Value end) WidthLabel:SetParent(BotDiv) @@ -228,18 +203,15 @@ function ACF.CreateSoundMenu(Panel) WidthWang:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding WidthWang:DockMargin(-24, 0, 4, 0) WidthWang:Dock(LEFT) - WidthWang:SetValue(Data.Width) + WidthWang:SetValue(GetClientNumber("Width " .. ID, DefaultWidth)) WidthWang:SetClientData("Width " .. ID, "OnValueChanged") WidthWang:DefineSetter(function(_, _, _, Value) SetClientData("Width " .. ID, Value) - Current.Panels[ID].Width = Value end) - table.insert(Current.Panels, MPanel) - UpdateGraph(SoundGraph) + table.insert(Current.Panels, MPanel) -- Insert this panel to keep count of them panels return MPanel end - -- Actual menu stuff local Menu = ACF.InitMenuBase(Panel, "SoundMenu", "acf_reload_sound_menu") Menu.ButtonHeight = 20 @@ -469,17 +441,56 @@ function ACF.CreateSoundMenu(Panel) -- The bottom group where the panels are added and removed dynamically local ValuesGroup = self:AddCollapsible("Values", nil, "icon16/application_double.png") ValuesGroup:DockMargin(0, 4, 0, 4) + ListSlider = self:AddSlider("Values", 1, _MAXSOUNDS, 0) + ListPanel = self:AddPanel("DListLayout") + AddValue = self:AddPanel("DImageButton") + + local LastValueAmount = 0 + ListSlider:SetParent(ValuesGroup) + ListSlider:Dock(TOP) + ListSlider:SetValue(GetClientData("ListSlider")) + ListSlider:SetClientData("ListSlider", "OnValueChanged") + ListSlider:DefineSetter(function(Panel, _, _, Value) + local ValueAmount = math.Clamp(math.Round(tonumber(Value)), 1, _MAXSOUNDS) + if ValueAmount ~= LastValueAmount then + if ValueAmount > LastValueAmount then + for _ = LastValueAmount + 1, ValueAmount do + ListPanel:Add(AddValuePanel(self)) + end + elseif ValueAmount < LastValueAmount then + for I = ValueAmount + 1, LastValueAmount do + if IsValid(Current.Panels[I]) then + Current.Panels[I]:Remove() + Current.Panels[I] = nil + end + end + end + end + LastValueAmount = ValueAmount + Panel:SetClientData("ListSlider", ValueAmount) + end) -- I don't know if this makes sense, but somehow it gives me less trouble to later remove any arbitrary panels self:StartTemporal(ValuesGroup) self:ClearTemporal(ValuesGroup) - ListPanel = self:AddPanel("DListLayout") ListPanel:SetParent(ValuesGroup) ListPanel:Dock(TOP) + ListPanel.OnChildAdded = function() + -- Disable the button if enough panels exist already + if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end + + Current.Count = #Current.Panels + UpdateGraph(SoundGraph) -- Update our graph + end + ListPanel.OnChildRemoved = function() + AddValue:SetEnabled(true) -- Re-enable our add button + + Current.Count = #Current.Panels + UpdateGraph(SoundGraph) -- Same here + end self:EndTemporal(ValuesGroup) - AddValue = self:AddPanel("DImageButton") AddValue:SetParent(ValuesGroup) AddValue:Dock(TOP) AddValue:SetImage("icon16/add.png") @@ -487,14 +498,8 @@ function ACF.CreateSoundMenu(Panel) AddValue:SetStretchToFit(false) AddValue:SetSize(16, 16) AddValue.DoClick = function() - ListPanel:Add(AddValuePanel(self, _)) - -- Disable the button if enough panels exist already - if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end - - end - -- Add the first panel if none exists - if #Current.Panels == 0 then - ListPanel:Add(AddValuePanel(self, _)) + local Value = GetClientData("ListSlider") + ListSlider:SetValue(Value + 1) end end } @@ -503,23 +508,20 @@ function ACF.CreateSoundMenu(Panel) end do -- SoundBank entity data reception and menu population - local function PopulateMenu(Table, Count) + local function PopulateMenu(Count) -- We set it to option 4 since that's where the values are located at OptionSelectionBox:ChooseOption(OptionSelectionBox:GetOptionText(4), 4) - -- Reset them panels - Current.Panels = nil - Current.Panels = {} - Current.Count = 0 - -- Wipe the clients values list Menu:ClearTemporal(ListPanel) - for I = 1, Count do - local Data = Table[I] + -- Reset them panels once again, but initialized count at 1 + Current.Panels = nil + Current.Panels = {} + Current.Count = 1 - ListPanel:Add(AddValuePanel(Menu, Data)) - end + -- Set the slider to whatever count is + ListSlider:SetValue(Count) end -- A net string to receive and populate the menu or to send back to server the client's datavars net.Receive("ACF_SoundMenu_Get_Multi", function(len) @@ -529,12 +531,11 @@ function ACF.CreateSoundMenu(Panel) if not Origin then return end local Feedback = net.ReadBool() - -- Get and populate menu + -- Get the data from the entity and populate menu if not Feedback then local Count = net.ReadUInt(4) - local SoundTable = {} - for _ = 1, Count do + for I = 1, Count do local RPM = net.ReadUInt(14) local StringPath = net.ReadString() local Pitch = net.ReadUInt(8) @@ -543,16 +544,13 @@ function ACF.CreateSoundMenu(Panel) Volume = Volume * 0.01 -- Reduce the received value down to a float - table.insert(SoundTable, { RPM = RPM, - Path = StringPath, - Pitch = Pitch or 100, - Volume = Volume or 1, - Width = Width or 0 }) + SetClientData("RPM " .. I, RPM) + SetClientData("Path " .. I, StringPath) + SetClientData("Pitch " .. I, Pitch) + SetClientData("Volume " .. I, Volume) + SetClientData("Width " .. I, Width) end - -- Sort the table before calling the function below - table.sort(SoundTable, function(a, b) return a.RPM < b.RPM end) - - PopulateMenu(SoundTable, Count) + PopulateMenu(Count) else -- Get and Set entities' soundbank net.Start("ACF_SoundMenu_Set_Multi") net.WriteEntity(Origin) diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 80ad6fc92..e9c434e8c 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -630,7 +630,9 @@ end function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() local SoundBank = SelfTbl.SoundBank - local SoundCount = SelfTbl.SoundCount + local SoundCount = GetSoundCount(self) + + --PrintTable(SelfTbl.SoundBank) if SelfTbl.Sound then local Throttle = Round(SelfTbl.Throttle, 2) * 100 @@ -639,6 +641,7 @@ function ENT:UpdateSoundBank(SelfTbl) else Sounds.CreateMultipleAdjustableSounds(self, SoundBank, SoundCount) SelfTbl.Sound = true + SelfTbl.SoundCount = GetSoundCount(self) end end diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index b085d393b..c843b49b0 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -21,7 +21,7 @@ end local Sounds = ACF.SoundToolSupport -local function GetSoundBankData(Player, Entity, Data, Loopback) +local function DoSoundBankData(Player, Entity, Data, Loopback) net.Start("ACF_SoundMenu_Get_Multi") net.WriteEntity(Entity) if not Loopback then @@ -112,7 +112,7 @@ function TOOL:LeftClick(trace) ReplaceSound(owner, trace.Entity, { sound, pitch, volume }) -- Simple call just to get the client's sound menu data - GetSoundBankData(owner, trace.Entity, _, true) + DoSoundBankData(owner, trace.Entity, _, true) do -- Sound Table from client reception, this is the same as the one displayed on the client's menu net.Receive("ACF_SoundMenu_Set_Multi", function (len, ply) print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Set_Multi\" from player " .. ply:Nick()) -- Debug print @@ -172,7 +172,7 @@ function TOOL:RightClick(trace) -- Send the found soundbank table from the entity to the client for sound menu population if soundTable then - GetSoundBankData(owner, trace.Entity, soundTable, false) + DoSoundBankData(owner, trace.Entity, soundTable, false) end return true From 74efabd1f523ea143c51945e79166587c1b2c127 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Tue, 3 Mar 2026 18:28:23 -0300 Subject: [PATCH 44/59] Fix function call not getting passed the correct table to replace the sound - It can now fully replace the sounds! --- lua/entities/acf_engine/init.lua | 3 +-- lua/weapons/gmod_tool/stools/acfsound.lua | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index e9c434e8c..44d034637 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -629,11 +629,10 @@ end function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() + local SoundBank = SelfTbl.SoundBank local SoundCount = GetSoundCount(self) - --PrintTable(SelfTbl.SoundBank) - if SelfTbl.Sound then local Throttle = Round(SelfTbl.Throttle, 2) * 100 local RPM = Round(SelfTbl.FlyRPM) diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index c843b49b0..4ca941f9e 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -136,7 +136,7 @@ function TOOL:LeftClick(trace) Volume = Volume or 1, Width = Width or 0}) end - ReplaceSounds(ply, Origin, Table) + ReplaceSounds(ply, Origin, SoundTable) end) end From f6857e21017cd169b787135f9125915fefd5b18f Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Tue, 3 Mar 2026 18:28:23 -0300 Subject: [PATCH 45/59] Fix function call not getting passed the correct table to replace the sound - It can now fully replace the sounds! --- lua/entities/acf_engine/init.lua | 3 +-- lua/weapons/gmod_tool/stools/acfsound.lua | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index e9c434e8c..44d034637 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -629,11 +629,10 @@ end function ENT:UpdateSoundBank(SelfTbl) SelfTbl = SelfTbl or self:GetTable() + local SoundBank = SelfTbl.SoundBank local SoundCount = GetSoundCount(self) - --PrintTable(SelfTbl.SoundBank) - if SelfTbl.Sound then local Throttle = Round(SelfTbl.Throttle, 2) * 100 local RPM = Round(SelfTbl.FlyRPM) diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index c843b49b0..779782b9e 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -24,7 +24,7 @@ local Sounds = ACF.SoundToolSupport local function DoSoundBankData(Player, Entity, Data, Loopback) net.Start("ACF_SoundMenu_Get_Multi") net.WriteEntity(Entity) - if not Loopback then + if not Loopback then -- Send the sound table to populate the client's sound replacer menu local soundTable = Data local count = #soundTable @@ -47,7 +47,7 @@ local function DoSoundBankData(Player, Entity, Data, Loopback) net.WriteUInt(width, 4) end else - net.WriteBool(true) + net.WriteBool(true) -- Otherwise we get from the client's data vars to create and replace the entity's soundbank end net.Send(Player) end @@ -136,7 +136,7 @@ function TOOL:LeftClick(trace) Volume = Volume or 1, Width = Width or 0}) end - ReplaceSounds(ply, Origin, Table) + ReplaceSounds(ply, Origin, SoundTable) end) end From 083ca03fdf3ae706fd94812ce2ba28da0a2aa3f6 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Thu, 5 Mar 2026 18:41:10 -0300 Subject: [PATCH 46/59] Add dupe saving to soundtables, Add mechanism to reset the sounds as well --- .../core/utilities/sounds/tool_support_sh.lua | 5 +++ lua/entities/acf_engine/init.lua | 15 +++++--- lua/weapons/gmod_tool/stools/acfsound.lua | 35 ++++++++++++------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/lua/acf/core/utilities/sounds/tool_support_sh.lua b/lua/acf/core/utilities/sounds/tool_support_sh.lua index 538bec4ea..359a5d140 100644 --- a/lua/acf/core/utilities/sounds/tool_support_sh.lua +++ b/lua/acf/core/utilities/sounds/tool_support_sh.lua @@ -62,6 +62,11 @@ Sounds.acf_engine = { SetSoundBank = function(Ent, SoundBankData) Ent.SoundBank = SoundBankData + Ent:UpdateSoundBank() + end, + ResetSoundBank = function(Ent) + Ent.SoundBank = {} + Ent:UpdateSoundBank() end } diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 44d034637..567a17eab 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -130,7 +130,7 @@ local function GetSoundCount(Engine) for _ in pairs(Engine.SoundBank) do SoundCount = SoundCount + 1 end - return SoundCount + return math.max(SoundCount, 1) end local function GetPitchVolume(Engine) @@ -319,8 +319,9 @@ do -- Spawn and Update functions Entity.EntType = Class.Name Entity.ClassData = Class Entity.DefaultSound = Engine.Sound + Entity.DefaultSoundBank = Engine.SoundBank Entity.SoundBank = Engine.SoundBank - Entity.SoundCount = GetSoundCount(Engine) or 1 + Entity.SoundCount = GetSoundCount(Engine) Entity.SoundPitch = Engine.Pitch or 1 Entity.SoundVolume = Engine.SoundVolume or 1 Entity.TorqueCurve = Engine.TorqueCurve @@ -636,10 +637,16 @@ function ENT:UpdateSoundBank(SelfTbl) if SelfTbl.Sound then local Throttle = Round(SelfTbl.Throttle, 2) * 100 local RPM = Round(SelfTbl.FlyRPM) + Sounds.SendMultipleAdjustableSounds(self, false, Throttle, RPM) else - Sounds.CreateMultipleAdjustableSounds(self, SoundBank, SoundCount) - SelfTbl.Sound = true + if table.IsEmpty(SoundBank) then + SelfTbl.SoundBank = SelfTbl.DefaultSoundBank or {} + else + Sounds.CreateMultipleAdjustableSounds(self, SoundBank, SoundCount) + SelfTbl.Sound = true + end + SelfTbl.SoundCount = GetSoundCount(self) end end diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index 779782b9e..1710206b7 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -46,8 +46,8 @@ local function DoSoundBankData(Player, Entity, Data, Loopback) net.WriteUInt(volume, 8) net.WriteUInt(width, 4) end - else - net.WriteBool(true) -- Otherwise we get from the client's data vars to create and replace the entity's soundbank + else -- Otherwise we get from the client's data vars to create and replace the entity's soundbank + net.WriteBool(true) end net.Send(Player) end @@ -71,6 +71,20 @@ end duplicator.RegisterEntityModifier("acf_replacesound", ReplaceSound) +-- Just like the above function, except for soundbanks +local function ReplaceSounds(_, Entity, Data) + if not IsValid(Entity) then return end + + local Support = Sounds[Entity:GetClass()] + if not Support then return end + + Support.SetSoundBank(Entity, Data) + + duplicator.StoreEntityModifier(Entity, "acf_replacesoundbank", Data) +end + +duplicator.RegisterEntityModifier("acf_replacesoundbank", ReplaceSounds) + local function IsReallyValid(trace, ply) if not trace.Entity:IsValid() then return false end if trace.Entity:IsPlayer() then return false end @@ -90,15 +104,6 @@ local function IsReallyValid(trace, ply) return true end -local function ReplaceSounds(_, Entity, Data) - if not IsValid(Entity) then return end - - local Support = Sounds[Entity:GetClass()] - if not Support then return end - - Support.SetSoundBank(Entity, Data) -end - function TOOL:LeftClick(trace) local owner = self:GetOwner() @@ -114,8 +119,8 @@ function TOOL:LeftClick(trace) -- Simple call just to get the client's sound menu data DoSoundBankData(owner, trace.Entity, _, true) do -- Sound Table from client reception, this is the same as the one displayed on the client's menu - net.Receive("ACF_SoundMenu_Set_Multi", function (len, ply) - print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Set_Multi\" from player " .. ply:Nick()) -- Debug print + net.Receive("ACF_SoundMenu_Set_Multi", function (_, ply) + --print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Set_Multi\" from player " .. ply:Nick()) -- Debug print local SoundTable = {} local Origin = net.ReadEntity() @@ -187,6 +192,10 @@ function TOOL:Reload(trace) if not support then return false end support.ResetSound(trace.Entity) + -- If it has a soundbank set, we also reset that + if not trace.Entity.SoundBank then return true end + support.ResetSoundBank(trace.Entity) + return true end From 38a9323c94bb4f9f935610ae6c9c322bb52862e9 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Thu, 5 Mar 2026 19:09:16 -0300 Subject: [PATCH 47/59] A round of documentation and some minor cleanup --- lua/acf/menu/items_cl/sound_replacer.lua | 13 ++++++------- lua/weapons/gmod_tool/stools/acfsound.lua | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index e7ac744f4..62ebd2e9e 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -523,16 +523,15 @@ function ACF.CreateSoundMenu(Panel) -- Set the slider to whatever count is ListSlider:SetValue(Count) end - -- A net string to receive and populate the menu or to send back to server the client's datavars - net.Receive("ACF_SoundMenu_Get_Multi", function(len) - print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Get_Multi\"") -- Debug print + -- Receives data to populate the menu or to send back to server the client's datavars + net.Receive("ACF_SoundMenu_Get_Multi", function() + --print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Get_Multi\"") -- Debug print local Origin = net.ReadEntity() if not Origin then return end local Feedback = net.ReadBool() - -- Get the data from the entity and populate menu - if not Feedback then + if not Feedback then -- Get the data from the entity and populate menu local Count = net.ReadUInt(4) for I = 1, Count do @@ -551,7 +550,7 @@ function ACF.CreateSoundMenu(Panel) SetClientData("Width " .. I, Width) end PopulateMenu(Count) - else -- Get and Set entities' soundbank + else -- Gets any datavars and networks them back to the server net.Start("ACF_SoundMenu_Set_Multi") net.WriteEntity(Origin) net.WriteUInt(Current.Count, 4) @@ -569,7 +568,7 @@ function ACF.CreateSoundMenu(Panel) net.WriteUInt(Volume, 8) net.WriteUInt(Width, 4) end - -- We're taking the supposition here that the values being sent are already sorted + -- We're making the supposition here that the values being sent are already sorted net.SendToServer() end end) diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index 1710206b7..1470ce96f 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -12,15 +12,22 @@ TOOL.Information = { { name = "info" } } --- NOTE: I would have used concommands just to set clients data, however i didn't feel like using them here since i don't know how to use them lol +-- NOTE(TMF): I would have used concommands just to set clients data, however i didn't feel like using them here since i don't know how to use them lol -- So instead i went the dumb, hard and convoluted way and network the data needed back and forth if SERVER then - util.AddNetworkString("ACF_SoundMenu_Get_Multi") -- Server to Client - util.AddNetworkString("ACF_SoundMenu_Set_Multi") -- Client to Server + util.AddNetworkString("ACF_SoundMenu_Get_Multi") -- Networks data from Entity(Server) to Client + util.AddNetworkString("ACF_SoundMenu_Set_Multi") -- Networks data from Client to Entity(Server) end local Sounds = ACF.SoundToolSupport + --- This function acts like a getter/setter where we network an entity soundbank data back and forth between the client and the server + --- This allows the client to populate a menu with the data received from the server's entity(engine) or... + --- Sends any datavars that the client has back to the server to update an entity's soundbank table with the datavars that the client had, if any. + --- @param Player player The player who clicked on the Entity + --- @param Entity entity The entity, which has to be an engine(for now) + --- @param Data table? The soundbank table to set soundbank Data to the Entity or not + --- @param Loopback bool? False to just populate a client menu and its datavars or True to GET the datavars from client and send them back local function DoSoundBankData(Player, Entity, Data, Loopback) net.Start("ACF_SoundMenu_Get_Multi") net.WriteEntity(Entity) @@ -52,6 +59,7 @@ local function DoSoundBankData(Player, Entity, Data, Loopback) net.Send(Player) end +-- Sets the sound, pitch and volume to a valid ACF entity local function ReplaceSound(_, Entity, Data) if not IsValid(Entity) then return end @@ -85,6 +93,7 @@ end duplicator.RegisterEntityModifier("acf_replacesoundbank", ReplaceSounds) +-- An improved IsValid function, just to check if an entity is ACF class and if it has support from this tool local function IsReallyValid(trace, ply) if not trace.Entity:IsValid() then return false end if trace.Entity:IsPlayer() then return false end @@ -118,10 +127,8 @@ function TOOL:LeftClick(trace) -- Simple call just to get the client's sound menu data DoSoundBankData(owner, trace.Entity, _, true) - do -- Sound Table from client reception, this is the same as the one displayed on the client's menu + do -- Receives any datavars from the client, which matches what's seen regarding any values on the menu net.Receive("ACF_SoundMenu_Set_Multi", function (_, ply) - --print("Received " .. len .. " bits for call: \"ACF_SoundMenu_Set_Multi\" from player " .. ply:Nick()) -- Debug print - local SoundTable = {} local Origin = net.ReadEntity() local Count = net.ReadUInt(4) From 08b32099b6746491b28d87a56b645ff41cae3e54 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 6 Mar 2026 00:31:36 -0300 Subject: [PATCH 48/59] Several improvements to the sound menu - Added a button to refresh the graph - Plot line graph color is now randomized - Some minor optimizations to both the graph and soundbank play --- lua/acf/core/utilities/sounds/sounds_cl.lua | 30 ++++----- lua/acf/menu/items_cl/sound_replacer.lua | 75 ++++++++++++++------- 2 files changed, 63 insertions(+), 42 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 4474bf969..4b527a3d1 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -1,5 +1,7 @@ local Sounds = ACF.Utilities.Sounds +local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! local map = math.Remap +local clamp = math.Clamp do -- Valid sound check local file = file @@ -183,7 +185,6 @@ end -- Fade function taken from: -- https://dsp.stackexchange.com/questions/37477/understanding-equal-power-crossfades -- https://dsp.stackexchange.com/questions/14754/equal-power-crossfade --- https://i.imgur.com/KaFmaMf.png function Sounds.Fade(n, min, mid, max) local _PI = math.pi @@ -201,12 +202,16 @@ end --local SmoothThrottle = 0 -- This is where the magic to interpolate sounds happen. +-- In order to make yourself a better idea of what this does you can consult the image below: +-- https://i.imgur.com/KaFmaMf.png local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local SoundObjects = Origin.SoundObjects + local fade = Sounds.Fade -- idk if this is faster to do, but given this is a hot path, might as well be... --SmoothRPM = SmoothRPM * (1 - 0.1) + RPM * 0.1 --SmoothThrottle = SmoothThrottle * (1 - 0.1) + Throttle * 10 -- Sound volumes when throttle is 0 and 100 respectively + -- TODO(TMF): This should be able to be configured from the sound menu or to be a function of the engine's load local _OFFVOLUME = 0.25 local _ONVOLUME = 1 @@ -217,10 +222,10 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local addCurveWidth = soundTable.Width or 0 local enginePitch = soundTable.Pitch or 1 - local min = idx == 1 and 0 or SoundObjects[idx - 1].RPM + local min = idx == 1 and 0 or SoundObjects[clamp(idx - 1 - addCurveWidth, 1, _MAXSOUNDS)].RPM local mid = RPM - local max = idx == #SoundObjects and 16383 or SoundObjects[idx + 1].RPM - local curve = Sounds.Fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) + local max = idx == #SoundObjects and 16383 or SoundObjects[clamp(idx + 1 + addCurveWidth, 1, _MAXSOUNDS)].RPM + local curve = fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) local volume = curve * map(Throttle, 0, 100, _OFFVOLUME, _ONVOLUME) * (soundTable.Volume or 1) local pitch = (RPM / soundTable.RPM) * enginePitch @@ -273,21 +278,14 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) Origin.SoundCount = 0 end + -- For multiple sounds creation net.Receive("ACF_Sounds_AdjustableCreate_Multi", function(len) print("Received " .. len .. " bits from \"ACF_Sounds_AdjustableCreate_Multi\" for sound creation!") -- Debug print local SoundTable = {} local Origin = net.ReadEntity() local Count = net.ReadUInt(4) - local CountTable = function (Table) -- This function might not be needed, its for debugging purposes - if not istable(Table) then return end - local Count = 0 - for _ in pairs(Table) do - Count = Count + 1 - end - return Count - end local I = 0 while (I < Count) do @@ -306,14 +304,10 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) Sound = nil }) -- Fuck it we ball I = I + 1 end - - if CountTable(SoundTable) == Count then - Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) - else - print("Got " .. CountTable(SoundTable) .. " out of a total of " .. Count .. " sounds!") -- Debug print - end + Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) end) + -- For updates on multiple sounds net.Receive("ACF_Sounds_Adjustable_Multi", function(len) print("Received " .. len .. " bits from \"ACF_Sounds_Adjustable_Multi\" for sound updates!") -- Debug print local Origin = net.ReadEntity() diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 62ebd2e9e..51b0d657d 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -4,13 +4,20 @@ local GetClientData, SetClientData = ACF.GetClientData, ACF.SetClientData local GetClientNumber, GetClientString = ACF.GetClientNumber, ACF.GetClientString local _MAXSOUNDS = 16 -- Maximum amount of sounds we're willing to send and have. TODO(TMF): Make this a global! -local Current = {Panels = {}, -- Contains the panel objects - Count = 0, -- Keeps count of them - Graph = { -- This only relates to the graph +local Current = {Panels = {}, -- Contains the panel objects + Count = 0, -- Keeps count of them + Graph = { -- This only relates to the graph Idle = 0, Redline = 1, - RPMSlider = 2} - } + RPMSlider = 2}, + Colors = (function() -- This IIFE returns a table with all the randomized colors + local ColorTable = {} + for I = 1, _MAXSOUNDS do + ColorTable[I] = ColorRand() + end + return ColorTable + end)() + } --- Generates the menu used in the Sound Replacer tool. --- @param Panel panel The base panel to build the menu off of. function ACF.CreateSoundMenu(Panel) @@ -18,29 +25,31 @@ function ACF.CreateSoundMenu(Panel) -- The graphing function, this is a mirror of the function found in sounds_cl.lua and is redundant -- TODO(TMF): This should be a single function pulled from ACF.Sounds object local function UpdateGraph(Panel) - local Count = #Current.Panels + local Count = Current.Count if not Count then return end + local clamp = math.Clamp + local fade = Sounds.Fade + Panel:Clear() for I = 1, Count do - local addCurveWidth = GetClientNumber("Width " .. I, 0) -- Current.Panels[I].Width - local pitch = GetClientNumber("Pitch " .. I, 0) -- Current.Panels[I].Pitch + local addCurveWidth = GetClientNumber("Width " .. I, 0) + local pitch = GetClientNumber("Pitch " .. I, 0) --local volume = Current.Panels[I].Volume * 100 -- Idk if we want to plot volume as a function - local min = I == 1 and 0 or GetClientNumber("RPM " .. math.Clamp(I - 1 - addCurveWidth, 0, 16383)) + local min = I == 1 and 0 or GetClientNumber("RPM " .. clamp(I - 1 - addCurveWidth, 1, _MAXSOUNDS)) local mid = GetClientNumber("RPM " .. I, 0) -- TODO(TMF): The max value below is hardcoded, this should be a global! - local max = I == Count and 16383 or GetClientNumber("RPM " .. math.Clamp(I + 1 + addCurveWidth, 0, 16383)) + local max = I == Count and 16383 or GetClientNumber("RPM " .. clamp(I + 1 + addCurveWidth, 1, _MAXSOUNDS)) - Panel:PlotFunction("Sound " .. I, nil, function(X) - return (Sounds.Fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * pitch + Panel:PlotFunction("Sound " .. I, Current.Colors[I], function(X) + return (fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * pitch end) end end -- The function that adds the panels to the menu local function AddValuePanel(Menu) - local ID = #Current.Panels == 0 and 1 or #Current.Panels + 1 - local ButtonHeight = 20 + local ID = #Current.Panels == 0 and 1 or #Current.Panels + 1 -- Ensure it always begins from 1 and increments from there on -- Defaults local DefaultPath = "" @@ -106,7 +115,7 @@ function ACF.CreateSoundMenu(Panel) PathText:SetParent(TopDiv) PathText:Dock(FILL) PathText:DockMargin(-25, 0, 0, 0) - PathText:SetTall(ButtonHeight) + PathText:SetTall(Menu.ButtonHeight) PathText:SetValue(GetClientString("Path " .. ID, DefaultPath)) PathText:SetClientData("Path " .. ID, "OnValueChange") PathText:DefineSetter(function(Panel, _, _, Value) @@ -323,8 +332,10 @@ function ACF.CreateSoundMenu(Panel) -- The top group where the graph lies local GraphGroup = self:AddCollapsible("Graph", nil, "icon16/chart_curve_edit.png") local GraphPanel = self:AddPanel("DPanel") - local LabelTop = self:AddLabel() - SoundGraph = self:AddGraph() -- A Glocal so we can feed the other functions + local LabelTop = self:AddLabel("This graph shows how your engine sound/s will be heard in function of RPM.\ + Beware this panel can be resource intensive if you add too many sounds!") + local RefreshBtn = self:AddPanel("DImageButton") + SoundGraph = self:AddGraph() -- A Glocal so other functions can call this local PanelBottom = self:AddPanel("ACF_Panel") local IdleLabel = self:AddLabel("Idle:") local IdleWang = self:AddPanel("DNumberWang", 0, 2000) @@ -335,6 +346,7 @@ function ACF.CreateSoundMenu(Panel) local SoundPre = self:AddPanel("ACF_Panel") local SoundPrePlay = SoundPre:AddButton("#tool.acfsound.play") local SoundPreStop = SoundPre:AddButton("#tool.acfsound.stop", "play", "common/null.wav") -- Playing a silent sound will mute the preview but not the sound emitters + local VolumeSlider = self:AddSlider("#tool.acfsound.volume", 0.1, 1, 2) -- Set defaults local DefaultIdle = GetClientData("Idle", 800) @@ -349,10 +361,21 @@ function ACF.CreateSoundMenu(Panel) GraphPanel:SetParent(GraphGroup) GraphPanel:DockPadding(4, 4, 4, 8) GraphPanel:Dock(TOP) - GraphPanel:SetTall(368) -- Why can't this grow dynamically + GraphPanel:SetTall(436) -- Why can't this grow dynamically LabelTop:SetParent(GraphPanel) LabelTop:Dock(TOP) + LabelTop:DockMargin(0, 2, 0, 2) + + RefreshBtn:SetParent(LabelTop) + RefreshBtn:Dock(RIGHT) + RefreshBtn:SetImage("icon16/arrow_refresh_small.png") + RefreshBtn:SetTooltip("Refresh this graph.") + RefreshBtn:SetStretchToFit(false) + RefreshBtn:SetSize(16, 16) + RefreshBtn.DoClick = function() + UpdateGraph(SoundGraph) + end SoundGraph:SetParent(GraphPanel) SoundGraph:Dock(TOP) @@ -377,6 +400,7 @@ function ACF.CreateSoundMenu(Panel) IdleWang:DefineSetter(function(Panel, _, _, Value) Panel:SetMinMax(0, 2000) -- I shouldn't even need to do this! Panel:SetValue(Value) + RedlineWang:SetMin(Value or 1) Current.Graph["Idle"] = Value return Value @@ -389,15 +413,14 @@ function ACF.CreateSoundMenu(Panel) RedlineWang:SetParent(PanelBottom) RedlineWang:Dock(LEFT) RedlineWang:SetValue(DefaultRedline) + RedlineWang:SetMinMax(Current.Graph["Idle"], 16383) RedlineWang:SetClientData("Redline", "OnValueChanged") RedlineWang:DefineSetter(function(Panel, _, _, Value) -- TODO(TMF): The max value below is hardcoded, this should be a global! - Panel:SetMin(Current.Graph["Idle"] or 1) - Panel:SetMax(16383) Panel:SetValue(Value) Current.Graph["Redline"] = Value - SoundGraph:SetXRange(0, Value + Current.Graph["Idle"]) + SoundGraph:SetXRange(0, Value + 1000) SoundGraph:SetXSpacing(Value < 1000 and 100 or 1000) return Value end) @@ -420,6 +443,10 @@ function ACF.CreateSoundMenu(Panel) return Value end) + VolumeSlider:SetConVar("acfsound_volume") + VolumeSlider:SetParent(GraphPanel) + VolumeSlider:Dock(TOP) + SoundPre:SetParent(GraphPanel) SoundPre:SetWide(Menu.Wide) SoundPre:SetTall(Menu.ButtonHeight) @@ -476,11 +503,11 @@ function ACF.CreateSoundMenu(Panel) ListPanel:SetParent(ValuesGroup) ListPanel:Dock(TOP) ListPanel.OnChildAdded = function() - -- Disable the button if enough panels exist already - if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end - Current.Count = #Current.Panels UpdateGraph(SoundGraph) -- Update our graph + + -- Disable the button if enough panels exist already + if #Current.Panels >= _MAXSOUNDS then AddValue:SetEnabled(false) return end end ListPanel.OnChildRemoved = function() AddValue:SetEnabled(true) -- Re-enable our add button From 2b57e5068bfbd0b9f03e6f78ed4d6114d09d38a5 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 6 Mar 2026 00:44:08 -0300 Subject: [PATCH 49/59] Add XY labels to the graph --- lua/acf/menu/items_cl/sound_replacer.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 51b0d657d..49b178ec6 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -380,6 +380,8 @@ function ACF.CreateSoundMenu(Panel) SoundGraph:SetParent(GraphPanel) SoundGraph:Dock(TOP) SoundGraph:SetTall(192) + SoundGraph:SetXLabel("RPM") + SoundGraph:SetYLabel("Pitch") SoundGraph:SetYRange(0, 255) SoundGraph:SetFidelity(10) SoundGraph:SetXSpacing(1000) From 817bcaa6368e31aa4cf0576246182add10dbe28e Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Fri, 6 Mar 2026 23:25:13 -0300 Subject: [PATCH 50/59] Add custom background color to list panels --- lua/acf/menu/items_cl/sound_replacer.lua | 26 +++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 49b178ec6..5f6d4bd61 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -10,11 +10,18 @@ local Current = {Panels = {}, -- Contains the panel objects Idle = 0, Redline = 1, RPMSlider = 2}, - Colors = (function() -- This IIFE returns a table with all the randomized colors + Colors = (function() -- This IIFE returns a table with all the randomized colors and if the text should be dark or light colored local ColorTable = {} + for I = 1, _MAXSOUNDS do - ColorTable[I] = ColorRand() + local colorRand = ColorRand() + + -- Calculate luminance to determine text color (0.2126*R + 0.7152*G + 0.0722*B) + local luminance = (0.2126 * colorRand.r + 0.7152 * colorRand.g + 0.0722 * colorRand.b) / 255 + local textColor = luminance > 0.5 and color_black or color_white + ColorTable[I] = {colorRand, textColor} end + return ColorTable end)() } @@ -42,7 +49,7 @@ function ACF.CreateSoundMenu(Panel) -- TODO(TMF): The max value below is hardcoded, this should be a global! local max = I == Count and 16383 or GetClientNumber("RPM " .. clamp(I + 1 + addCurveWidth, 1, _MAXSOUNDS)) - Panel:PlotFunction("Sound " .. I, Current.Colors[I], function(X) + Panel:PlotLimitFunction("Sound " .. I, 0, 16383, Current.Colors[I][1], function(X) return (fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * pitch end) end @@ -50,10 +57,12 @@ function ACF.CreateSoundMenu(Panel) -- The function that adds the panels to the menu local function AddValuePanel(Menu) local ID = #Current.Panels == 0 and 1 or #Current.Panels + 1 -- Ensure it always begins from 1 and increments from there on + local BGColor = Current.Colors[ID][1] or color_white + local TextColor = Current.Colors[ID][2] -- Defaults - local DefaultPath = "" local DefaultRPM = 1000 * ID + local DefaultPath = "" local DefaultPitch = 100 local DefaultVolume = 1 local DefaultWidth = 0 @@ -81,6 +90,7 @@ function ACF.CreateSoundMenu(Panel) Base:SetTall(72) Base:DockPadding(4, 6, 4, 0) Base:DockMargin(0, 0, 0, 0) + Base:SetBackgroundColor(BGColor) TopDiv:SetParent(Base) TopDiv:Dock(TOP) @@ -91,6 +101,7 @@ function ACF.CreateSoundMenu(Panel) RPMLabel:SetParent(TopDiv) RPMLabel:DockMargin(0, 0, 0, 0) RPMLabel:Dock(LEFT) + RPMLabel:SetTextColor(TextColor) RPMWang:SetParent(TopDiv) RPMWang:SetWide(48) -- Equivalent to 00000 + up/down buttons at font size = 16 + padding @@ -111,6 +122,7 @@ function ACF.CreateSoundMenu(Panel) PathLabel:SetParent(TopDiv) PathLabel:Dock(LEFT) + PathLabel:SetTextColor(TextColor) PathText:SetParent(TopDiv) PathText:Dock(FILL) @@ -162,8 +174,9 @@ function ACF.CreateSoundMenu(Panel) -- Set the label of the remaining panels up for i = ID, Current.Count do - if not Current.Panels[i] then continue end + if not Current.Panels[i] and IsValid(Current.Panels[i]) then continue end Current.Panels[i]:SetLabel("Value " .. i) + Current.Panels[i]:SetBGColor(Current.Colors[i][1]) end end @@ -181,6 +194,7 @@ function ACF.CreateSoundMenu(Panel) PitchLabel:SetParent(BotDiv) PitchLabel:Dock(LEFT) + PitchLabel:SetTextColor(TextColor) PitchWang:SetParent(BotDiv) PitchWang:SetWide(40) -- Equivalent to 000 + up/down buttons at font size = 16 + padding @@ -194,6 +208,7 @@ function ACF.CreateSoundMenu(Panel) VolumeLabel:SetParent(BotDiv) VolumeLabel:Dock(LEFT) + VolumeLabel:SetTextColor(TextColor) VolumeWang:SetParent(BotDiv) VolumeWang:SetWide(40) -- Equivalent to 0.00 + up/down buttons at font size = 16 + padding @@ -207,6 +222,7 @@ function ACF.CreateSoundMenu(Panel) WidthLabel:SetParent(BotDiv) WidthLabel:Dock(LEFT) + WidthLabel:SetTextColor(TextColor) WidthWang:SetParent(BotDiv) WidthWang:SetWide(32) -- Equivalent to 00 + up/down buttons at font size = 16 + padding From 1498589a4b989347147e21e5de4a9fc444eb53f5 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 7 Mar 2026 13:33:20 -0300 Subject: [PATCH 51/59] Fix error on engines without a soundbank table --- lua/entities/acf_engine/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 567a17eab..6b1f88f39 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -124,7 +124,7 @@ local TickInterval = engine.TickInterval -- Count all the existing sounds in a SoundBank local function GetSoundCount(Engine) - if not Engine.SoundBank then return end + if not Engine.SoundBank then return 1 end local SoundCount = 0 for _ in pairs(Engine.SoundBank) do From 52f8392fefcc2ebfb1518dce55cba3cb614c1c85 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 7 Mar 2026 19:46:34 -0300 Subject: [PATCH 52/59] Improve value removal logic --- lua/acf/menu/items_cl/sound_replacer.lua | 33 +++++++++++++++++------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 5f6d4bd61..a9b93d4cc 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -167,16 +167,29 @@ function ACF.CreateSoundMenu(Panel) return end - -- Remove the panel in question - MPanel:Remove() - table.remove(Current.Panels, ID) - ListSlider:SetValue(math.max(ListSlider:GetValue() - 1, 1)) - - -- Set the label of the remaining panels up - for i = ID, Current.Count do + for i = ID, Current.Count + 1 do if not Current.Panels[i] and IsValid(Current.Panels[i]) then continue end - Current.Panels[i]:SetLabel("Value " .. i) - Current.Panels[i]:SetBGColor(Current.Colors[i][1]) + -- Remove the last panel and reset its values + if i == Current.Count then + SetClientData("RPM " .. i, 1000 * i) --DefaultRPM + SetClientData("Path " .. i, DefaultPath) + SetClientData("Pitch " .. i, DefaultPitch) + SetClientData("Volume " .. i, DefaultVolume) + SetClientData("Width " .. i, DefaultWidth) + + SetClientData("ListSlider", GetClientNumber("ListSlider") - 1) + ListSlider:SetValue(GetClientData("ListSlider")) + table.remove(Current.Panels, i) + Current.Count = #Current.Panels + break + end + -- Move the datavars values one step back(or forward) to compensate + Current.Panels[i] = Current.Panels[i + 1] + SetClientData("RPM " .. i, GetClientNumber("RPM " .. i + 1)) + SetClientData("Path " .. i, GetClientNumber("Path " .. i + 1)) + SetClientData("Pitch " .. i, GetClientNumber("Pitch " .. i + 1)) + SetClientData("Volume " .. i, GetClientNumber("Volume " .. i + 1)) + SetClientData("Width " .. i, GetClientNumber("Width " .. i + 1)) end end @@ -496,7 +509,7 @@ function ACF.CreateSoundMenu(Panel) ListSlider:SetValue(GetClientData("ListSlider")) ListSlider:SetClientData("ListSlider", "OnValueChanged") ListSlider:DefineSetter(function(Panel, _, _, Value) - local ValueAmount = math.Clamp(math.Round(tonumber(Value)), 1, _MAXSOUNDS) + local ValueAmount = math.Clamp(math.floor(tonumber(Value)), 1, _MAXSOUNDS) if ValueAmount ~= LastValueAmount then if ValueAmount > LastValueAmount then for _ = LastValueAmount + 1, ValueAmount do From bd2588350f4759b99a38684670ba801ff75bfa7b Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 7 Mar 2026 20:18:56 -0300 Subject: [PATCH 53/59] Comment debug prints --- lua/acf/core/utilities/sounds/sounds_cl.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 4b527a3d1..2e4dfe348 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -279,8 +279,8 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) end -- For multiple sounds creation - net.Receive("ACF_Sounds_AdjustableCreate_Multi", function(len) - print("Received " .. len .. " bits from \"ACF_Sounds_AdjustableCreate_Multi\" for sound creation!") -- Debug print + net.Receive("ACF_Sounds_AdjustableCreate_Multi", function() + --print("Received " .. len .. " bits from \"ACF_Sounds_AdjustableCreate_Multi\" for sound creation!") -- Debug print local SoundTable = {} local Origin = net.ReadEntity() @@ -308,8 +308,8 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) end) -- For updates on multiple sounds - net.Receive("ACF_Sounds_Adjustable_Multi", function(len) - print("Received " .. len .. " bits from \"ACF_Sounds_Adjustable_Multi\" for sound updates!") -- Debug print + net.Receive("ACF_Sounds_Adjustable_Multi", function() + --print("Received " .. len .. " bits from \"ACF_Sounds_Adjustable_Multi\" for sound updates!") -- Debug print local Origin = net.ReadEntity() local ShouldStop = net.ReadBool() From 646bee68ee6e09e499404550954644958ff25e0b Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Sat, 7 Mar 2026 20:57:43 -0300 Subject: [PATCH 54/59] Tweak panel colors to be a predictable rainbow, grey out play/stop buttons for now --- lua/acf/menu/items_cl/sound_replacer.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index a9b93d4cc..9b18d1233 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -14,12 +14,12 @@ local Current = {Panels = {}, -- Contains the panel objects local ColorTable = {} for I = 1, _MAXSOUNDS do - local colorRand = ColorRand() - + local freq = 360 / _MAXSOUNDS + local color = HSVToColor(I * freq % 360, 1, 1) -- Calculate luminance to determine text color (0.2126*R + 0.7152*G + 0.0722*B) - local luminance = (0.2126 * colorRand.r + 0.7152 * colorRand.g + 0.0722 * colorRand.b) / 255 + local luminance = (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b) / 255 local textColor = luminance > 0.5 and color_black or color_white - ColorTable[I] = {colorRand, textColor} + ColorTable[I] = {color, textColor} end return ColorTable @@ -483,10 +483,16 @@ function ACF.CreateSoundMenu(Panel) SoundPre:SetTall(Menu.ButtonHeight) SoundPrePlay:SetIcon("icon16/sound.png") + SoundPrePlay:SetTooltip("Unimplemented!") + SoundPrePlay:SetEnabled(false) SoundPrePlay.DoClick = function() -- Do something here to play them sounds! end + SoundPreStop:SetIcon("icon16/sound_mute.png") + SoundPreStop:SetTooltip("Unimplemented!") + SoundPreStop:SetEnabled(false) + -- Set the Play/Stop button positions here SoundPre:InvalidateLayout(true) SoundPre.PerformLayout = function() From 829f72aaa738f7d513fbcc516d9e4920fffef67e Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Mon, 9 Mar 2026 00:13:30 -0300 Subject: [PATCH 55/59] Potential fix for client error on missing sounds and accessing empty or non existant sound table. Some improvements to the sound replacer menu * Switched Pitch for Volume Y label to avoid confusion * Removed two unimplemented submenu choices temporarily * Added contact group for the sound replacer menu, warn that this is a WIP panel * Improved the graph panel by allowing a higher fidelity --- lua/acf/core/utilities/sounds/sounds_cl.lua | 9 +++- lua/acf/menu/items_cl/sound_replacer.lua | 46 +++++++++++++-------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 2e4dfe348..e8074f367 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -133,6 +133,7 @@ do -- Processing adjustable sounds (for example, engine noises) --- @return Sound CSoundPatch The sound object function Sounds.CreateAdjustableSound(Origin, Path, Pitch, Volume) if not IsValid(Origin) then return end + if not Sounds.IsValidSound(Path) then return end local Sound = CreateSound(Origin, Path) Origin.Sound = Sound @@ -206,6 +207,8 @@ end -- https://i.imgur.com/KaFmaMf.png local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local SoundObjects = Origin.SoundObjects + if not SoundObjects or table.IsEmpty(SoundObjects) then return end + local fade = Sounds.Fade -- idk if this is faster to do, but given this is a hot path, might as well be... --SmoothRPM = SmoothRPM * (1 - 0.1) + RPM * 0.1 --SmoothThrottle = SmoothThrottle * (1 - 0.1) + Throttle * 10 @@ -224,7 +227,7 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local enginePitch = soundTable.Pitch or 1 local min = idx == 1 and 0 or SoundObjects[clamp(idx - 1 - addCurveWidth, 1, _MAXSOUNDS)].RPM local mid = RPM - local max = idx == #SoundObjects and 16383 or SoundObjects[clamp(idx + 1 + addCurveWidth, 1, _MAXSOUNDS)].RPM + local max = idx == #SoundObjects and 1000000 or SoundObjects[clamp(idx + 1 + addCurveWidth, 1, _MAXSOUNDS)].RPM local curve = fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) local volume = curve * map(Throttle, 0, 100, _OFFVOLUME, _ONVOLUME) * (soundTable.Volume or 1) local pitch = (RPM / soundTable.RPM) * enginePitch @@ -249,6 +252,10 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) sndTable.Path, sndTable.Pitch or 100, 0 -- Create the sound deafened ) + if not Sound then + print("Failed to create sound for entity " .. tostring(Origin) .. ". Sound path does not exist!") + continue + end sndTable.Sound = Sound SoundCount = SoundCount + 1 diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index 9b18d1233..c6487d73f 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -30,6 +30,7 @@ local Current = {Panels = {}, -- Contains the panel objects function ACF.CreateSoundMenu(Panel) local AddValue, ListSlider, ListPanel, SoundGraph -- Glocals -- The graphing function, this is a mirror of the function found in sounds_cl.lua and is redundant + -- The plotting function is wrong, its plotting as if it was a function of pitch when the fade is calculated for volume instead -- TODO(TMF): This should be a single function pulled from ACF.Sounds object local function UpdateGraph(Panel) local Count = Current.Count @@ -42,15 +43,14 @@ function ACF.CreateSoundMenu(Panel) for I = 1, Count do local addCurveWidth = GetClientNumber("Width " .. I, 0) - local pitch = GetClientNumber("Pitch " .. I, 0) - --local volume = Current.Panels[I].Volume * 100 -- Idk if we want to plot volume as a function + local volume = GetClientNumber("Volume " .. I, 0) * 100 local min = I == 1 and 0 or GetClientNumber("RPM " .. clamp(I - 1 - addCurveWidth, 1, _MAXSOUNDS)) local mid = GetClientNumber("RPM " .. I, 0) - -- TODO(TMF): The max value below is hardcoded, this should be a global! - local max = I == Count and 16383 or GetClientNumber("RPM " .. clamp(I + 1 + addCurveWidth, 1, _MAXSOUNDS)) + local max = I == Count and 1000000 or GetClientNumber("RPM " .. clamp(I + 1 + addCurveWidth, 1, _MAXSOUNDS)) - Panel:PlotLimitFunction("Sound " .. I, 0, 16383, Current.Colors[I][1], function(X) - return (fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * pitch + -- The 1000 extra is so it can see til the graph X limit and not cutoff + Panel:PlotLimitFunction("Sound " .. I, 0, 16383 + 1000, Current.Colors[I][1], function(X) + return (fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * volume end) end end @@ -261,9 +261,9 @@ function ACF.CreateSoundMenu(Panel) OptionSelectionBox:Dock(TOP) OptionSelectionBox:SetTall(Menu.ButtonHeight) OptionSelectionBox:AddChoice("Generic - One sound. ", 1) - OptionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ", 2) - OptionSelectionBox:AddChoice("Engines - Simple interpolated. ", 3) - OptionSelectionBox:AddChoice("Engines - Custom interpolated. ", 4) + --OptionSelectionBox:AddChoice("Weapons - Start/Loop/Stop. ", 2) + --OptionSelectionBox:AddChoice("Engines - Simple interpolated. ", 3) + OptionSelectionBox:AddChoice("Engines - Multiple interpolated. ", 4) OptionSelectionBox.OnSelect = function(_, Index, _) Menu:StartTemporal(Panel) Menu:ClearTemporal(Panel) @@ -278,7 +278,7 @@ function ACF.CreateSoundMenu(Panel) -- I explictly gave these their numeric keys so its easier to infer which panel we're working with -- First panel, Generic - One sound. Old menu with text entry for a single sound [1] = function () - self:AddLabel("This is the first panel, I don't know what to add here yet but you can imagine its gonna be something good, so stay tuned!") + self:AddLabel("Play a single sound for all the supported ACF entities, including engines.") local SoundNameText = self:AddPanel("DTextEntry") SoundNameText:SetText("") @@ -339,7 +339,7 @@ function ACF.CreateSoundMenu(Panel) end, -- Second panel, Weapons - Start/Loop/Stop. New menu with three text entries labeled as "Start", "Loop", "End" respectively, to put the sound paths -- Layout is similar to the first option - [2] = function() + --[[[2] = function() self:AddLabel("This is the second panel, I don't know what to add here yet but you can imagine its gonna be something nice, so stay tuned!") end, @@ -348,11 +348,21 @@ function ACF.CreateSoundMenu(Panel) [3] = function() self:AddLabel("This is the third panel, I don't know what to add here yet but you can imagine its gonna be something fantastic, so stay tuned!") - end, + end,]]-- -- Fourth panel, Engines - Custom interpolated. New menu with a button to add up to 16 sound paths, with configurable pitch, volume and width for each sound -- Has a graph at the top of the list to better visualise how they play at a determined engine RPM - [4] = function() - self:AddLabel("This is the fourth panel, I don't know what to add here yet but you can imagine its gonna be something mindblowing, so stay tuned!") + [2] = function() + self:AddLabel("Play multiple interpolated sounds exclusively for ACF engines.") + -- Contact panel + local Contact = self:AddCollapsible("Contact", true, "icon16/bug_link.png") + local Help = self:AddHelp("This panel is a Work In Progress. Expect bugs to arise and things to not work! \n \ + If you have any errors to report and/or suggestions to make, please contact us on our official discord server.") + local ContactBtn = self:AddButton("Discord link") + function ContactBtn:DoClick() gui.OpenURL("https://discord.gg/jf4cwarPUG") end + + Help:SetParent(Contact) + ContactBtn:SetParent(Contact) + -- Reset them panels Current.Panels = nil Current.Panels = {} @@ -361,7 +371,7 @@ function ACF.CreateSoundMenu(Panel) -- The top group where the graph lies local GraphGroup = self:AddCollapsible("Graph", nil, "icon16/chart_curve_edit.png") local GraphPanel = self:AddPanel("DPanel") - local LabelTop = self:AddLabel("This graph shows how your engine sound/s will be heard in function of RPM.\ + local LabelTop = self:AddLabel("This graph shows how your engine sound/s will be heard as a function of RPM.\ Beware this panel can be resource intensive if you add too many sounds!") local RefreshBtn = self:AddPanel("DImageButton") SoundGraph = self:AddGraph() -- A Glocal so other functions can call this @@ -410,9 +420,9 @@ function ACF.CreateSoundMenu(Panel) SoundGraph:Dock(TOP) SoundGraph:SetTall(192) SoundGraph:SetXLabel("RPM") - SoundGraph:SetYLabel("Pitch") - SoundGraph:SetYRange(0, 255) - SoundGraph:SetFidelity(10) + SoundGraph:SetYLabel("Volume") + SoundGraph:SetYRange(0, 200) + SoundGraph:SetFidelity(1) SoundGraph:SetXSpacing(1000) SoundGraph:SetYSpacing(100) From 9a2623c3a4c1067f84a10242f26955524a334faa Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Mon, 9 Mar 2026 15:57:22 -0300 Subject: [PATCH 56/59] Fix error for call to delete sounds from a non existant table, other miscellaneous tweaks --- lua/acf/core/utilities/sounds/sounds_cl.lua | 6 ++++-- lua/acf/menu/items_cl/sound_replacer.lua | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index e8074f367..014480718 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -226,9 +226,9 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) local addCurveWidth = soundTable.Width or 0 local enginePitch = soundTable.Pitch or 1 local min = idx == 1 and 0 or SoundObjects[clamp(idx - 1 - addCurveWidth, 1, _MAXSOUNDS)].RPM - local mid = RPM + local mid = soundTable.RPM local max = idx == #SoundObjects and 1000000 or SoundObjects[clamp(idx + 1 + addCurveWidth, 1, _MAXSOUNDS)].RPM - local curve = fade(RPM, min - addCurveWidth, mid, max + addCurveWidth) + local curve = fade(RPM, min, mid, max) local volume = curve * map(Throttle, 0, 100, _OFFVOLUME, _ONVOLUME) * (soundTable.Volume or 1) local pitch = (RPM / soundTable.RPM) * enginePitch @@ -277,6 +277,8 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) --- @param Origin table The entity to stop all the sounds from function Sounds.DeleteMultipleAdjustableSounds(Origin, _) if not IsValid(Origin) then return end + if not Origin.SoundObjects then return end + for idx, snd in ipairs(Origin.SoundObjects) do snd.Sound:Stop() Origin.SoundObjects[idx] = nil diff --git a/lua/acf/menu/items_cl/sound_replacer.lua b/lua/acf/menu/items_cl/sound_replacer.lua index c6487d73f..1af303777 100644 --- a/lua/acf/menu/items_cl/sound_replacer.lua +++ b/lua/acf/menu/items_cl/sound_replacer.lua @@ -50,7 +50,7 @@ function ACF.CreateSoundMenu(Panel) -- The 1000 extra is so it can see til the graph X limit and not cutoff Panel:PlotLimitFunction("Sound " .. I, 0, 16383 + 1000, Current.Colors[I][1], function(X) - return (fade(X, min - addCurveWidth, mid, max + addCurveWidth)) * volume + return (fade(X, min, mid, max)) * volume end) end end @@ -583,8 +583,8 @@ function ACF.CreateSoundMenu(Panel) do -- SoundBank entity data reception and menu population local function PopulateMenu(Count) - -- We set it to option 4 since that's where the values are located at - OptionSelectionBox:ChooseOption(OptionSelectionBox:GetOptionText(4), 4) + -- We set it to option 2 since that's where the values are located at + OptionSelectionBox:ChooseOption(OptionSelectionBox:GetOptionText(2), 2) -- Wipe the clients values list Menu:ClearTemporal(ListPanel) From b9057435e68b4adb05a12ecf49cfedfebf4f18f2 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Wed, 11 Mar 2026 21:54:35 -0300 Subject: [PATCH 57/59] Validate entities on client when receiving them, move soundtable after validation to avoid having it potentially leak in memory --- lua/acf/core/utilities/sounds/sounds_cl.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 014480718..d27eb2105 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -290,9 +290,10 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) -- For multiple sounds creation net.Receive("ACF_Sounds_AdjustableCreate_Multi", function() --print("Received " .. len .. " bits from \"ACF_Sounds_AdjustableCreate_Multi\" for sound creation!") -- Debug print - local SoundTable = {} - local Origin = net.ReadEntity() + if not IsValid(Origin) then return end + + local SoundTable = {} local Count = net.ReadUInt(4) local I = 0 @@ -320,6 +321,8 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) net.Receive("ACF_Sounds_Adjustable_Multi", function() --print("Received " .. len .. " bits from \"ACF_Sounds_Adjustable_Multi\" for sound updates!") -- Debug print local Origin = net.ReadEntity() + if not IsValid(Origin) then return end + local ShouldStop = net.ReadBool() -- Do we really need to remove every existing sound when the engine just turns off? From eccfe52ac563d55eb5f3fac38a11067cfb727c91 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Thu, 12 Mar 2026 00:10:47 -0300 Subject: [PATCH 58/59] Fix a goober of mine --- lua/acf/core/utilities/sounds/sounds_cl.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index d27eb2105..151148780 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -291,7 +291,6 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) net.Receive("ACF_Sounds_AdjustableCreate_Multi", function() --print("Received " .. len .. " bits from \"ACF_Sounds_AdjustableCreate_Multi\" for sound creation!") -- Debug print local Origin = net.ReadEntity() - if not IsValid(Origin) then return end local SoundTable = {} local Count = net.ReadUInt(4) @@ -314,6 +313,8 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) Sound = nil }) -- Fuck it we ball I = I + 1 end + + if not IsValid(Origin) then return end Sounds.CreateMultipleAdjustableSounds(Origin, SoundTable) end) @@ -321,10 +322,10 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) net.Receive("ACF_Sounds_Adjustable_Multi", function() --print("Received " .. len .. " bits from \"ACF_Sounds_Adjustable_Multi\" for sound updates!") -- Debug print local Origin = net.ReadEntity() - if not IsValid(Origin) then return end - local ShouldStop = net.ReadBool() + if not IsValid(Origin) then return end + -- Do we really need to remove every existing sound when the engine just turns off? if ShouldStop then Sounds.DeleteMultipleAdjustableSounds(Origin) From e4cc28e4ba195642fc54855a6bd4cbdddc8b8474 Mon Sep 17 00:00:00 2001 From: TeeMeeFe <39315813+TeeMeeFe@users.noreply.github.com.> Date: Thu, 12 Mar 2026 00:16:26 -0300 Subject: [PATCH 59/59] Move this here --- lua/acf/core/utilities/sounds/sounds_cl.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/acf/core/utilities/sounds/sounds_cl.lua b/lua/acf/core/utilities/sounds/sounds_cl.lua index 151148780..2db7a3188 100644 --- a/lua/acf/core/utilities/sounds/sounds_cl.lua +++ b/lua/acf/core/utilities/sounds/sounds_cl.lua @@ -237,8 +237,6 @@ local function DoPitchVolumeAtRPM(Origin, Throttle, RPM) end do -- Multiple Engine Sounds(ex. Interpolated sounds) - local IsValid = IsValid -- Should this stay as local to each scope? - --- Creates many sounds from a table, and stores their entries in the Origin's entity. --- Reuses existing methods to create and update sounds. --- @param Origin table The entity to play the sounds from @@ -273,6 +271,7 @@ do -- Multiple Engine Sounds(ex. Interpolated sounds) end) end + local IsValid = IsValid --- Stops all the existing sounds from the entity --- @param Origin table The entity to stop all the sounds from function Sounds.DeleteMultipleAdjustableSounds(Origin, _)