Understanding GTA V Animation Dictionaries
Animations in GTA V are organized into animation dictionaries, which are collections of related animation clips. Each animation is identified by two strings: the dictionary name and the animation name within that dictionary. For example, the dictionary amb@world_human_hang_out_street@female_hold_arm@idle_a contains idle standing animations for female peds. Before you can play any animation, you must request the dictionary using RequestAnimDict and wait until it has finished loading with HasAnimDictLoaded. Failing to request the dictionary first is one of the most common mistakes new developers make, resulting in animations that silently fail to play. GTA V ships with thousands of animation dictionaries covering everything from combat stances to yoga poses, and discovering useful ones is half the challenge of building a comprehensive emote menu.
Playing and Managing Animations
The core function for playing animations is TaskPlayAnim, which accepts the player ped, dictionary, animation name, blend-in speed, blend-out speed, duration, flags, and playback rate. The animation flags control critical behavior: flag 1 loops the animation, flag 2 stops the animation on the last frame, flag 16 plays only the upper body allowing the player to walk, and flag 32 allows rotation during the animation. Combining flags with bitwise OR lets you create nuanced behaviors. A sitting emote needs flag 1 for looping plus flag 2 to hold the last frame if interrupted. A waving emote works best with flag 49 which combines upper body only with looping and rotation, letting the player wave while walking around.
-- Core animation helper functions
local function LoadAnimDict(dict)
if HasAnimDictLoaded(dict) then return true end
RequestAnimDict(dict)
local timeout = GetGameTimer() + 5000
while not HasAnimDictLoaded(dict) do
Wait(10)
if GetGameTimer() > timeout then
return false
end
end
return true
end
local function PlayEmote(dict, anim, flags, duration)
local ped = PlayerPedId()
if IsPedInAnyVehicle(ped, false) then return end
if not LoadAnimDict(dict) then return end
flags = flags or 1 -- default: loop
duration = duration or -1 -- -1 = indefinite
TaskPlayAnim(ped, dict, anim, 2.0, 2.0, duration, flags, 0, false, false, false)
end
local function StopCurrentEmote()
local ped = PlayerPedId()
ClearPedTasks(ped)
-- Also remove any attached props
ClearPedSecondaryTask(ped)
end
-- Export for other resources
exports('PlayEmote', PlayEmote)
exports('StopEmote', StopCurrentEmote)
Organizing Emotes into Categories
A well-organized emote menu groups animations into intuitive categories that players can quickly browse. Common categories include Dances, Greetings, Reactions, Poses, Sports, Scenarios (like sitting, leaning, or lying down), Prop Emotes (involving objects like cigarettes or phones), Walking Styles, and Facial Expressions. Each emote entry should store the dictionary, animation name, flags, whether it uses a prop, the prop model hash if applicable, the bone index for prop attachment, and the offset and rotation values for proper prop placement. Storing all of this in a structured configuration table makes it easy to add new emotes without modifying the playback logic.
-- Emote configuration (config.lua)
Config = {}
Config.Emotes = {
dances = {
{label = 'Dance 1', dict = 'anim@amb@nightclub@dancers@club_dance_bar_1', anim = 'high_center', flags = 1},
{label = 'Dance 2', dict = 'anim@amb@nightclub@mini@dance@dance_solo@female@var_a@', anim = 'high_center', flags = 1},
{label = 'Dance 3', dict = 'anim@amb@nightclub@dancers@crowddance_facedj@', anim = 'hi_dance_facedj_09_v2_female^1', flags = 1},
{label = 'Flamenco', dict = 'special_ped@jessie@idle_latina', anim = 'idle_latina', flags = 1},
},
greetings = {
{label = 'Wave', dict = 'friends@frj@ig_1', anim = 'wave_A', flags = 49, duration = 3000},
{label = 'Salute', dict = 'anim@mp_player_intuppersalute', anim = 'idle_a', flags = 49, duration = 3000},
{label = 'Handshake', dict = 'mp_ped_interaction', anim = 'handshake_guy_a', flags = 49, duration = 4000},
{label = 'Fist Bump', dict = 'mp_ped_interaction', anim = 'fist_bump', flags = 49, duration = 3000},
},
props = {
{label = 'Cigarette', dict = 'amb@world_human_smoking@male@male_a@idle_a', anim = 'idle_c', flags = 49,
prop = 'prop_cs_ciggy_01', bone = 28422, pos = {0.0, 0.0, 0.0}, rot = {0.0, 0.0, 0.0}},
{label = 'Coffee', dict = 'amb@world_human_drinking@coffee@male@idle_a', anim = 'idle_c', flags = 49,
prop = 'p_amb_coffeecup_01', bone = 28422, pos = {0.0, 0.0, 0.0}, rot = {0.0, 0.0, 0.0}},
{label = 'Clipboard', dict = 'amb@world_human_clipboard@male@idle_a', anim = 'idle_c', flags = 49,
prop = 'p_amb_clipboard_01', bone = 36029, pos = {0.16, 0.08, 0.03}, rot = {-100.0, 0.0, 0.0}},
},
scenarios = {
{label = 'Sit Chair', scenario = 'PROP_HUMAN_SEAT_CHAIR', isScenario = true},
{label = 'Lean Wall', scenario = 'WORLD_HUMAN_LEANING', isScenario = true},
{label = 'Guard', scenario = 'WORLD_HUMAN_GUARD_STAND', isScenario = true},
{label = 'Jog', scenario = 'WORLD_HUMAN_JOG_STANDING', isScenario = true},
},
}
Prop Emotes and Object Attachment
Prop emotes involve spawning a game object and attaching it to a specific bone on the player's ped. GTA V has a skeletal bone system where each bone has a unique index. Common attachment bones include 57005 for the right hand, 18905 for the left hand, 28422 for the right finger area (ideal for cigarettes), and 31086 for the head. After spawning the prop with CreateObject, attach it using AttachEntityToEntity which requires the prop entity, the ped entity, the bone index, position offsets, rotation offsets, and several boolean flags for collision and physics behavior. Getting the position and rotation offsets right requires trial and error, and different ped models may need slight adjustments. Always clean up props when the emote is cancelled by tracking the spawned entity handle and deleting it.
-- Prop emote system
local currentProp = nil
local function PlayPropEmote(emoteData)
local ped = PlayerPedId()
if IsPedInAnyVehicle(ped, false) then return end
-- Clean up any existing prop
if currentProp and DoesEntityExist(currentProp) then
DeleteEntity(currentProp)
currentProp = nil
end
if not LoadAnimDict(emoteData.dict) then return end
-- Spawn and attach prop
local model = GetHashKey(emoteData.prop)
RequestModel(model)
while not HasModelLoaded(model) do Wait(10) end
local pos = GetEntityCoords(ped)
currentProp = CreateObject(model, pos.x, pos.y, pos.z, true, true, false)
SetModelAsNoLongerNeeded(model)
local boneIndex = GetPedBoneIndex(ped, emoteData.bone)
AttachEntityToEntity(
currentProp, ped, boneIndex,
emoteData.pos[1], emoteData.pos[2], emoteData.pos[3],
emoteData.rot[1], emoteData.rot[2], emoteData.rot[3],
true, true, false, true, 1, true
)
-- Play animation
TaskPlayAnim(ped, emoteData.dict, emoteData.anim,
2.0, 2.0, -1, emoteData.flags or 49, 0, false, false, false)
end
-- Cleanup on emote cancel
local function CleanupProps()
if currentProp and DoesEntityExist(currentProp) then
DeleteEntity(currentProp)
currentProp = nil
end
end
Walking Styles and Movement Clips
Walking styles change how the player character moves and are applied using SetPedMovementClipset. Unlike regular animations that override actions, movement clipsets persist across all locomotion states including walking, running, and sprinting. GTA V includes dozens of built-in movement clipsets such as MOVE_M@DRUNK@MODERATEDRUNK for a drunk stumble, MOVE_M@GANGSTER@NG for a gangster swagger, MOVE_M@CONFIDENT for a confident stride, and MOVE_F@HEELS@C for walking in high heels. Like animation dictionaries, clipsets must be requested and loaded before application. Provide a reset option so players can return to the default walk. Store the player's preferred walking style in their character data so it persists across sessions, reapplying it automatically when they log in.
-- Walking style system
local currentWalkStyle = nil
Config.WalkStyles = {
{label = 'Default', clipset = nil},
{label = 'Drunk', clipset = 'MOVE_M@DRUNK@MODERATEDRUNK'},
{label = 'Gangster', clipset = 'MOVE_M@GANGSTER@NG'},
{label = 'Confident', clipset = 'MOVE_M@CONFIDENT'},
{label = 'Tough', clipset = 'MOVE_M@TOUGH_GUY@'},
{label = 'Femme', clipset = 'MOVE_F@FEMME@'},
{label = 'Heels', clipset = 'MOVE_F@HEELS@C'},
{label = 'Injured', clipset = 'move_m@injured'},
{label = 'Hobo', clipset = 'MOVE_M@HOBO@A'},
{label = 'Brave', clipset = 'MOVE_M@BRAVE@A'},
}
local function SetWalkStyle(clipset)
local ped = PlayerPedId()
ResetPedMovementClipset(ped, 0.2)
if not clipset then
currentWalkStyle = nil
return
end
RequestClipSet(clipset)
local timeout = GetGameTimer() + 3000
while not HasClipSetLoaded(clipset) do
Wait(10)
if GetGameTimer() > timeout then return end
end
SetPedMovementClipset(ped, clipset, 0.2)
currentWalkStyle = clipset
end
exports('SetWalkStyle', SetWalkStyle)
exports('GetWalkStyle', function() return currentWalkStyle end)
Facial Expressions
Facial expressions add subtle emotional depth to roleplay interactions. GTA V supports facial animations through SetFacialIdleAnimOverride, which changes the ped's resting face expression. Available expressions include mood_normal_1, mood_happy_1, mood_angry_1, mood_injured_1, mood_stressed_1, mood_smug_1, and mood_sulk_1 among others. These expressions persist until cleared with ClearFacialIdleAnimOverride. Combining a facial expression with a body animation and a walking style gives players full control over how their character presents themselves. A character leaning against a wall with a smug expression and a confident walk tells a completely different story than one with an injured limp and a stressed face. These small details elevate roleplay from basic interactions to rich character portrayal.
-- Facial expression system
local currentExpression = nil
Config.Expressions = {
{label = 'Normal', expression = 'mood_Normal_1'},
{label = 'Happy', expression = 'mood_Happy_1'},
{label = 'Angry', expression = 'mood_Angry_1'},
{label = 'Injured', expression = 'mood_Injured_1'},
{label = 'Stressed', expression = 'mood_Stressed_1'},
{label = 'Smug', expression = 'mood_smug_1'},
{label = 'Sulking', expression = 'mood_sulk_1'},
{label = 'Sleeping', expression = 'pose_injured_1'},
{label = 'Scared', expression = 'mood_scared_1'},
}
local function SetExpression(expression)
local ped = PlayerPedId()
ClearFacialIdleAnimOverride(ped)
if expression then
SetFacialIdleAnimOverride(ped, expression, nil)
currentExpression = expression
else
currentExpression = nil
end
end
exports('SetExpression', SetExpression)
-- Shared emotes: sync with nearby players
RegisterNetEvent('emote:syncShared', function(senderId, dict, anim)
local senderPed = GetPlayerPed(GetPlayerFromServerId(senderId))
if not DoesEntityExist(senderPed) then return end
local myPed = PlayerPedId()
local dist = #(GetEntityCoords(myPed) - GetEntityCoords(senderPed))
if dist > 3.0 then return end
if not LoadAnimDict(dict) then return end
TaskPlayAnim(myPed, dict, anim, 2.0, 2.0, -1, 1, 0, false, false, false)
end)
Building the Emote Menu Interface
The emote menu can be implemented as either a NUI-based radial menu or an in-game menu using a library like ox_lib or qb-menu. A radial menu feels natural for emotes because players can quickly navigate categories and select an emote without reading through long text lists. Bind the menu to a key like F4 or a configurable keybind, and provide a command fallback like /emote [name] for direct access. The menu should show the emote name, an optional preview thumbnail, and indicate which emotes use props. Include a favorites system that lets players pin their most-used emotes to a quick-access ring, saving them from browsing categories every time they want to perform a common action. Persist favorites in the player's KVP storage using SetResourceKvp so they survive server restarts without needing database writes. A well-built emote menu becomes one of the most-used features on any roleplay server, so investing time in the interface pays dividends in player satisfaction.