Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
35f4105
The social emote is played locally and metadata loaded into memory
QThund Sep 22, 2025
9d97122
Merge branch 'feat/social-emotes/main' into feat/social-emotes/playin…
QThund Sep 23, 2025
fe7ec80
Fixed compilation error
QThund Sep 24, 2025
8b7ba46
DTOs adapted to new ADR format
QThund Sep 24, 2025
dedc7bc
Added extra to PlayerEmote DTO, needed to replicate social emotes amo…
QThund Sep 25, 2025
e8c3e14
Connection between input and comms + Replicating animations across cl…
QThund Sep 27, 2025
2514549
Debug artifacts UNDO BEFORE MERGING
QThund Sep 27, 2025
4b41d73
UNDO THIS
QThund Sep 27, 2025
1aedcb1
Avatars animate synchronously when interacting with a social emote
QThund Sep 28, 2025
4de5a46
EmoteDTO updated to match new JSON definition + Audio implementation
QThund Oct 3, 2025
10b438c
Updated protocol
QThund Oct 3, 2025
49b3758
Moving the receiver avatar to its original position
QThund Oct 6, 2025
0d0ab62
Fixed most of the bugs, main interaction should work always
QThund Oct 7, 2025
1310809
Merge branch 'feat/social-emotes/main' into feat/social-emotes/playin…
QThund Oct 7, 2025
5f55fe3
Protocol updated
QThund Oct 7, 2025
9506e57
Fixed: Authentication screen was not progressing due to avatar animat…
QThund Oct 8, 2025
7345694
Missing changes
QThund Oct 8, 2025
2483e14
TONS of log traces
QThund Oct 8, 2025
602acaf
MORE Log traces
QThund Oct 9, 2025
ee10869
MORE logsss
QThund Oct 9, 2025
3dfda1f
MORE logs and minimal stripping
QThund Oct 9, 2025
6f6ec87
Fixed: Downloaded AudioClip had no name in builds
QThund Oct 9, 2025
9c2ee36
Fix: Changed how the URL is read
QThund Oct 9, 2025
5fe9542
MORE log
QThund Oct 9, 2025
b455c06
Merge branch 'feat/social-emotes/main' into feat/social-emotes/playin…
QThund Oct 10, 2025
28de0bc
Protocol updated
QThund Oct 10, 2025
cec2340
Merge branch 'feat/social-emotes/main' into feat/social-emotes/playin…
QThund Oct 14, 2025
eaf9b74
Now the Emotes Wheel shows the proper icon in the slots for social em…
QThund Oct 14, 2025
92c2756
All debug log traces replaces with ReportHub
QThund Oct 14, 2025
15ad2bf
Undone changes of the reports handling settings...
QThund Oct 14, 2025
cbe4d48
Revert "MORE logs and minimal stripping"
QThund Oct 14, 2025
2ba5efa
Enabled EMOTE_DEBUG in the ReportsHandlingSettings
QThund Oct 14, 2025
01aa99e
Cleaning and documenting
QThund Oct 15, 2025
5fb258c
Fixed a problem when loading a social emote without audios
QThund Oct 15, 2025
9657600
Fixed: Start audio wasnt playing
QThund Oct 15, 2025
029317b
Fixed: The social emotes were not working in the backpack
QThund Oct 17, 2025
9f41134
Merge branch 'feat/social-emotes/main' into feat/social-emotes/playin…
QThund Oct 22, 2025
ab90f2c
Fixed: Emotes are not playing when they need to be downloaded and the…
QThund Oct 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3572,6 +3572,7 @@ MonoBehaviour:
<RightLegAnchorPoint>k__BackingField: {fileID: 7773640597938076529}
<RightFootAnchorPoint>k__BackingField: {fileID: 8530642813781257132}
<RightToeBaseAnchorPoint>k__BackingField: {fileID: 3392771611989602023}
<ArmatureObject>k__BackingField: {fileID: 174017465429567360}
nametagMaxOffset: 2
nametagBoundedOffset: 1
nametagBuffer: 0.025
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using CommunicationData.URLHelpers;
using DCL.AvatarRendering.Wearables.Components.Intentions;
using DCL.Diagnostics;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
Expand All @@ -14,7 +15,6 @@ public class AvatarBase : MonoBehaviour, IAvatarView
public int RandomID;

private List<KeyValuePair<AnimationClip, AnimationClip>> animationOverrides;
private AnimationClip lastEmote;

private AnimatorOverrideController overrideController;

