-
Notifications
You must be signed in to change notification settings - Fork 333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Will be a separate addon] Wire Advanced Microphone and Speaker #2722
Closed
+537
−0
Closed
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
041bc2d
Advanced Microphone and Speaker work
stepa2 f0fc23d
Implemented EntityEmitSound hook handling
stepa2 71421a8
Added player voice reproduction support to microphones/speakers
stepa2 bd9b711
Microphone/speaker fixes
stepa2 893e065
Merge remote-tracking branch 'remotes/origin-real/master' into pr-mic…
stepa2 06ad271
Linter pass
stepa2 161982c
Added duplication support, added tools
stepa2 4121d71
Adv. Microphone / Speaker fixes
stepa2 1dd6c9a
Added WireLib.Sound.IsLooped and WireLib.Sound.StripPrefix, added tem…
stepa2 cef9f6c
Fixed WireLib.Sound.IsLooped
stepa2 b8459ad
Code style fixes
stepa2 95bdfe7
Removed debug log in soundlib.lua
stepa2 93d6543
Added sound.Play listening support
stepa2 e09681a
Lint fixes
stepa2 d99ce25
Removed debug logging
stepa2 319a56b
Merge commit 'b0fa290438124518ae46813cd21bb2dcb168ff2c' into pr-micro…
stepa2 40f6a7d
Fixed error on microphone dupe pasting
stepa2 3c84dcc
Fixed `PlayerCanHearPlayersVoice` hook
b852f6f
Merge branch 'pr-microphone' of https://github.com/conred-gmod/wire i…
d6f7b21
Linter pass, removed debug logging, small comment improvement
stepa2 6ca401f
Added some error handling
stepa2 cf9011e
Fixed Wire_SoundPlay hook not replacing nils with default values
stepa2 94ded38
Voice transmission optimization
stepa2 861233d
Added default values for EntityEmitSound hook calling microphone Hand…
stepa2 c3df358
Added comments for `Mic_SetLive` logic
stepa2 ef4cb58
Fixed `sound.Play` microphone/speaker support
stepa2 122bb75
Possibly fixed ReproduceSound error
stepa2 f40197e
Added fallback position provider for microphone
stepa2 1b46615
Added nearby players cache to microphone-speaker system, moved some f…
stepa2 9c5e1b3
Merge remote-tracking branch 'remotes/origin-master/master' into pr-m…
stepa2 c778e8f
Linter pass
stepa2 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
AddCSLuaFile() | ||
|
||
local MIN_VOLUME = 0.02 | ||
|
||
ENT.Type = "anim" | ||
ENT.Base = "base_wire_entity" | ||
ENT.Author = "stpM64" | ||
ENT.PrintName = "Wire Advanced Microphone" | ||
ENT.Purpose = "Listens to sounds, soundscapes and player voices" | ||
-- Named 'advanced' because 'gmod_wire_microphone' exists in Wire Extras | ||
ENT.WireDebugName = "Advanced Microphone" | ||
|
||
-- Note: we listen both serverside and clientside, | ||
-- because some sounds are played clientside only | ||
|
||
-- array(Entity(gmod_wire_adv_microphone)) | ||
-- Array instead of lookup table because sounds are emitted more often than microphones switched on or off, | ||
-- so iteration is more frequent than insertion/removal. | ||
-- Microphone is live when it is active and at least one active speaker connected to it. | ||
_WireLiveMicrophones = _WireLiveMicrophones or {} | ||
local LiveMics = _WireLiveMicrophones | ||
|
||
function ENT:SetupDataTables() | ||
self:NetworkVar("Bool", 0, "Active") | ||
self:NetworkVarNotify("Active", self.OnActiveChanged) | ||
end | ||
|
||
function ENT:Initialize() | ||
if SERVER then | ||
self:PhysicsInit( SOLID_VPHYSICS ) | ||
self:SetMoveType( MOVETYPE_VPHYSICS ) | ||
self:SetSolid( SOLID_VPHYSICS ) | ||
|
||
self.Inputs = WireLib.CreateInputs(self, { | ||
"Active" | ||
}) | ||
|
||
self._plyCache = WireLib.Sound.NewPlayerDistanceCache(self, WireLib.Sound.VOICE_MAXDIST_SQR) | ||
end | ||
|
||
-- table(Entity(gmod_wire_adv_speaker), true) | ||
self._activeSpeakers = {} | ||
|
||
-- Callback not called if 'Active' changes right after creation. | ||
self:OnActiveChanged(nil, nil, self:GetActive()) | ||
end | ||
|
||
if SERVER then | ||
function ENT:Think() | ||
if not self:GetLive() then return end | ||
|
||
self._plyCache:Think() | ||
end | ||
end | ||
|
||
local function PlayerCanHearPlayersVoice_Hook(listener, talker) | ||
-- Note: any given speaker can only be connected to one microphone, | ||
-- so this loops can be considered O(nMic), not O(nMic*nSpeaker) | ||
for _, mic in ipairs(LiveMics) do | ||
if not mic._plyCache.PlayersInRange[talker] then goto mic_next end | ||
|
||
for speaker in pairs(mic._activeSpeakers) do | ||
if IsValid(speaker) and speaker._plyCache.PlayersInRange[listener] then | ||
return true, false -- Can hear, not in 3D | ||
end | ||
end | ||
::mic_next:: | ||
end | ||
end | ||
|
||
local function Mic_SetLive(self, isLive) | ||
if not IsValid(self) then | ||
isLive = false | ||
else | ||
if self:GetLive() == isLive then return end | ||
self:AddEFlags(EFL_FORCE_CHECK_TRANSMIT) | ||
end | ||
|
||
if isLive then | ||
if LiveMics[1] == nil then -- Adding first microphone to live list | ||
hook.Add("PlayerCanHearPlayersVoice", "Wire.AdvMicrophone", PlayerCanHearPlayersVoice_Hook) | ||
end | ||
|
||
if not table.HasValue(LiveMics, self) then | ||
stepa2 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
table.insert(LiveMics, self) | ||
end | ||
else | ||
if LiveMics[1] ~= nil and LiveMics[2] == nil then -- Removing last microphone from live list | ||
hook.Remove("PlayerCanHearPlayersVoice", "Wire.AdvMicrophone") | ||
end | ||
|
||
table.RemoveByValue(LiveMics, self) | ||
end | ||
end | ||
|
||
ENT.SetLive = Mic_SetLive | ||
|
||
function ENT:UpdateTransmitState() | ||
return self:GetLive() and TRANSMIT_ALWAYS or TRANSMIT_PVS | ||
end | ||
|
||
function ENT:GetLive() | ||
return self:GetActive() and not table.IsEmpty(self._activeSpeakers) | ||
end | ||
|
||
function ENT:OnActiveChanged(_,_,active) | ||
if self._activeSpeakers == nil then return end -- This happens on duplication restoration | ||
if table.IsEmpty(self._activeSpeakers) then return end | ||
self:SetLive(active) | ||
end | ||
|
||
function ENT:TriggerInput( name, value ) | ||
if name == "Active" then | ||
self:SetActive(value ~= 0) | ||
end | ||
end | ||
|
||
function ENT:SpeakerActivated(speaker) | ||
if not IsValid(speaker) then return end | ||
|
||
if self:GetActive() then | ||
-- Must be updated before ._activeSpeakers are updated | ||
self:SetLive(true) | ||
end | ||
self._activeSpeakers[speaker] = true | ||
end | ||
|
||
function ENT:SpeakerDeactivated(speaker) | ||
if self:GetActive() then | ||
local live = true | ||
do | ||
local spk = self._activeSpeakers | ||
|
||
local k1 = next(spk) | ||
if k1 == nil then -- No active speakers | ||
live = false | ||
else | ||
local k2 = next(spk) | ||
if k2 == nil and k1 == speaker then -- The only active speaker is 'speaker' | ||
live = false | ||
end | ||
end | ||
end | ||
|
||
-- Must be updated before ._activeSpeakers are updated | ||
self:SetLive(live) | ||
end | ||
self._activeSpeakers[speaker] = nil | ||
end | ||
|
||
function ENT:OnRemove() | ||
timer.Simple(0, function() | ||
if IsValid(self) then return end | ||
|
||
Mic_SetLive(self, false) | ||
end) | ||
end | ||
|
||
local CalculateDistanceGain = WireLib.Sound.CalculateDistanceGain | ||
|
||
hook.Add("EntityEmitSound", "Wire.AdvMicrophone", function(snd) | ||
for _, mic in ipairs(LiveMics) do | ||
if IsValid(mic) then | ||
mic:HandleSound( | ||
snd.SoundName, snd.Volume or 1, snd.Pitch or 100, snd.SoundLevel or 75, | ||
snd.Entity, snd.Pos, snd.DSP or 0, | ||
"EmitSound" | ||
) | ||
end | ||
end | ||
end) | ||
|
||
hook.Add("Wire_SoundPlay", "Wire.AdvMicrophone", function(name, pos, level, pitch, volume) | ||
for _, mic in ipairs(LiveMics) do | ||
if IsValid(mic) then | ||
mic:HandleSound( | ||
name, volume, pitch, level, | ||
nil --[[entity]], pos, 0 --[[DSP]], | ||
"sound.Play" | ||
) | ||
end | ||
end | ||
end) | ||
|
||
function ENT:HandleSound(sndname, volume, pitch, sndlevel, entity, pos, dsp, emittype) | ||
-- Prevent feedback loops | ||
if IsValid(entity) and entity:GetClass() == "gmod_wire_adv_speaker" then return end | ||
if pos == nil and IsValid(entity) then pos = entity:GetPos() end | ||
|
||
if sndlevel ~= 0 and pos ~= nil then | ||
-- Over-256 values are 'reserved for sounds using goldsrc compatibility attenuation' | ||
-- I don't care about correct attenuation for HLSource entities, | ||
-- but I don't want the system to break. | ||
if sndlevel >= 256 then sndlevel = sndlevel - 256 end | ||
|
||
volume = volume * CalculateDistanceGain( | ||
self:GetPos():Distance(pos), sndlevel) | ||
end | ||
|
||
if volume < MIN_VOLUME then return end | ||
if volume > 1 then volume = 1 end | ||
|
||
self:ReproduceSound(sndname, volume, pitch, dsp, emittype) | ||
end | ||
|
||
function ENT:ReproduceSound(snd, vol, pitch, dsp, emittype) | ||
for speaker in pairs(self._activeSpeakers) do | ||
if IsValid(speaker) then | ||
speaker:ReproduceSound(snd, vol, pitch, dsp, emittype) | ||
end | ||
end | ||
end | ||
|
||
-- TODO: hook into sound.PlayFile | ||
-- TODO: hook into sound.PlayURL | ||
|
||
|
||
duplicator.RegisterEntityClass("gmod_wire_adv_microphone", WireLib.MakeWireEnt, "Data") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
AddCSLuaFile() | ||
|
||
ENT.Type = "anim" | ||
ENT.Base = "base_wire_entity" | ||
ENT.Author = "stpM64" | ||
ENT.PrintName = "Wire Advanced Speaker" | ||
ENT.Purpose = "Reproduces sounds, soundscapes and player voices listened by Advanced Microphone" | ||
ENT.WireDebugName = "Advanced Speaker" | ||
|
||
-- TODO: stop currently played EmitSound sounds on deactivation | ||
|
||
function ENT:SetupDataTables() | ||
self:NetworkVar("Bool", 0, "Active") | ||
self:NetworkVarNotify("Active", self.OnActiveChanged) | ||
self:NetworkVar("Entity", 0, "Microphone") | ||
self:NetworkVarNotify("Microphone",self.OnMicrophoneChanged) | ||
end | ||
|
||
if SERVER then | ||
function ENT:Initialize() | ||
self:PhysicsInit( SOLID_VPHYSICS ) | ||
self:SetMoveType( MOVETYPE_VPHYSICS ) | ||
self:SetSolid( SOLID_VPHYSICS ) | ||
|
||
self.Inputs = WireLib.CreateInputs(self, { | ||
"Active", | ||
"Microphone (Must be Wire Advanced Microphone to work) [ENTITY]" | ||
}) | ||
|
||
self:OnMicrophoneChanged(nil, nil, self:GetMicrophone()) | ||
|
||
self._plyCache = WireLib.Sound.NewPlayerDistanceCache(self, WireLib.Sound.VOICE_MAXDIST_SQR) | ||
end | ||
|
||
function ENT:Think() | ||
if not self:GetActive() then return end | ||
|
||
local mic = self:GetMicrophone() | ||
if not IsValid(mic) or not mic:GetActive() then return end | ||
|
||
self._plyCache:Think() | ||
end | ||
end | ||
|
||
function ENT:TriggerInput( name, value ) | ||
if name == "Active" then | ||
self:SetActive(value ~= 0) | ||
elseif name == "Microphone" then | ||
if not (IsValid(value) and value:GetClass() == "gmod_wire_adv_microphone") then | ||
value = nil | ||
end | ||
|
||
self:SetMicrophone(value) | ||
end | ||
end | ||
|
||
function ENT:OnActiveChanged(_, oldactive, active) | ||
if oldactive == active then return end | ||
|
||
local mic = self:GetMicrophone() | ||
if not IsValid(mic) then return end | ||
|
||
if active then | ||
mic:SpeakerActivated(self) | ||
else | ||
mic:SpeakerDeactivated(self) | ||
end | ||
end | ||
|
||
function ENT:OnMicrophoneChanged(_, oldmic, newmic) | ||
if self:GetActive() and oldmic ~= newmic then | ||
if IsValid(oldmic) then | ||
oldmic:SpeakerDeactivated(self) | ||
end | ||
|
||
if IsValid(newmic) then | ||
newmic:SpeakerActivated(self) | ||
end | ||
end | ||
end | ||
|
||
function ENT:OnRemove() | ||
local mic = self:GetMicrophone() | ||
if not IsValid(mic) then return end | ||
|
||
timer.Simple(0, function() | ||
if IsValid(self) or not IsValid(mic) then return end | ||
mic:SpeakerDeactivated(self) | ||
end) | ||
end | ||
|
||
function ENT:ReproduceSound(snd, vol, pitch, dsp, emittype) | ||
if not self:GetActive() then return end | ||
|
||
if WireLib.Sound.IsLooped(WireLib.Sound.StripPrefix(snd)) then | ||
return | ||
end | ||
|
||
|
||
local soundlevel = 75 | ||
if emittype == "EmitSound" then | ||
self:EmitSound(snd, soundlevel, pitch, vol, nil, nil, dsp) | ||
elseif emittype == "sound.Play" then | ||
sound.Play_NoWireHook(snd, self:GetPos(), soundlevel, pitch, vol) | ||
else | ||
ErrorNoHalt("Invalid emittype: ", emittype,"\n --sound ",snd) | ||
end | ||
end | ||
|
||
duplicator.RegisterEntityClass("gmod_wire_adv_speaker", WireLib.MakeWireEnt, "Data") |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any notable difference in speed between ipairs and pairs in this case with a small amount of items?
Cause I'd really prefer a lookup table over using
table.RemoveByValue
andtable.insert
. Even if I know there won't be more than maybe ten microphones placed in a server at once. At worst you could maintain both a lookup table and an array for the best of both worlds.I know ipairs can jit but that's just on the x86_64 branch. Although you could use a manual for loop, but you aren't doing that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can only agree on this. And I just don't get it why it such a problem to just change it. It is not like you are going to need to add like extra 1000 lines of code for it.