800 lines
23 KiB
C++
800 lines
23 KiB
C++
|
|
#include "AnimQueue.h"
|
|
#include "Common.h"
|
|
#include "StreamBuffer.h"
|
|
#include "UtilityLibrary.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "Components/MeshComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "AssetLookup.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include <iostream>
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//
|
|
// An animation step that doesn't actually store the step,
|
|
// it just contains a pointer to the string.
|
|
//
|
|
////////////////////////////////////////////////////////////
|
|
|
|
struct FlxAnimationStepView {
|
|
int64 Hash;
|
|
std::string_view Body;
|
|
|
|
FlxAnimationStepView() : Hash(0), Body("") {}
|
|
FlxAnimationStepView(int64 h, std::string_view b) : Hash(h), Body(b) {}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//
|
|
// FlxAnimationField
|
|
//
|
|
// A single field from an animation step: a variable name, a
|
|
// persistent flag, a type, and value storage.
|
|
//
|
|
// IMPORTANT: Stores a string_view, not a string, so the lifetime
|
|
// of this field is only as long as the step you're parsing.
|
|
//
|
|
// Boolean values are stored in X as 1 or 0. Double values
|
|
// are stored in X.
|
|
//
|
|
////////////////////////////////////////////////////////////
|
|
|
|
struct FlxAnimationField {
|
|
std::string_view Name;
|
|
bool Persistent;
|
|
LuaValueType Type;
|
|
double X, Y, Z;
|
|
std::string_view S;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//
|
|
// FlxAnimationStepDecoder
|
|
//
|
|
// Stream reader for a single animation step. Reads one
|
|
// FlxAnimationField at a time until EOF.
|
|
//
|
|
////////////////////////////////////////////////////////////
|
|
|
|
class FlxAnimationStepDecoder {
|
|
private:
|
|
FlxStreamBuffer Decoder;
|
|
|
|
public:
|
|
// Initialize from an encoded step body.
|
|
//
|
|
FlxAnimationStepDecoder(std::string_view body) : Decoder(body) {}
|
|
|
|
// Return true if the parser has reached EOF.
|
|
//
|
|
bool AtEOF() { return Decoder.empty(); }
|
|
|
|
// Read one field.
|
|
//
|
|
FlxAnimationField ReadField();
|
|
|
|
// Convert an animation step to a debug string.
|
|
//
|
|
static FString DebugString(bool finished, int64 hash, std::string_view body);
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//
|
|
// FlxAnimQueueDecoder
|
|
//
|
|
// Stream reader for animation queues. Reads one
|
|
// FlxAnimationStepView at a time until EOF.
|
|
//
|
|
////////////////////////////////////////////////////////////
|
|
|
|
class FlxAnimQueueDecoder {
|
|
private:
|
|
FlxStreamBuffer Decoder;
|
|
|
|
// Read from the header immediately on
|
|
// construction.
|
|
//
|
|
int SizeLimit;
|
|
int ActualSize;
|
|
|
|
public:
|
|
// Initialize with an encoded animation queue.
|
|
//
|
|
FlxAnimQueueDecoder(std::string_view s);
|
|
|
|
// Get the size limit of the animation queue.
|
|
//
|
|
int GetSizeLimit() const { return SizeLimit; }
|
|
|
|
// Get the actual size of the animation queue.
|
|
//
|
|
int GetActualSize() const { return ActualSize; }
|
|
|
|
// Return true if the parser has reached EOF.
|
|
//
|
|
bool AtEOF() { return Decoder.empty(); }
|
|
|
|
// Read one animation step.
|
|
//
|
|
FlxAnimationStepView ReadStep();
|
|
|
|
// Peek at the hash of the next step.
|
|
//
|
|
int64 PeekHash();
|
|
|
|
// Convert an AnimQueue to an FString.
|
|
//
|
|
// static FString DebugString(std::string_view s);
|
|
};
|
|
|
|
FlxAnimationStep::FlxAnimationStep(int64 hash, std::string_view body) {
|
|
Finished = false;
|
|
Hash = hash;
|
|
Body.SetNum(body.size());
|
|
memcpy(Body.GetData(), body.data(), body.size());
|
|
Blueprint = UlxAnimationStepLibrary::AnimationStepGetString(*this, "bp");
|
|
}
|
|
|
|
static void ClearProperties(const FString& prefix, UObject* obj, FProperty *except) {
|
|
UClass* uclass = obj->GetClass();
|
|
FName prefixlo(prefix);
|
|
FName prefixhi(prefix + TEXT("\xFFFF"));
|
|
for (TFieldIterator<FProperty> It(uclass); It; ++It)
|
|
{
|
|
FProperty* fprop = *It;
|
|
if (fprop == except) continue;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma optimize("", off)
|
|
|
|
static bool SetProperty(const FString& prefix, UObject* obj, const FlxAnimationField& field) {
|
|
UClass* uclass = obj->GetClass();
|
|
FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data());
|
|
FName nname(prefix + sname);
|
|
switch (field.Type) {
|
|
case LuaValueType::STRING: {
|
|
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;
|
|
}
|
|
case LuaValueType::TOKEN: {
|
|
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;
|
|
}
|
|
case LuaValueType::NUMBER: {
|
|
FDoubleProperty* fprop = FindFProperty<FDoubleProperty>(uclass, nname);
|
|
if (fprop == nullptr) return false;
|
|
double* pptr = fprop->ContainerPtrToValuePtr<double>(obj);
|
|
*pptr = field.X;
|
|
return true;
|
|
}
|
|
case LuaValueType::BOOLEAN: {
|
|
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;
|
|
}
|
|
case LuaValueType::VECTOR: {
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static FInt64Property *FindAnimationIDProperty(UClass *uclass, const FString &prefix) {
|
|
FName nname(prefix + TEXT("Animation ID"));
|
|
FInt64Property* fprop = FindFProperty<FInt64Property>(uclass, nname);
|
|
return fprop;
|
|
}
|
|
|
|
void UlxAnimationStepLibrary::UnpackAnimationStep(bool &bChanged, FString &Action, const FlxAnimationStep& step, UObject* target, const FString& prefix) {
|
|
bChanged = false;
|
|
Action = TEXT("");
|
|
|
|
if (prefix.IsEmpty())
|
|
{
|
|
UE_LOG(LogBlueprint, Error, TEXT("UnpackAnimationStep: You may not pass an empty string for prefix"));
|
|
return;
|
|
}
|
|
|
|
UClass* uclass = target->GetClass();
|
|
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
|
|
FlxAnimationStepDecoder decoder(body);
|
|
|
|
FStructProperty* stepproperty = FindAnimationStepProperty(uclass, prefix);
|
|
if (stepproperty == nullptr) {
|
|
UE_LOG(LogBlueprint, Error, TEXT("UnpackAnimationStep: Target object does not have a variable named: '%s Animation Step'"), *prefix);
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
FlxAnimationStep* stepstorage = stepproperty->ContainerPtrToValuePtr<FlxAnimationStep>(target);
|
|
int64 oldhash = stepstorage->Hash;
|
|
|
|
FlxAnimationField actionfield;
|
|
actionfield.Name = "action";
|
|
actionfield.Persistent = false;
|
|
actionfield.Type = LuaValueType::STRING;
|
|
actionfield.S = "unknown";
|
|
|
|
// Decode everything. If an action field is found, save it for later.
|
|
ClearProperties(prefix, target, stepproperty);
|
|
while (!decoder.AtEOF()) {
|
|
FlxAnimationField field = decoder.ReadField();
|
|
if ((field.Type == LuaValueType::STRING) && (field.Name == "action")) {
|
|
actionfield.S = field.S;
|
|
continue;
|
|
}
|
|
if (step.Finished && !field.Persistent) continue;
|
|
SetProperty(prefix, target, field);
|
|
}
|
|
|
|
// Store the action field.
|
|
if (step.Finished) actionfield.S = "idle";
|
|
SetProperty(prefix, target, actionfield);
|
|
|
|
// Store the whole step.
|
|
*stepstorage = step;
|
|
|
|
// Store the ID
|
|
int64 *idstorage = idproperty->ContainerPtrToValuePtr<int64>(target);
|
|
*idstorage = step.Hash;
|
|
|
|
// Return the correct values.
|
|
Action = FString(actionfield.S.size(), (const UTF8CHAR*)actionfield.S.data());
|
|
bChanged = (step.Hash != oldhash);
|
|
}
|
|
|
|
FString UlxAnimationStepLibrary::AnimationStepDebugString(const FlxAnimationStep& step) {
|
|
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
|
|
return FlxAnimationStepDecoder::DebugString(step.Finished, step.Hash, body);
|
|
}
|
|
|
|
static FlxAnimationField FindAnimationFieldLL(const FlxAnimationStep& step, std::string_view name) {
|
|
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
|
|
FlxAnimationStepDecoder decoder(body);
|
|
FlxAnimationField result;
|
|
while (!decoder.AtEOF()) {
|
|
result = decoder.ReadField();
|
|
if (result.Name == name) {
|
|
return result;
|
|
}
|
|
}
|
|
result.Type = LuaValueType::UNINITIALIZED;
|
|
return result;
|
|
}
|
|
|
|
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");
|
|
if (xyz.Type == LuaValueType::VECTOR) {
|
|
actor->SetActorLocation(FVector(xyz.X, xyz.Y, xyz.Z));
|
|
}
|
|
}
|
|
|
|
void FlxAnimationStep::AutoUpdateFacing(AActor *actor) const {
|
|
FlxAnimationField facing = FindAnimationFieldLL(*this, "facing");
|
|
if (facing.Type == LuaValueType::NUMBER) {
|
|
actor->SetActorRotation(FRotator(0, facing.X, 0));
|
|
}
|
|
}
|
|
|
|
void FlxAnimationStep::AutoUpdatePlane(FName *planep) const {
|
|
FlxAnimationField plane = FindAnimationFieldLL(*this, "plane");
|
|
if (plane.Type == LuaValueType::STRING) {
|
|
FString pname(plane.S.size(), (const UTF8CHAR*)(plane.S.data()));
|
|
*planep = FName(pname);
|
|
}
|
|
}
|
|
|
|
bool UlxAnimationStepLibrary::AnimationStepEqual(const FlxAnimationStep &stepa, const FlxAnimationStep &stepb) {
|
|
return (stepa.Finished == stepb.Finished) && (stepa.Hash == stepb.Hash);
|
|
}
|
|
|
|
bool UlxAnimationStepLibrary::AnimationStepIsIdle(const FlxAnimationStep &step) {
|
|
return step.Finished;
|
|
}
|
|
|
|
FVector UlxAnimationStepLibrary::AnimationStepGetVector(const FlxAnimationStep& step, const FString& name) {
|
|
FlxAnimationField field = FindAnimationField(step, name);
|
|
if (field.Type != LuaValueType::VECTOR) return FVector(0, 0, 0);
|
|
return FVector(field.X, field.Y, field.Z);
|
|
}
|
|
|
|
double UlxAnimationStepLibrary::AnimationStepGetFloat(const FlxAnimationStep& step, const FString& name) {
|
|
FlxAnimationField field = FindAnimationField(step, name);
|
|
if (field.Type != LuaValueType::NUMBER) return 0.0;
|
|
return field.X;
|
|
}
|
|
|
|
FString UlxAnimationStepLibrary::AnimationStepGetString(const FlxAnimationStep& step, const FString& name) {
|
|
if (step.Finished && (name == TEXT("action"))) {
|
|
return TEXT("idle");
|
|
}
|
|
FlxAnimationField field = FindAnimationField(step, name);
|
|
if (field.Type != LuaValueType::STRING) return TEXT("");
|
|
return FString(field.S.size(), (const UTF8CHAR*)(field.S.data()));
|
|
}
|
|
|
|
FName UlxAnimationStepLibrary::AnimationStepGetName(const FlxAnimationStep& step, const FString& name) {
|
|
FlxAnimationField field = FindAnimationField(step, name);
|
|
if (field.Type != LuaValueType::TOKEN) return FName();
|
|
return FName(field.S.size(), (const UTF8CHAR*)(field.S.data()), FNAME_Add);
|
|
}
|
|
|
|
bool UlxAnimationStepLibrary::AnimationStepGetBool(const FlxAnimationStep& step, const FString& name) {
|
|
FlxAnimationField field = FindAnimationField(step, name);
|
|
if (field.Type != LuaValueType::BOOLEAN) return false;
|
|
return field.X == 1.0;
|
|
}
|
|
|
|
|
|
FlxAnimationField FlxAnimationStepDecoder::ReadField() {
|
|
FlxAnimationField result;
|
|
result.Name = Decoder.read_string_view();
|
|
result.Persistent = Decoder.read_bool();
|
|
result.Type = (LuaValueType)Decoder.read_uint8();
|
|
switch (result.Type) {
|
|
case LuaValueType::STRING: {
|
|
result.S = Decoder.read_string_view();
|
|
break;
|
|
}
|
|
case LuaValueType::TOKEN: {
|
|
result.S = Decoder.read_string_view();
|
|
break;
|
|
}
|
|
case LuaValueType::NUMBER: {
|
|
result.X = Decoder.read_double();
|
|
break;
|
|
}
|
|
case LuaValueType::BOOLEAN: {
|
|
result.X = Decoder.read_bool() ? 1.0 : 0.0;
|
|
break;
|
|
}
|
|
case LuaValueType::VECTOR: {
|
|
result.X = Decoder.read_double();
|
|
result.Y = Decoder.read_double();
|
|
result.Z = Decoder.read_double();
|
|
break;
|
|
}
|
|
default: {
|
|
Decoder.read_bytes(Decoder.fill());
|
|
result.Type = LuaValueType::UNINITIALIZED;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
FString FlxAnimationStepDecoder::DebugString(bool finished, int64 hash, std::string_view body) {
|
|
FString result;
|
|
FlxAnimationStepDecoder decoder(body);
|
|
result.Appendf(TEXT("Hash=%016llx"), hash);
|
|
if (finished) {
|
|
result.Appendf(TEXT(" finished"));
|
|
}
|
|
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) {
|
|
case LuaValueType::STRING:
|
|
result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data()));
|
|
break;
|
|
case LuaValueType::TOKEN:
|
|
result.Appendf(TEXT("[%s]"), *FString(field.S.size(), (const UTF8CHAR*)field.S.data()));
|
|
break;
|
|
case LuaValueType::NUMBER:
|
|
result.Appendf(TEXT("%lf"), field.X);
|
|
break;
|
|
case LuaValueType::BOOLEAN:
|
|
result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false"));
|
|
break;
|
|
case LuaValueType::VECTOR:
|
|
result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
FlxAnimationStepView FlxAnimQueueDecoder::ReadStep() {
|
|
FlxAnimationStepView result;
|
|
result.Hash = Decoder.read_int64();
|
|
result.Body = Decoder.read_string_view();
|
|
return result;
|
|
}
|
|
|
|
int64 FlxAnimQueueDecoder::PeekHash() {
|
|
int64_t read_count = Decoder.total_reads();
|
|
int64 result = Decoder.read_int64();
|
|
Decoder.unread_to(read_count);
|
|
return result;
|
|
}
|
|
|
|
FlxAnimQueueDecoder::FlxAnimQueueDecoder(std::string_view queue) : Decoder(queue) {
|
|
SizeLimit = Decoder.read_uint8();
|
|
ActualSize = Decoder.read_uint8();
|
|
}
|
|
|
|
// 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;
|
|
// }
|
|
|
|
FlxAnimTracker::FlxAnimTracker() {
|
|
Clear();
|
|
}
|
|
|
|
void FlxAnimTracker::Clear() {
|
|
AQ.Empty();
|
|
Changed = true;
|
|
}
|
|
|
|
void FlxAnimTracker::FinishedAnimation(int64 hash) {
|
|
for (int i = 0; i < AQ.Num(); i++) {
|
|
if (AQ[i].Hash == hash) {
|
|
AQ[i].Finished = true;
|
|
Changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FlxAnimTracker::IsFinished(int64 hash) {
|
|
for (int i = 0; i < AQ.Num(); i++) {
|
|
if (AQ[i].Hash == hash) {
|
|
return AQ[i].Finished;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void FlxAnimTracker::SkipToEnd() {
|
|
for (int i = 0; i < AQ.Num(); i++) {
|
|
if (!AQ[i].Finished) {
|
|
AQ[i].Finished = true;
|
|
Changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
const FlxAnimationStep *FlxAnimTracker::FindAnimation(int64 hash) const
|
|
{
|
|
for (int i = 0; i < AQ.Num(); i++) {
|
|
if (AQ[i].Hash == hash) return &AQ[i];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void FlxAnimTracker::Update(std::string_view encqueue) {
|
|
// An empty encqueue represents a blank queue: one step with hash=0, empty body.
|
|
//
|
|
if (encqueue.empty()) {
|
|
if (AQ.Num() == 1 && AQ[0].Hash == 0) return;
|
|
Changed = true;
|
|
AQ.Empty();
|
|
AQ.Emplace(0, std::string_view());
|
|
return;
|
|
}
|
|
|
|
// If the first hash matches, we don't bother updating at all.
|
|
//
|
|
FlxAnimQueueDecoder decoder(encqueue);
|
|
if (!AQ.IsEmpty()) {
|
|
if (decoder.PeekHash() == AQ.Last().Hash) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
// 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.
|
|
//
|
|
TMap<int64, int32> HashToIndex;
|
|
for (int i = 0; i < AQ.Num(); i++) {
|
|
HashToIndex.Emplace(AQ[i].Hash, i);
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
TArray<FlxAnimationStepView> newsteps;
|
|
int32 matchingindex = -1;
|
|
while (!decoder.AtEOF()) {
|
|
FlxAnimationStepView step = decoder.ReadStep();
|
|
int32* indexp = HashToIndex.Find(step.Hash);
|
|
if (indexp == nullptr) {
|
|
newsteps.Emplace(step);
|
|
} else {
|
|
matchingindex = *indexp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Remove all animations after the most recent matching
|
|
// record.
|
|
//
|
|
int32 nremove = (AQ.Num() - (matchingindex + 1));
|
|
check((nremove >= 0) && (nremove <= AQ.Num()));
|
|
for (int32 i = 0; i < nremove; i++) {
|
|
AQ.Pop();
|
|
}
|
|
|
|
// Transfer the new animations onto the queue.
|
|
//
|
|
while (!newsteps.IsEmpty()) {
|
|
FlxAnimationStepView step = newsteps.Pop();
|
|
AQ.Emplace(step.Hash, step.Body);
|
|
}
|
|
|
|
// If there are too many animations in AQ, discard
|
|
// any very old ones.
|
|
//
|
|
// TODO: this is hardwired to keep 10. Instead, we
|
|
// should keep the number specified in the queue.
|
|
//
|
|
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];
|
|
}
|
|
AQ.SetNum(limit);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
FString FlxAnimTracker::GetCurrentBlueprintName() {
|
|
for (int i = 0; i < AQ.Num(); i++) {
|
|
if (!AQ[i].Finished) {
|
|
return AQ[i].Blueprint;
|
|
}
|
|
}
|
|
return AQ.Last().Blueprint;
|
|
}
|
|
|
|
FlxAnimationStep FlxAnimTracker::GetCurrentAnimation() {
|
|
FlxAnimationStep result;
|
|
for (int i = 0; i < AQ.Num(); i++) {
|
|
if (!AQ[i].Finished) {
|
|
return AQ[i];
|
|
}
|
|
}
|
|
result = AQ.Last();
|
|
// 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;
|
|
return result;
|
|
}
|
|
|
|
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());
|
|
if (field.Type == LuaValueType::VECTOR) {
|
|
VectorParams.Add(paramName, FVector(field.X, field.Y, field.Z));
|
|
} else if (field.Type == LuaValueType::NUMBER) {
|
|
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::AnimationStepApplyStaticMesh(const FlxAnimationStep& step, bool FallbackToBP,
|
|
UStaticMeshComponent* MeshComp, UStaticMesh* Fallback) {
|
|
if (MeshComp == nullptr) return;
|
|
|
|
FString MeshName = AnimationStepGetString(step, TEXT("mesh"));
|
|
if (MeshName.IsEmpty() && FallbackToBP) {
|
|
MeshName = AnimationStepGetString(step, TEXT("bp"));
|
|
}
|
|
|
|
UStaticMesh* NewMesh = nullptr;
|
|
if (!MeshName.IsEmpty()) {
|
|
UlxAssetLookup::LoadStaticMeshAsset(NewMesh, MeshComp, MeshName);
|
|
}
|
|
if (NewMesh == nullptr) NewMesh = Fallback;
|
|
if (NewMesh == nullptr) return;
|
|
if (MeshComp->GetStaticMesh() != NewMesh) {
|
|
MeshComp->SetStaticMesh(NewMesh);
|
|
}
|
|
}
|
|
|
|
void UlxAnimationStepLibrary::AnimationStepApplySkeletalMesh(const FlxAnimationStep& step, bool FallbackToBP,
|
|
USkeletalMeshComponent* MeshComp, USkeletalMesh* Fallback) {
|
|
if (MeshComp == nullptr) return;
|
|
|
|
FString MeshName = AnimationStepGetString(step, TEXT("mesh"));
|
|
if (MeshName.IsEmpty() && FallbackToBP) {
|
|
MeshName = AnimationStepGetString(step, TEXT("bp"));
|
|
}
|
|
|
|
USkeletalMesh* NewMesh = nullptr;
|
|
if (!MeshName.IsEmpty()) {
|
|
UlxAssetLookup::LoadSkeletalMeshAsset(NewMesh, MeshComp, MeshName);
|
|
}
|
|
if (NewMesh == nullptr) NewMesh = Fallback;
|
|
if (NewMesh == nullptr) return;
|
|
if (MeshComp->GetSkeletalMeshAsset() != NewMesh) {
|
|
MeshComp->SetSkeletalMeshAsset(NewMesh);
|
|
}
|
|
}
|
|
|