Expand Down Expand Up @@ -94,6 +94,7 @@ public class AvatarBase : MonoBehaviour, IAvatarView
[field: SerializeField] public Transform RightLegAnchorPoint { get; private set; }
[field: SerializeField] public Transform RightFootAnchorPoint { get; private set; }
[field: SerializeField] public Transform RightToeBaseAnchorPoint { get; private set; }
[field: SerializeField] public Transform ArmatureObject { get; private set; }

[Header("NAMETAG RELATED")]
[SerializeField] [Tooltip("How high could nametag be, [m]")]
Expand All @@ -107,6 +108,8 @@ [SerializeField] [Tooltip("Small buffer to have some air/space between nametag a
[SerializeField] private Transform[] potentialHighestBones;
private float cachedHeadWearableOffset; // Cached offset from head bone to the highest point of head wearables (like tall hats). Updated when wearables change.

private string originalArmatureName;

private void Awake()
{
if (!AvatarAnimator)
Expand All @@ -121,6 +124,9 @@ private void Awake()
// to avoid setting all animations to 'null' after replacing an emote, we set all overrides to their original clips
animationOverrides = animationOverrides.Select(a => new KeyValuePair<AnimationClip, AnimationClip>(a.Key, a.Key)).ToList();
overrideController.ApplyOverrides(animationOverrides);
AvatarAnimator.runtimeAnimatorController = overrideController; // This fixes a problem when executing a social emote before the overrideController was set (the avatar got the T pose)

originalArmatureName = ArmatureObject.name;
}

public Transform GetTransform() =>
Expand Down Expand Up @@ -159,6 +165,21 @@ public void ResetAnimatorTrigger(int hash)
AvatarAnimator.ResetTrigger(hash);
}

/// <summary>
/// Replaces the name of the Armature object with the name it had originally.
/// The name of the Armature has to be changed in order to make it work with social emote outcome reaction animations.
/// </summary>
public void RestoreArmatureName()
{
if (ArmatureObject.name != originalArmatureName)
{
ArmatureObject.name = originalArmatureName;

// This is necessary for the animation to work after the name of the armature has been replaced
overrideController.ApplyOverrides(animationOverrides);
}
}

public bool GetAnimatorBool(int hash) =>
AvatarAnimator.GetBool(hash);

Expand All @@ -173,14 +194,22 @@ public void SetAnimatorBool(int hash, bool value)
public float GetAnimatorFloat(int hash) =>
AvatarAnimator.GetFloat(hash);

public void ReplaceEmoteAnimation(AnimationClip animationClip)
/// <summary>
/// Replaces the animation clip of the emote state in the animator.
/// </summary>
/// <param name="animationClip">The clip to be stored.</param>
/// <param name="armatureNameOverride">Optional. The name of the Armature object will be replaced with this. Call <see cref="RestoreArmatureName"/> to undo.</param>
public void ReplaceEmoteAnimation(AnimationClip animationClip, string? armatureNameOverride = null)
{
if (lastEmote == animationClip) return;
if (overrideController["Emote"] == animationClip)
return;

if(!string.IsNullOrEmpty(armatureNameOverride))
ArmatureObject.name = armatureNameOverride;

overrideController["Emote"] = animationClip;
AvatarAnimator.runtimeAnimatorController = overrideController;

lastEmote = animationClip;
AvatarAnimator.enabled = true;
}

Expand Down Expand Up @@ -245,7 +274,12 @@ public interface IAvatarView

bool GetAnimatorBool(int hash);

void ReplaceEmoteAnimation(AnimationClip animationClip);
/// <summary>
/// Replaces the animation clip of the emote state in the animator.
/// </summary>
/// <param name="animationClip">The clip to be stored.</param>
/// <param name="armatureNameOverride">Optional. The name of the Armature object will be replaced with this. Call <see cref="RestoreArmatureName"/> to undo.</param>
void ReplaceEmoteAnimation(AnimationClip animationClip, string? armatureNameOverride = null);

float GetAnimatorFloat(int hash);

Expand All @@ -254,5 +288,11 @@ public interface IAvatarView
int GetAnimatorCurrentStateTag();

void ResetAnimatorTrigger(int hash);

/// <summary>
/// Replaces the name of the Armature object with the name it had originally.
/// The name of the Armature has to be changed in order to make it work with social emote outcome reaction animations.
/// </summary>
void RestoreArmatureName();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using CommunicationData.URLHelpers;
using DCL.Diagnostics;
using UnityEngine;
using Utility.Animations;

namespace DCL.AvatarRendering.Emotes
Expand All @@ -10,6 +12,12 @@ public struct CharacterEmoteComponent
public EmoteReferences? CurrentEmoteReference;
public int CurrentAnimationTag;
public bool StopEmote;
public EmoteDTO.EmoteMetadataDto? Metadata;
public bool IsPlayingSocialEmoteOutcome;
public int CurrentSocialEmoteOutcome;
public bool IsReactingToSocialEmote;
public string SocialEmoteInitiatorWalletAddress;
public bool HasOutcomeAnimationStarted;

public float PlayingEmoteDuration => CurrentEmoteReference?.avatarClip
? CurrentEmoteReference.avatarClip.length * CurrentEmoteReference.animatorComp!.speed
Expand Down Expand Up @@ -38,9 +46,16 @@ public readonly bool IsPlayingEmote

public void Reset()
{
ReportHub.Log(ReportCategory.EMOTE_DEBUG, "-emote Reset-");
EmoteLoop = false;
CurrentEmoteReference = null;
StopEmote = false;
Metadata = null;
IsPlayingSocialEmoteOutcome = false;
CurrentSocialEmoteOutcome = -1;
IsReactingToSocialEmote = false;
SocialEmoteInitiatorWalletAddress = string.Empty;
HasOutcomeAnimationStarted = false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,25 @@ public enum TriggerSource

public struct CharacterEmoteIntent
{
public string WalletAddress;
public URN EmoteId;
public bool Spatial;
public TriggerSource TriggerSource;
public bool UseSocialEmoteOutcomeAnimation;
public int SocialEmoteOutcomeIndex;
public bool UseOutcomeReactionAnimation;
public string SocialEmoteInitiatorWalletAddress;

public void UpdateRemoteId(URN emoteId)
{
this.WalletAddress = string.Empty;
this.EmoteId = emoteId;
this.Spatial = true;
this.TriggerSource = TriggerSource.REMOTE;
this.UseSocialEmoteOutcomeAnimation = false;
this.SocialEmoteOutcomeIndex = -1;
this.UseOutcomeReactionAnimation = false;
this.SocialEmoteInitiatorWalletAddress = string.Empty;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,33 @@ namespace DCL.AvatarRendering.Emotes
{
public class EmoteReferences : MonoBehaviour
{
public struct EmoteOutcome
{
public AnimationClip? LocalAvatarAnimation;
public AnimationClip? OtherAvatarAnimation;
public AnimationClip? PropAnimation;
public int PropAnimationHash;
}

public int propClipHash { get; private set; }
public AnimationClip? avatarClip { get; private set; }
public AnimationClip? propClip { get; private set; }
public Animator? animatorComp { get; private set; }
public Animation? animationComp { get; private set; }
public bool legacy { get; private set; }
public EmoteOutcome[]? socialEmoteOutcomes { get; private set; }

public AudioSource? audioSource;

public void Initialize(AnimationClip? animationClip, AnimationClip? propCLip, Animator? animatorComp, Animation? animationComp, int propClipHash, bool legacy)
public void Initialize(AnimationClip? animationClip, AnimationClip? propCLip, EmoteOutcome[]? socialEmoteOutcomes, Animator? animatorComp, Animation? animationComp, int propClipHash, bool legacy)
{
this.avatarClip = animationClip;
this.propClip = propCLip;
this.animatorComp = animatorComp;
this.animationComp = animationComp;
this.propClipHash = propClipHash;
this.legacy = legacy;
this.socialEmoteOutcomes = socialEmoteOutcomes;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
using DCL.AvatarRendering.Loading.Components;
using ECS.StreamableLoading.AudioClips;
using ECS.StreamableLoading.Common.Components;
using System.Collections.Generic;

namespace DCL.AvatarRendering.Emotes
{
public interface IEmote : IAvatarAttachment<EmoteDTO>
{
StreamableLoadingResult<AudioClipData>?[] AudioAssetResults { get; }
StreamableLoadingResult<AttachmentRegularAsset>?[] AssetResults { get; }
List<StreamableLoadingResult<AudioClipData>> SocialEmoteOutcomeAudioAssetResults { get; }

bool IsSocial { get; }

bool IsLooping();

Expand Down
22 changes: 18 additions & 4 deletions Explorer/Assets/DCL/AvatarRendering/Emotes/Emote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
using ECS.StreamableLoading.Common.Components;
using ECS.StreamableLoading.Textures;
using SceneRunner.Scene;
using System.Collections.Generic;

namespace DCL.AvatarRendering.Emotes
{
public class Emote : IEmote
{
public StreamableLoadingResult<SceneAssetBundleManifest>? ManifestResult { get; set; }
public StreamableLoadingResult<AttachmentRegularAsset>?[] AssetResults { get; } = new StreamableLoadingResult<AttachmentRegularAsset>?[BodyShape.COUNT];
public bool IsSocial => ((EmoteDTO.EmoteMetadataDto)this.DTO.Metadata).IsSocialEmote;
public StreamableLoadingResult<SpriteData>.WithFallback? ThumbnailAssetResult { get; set; }
public StreamableLoadingResult<EmoteDTO> Model { get; set; }
public StreamableLoadingResult<AudioClipData>?[] AudioAssetResults { get; } = new StreamableLoadingResult<AudioClipData>?[BodyShape.COUNT];
public List<StreamableLoadingResult<AudioClipData>> SocialEmoteOutcomeAudioAssetResults { get; } = new ();

public bool IsLoading { get; private set; }

Expand Down Expand Up @@ -43,10 +46,21 @@ public static bool IsOnChain(string id) =>
public override string ToString() =>
((IAvatarAttachment<EmoteDTO>)this).ToString();

public bool IsLooping() =>
//as the Asset is nullable the loop property might be retrieved in situations in which the Asset has not been yet loaded
//to avoid a breaking null reference we provide safe access to the loop property by using the is pattern
Model.Asset is { metadata: { emoteDataADR74: { loop: true } } };
public bool IsLooping()
{
if (IsSocial)
{
// The Armature applies to the avatar that plays the start animation
return Model.Asset is { metadata: { socialEmoteData: { startAnimation: { loop: true } } } };
}
else
{
//as the Asset is nullable the loop property might be retrieved in situations in which the Asset has not been yet loaded
//to avoid a breaking null reference we provide safe access to the loop property by using the is pattern
return Model.Asset is { metadata: { data: { loop: true } } };
}
}


public bool HasSameClipForAllGenders()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,49 @@ namespace DCL.AvatarRendering.Emotes
[Serializable]
public class EmoteDTO : AvatarAttachmentDTO<EmoteDTO.EmoteMetadataDto>
{
[Serializable]
public class EmoteOutcomeDTO
{
public string title;
public bool loop;
public EmoteOutcomeClipsDTO? clips;
public string? audio;
}

[Serializable]
public class EmoteAnimationDTO
{
public string animation;
}

[Serializable]
public class EmoteOutcomeClipsDTO
{
public EmoteAnimationDTO? Armature;
public EmoteAnimationDTO? Armature_Other;
public EmoteAnimationDTO? Armature_Prop;
}

[Serializable]
public class EmoteStartClipsDTO
{
public bool loop;
public EmoteAnimationDTO? Armature;
public EmoteAnimationDTO? Armature_Prop;
public string? audio;
}

[Serializable]
public class EmoteMetadataDto : MetadataBase
{
public bool IsSocialEmote => emoteDataADR287 != null;

// emotes DTO fetched from builder-API use the normal 'data' property
// emotes DTO fetched from realm use 'emoteDataADR74' property...
public Data emoteDataADR74;
public Data data
public Data? emoteDataADR74;
public EmoteDataADR287? emoteDataADR287;

public Data? data
{
get => emoteDataADR74;
set
Expand All @@ -25,13 +61,32 @@ public Data data
}
}

public override DataBase AbstractData => emoteDataADR74;
public EmoteDataADR287? socialEmoteData
{
get => emoteDataADR287;
set
{
emoteDataADR287 = value;
}
}

public override DataBase AbstractData => IsSocialEmote ? emoteDataADR287! : emoteDataADR74!;

[Serializable]
public class Data : DataBase
{
public bool loop;
}

// Data structure extended in ADR-287 to support social emotes
[Serializable]
public class EmoteDataADR287 : DataBase
{
public bool loop;
public bool randomizeOutcomes;
public EmoteStartClipsDTO? startAnimation;
public EmoteOutcomeDTO[]? outcomes;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@ namespace DCL.AvatarRendering.Emotes
{
public class EmotesBus
{
public delegate void SocialEmoteReactionPlayingRequestedDelegate(string initiatorWalletAddress, IEmote emote, int outcomeIndex);

public event Action QuickActionEmotePlayed;
public event SocialEmoteReactionPlayingRequestedDelegate SocialEmoteReactionPlayingRequested;

public void OnQuickActionEmotePlayed()
{
QuickActionEmotePlayed?.Invoke();
}

public void PlaySocialEmoteReaction(string initiatorWalletAddress, IEmote emote, int outcomeIndex)
{
SocialEmoteReactionPlayingRequested?.Invoke(initiatorWalletAddress, emote, outcomeIndex);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading