Detect when a Player Emotes (works with R6 & R15)

I wanted a script that detects when a Player emotes. I couldn’t find any solutions for this, so I made my own.

Server Script:

local Players = game:GetService("Players")

-- we only use this array for R15 avatars, because R6 avatars do not store these
local defaultDances = {
	"cheer",
	"dance",
	"dance2",
	"dance3",
	"laugh",
	"point",
	"wave"
}

local function urlToId(animationId: string): string
	animationId = string.gsub(animationId, "http://www%.roblox%.com/asset/%?id=", "")
	animationId = string.gsub(animationId, "rbxassetid://", "")
	return animationId
end

local function isGivenAnimation(animationHolder: StringValue, animationId: string): boolean
	for _, animation: Animation in animationHolder:GetChildren() do
		if urlToId(animation.AnimationId) == animationId then
			return true
		end
	end

	return false
end

local function isDancing(character: model, animationTrack: AnimationTrack): boolean
	--[[
	afiak, it isn't possible to obtain animationIds of player Emotes efficiently
	HumanoidDescription:GetEmotes() returns the Asset IDs of emotes, which is not what we want
	instead, this function does a process of elimination to determine if the animationId is a dance animation or not
	
	because everything in defaultDances are not descendants of R6 characters:
	this function will return true if the player does any "/e" emote in R6, such as "/e cheer"
	--]]

	local animationId = urlToId(animationTrack.Animation.AnimationId)
	local isR15 = character.Humanoid.RigType == Enum.HumanoidRigType.R15
	
	-- emotes from the emote wheel are not children of the Animate script,
	-- so we can do a process of elimation to determine if it's an emote
	for _, animationHolder: StringValue in character.Animate:GetChildren() do
		local sharesAnimationId = isGivenAnimation(animationHolder, animationId)
		
		if isR15 then
			local isDance = sharesAnimationId and table.find(defaultDances, animationHolder.Name)
			-- the given animationId is from a default /e emote
			if isDance then
				return true
			end
		end

		if sharesAnimationId then
			-- this is just a normal character animation
			return false
		end
	end
	
	-- the given AnimationId was not a descendant of the Animate script, so it must be an emote
	return true
end

local function onCharacterAdded(character: Model)
	local humanoid: Humanoid = character.Humanoid
	local animator: Animator = humanoid.Animator

	animator.AnimationPlayed:Connect(function(animationTrack)
		print(isDancing(character, animationTrack)) --> boolean
	end)
end

local function onPlayerAdded(player: Player)
	player.CharacterAdded:Connect(onCharacterAdded)
end

for _, player: Player in Players:GetPlayers() do
	task.spawn(onPlayerAdded, player)
end

Players.PlayerAdded:Connect(onPlayerAdded)

This is my first Community Resource. If you have any feedback, let me know.

7 Likes

This detects custom animations, not just emotes!

I’m not sure how to get around this

Sitting here and I can’t believe there is a .EquippedEmotesChanged and not a .EmoteUsed

Cache players’ emotes and do this.

local function onCharacterAdded(character: Model)
	local humanoid: Humanoid = character.Humanoid
	local animator: Animator = humanoid.Animator
	
	for name,v in humanoid:WaitForChild("HumanoidDescription"):GetEmotes() do
		character:SetAttribute(name, game:GetService("InsertService"):LoadAsset(v[1]):FindFirstChildOfClass("Animation").AnimationId)
	end

	animator.AnimationPlayed:Connect(function(animationTrack)
		for name, animationId in character:GetAttributes() do
			if animationId == animationTrack.Animation.AnimationId then
				print(true, name)
			end
		end
	end)
end

For emotes like dance you can cache them separately.
image

1 Like

I wrote this without taking custom animations into consideration. Do you have some sort of cache containing every custom animation?

i.e,

local customAnims = {
	["rbxassetid://12345678"] = true,
	["rbxassetid://910111213"] = true,
	...
}

If so, you could do something like this:

local function isDancing(character: model, animationTrack: AnimationTrack): boolean
	...

	if customAnims[animationTrack.Animation.AnimationId] then
		return false
	end

	-- rest of code
	local animationId = urlToId(animationTrack.Animation.AnimationId)
	...
end

as for the solution proposed by @weakroblox35, loading every single owned emote every time the character spawns is highly inefficient. I see the vision, but I would try my best to avoid relying on API calls for something like this.

the Animate script in the character has this code:

Only possible solution without editing the Animate script from post above.

Appears all emotes are set to Core priority.. which is almost never used for custom animations, at least for my projects. Hacky but works for now.

All solutions to this must be hacky unfortunately. I’d recommend creating a feature request for emote events like “EmoteStarted”, “EmoteEnded”.

2 Likes