2023-09-08 05:38:09 -04:00
2026-02-09 13:54:00 -05:00
# include "Common.h"
2023-09-08 05:38:09 -04:00
# include "AnimQueue.h"
2024-09-30 13:59:17 -04:00
# include "UtilityLibrary.h"
2025-06-18 16:25:46 -04:00
# include "GameFramework/Actor.h"
2026-02-06 17:34:26 -05:00
# include "Components/MeshComponent.h"
# include "Components/StaticMeshComponent.h"
# include "Components/SkeletalMeshComponent.h"
# include "AssetLookup.h"
# include "Materials/MaterialInstanceDynamic.h"
2025-07-02 16:01:18 -04:00
# include <iostream>
2023-09-08 05:38:09 -04:00
2025-10-10 19:03:04 -04:00
FlxAnimationStep : : FlxAnimationStep ( int64 hash , std : : string_view body ) {
2023-10-02 15:48:42 -04:00
Finished = false ;
2023-09-19 22:08:15 -04:00
Hash = hash ;
Body . SetNum ( body . size ( ) ) ;
memcpy ( Body . GetData ( ) , body . data ( ) , body . size ( ) ) ;
2023-10-02 15:48:42 -04:00
Blueprint = UlxAnimationStepLibrary : : AnimationStepGetString ( * this , " bp " ) ;
2023-09-08 05:38:09 -04:00
}
2024-09-30 13:59:17 -04:00
static void ClearProperties ( const FString & prefix , UObject * obj , FProperty * except ) {
2023-09-18 19:24:52 -04:00
UClass * uclass = obj - > GetClass ( ) ;
FName prefixlo ( prefix ) ;
FName prefixhi ( prefix + TEXT ( " \xFFFF " ) ) ;
for ( TFieldIterator < FProperty > It ( uclass ) ; It ; + + It )
{
FProperty * fprop = * It ;
2024-09-30 13:59:17 -04:00
if ( fprop = = except ) continue ;
2023-09-18 19:24:52 -04:00
bool match1 = ( fprop - > GetFName ( ) . Compare ( prefixlo ) > 0 ) ;
bool match2 = ( fprop - > GetFName ( ) . Compare ( prefixhi ) < 0 ) ;
if ( match1 & & match2 ) {
uint8 * pptr = fprop - > ContainerPtrToValuePtr < uint8 > ( obj ) ;
fprop - > ClearValue ( pptr ) ;
}
}
}
2023-10-12 18:15:56 -04:00
# pragma optimize("", off)
static bool SetProperty ( const FString & prefix , UObject * obj , const FlxAnimationField & field ) {
2023-09-18 19:24:52 -04:00
UClass * uclass = obj - > GetClass ( ) ;
2023-10-12 18:15:56 -04:00
FString sname ( field . Name . size ( ) , ( const UTF8CHAR * ) field . Name . data ( ) ) ;
FName nname ( prefix + sname ) ;
2023-09-18 19:24:52 -04:00
switch ( field . Type ) {
2026-02-09 13:54:00 -05:00
case LuaValueType : : STRING : {
2023-09-18 19:24:52 -04:00
FStrProperty * fprop = FindFProperty < FStrProperty > ( uclass , nname ) ;
if ( fprop = = nullptr ) return false ;
FString * pptr = fprop - > ContainerPtrToValuePtr < FString > ( obj ) ;
* pptr = FString ( field . S . size ( ) , ( const UTF8CHAR * ) field . S . data ( ) ) ;
return true ;
}
2026-02-09 13:54:00 -05:00
case LuaValueType : : TOKEN : {
2025-02-05 15:45:48 -05:00
FNameProperty * fprop = FindFProperty < FNameProperty > ( uclass , nname ) ;
if ( fprop = = nullptr ) return false ;
FName * pptr = fprop - > ContainerPtrToValuePtr < FName > ( obj ) ;
* pptr = FName ( field . S . size ( ) , ( const UTF8CHAR * ) field . S . data ( ) , FNAME_Add ) ;
return true ;
}
2026-02-09 13:54:00 -05:00
case LuaValueType : : NUMBER : {
2023-09-18 19:24:52 -04:00
FDoubleProperty * fprop = FindFProperty < FDoubleProperty > ( uclass , nname ) ;
if ( fprop = = nullptr ) return false ;
double * pptr = fprop - > ContainerPtrToValuePtr < double > ( obj ) ;
2023-09-25 14:25:24 -04:00
* pptr = field . X ;
2023-09-18 19:24:52 -04:00
return true ;
}
2026-02-09 13:54:00 -05:00
case LuaValueType : : BOOLEAN : {
2023-09-18 19:24:52 -04:00
FBoolProperty * fprop = FindFProperty < FBoolProperty > ( uclass , nname ) ;
if ( fprop = = nullptr ) return false ;
uint8 * pptr = fprop - > ContainerPtrToValuePtr < uint8 > ( obj ) ;
fprop - > SetPropertyValue ( pptr , ( field . X = = 1.0 ) ) ;
return true ;
}
2026-02-09 13:54:00 -05:00
case LuaValueType : : VECTOR : {
2023-09-18 19:24:52 -04:00
FStructProperty * fprop = FindFProperty < FStructProperty > ( uclass , nname ) ;
if ( fprop = = nullptr ) return false ;
if ( fprop - > Struct ! = TBaseStructure < FVector > : : Get ( ) ) return false ;
FVector * pptr = fprop - > ContainerPtrToValuePtr < FVector > ( obj ) ;
* pptr = FVector ( field . X , field . Y , field . Z ) ;
return true ;
}
}
return false ;
}
2024-09-30 13:59:17 -04:00
static FStructProperty * FindAnimationStepProperty ( UClass * uclass , const FString & prefix ) {
FName nname ( prefix + TEXT ( " Animation Step " ) ) ;
FStructProperty * fprop = FindFProperty < FStructProperty > ( uclass , nname ) ;
if ( fprop = = nullptr ) return nullptr ;
if ( fprop - > Struct ! = TBaseStructure < FlxAnimationStep > : : Get ( ) ) return nullptr ;
return fprop ;
}
2025-11-18 00:40:43 -05:00
static FInt64Property * FindAnimationIDProperty ( UClass * uclass , const FString & prefix ) {
FName nname ( prefix + TEXT ( " Animation ID " ) ) ;
FInt64Property * fprop = FindFProperty < FInt64Property > ( uclass , nname ) ;
return fprop ;
}
2024-09-30 13:59:17 -04:00
void UlxAnimationStepLibrary : : UnpackAnimationStep ( bool & bChanged , FString & Action , const FlxAnimationStep & step , UObject * target , const FString & prefix ) {
bChanged = false ;
Action = TEXT ( " " ) ;
2024-11-22 23:01:05 -05:00
if ( prefix . IsEmpty ( ) )
{
UE_LOG ( LogBlueprint , Error , TEXT ( " UnpackAnimationStep: You may not pass an empty string for prefix " ) ) ;
2024-09-30 13:59:17 -04:00
return ;
}
UClass * uclass = target - > GetClass ( ) ;
std : : string_view body ( ( const char * ) ( step . Body . GetData ( ) ) , step . Body . Num ( ) ) ;
2023-09-19 22:08:15 -04:00
FlxAnimationStepDecoder decoder ( body ) ;
2024-09-30 13:59:17 -04:00
FStructProperty * stepproperty = FindAnimationStepProperty ( uclass , prefix ) ;
if ( stepproperty = = nullptr ) {
2024-11-22 23:01:05 -05:00
UE_LOG ( LogBlueprint , Error , TEXT ( " UnpackAnimationStep: Target object does not have a variable named: '%s Animation Step' " ) , * prefix ) ;
2024-09-30 13:59:17 -04:00
return ;
}
2025-11-18 00:40:43 -05:00
FInt64Property * idproperty = FindAnimationIDProperty ( uclass , prefix ) ;
if ( idproperty = = nullptr ) {
UE_LOG ( LogBlueprint , Error , TEXT ( " UnpackAnimationStep: Target object does not have a variable named: '%s Animation ID' " ) , * prefix ) ;
return ;
}
2024-09-30 13:59:17 -04:00
FlxAnimationStep * stepstorage = stepproperty - > ContainerPtrToValuePtr < FlxAnimationStep > ( target ) ;
2025-10-10 19:03:04 -04:00
int64 oldhash = stepstorage - > Hash ;
2024-09-30 13:59:17 -04:00
FlxAnimationField actionfield ;
actionfield . Name = " action " ;
actionfield . Persistent = false ;
2026-02-09 13:54:00 -05:00
actionfield . Type = LuaValueType : : STRING ;
2024-09-30 13:59:17 -04:00
actionfield . S = " unknown " ;
// Decode everything. If an action field is found, save it for later.
ClearProperties ( prefix , target , stepproperty ) ;
2023-09-18 19:24:52 -04:00
while ( ! decoder . AtEOF ( ) ) {
2023-09-19 22:08:15 -04:00
FlxAnimationField field = decoder . ReadField ( ) ;
2026-02-09 13:54:00 -05:00
if ( ( field . Type = = LuaValueType : : STRING ) & & ( field . Name = = " action " ) ) {
2024-09-30 13:59:17 -04:00
actionfield . S = field . S ;
continue ;
}
if ( step . Finished & & ! field . Persistent ) continue ;
SetProperty ( prefix , target , field ) ;
2023-09-18 19:24:52 -04:00
}
2024-09-30 13:59:17 -04:00
// Store the action field.
if ( step . Finished ) actionfield . S = " idle " ;
SetProperty ( prefix , target , actionfield ) ;
// Store the whole step.
* stepstorage = step ;
2025-11-18 00:40:43 -05:00
// Store the ID
int64 * idstorage = idproperty - > ContainerPtrToValuePtr < int64 > ( target ) ;
* idstorage = step . Hash ;
2024-09-30 13:59:17 -04:00
// Return the correct values.
Action = FString ( actionfield . S . size ( ) , ( const UTF8CHAR * ) actionfield . S . data ( ) ) ;
bChanged = ( step . Hash ! = oldhash ) ;
2023-09-18 19:24:52 -04:00
}
2023-09-15 00:01:41 -04:00
2023-09-19 22:08:15 -04:00
FString UlxAnimationStepLibrary : : AnimationStepDebugString ( const FlxAnimationStep & step ) {
std : : string_view body ( ( const char * ) ( step . Body . GetData ( ) ) , step . Body . Num ( ) ) ;
2025-07-02 16:01:18 -04:00
return FlxAnimationStepDecoder : : DebugString ( step . Finished , step . Hash , body ) ;
2023-09-19 22:08:15 -04:00
}
2023-10-09 14:59:48 -04:00
static FlxAnimationField FindAnimationFieldLL ( const FlxAnimationStep & step , std : : string_view name ) {
2023-09-20 01:40:58 -04:00
std : : string_view body ( ( const char * ) ( step . Body . GetData ( ) ) , step . Body . Num ( ) ) ;
FlxAnimationStepDecoder decoder ( body ) ;
FlxAnimationField result ;
while ( ! decoder . AtEOF ( ) ) {
result = decoder . ReadField ( ) ;
2023-10-09 14:59:48 -04:00
if ( result . Name = = name ) {
2023-09-20 01:40:58 -04:00
return result ;
}
}
2026-02-09 13:54:00 -05:00
result . Type = LuaValueType : : UNINITIALIZED ;
2023-09-20 01:40:58 -04:00
return result ;
}
2023-10-09 14:59:48 -04:00
static FlxAnimationField FindAnimationField ( const FlxAnimationStep & step , const FString & name ) {
FTCHARToUTF8 utf8name ( name ) ;
std : : string_view uname ( utf8name . Get ( ) , utf8name . Length ( ) ) ;
return FindAnimationFieldLL ( step , uname ) ;
}
void FlxAnimationStep : : AutoUpdateXYZ ( AActor * actor ) const {
FlxAnimationField xyz = FindAnimationFieldLL ( * this , " xyz " ) ;
2026-02-09 13:54:00 -05:00
if ( xyz . Type = = LuaValueType : : VECTOR ) {
2023-10-09 14:59:48 -04:00
actor - > SetActorLocation ( FVector ( xyz . X , xyz . Y , xyz . Z ) ) ;
}
}
void FlxAnimationStep : : AutoUpdateFacing ( AActor * actor ) const {
FlxAnimationField facing = FindAnimationFieldLL ( * this , " facing " ) ;
2026-02-09 13:54:00 -05:00
if ( facing . Type = = LuaValueType : : NUMBER ) {
2023-10-09 14:59:48 -04:00
actor - > SetActorRotation ( FRotator ( 0 , facing . X , 0 ) ) ;
}
}
void FlxAnimationStep : : AutoUpdatePlane ( FName * planep ) const {
FlxAnimationField plane = FindAnimationFieldLL ( * this , " plane " ) ;
2026-02-09 13:54:00 -05:00
if ( plane . Type = = LuaValueType : : STRING ) {
2023-10-09 14:59:48 -04:00
FString pname ( plane . S . size ( ) , ( const UTF8CHAR * ) ( plane . S . data ( ) ) ) ;
* planep = FName ( pname ) ;
}
}
2023-10-12 18:15:56 -04:00
bool UlxAnimationStepLibrary : : AnimationStepEqual ( const FlxAnimationStep & stepa , const FlxAnimationStep & stepb ) {
return ( stepa . Finished = = stepb . Finished ) & & ( stepa . Hash = = stepb . Hash ) ;
}
2023-10-02 15:48:42 -04:00
bool UlxAnimationStepLibrary : : AnimationStepIsIdle ( const FlxAnimationStep & step ) {
return step . Finished ;
}
2023-09-20 01:40:58 -04:00
FVector UlxAnimationStepLibrary : : AnimationStepGetVector ( const FlxAnimationStep & step , const FString & name ) {
FlxAnimationField field = FindAnimationField ( step , name ) ;
2026-02-09 13:54:00 -05:00
if ( field . Type ! = LuaValueType : : VECTOR ) return FVector ( 0 , 0 , 0 ) ;
2023-09-20 01:40:58 -04:00
return FVector ( field . X , field . Y , field . Z ) ;
}
double UlxAnimationStepLibrary : : AnimationStepGetFloat ( const FlxAnimationStep & step , const FString & name ) {
FlxAnimationField field = FindAnimationField ( step , name ) ;
2026-02-09 13:54:00 -05:00
if ( field . Type ! = LuaValueType : : NUMBER ) return 0.0 ;
2023-09-20 01:40:58 -04:00
return field . X ;
}
FString UlxAnimationStepLibrary : : AnimationStepGetString ( const FlxAnimationStep & step , const FString & name ) {
2023-10-12 18:15:56 -04:00
if ( step . Finished & & ( name = = TEXT ( " action " ) ) ) {
return TEXT ( " idle " ) ;
}
2023-09-20 01:40:58 -04:00
FlxAnimationField field = FindAnimationField ( step , name ) ;
2026-02-09 13:54:00 -05:00
if ( field . Type ! = LuaValueType : : STRING ) return TEXT ( " " ) ;
2023-09-20 01:40:58 -04:00
return FString ( field . S . size ( ) , ( const UTF8CHAR * ) ( field . S . data ( ) ) ) ;
}
2025-02-05 15:45:48 -05:00
FName UlxAnimationStepLibrary : : AnimationStepGetName ( const FlxAnimationStep & step , const FString & name ) {
FlxAnimationField field = FindAnimationField ( step , name ) ;
2026-02-09 13:54:00 -05:00
if ( field . Type ! = LuaValueType : : TOKEN ) return FName ( ) ;
2025-02-05 15:45:48 -05:00
return FName ( field . S . size ( ) , ( const UTF8CHAR * ) ( field . S . data ( ) ) , FNAME_Add ) ;
}
2023-09-20 01:40:58 -04:00
bool UlxAnimationStepLibrary : : AnimationStepGetBool ( const FlxAnimationStep & step , const FString & name ) {
FlxAnimationField field = FindAnimationField ( step , name ) ;
2026-02-09 13:54:00 -05:00
if ( field . Type ! = LuaValueType : : BOOLEAN ) return false ;
2023-09-20 01:40:58 -04:00
return field . X = = 1.0 ;
}
2023-10-02 15:48:42 -04:00
2023-09-19 22:08:15 -04:00
FlxAnimationField FlxAnimationStepDecoder : : ReadField ( ) {
FlxAnimationField result ;
result . Name = Decoder . read_string_view ( ) ;
result . Persistent = Decoder . read_bool ( ) ;
2026-02-09 13:54:00 -05:00
result . Type = ( LuaValueType ) Decoder . read_uint8 ( ) ;
2023-09-19 22:08:15 -04:00
switch ( result . Type ) {
2026-02-09 13:54:00 -05:00
case LuaValueType : : STRING : {
2023-09-19 22:08:15 -04:00
result . S = Decoder . read_string_view ( ) ;
break ;
}
2026-02-09 13:54:00 -05:00
case LuaValueType : : TOKEN : {
2025-02-05 15:45:48 -05:00
result . S = Decoder . read_string_view ( ) ;
break ;
}
2026-02-09 13:54:00 -05:00
case LuaValueType : : NUMBER : {
2023-09-19 22:08:15 -04:00
result . X = Decoder . read_double ( ) ;
break ;
}
2026-02-09 13:54:00 -05:00
case LuaValueType : : BOOLEAN : {
2023-09-19 22:08:15 -04:00
result . X = Decoder . read_bool ( ) ? 1.0 : 0.0 ;
break ;
}
2026-02-09 13:54:00 -05:00
case LuaValueType : : VECTOR : {
2023-09-19 22:08:15 -04:00
result . X = Decoder . read_double ( ) ;
result . Y = Decoder . read_double ( ) ;
result . Z = Decoder . read_double ( ) ;
break ;
}
default : {
2023-10-24 01:44:09 -04:00
Decoder . read_bytes ( Decoder . fill ( ) ) ;
2026-02-09 13:54:00 -05:00
result . Type = LuaValueType : : UNINITIALIZED ;
2023-09-19 22:08:15 -04:00
break ;
}
}
return result ;
}
2025-10-10 19:03:04 -04:00
FString FlxAnimationStepDecoder : : DebugString ( bool finished , int64 hash , std : : string_view body ) {
2023-09-19 22:08:15 -04:00
FString result ;
FlxAnimationStepDecoder decoder ( body ) ;
result . Appendf ( TEXT ( " Hash=%016llx " ) , hash ) ;
2025-07-02 16:01:18 -04:00
if ( finished ) {
result . Appendf ( TEXT ( " finished " ) ) ;
2023-10-12 18:15:56 -04:00
}
2023-09-19 22:08:15 -04:00
while ( ! decoder . AtEOF ( ) ) {
FlxAnimationField field = decoder . ReadField ( ) ;
result . Append ( TEXT ( " " ) ) ;
result . Append ( FString ( field . Name . size ( ) , ( const UTF8CHAR * ) field . Name . data ( ) ) ) ;
result . Append ( field . Persistent ? TEXT ( " = " ) : TEXT ( " : " ) ) ;
switch ( field . Type ) {
2026-02-09 13:54:00 -05:00
case LuaValueType : : STRING :
2023-09-19 22:08:15 -04:00
result . Append ( FString ( field . S . size ( ) , ( const UTF8CHAR * ) field . S . data ( ) ) ) ;
break ;
2026-02-09 13:54:00 -05:00
case LuaValueType : : TOKEN :
2025-02-05 15:45:48 -05:00
result . Appendf ( TEXT ( " [%s] " ) , * FString ( field . S . size ( ) , ( const UTF8CHAR * ) field . S . data ( ) ) ) ;
break ;
2026-02-09 13:54:00 -05:00
case LuaValueType : : NUMBER :
2023-09-19 22:08:15 -04:00
result . Appendf ( TEXT ( " %lf " ) , field . X ) ;
break ;
2026-02-09 13:54:00 -05:00
case LuaValueType : : BOOLEAN :
2023-09-19 22:08:15 -04:00
result . Append ( ( field . X ) = = 1.0 ? TEXT ( " true " ) : TEXT ( " false " ) ) ;
break ;
2026-02-09 13:54:00 -05:00
case LuaValueType : : VECTOR :
2023-09-19 22:08:15 -04:00
result . Appendf ( TEXT ( " %lf,%lf,%lf " ) , field . X , field . Y , field . Z ) ;
break ;
}
}
return result ;
2023-10-24 01:44:09 -04:00
}
FlxAnimationStepView FlxAnimQueueDecoder : : ReadStep ( ) {
FlxAnimationStepView result ;
2025-10-10 19:03:04 -04:00
result . Hash = Decoder . read_int64 ( ) ;
2023-10-24 01:44:09 -04:00
result . Body = Decoder . read_string_view ( ) ;
return result ;
}
2025-10-10 19:03:04 -04:00
int64 FlxAnimQueueDecoder : : PeekHash ( ) {
2023-10-24 01:44:09 -04:00
int64_t read_count = Decoder . total_reads ( ) ;
2025-10-10 19:03:04 -04:00
int64 result = Decoder . read_int64 ( ) ;
2023-10-24 01:44:09 -04:00
Decoder . unread_to ( read_count ) ;
return result ;
2023-09-19 22:08:15 -04:00
}
2023-10-02 15:48:42 -04:00
FlxAnimQueueDecoder : : FlxAnimQueueDecoder ( std : : string_view queue ) : Decoder ( queue ) {
SizeLimit = Decoder . read_uint8 ( ) ;
ActualSize = Decoder . read_uint8 ( ) ;
}
2025-07-02 16:01:18 -04:00
// FString FlxAnimQueueDecoder::DebugString(std::string_view queue) {
// FString result;
// FlxAnimQueueDecoder decoder(queue);
// while (!decoder.AtEOF()) {
// FlxAnimationStepView step = decoder.ReadStep();
// FString stepdebug = FlxAnimationStepDecoder::DebugString(false, step.Hash, step.Body);
// result.Appendf(TEXT("%s\n"), *stepdebug);
// }
// return result;
// }
2023-09-19 22:08:15 -04:00
2023-09-15 13:28:18 -04:00
FlxAnimTracker : : FlxAnimTracker ( ) {
2023-09-28 14:32:48 -04:00
Clear ( ) ;
}
void FlxAnimTracker : : Clear ( ) {
2023-09-15 00:01:41 -04:00
AQ . Empty ( ) ;
2023-10-02 15:48:42 -04:00
Changed = true ;
}
2025-10-10 19:03:04 -04:00
void FlxAnimTracker : : FinishedAnimation ( int64 hash ) {
2023-10-02 15:48:42 -04:00
for ( int i = 0 ; i < AQ . Num ( ) ; i + + ) {
if ( AQ [ i ] . Hash = = hash ) {
AQ [ i ] . Finished = true ;
Changed = true ;
}
}
}
2025-10-10 19:03:04 -04:00
bool FlxAnimTracker : : IsFinished ( int64 hash ) {
2025-09-22 16:17:21 -04:00
for ( int i = 0 ; i < AQ . Num ( ) ; i + + ) {
if ( AQ [ i ] . Hash = = hash ) {
return AQ [ i ] . Finished ;
}
}
return true ;
}
2023-10-02 15:48:42 -04:00
void FlxAnimTracker : : SkipToEnd ( ) {
for ( int i = 0 ; i < AQ . Num ( ) ; i + + ) {
if ( ! AQ [ i ] . Finished ) {
AQ [ i ] . Finished = true ;
Changed = true ;
}
}
2023-09-15 00:01:41 -04:00
}
2025-10-10 19:03:04 -04:00
TArray < int64 > FlxAnimTracker : : GetHashes ( )
{
TArray < int64 > Result ;
Result . SetNum ( AQ . Num ( ) ) ;
for ( int i = 0 ; i < AQ . Num ( ) ; i + + )
{
Result [ i ] = AQ [ i ] . Hash ;
}
return Result ;
}
2025-07-02 16:01:18 -04:00
const FlxAnimationStep * FlxAnimTracker : : FirstUnfinished ( ) const
{
for ( int i = 0 ; i < AQ . Num ( ) ; i + + ) {
if ( ! AQ [ i ] . Finished ) return & AQ [ i ] ;
}
return nullptr ;
}
const FlxAnimationStep * FlxAnimTracker : : LastFinished ( ) const
{
for ( int i = AQ . Num ( ) - 1 ; i > = 0 ; i - - ) {
if ( AQ [ i ] . Finished ) return & AQ [ i ] ;
}
return nullptr ;
}
2025-10-10 19:03:04 -04:00
const FlxAnimationStep * FlxAnimTracker : : FindAnimation ( int64 hash ) const
2025-07-02 16:01:18 -04:00
{
for ( int i = 0 ; i < AQ . Num ( ) ; i + + ) {
if ( AQ [ i ] . Hash = = hash ) return & AQ [ i ] ;
}
return nullptr ;
}
2023-09-15 13:28:18 -04:00
void FlxAnimTracker : : Update ( std : : string_view encqueue ) {
2023-10-02 15:48:42 -04:00
check ( ! encqueue . empty ( ) ) ;
// If the first hash matches, we don't bother updating at all.
2023-09-15 00:01:41 -04:00
//
2023-10-02 15:48:42 -04:00
FlxAnimQueueDecoder decoder ( encqueue ) ;
if ( ! AQ . IsEmpty ( ) ) {
if ( decoder . PeekHash ( ) = = AQ . Last ( ) . Hash ) {
2023-09-15 00:01:41 -04:00
return ;
}
2023-10-02 15:48:42 -04:00
}
// The Changed flag is set whenever there is any change to
// the animation queue of any kind.
//
Changed = true ;
// Build a map indexing the steps in AQ.
//
2025-10-10 19:03:04 -04:00
TMap < int64 , int32 > HashToIndex ;
2023-10-02 15:48:42 -04:00
for ( int i = 0 ; i < AQ . Num ( ) ; i + + ) {
HashToIndex . Emplace ( AQ [ i ] . Hash , i ) ;
2023-09-15 00:01:41 -04:00
}
// Search for a Luprex animation record that matches
// something that we already have. Yields the sequence
// number of the most recent matching record.
//
// At the same time, push all non-matching records
// after the matching record onto a stack of new records.
//
2023-09-19 22:08:15 -04:00
TArray < FlxAnimationStepView > newsteps ;
2023-10-02 15:48:42 -04:00
int32 matchingindex = - 1 ;
2023-09-15 00:01:41 -04:00
while ( ! decoder . AtEOF ( ) ) {
2023-09-19 22:08:15 -04:00
FlxAnimationStepView step = decoder . ReadStep ( ) ;
2023-10-02 15:48:42 -04:00
int32 * indexp = HashToIndex . Find ( step . Hash ) ;
if ( indexp = = nullptr ) {
2023-09-15 00:01:41 -04:00
newsteps . Emplace ( step ) ;
} else {
2023-10-02 15:48:42 -04:00
matchingindex = * indexp ;
2023-09-15 00:01:41 -04:00
break ;
}
}
2023-09-15 15:44:01 -04:00
// Remove all animations after the most recent matching
2023-10-02 15:48:42 -04:00
// record.
2023-09-15 15:44:01 -04:00
//
2023-10-02 15:48:42 -04:00
int32 nremove = ( AQ . Num ( ) - ( matchingindex + 1 ) ) ;
2023-09-15 15:44:01 -04:00
check ( ( nremove > = 0 ) & & ( nremove < = AQ . Num ( ) ) ) ;
for ( int32 i = 0 ; i < nremove ; i + + ) {
2025-07-02 16:01:18 -04:00
AQ . Pop ( ) ;
2023-09-15 00:01:41 -04:00
}
// Transfer the new animations onto the queue.
//
while ( ! newsteps . IsEmpty ( ) ) {
2023-09-19 22:08:15 -04:00
FlxAnimationStepView step = newsteps . Pop ( ) ;
2025-07-02 16:01:18 -04:00
AQ . Emplace ( step . Hash , step . Body ) ;
2023-09-15 00:01:41 -04:00
}
// If there are too many animations in AQ, discard
// any very old ones.
//
2023-10-02 15:48:42 -04:00
// TODO: this is hardwired to keep 10. Instead, we
// should keep the number specified in the queue.
//
2025-07-02 16:01:18 -04:00
const int limit = 10 ;
if ( AQ . Num ( ) > limit )
{
int offset = AQ . Num ( ) - limit ;
for ( int i = 0 ; i < limit ; i + + )
{
AQ [ i ] = AQ [ i + offset ] ;
2023-09-15 00:01:41 -04:00
}
2025-07-02 16:01:18 -04:00
AQ . SetNum ( limit ) ;
2023-09-15 15:44:01 -04:00
}
2024-02-16 15:48:22 -05:00
}
2025-07-02 16:01:18 -04:00
FString FlxAnimTracker : : DebugString ( ) const
{
FString Result ;
if ( Changed )
{
Result + = TEXT ( " changed=true " ) ;
}
else
{
Result + = TEXT ( " changed=false " ) ;
}
Result + = TEXT ( " \n " ) ;
for ( int i = 0 ; i < AQ . Num ( ) ; i + + )
{
Result + = UlxAnimationStepLibrary : : AnimationStepDebugString ( AQ [ i ] ) ;
Result + = TEXT ( " \n " ) ;
}
return Result ;
}
2024-01-24 14:51:21 -05:00
FString FlxAnimTracker : : GetCurrentBlueprintName ( ) {
for ( int i = 0 ; i < AQ . Num ( ) ; i + + ) {
if ( ! AQ [ i ] . Finished ) {
return AQ [ i ] . Blueprint ;
}
}
return AQ . Last ( ) . Blueprint ;
}
2023-10-02 15:48:42 -04:00
FlxAnimationStep FlxAnimTracker : : GetCurrentAnimation ( ) {
2023-10-12 18:15:56 -04:00
FlxAnimationStep result ;
2023-10-02 15:48:42 -04:00
for ( int i = 0 ; i < AQ . Num ( ) ; i + + ) {
if ( ! AQ [ i ] . Finished ) {
return AQ [ i ] ;
}
2023-09-15 15:44:01 -04:00
}
2023-10-12 18:15:56 -04:00
result = AQ . Last ( ) ;
2024-02-16 15:48:22 -05:00
// This next line is a hack. We need the idle step to have a unique
// hash. This is a passable way to get a unique hash value.
result . Hash + = 1 ;
2023-10-12 18:15:56 -04:00
return result ;
2023-09-15 00:01:41 -04:00
}
2025-07-02 16:01:18 -04:00
2026-02-06 17:34:26 -05:00
void UlxAnimationStepLibrary : : AnimationStepApplyMaterials ( const FlxAnimationStep & step , AActor * actor ) {
if ( actor = = nullptr ) return ;
// Step 1: Build tables of mat_ parameters (vectors and scalars).
//
TMap < FName , FVector > VectorParams ;
TMap < FName , float > ScalarParams ;
{
std : : string_view body ( ( const char * ) ( step . Body . GetData ( ) ) , step . Body . Num ( ) ) ;
FlxAnimationStepDecoder decoder ( body ) ;
while ( ! decoder . AtEOF ( ) ) {
FlxAnimationField field = decoder . ReadField ( ) ;
if ( field . Name . size ( ) < = 4 ) continue ;
if ( field . Name . substr ( 0 , 4 ) ! = " mat_ " ) continue ;
std : : string_view suffix = field . Name . substr ( 4 ) ;
FName paramName ( suffix . size ( ) , ( const UTF8CHAR * ) suffix . data ( ) ) ;
2026-02-09 13:54:00 -05:00
if ( field . Type = = LuaValueType : : VECTOR ) {
2026-02-06 17:34:26 -05:00
VectorParams . Add ( paramName , FVector ( field . X , field . Y , field . Z ) ) ;
2026-02-09 13:54:00 -05:00
} else if ( field . Type = = LuaValueType : : NUMBER ) {
2026-02-06 17:34:26 -05:00
ScalarParams . Add ( paramName , ( float ) field . X ) ;
}
}
}
// Step 2: Early exit if no mat_ parameters found.
//
if ( VectorParams . IsEmpty ( ) & & ScalarParams . IsEmpty ( ) ) return ;
// Step 3: Loop over all mesh components and apply material parameters.
//
TInlineComponentArray < UMeshComponent * > MeshComponents ;
actor - > GetComponents < UMeshComponent > ( MeshComponents ) ;
for ( UMeshComponent * MeshComp : MeshComponents ) {
int32 NumMaterials = MeshComp - > GetNumMaterials ( ) ;
for ( int32 SlotIndex = 0 ; SlotIndex < NumMaterials ; SlotIndex + + ) {
UMaterialInterface * CurrentMat = MeshComp - > GetMaterial ( SlotIndex ) ;
if ( CurrentMat = = nullptr ) continue ;
// Check if the material has any parameter that doesn't
// match what was specified.
//
bool AnyMismatch = false ;
for ( auto & Pair : VectorParams ) {
FLinearColor Compare ;
if ( CurrentMat - > GetVectorParameterValue ( Pair . Key , Compare ) ) {
FLinearColor Desired ( Pair . Value . X , Pair . Value . Y , Pair . Value . Z ) ;
if ( Compare ! = Desired ) {
AnyMismatch = true ;
}
}
}
for ( auto & Pair : ScalarParams ) {
float Compare ;
if ( CurrentMat - > GetScalarParameterValue ( Pair . Key , Compare ) ) {
if ( Compare ! = Pair . Value ) {
AnyMismatch = true ;
}
}
}
if ( ! AnyMismatch ) continue ;
// Strip away any existing dynamic material before creating a new one.
//
UMaterialInterface * BaseMat = CurrentMat ;
while ( UMaterialInstanceDynamic * MID = Cast < UMaterialInstanceDynamic > ( BaseMat ) ) {
BaseMat = MID - > Parent ;
}
if ( BaseMat ! = CurrentMat ) {
MeshComp - > SetMaterial ( SlotIndex , BaseMat ) ;
}
// Create the new dynamic material.
//
UMaterialInstanceDynamic * MID = MeshComp - > CreateDynamicMaterialInstance ( SlotIndex ) ;
for ( auto & Pair : VectorParams ) {
FVector & Vec = Pair . Value ;
MID - > SetVectorParameterValue ( Pair . Key , FLinearColor ( Vec . X , Vec . Y , Vec . Z ) ) ;
}
for ( auto & Pair : ScalarParams ) {
MID - > SetScalarParameterValue ( Pair . Key , Pair . Value ) ;
}
}
}
}
void UlxAnimationStepLibrary : : AnimationStepApplyMesh ( const FlxAnimationStep & step , bool FallbackToBP , AActor * actor ) {
if ( actor = = nullptr ) return ;
// Step 1: Look for a "mesh" or "bp" string field in the animation step.
//
FString MeshName = AnimationStepGetString ( step , TEXT ( " mesh " ) ) ;
if ( MeshName . IsEmpty ( ) & & FallbackToBP ) {
MeshName = AnimationStepGetString ( step , TEXT ( " bp " ) ) ;
}
if ( MeshName . IsEmpty ( ) ) return ;
// Step 2: Find the actor's mesh component. There must be exactly one.
//
TInlineComponentArray < UMeshComponent * > MeshComponents ;
actor - > GetComponents < UMeshComponent > ( MeshComponents ) ;
if ( MeshComponents . Num ( ) ! = 1 ) {
UE_LOG ( LogLuprexIntegration , Error , TEXT ( " AnimationStepApplyMesh: Actor %s has %d mesh components, expected exactly 1 " ) , * actor - > GetName ( ) , MeshComponents . Num ( ) ) ;
return ;
}
UMeshComponent * MeshComp = MeshComponents [ 0 ] ;
// Step 3: Apply the mesh based on the component type.
//
if ( UStaticMeshComponent * StaticComp = Cast < UStaticMeshComponent > ( MeshComp ) ) {
UStaticMesh * NewMesh = nullptr ;
UlxAssetLookup : : LoadStaticMeshAsset ( NewMesh , actor , MeshName , false ) ;
if ( NewMesh = = nullptr ) return ;
if ( StaticComp - > GetStaticMesh ( ) ! = NewMesh ) {
StaticComp - > SetStaticMesh ( NewMesh ) ;
}
} else if ( USkeletalMeshComponent * SkelComp = Cast < USkeletalMeshComponent > ( MeshComp ) ) {
2026-02-09 13:54:00 -05:00
USkeletalMesh * NewMesh = nullptr ;
UlxAssetLookup : : LoadSkeletalMeshAsset ( NewMesh , actor , MeshName , true ) ;
if ( NewMesh = = nullptr ) return ;
if ( SkelComp - > GetSkeletalMeshAsset ( ) ! = NewMesh ) {
SkelComp - > SetSkeletalMeshAsset ( NewMesh ) ;
}
2026-02-06 17:34:26 -05:00
} else {
UE_LOG ( LogLuprexIntegration , Error , TEXT ( " AnimationStepApplyMesh: Actor %s has unsupported mesh component type " ) , * actor - > GetName ( ) ) ;
}
}