using System.Runtime.InteropServices; namespace Sandbox; /// /// A model scene object that supports animations and can be rendered within a . /// public sealed partial class SceneModel : SceneObject { public record struct FootstepEvent( int FootId, Transform Transform, float Volume, string AttachmentName ); public record struct GenericEvent( string Type, int Int, float Float, string String, Vector3 Vector ); public record struct SoundEvent( string Name, Vector3 Position, string AttachmentName ); public record struct AnimTagEvent( string Name, AnimTagStatus Status ); /// /// Called when a footstep event happens /// public Action OnFootstepEvent { get; set; } /// /// Called when a generic event happens /// public Action OnGenericEvent { get; set; } /// /// Called when a sound event happens /// public Action OnSoundEvent { get; set; } /// /// Called when a anim tag event happens /// public Action OnAnimTagEvent { get; set; } [UnmanagedFunctionPointer( CallingConvention.StdCall )] internal delegate void AnimEventCallback( IntPtr typeName, float cycle, float time, IntPtr data ); [UnmanagedFunctionPointer( CallingConvention.StdCall )] internal delegate void AnimTagEventCallback( IntPtr pName, int status ); /// /// Enumeration that describes how the AnimGraph tag state changed. Used in . /// public enum AnimTagStatus { /// /// Tag was activated and deactivated on the same frame /// Fired = 0, /// /// The tag has become active /// Start, /// /// The tag has become inactive /// End } DelegateFunctionPointer animEventCallback; DelegateFunctionPointer animTagEventCallback; public void RunPendingEvents() { if ( animNative.PendingAnimationEvents() == 0 ) return; if ( animEventCallback == DelegateFunctionPointer.Null ) animEventCallback = DelegateFunctionPointer.Get( OnAnimationEvent ); // I wonder if this is slow and we should cache it? animNative.RunAnimationEvents( animEventCallback ); } public void DispatchTagEvents() { if ( animTagEventCallback == DelegateFunctionPointer.Null ) animTagEventCallback = DelegateFunctionPointer.Get( OnAnimTagEventCallback ); animNative.DispatchTagEvents( animTagEventCallback ); } void OnAnimTagEventCallback( IntPtr pName, int status ) { string name = Interop.GetString( pName ); try { OnAnimTagEvent?.Invoke( new( name, (AnimTagStatus)status ) ); } catch ( Exception e ) { Log.Warning( e ); } } void OnAnimationEvent( IntPtr typeName, float cycle, float time, IntPtr data ) { string name = Interop.GetString( typeName ); KeyValues3 kv = data; if ( name == "AE_FOOTSTEP" ) { var foot = kv.GetMemberInt( "Foot", 0 ); var attachment = kv.GetMemberString( "attachment" ); var volume = kv.GetMemberFloat( "volume", 1.0f ); var transform = GetAttachment( attachment ) ?? Transform; try { OnFootstepEvent?.Invoke( new( foot, transform, volume, attachment ) ); } catch ( System.Exception e ) { Log.Warning( e ); } return; } if ( name == "AE_CL_BODYGROUP_SET_VALUE" ) { var bodygroup = kv.GetMemberString( "bodygroup" ); var value = kv.GetMemberInt( "value", 0 ); SetBodyGroup( bodygroup, value ); } if ( name == "AE_GENERIC_EVENT" ) { GenericEvent ev = default; ev.Type = kv.GetMemberString( "TypeName" ); ev.Int = kv.GetMemberInt( "Int", 0 ); ev.Float = kv.GetMemberFloat( "Float", 0.0f ); ev.Vector = kv.GetMemberVector( "Vector", Vector3.Zero ); ev.String = kv.GetMemberString( "StringData" ); try { OnGenericEvent?.Invoke( ev ); } catch ( System.Exception e ) { Log.Warning( e ); } return; } if ( name == "AE_CL_PLAYSOUND" || name == "AE_CL_PLAYSOUND_ATTACHMENT" || name == "AE_CL_PLAYSOUND_POSITION" || name == "AE_SV_PLAYSOUND" ) { SoundEvent ev = default; ev.Name = kv.GetMemberString( "name" ); ev.Position = Transform.Position; ev.AttachmentName = kv.GetMemberString( "attachment" ); if ( !string.IsNullOrWhiteSpace( ev.AttachmentName ) && GetAttachment( ev.AttachmentName ) is Transform tx ) { ev.Position = tx.Position; } try { OnSoundEvent?.Invoke( ev ); } catch ( System.Exception e ) { Log.Warning( e ); } return; } // AE_CL_STOPSOUND // AE_CL_PLAYSOUND_LOOPING //Log.Info( $"EVENT {name} / {cycle} / {time}" ); //for ( int i = 0; i < kv.GetMemberCount(); i++ ) //{ // var membername = kv.GetMemberName( i ); // var member = kv.GetMember( i ); // var memberType = member.GetType_Native(); // Log.Info( $" - {i} / {membername} / {memberType}" ); //} } }