TalkingHeadFrame:CloseImmediately() Breaking NPC Dialog

Hello, I maintain an addon that uses this, and when this function is enabled, after some time playing, about 10-15 minutes all NPC dialog will stop playing. If I remove this function or prevent it from firing, this breakage does not happen. This is an addon to suppress talking heads and this issue only appeared in 10.0.7. There is no addon/Lua error being thrown. The hook I’m using is:

function f:OnEvent(event, ...) if event == "PLAYER_LOGIN" then hooksecurefunc(TalkingHeadFrame, "PlayCurrent", close_head); end end

I believe this may be a bug in the game.

close_head is presumably a function in your addon that we can’t see from the code proivided.

My guess is the problem is in there.

Here’s the file itself sorry:

function close_head()
	--Query current zone and subzone when talking head is triggered
	subZoneName = GetSubZoneText();
	zoneName = GetZoneText();
	--Only run this logic if the functionality is turned on
	if ENABLED == 1 then
		--Block the talking head unless its in the whitelist
		if (has_value(WHITELIST, subZoneName) ~= true and has_value(WHITELIST, zoneName) ~= true) then
			--Close the talking head
			TalkingHeadFrame:CloseImmediately();
			if VERBOSE == 1 then
				print("BeQuiet blocked a talking head! /bq verbose to turn this alert off.")
			end
		end
	end
end

It seems other functions such as Close() and IgnoreCurrentTalkingHead() also break dialog

Further update, calling:

StopSound(TalkingHeadFrame.voHandle)

At the moment PlayCurrent fires then the dialog will break, however:

RunNextFrame(function() StopSound(TalkingHeadFrame.voHandle) end);

By delaying it one frame it has essentially the same effect but does not break dialog. I’m guessing there is some race condition happening now in 10.0.7.

C_TalkingHead.IgnoreCurrentTalkingHead() appears to be the issue. If it’s run to soon after PlayCurrent() it causes the dialog sound to break (which probably makes it appear that StopSound is broken).

Probably worth logging as a possible bug.

I reported it in-game, hopefully they’ll see it

You could probably run your addon with something like:

--Create the frame
local f = CreateFrame("Frame")

local PlayCurrent = TalkingHeadFrame.PlayCurrent

TalkingHeadFrame.PlayCurrent = function(self)
	--function close_head()
	--Query current zone and subzone when talking head is triggered
	subZoneName = GetSubZoneText();
	zoneName = GetZoneText();
	--Only run this logic if the functionality is turned on
	if ENABLED == 1 then
		--Block the talking head unless its in the whitelist
		if (has_value(WHITELIST, subZoneName) == true and has_value(WHITELIST, zoneName) == true) then
			PlayCurrent(self)
			return
		end
		--Close the talking head
		if VERBOSE == 1 then
			print("BeQuiet blocked a talking head! /bq verbose to turn this alert off.")
		end
	end
end

--Main function
--function f:OnEvent(event, ...)
--	if event == "PLAYER_LOGIN" then
--		hooksecurefunc(TalkingHeadFrame, "PlayCurrent", close_head);
--	end
--end

This is still an issue as of 10.1.5, calling this method breaks npc dialog immediately, the current workaround is to do:

function block_head()
    --Close the talking head
    --TalkingHeadFrame:CloseImmediately(); pre 10.0.7
    TalkingHeadFrame:Hide()
    if TalkingHeadFrame.voHandle ~= nil and VO_ENABLED == 0 then
	    C_Timer.After(0.025, function() StopSound(TalkingHeadFrame.voHandle) end);
    end
    if VERBOSE == 1 then
	    print("BeQuiet blocked a talking head! /bq verbose to turn this alert off.")
    end
end

which for some users yields a lot of errors such as

42x BeQuiet/BeQuiet.lua:70: Usage: StopSound(soundHandleID, [optional: fadeout time in ms])

[string "=[C]"]: in function `StopSound'

[string "@BeQuiet/BeQuiet.lua"]:70: in function 

which i can never reproduce, i expect this is a client performance/race condition issue

Maybe by the time the C_Timer.After runs the sound has finished playing/been cancelled and TalkingHeadFrame.voHandle has been set to nil. Possibly check if voHandle is not nil before actually running StopSound().

Just a thought.

in that function we do only fire if if the vo handle is not nil and the custom option to play audio is permitted.

In the code you posted, you check for TalkingHeadFrame.voHandle being nil and then call C_Timer.After(…)

That gives control back to the sytem and a whopping 0.025 seconds during which anything can happen before the C_Timer function actually runs.

One of those things that might happen is that talking head code finds out the sound has been cancelled and resetting TalkingHeadFrame.voHandle to nil (any lua code running when C_Timer is due to run your function will have to complete before your function can be called).

 C_Timer.After(0.025, function() if TalkingHeadFrame.voHandle then StopSound(TalkingHeadFrame.voHandle) end end);

Makes sure there is still a handle to cancel.

The error:

Usage: StopSound(soundHandleID, [optional: fadeout time in ms])

Is telling you it’s not getting anything in the first parameter as that is the only one that is not optional.

I see, the reason for the delay was that immediately cancelling it was also breaking npc dialog, the change you suggest does seem more elegant since im not really GOOD at lua/wow api

You can still use the original check. if TalkingHeadFrame.voHandle is already nil, you shouldn’t need the C_Timer.After.