Checking in what I found on my hard drive
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,5 +1,6 @@
|
||||
* text=auto
|
||||
*.bat text eol=crlf
|
||||
*.sh text eol=lf
|
||||
*.lib filter=lfs diff=lfs merge=lfs -text
|
||||
*.exe filter=lfs diff=lfs merge=lfs -text
|
||||
*.a filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -19,6 +19,13 @@ gmon.out
|
||||
*.pdb
|
||||
*.sln
|
||||
*.vcproj
|
||||
.ignore
|
||||
|
||||
Integration.code-workspace
|
||||
Makefile
|
||||
|
||||
Source/Integration/lpx-*.hpp
|
||||
Source/Integration/lpx-*.cpp
|
||||
|
||||
.vscode/**
|
||||
Saved/**
|
||||
|
||||
BIN
Content/MeshStruct.uasset
LFS
Normal file
BIN
Content/MeshStruct.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/NameToMeshTable.uasset
LFS
Normal file
BIN
Content/NameToMeshTable.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/TangibleActor.uasset
LFS
BIN
Content/TangibleActor.uasset
LFS
Binary file not shown.
@@ -2,9 +2,11 @@
|
||||
#include "AnimQueue.h"
|
||||
|
||||
FlxAnimationStep::FlxAnimationStep(uint64 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 bool ClearProperties(const FString& prefix, UObject* obj) {
|
||||
@@ -27,32 +29,35 @@ static bool ClearProperties(const FString& prefix, UObject* obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SetProperty(const FString& name, UObject* obj, const FlxAnimationField& field) {
|
||||
#pragma optimize("", off)
|
||||
|
||||
static bool SetProperty(const FString& prefix, UObject* obj, const FlxAnimationField& field) {
|
||||
UClass* uclass = obj->GetClass();
|
||||
FName nname(name);
|
||||
FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data());
|
||||
FName nname(prefix + sname);
|
||||
switch (field.Type) {
|
||||
case ElxAnimValueType::STRING: {
|
||||
case SimpleDynamicTag::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 ElxAnimValueType::NUMBER: {
|
||||
case SimpleDynamicTag::NUMBER: {
|
||||
FDoubleProperty* fprop = FindFProperty<FDoubleProperty>(uclass, nname);
|
||||
if (fprop == nullptr) return false;
|
||||
double* pptr = fprop->ContainerPtrToValuePtr<double>(obj);
|
||||
fprop->SetPropertyValue(pptr, field.X);
|
||||
*pptr = field.X;
|
||||
return true;
|
||||
}
|
||||
case ElxAnimValueType::BOOLEAN: {
|
||||
case SimpleDynamicTag::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 ElxAnimValueType::XYZ: {
|
||||
case SimpleDynamicTag::VECTOR: {
|
||||
FStructProperty* fprop = FindFProperty<FStructProperty>(uclass, nname);
|
||||
if (fprop == nullptr) return false;
|
||||
if (fprop->Struct != TBaseStructure<FVector>::Get()) return false;
|
||||
@@ -64,73 +69,179 @@ static bool SetProperty(const FString& name, UObject* obj, const FlxAnimationFie
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FlxAnimationStep::Unpack(const FString& prefix, UObject* into, bool preclear) const {
|
||||
#pragma optimize("", off)
|
||||
bool FlxAnimationStep::Unpack(const FString& prefix, UObject* into) const {
|
||||
UClass* uclass = into->GetClass();
|
||||
std::string_view body((const char*)(Body.GetData()), Body.Num());
|
||||
FlxAnimationStepDecoder decoder(body);
|
||||
bool ok = true;
|
||||
if (preclear) {
|
||||
ok &= ClearProperties(prefix, into);
|
||||
}
|
||||
bool ok = ClearProperties(prefix, into);
|
||||
while (!decoder.AtEOF()) {
|
||||
FlxAnimationField field = decoder.ReadField();
|
||||
FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data());
|
||||
ok &= SetProperty(prefix + sname, into, field);
|
||||
if (Finished && !field.Persistent) continue;
|
||||
ok &= SetProperty(prefix, into, field);
|
||||
}
|
||||
if (Finished) {
|
||||
FlxAnimationField field;
|
||||
field.Name = "action";
|
||||
field.Persistent = false;
|
||||
field.Type = SimpleDynamicTag::STRING;
|
||||
field.S = "idle";
|
||||
ok &= SetProperty(prefix, into, field);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
FString UlxAnimationStepLibrary::AnimationStepDebugString(const FlxAnimationStep& step) {
|
||||
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
|
||||
return FlxAnimationStepDecoder::DebugString(step.Hash, body);
|
||||
return FlxAnimationStepDecoder::DebugString(step.Finished, step.Finished, step.Hash, body);
|
||||
}
|
||||
|
||||
void UlxAnimationStepLibrary::UnpackAnimationStep(const FlxAnimationStep& step,
|
||||
const FString& prefix, UObject* into) {
|
||||
step.Unpack(prefix, into, true);
|
||||
};
|
||||
void UlxAnimationStepLibrary::UnpackAnimationStep(UObject* into, const FlxAnimationStep& step, const FString& prefix) {
|
||||
step.Unpack(prefix, into);
|
||||
}
|
||||
|
||||
static FlxAnimationField FindAnimationField(const FlxAnimationStep& step, const FString& name) {
|
||||
static FlxAnimationField FindAnimationFieldLL(const FlxAnimationStep& step, std::string_view name) {
|
||||
std::string_view body((const char*)(step.Body.GetData()), step.Body.Num());
|
||||
FTCHARToUTF8 utf8name(name);
|
||||
std::string_view uname(utf8name.Get(), utf8name.Length());
|
||||
FlxAnimationStepDecoder decoder(body);
|
||||
FlxAnimationField result;
|
||||
while (!decoder.AtEOF()) {
|
||||
result = decoder.ReadField();
|
||||
if (result.Name == uname) {
|
||||
if (result.Name == name) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
result.Type = ElxAnimValueType::INVALID;
|
||||
result.Type = SimpleDynamicTag::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 == SimpleDynamicTag::VECTOR) {
|
||||
actor->SetActorLocation(FVector(xyz.X, xyz.Y, xyz.Z));
|
||||
}
|
||||
}
|
||||
|
||||
void FlxAnimationStep::AutoUpdateFacing(AActor *actor) const {
|
||||
FlxAnimationField facing = FindAnimationFieldLL(*this, "facing");
|
||||
if (facing.Type == SimpleDynamicTag::NUMBER) {
|
||||
actor->SetActorRotation(FRotator(0, facing.X, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void FlxAnimationStep::AutoUpdatePlane(FName *planep) const {
|
||||
FlxAnimationField plane = FindAnimationFieldLL(*this, "plane");
|
||||
if (plane.Type == SimpleDynamicTag::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 != ElxAnimValueType::XYZ) return FVector(0, 0, 0);
|
||||
if (field.Type != SimpleDynamicTag::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 != ElxAnimValueType::NUMBER) return 0.0;
|
||||
if (field.Type != SimpleDynamicTag::NUMBER) return 0.0;
|
||||
return field.X;
|
||||
}
|
||||
|
||||
#pragma optimize("", off)
|
||||
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 != ElxAnimValueType::STRING) return TEXT("");
|
||||
if (field.Type != SimpleDynamicTag::STRING) return TEXT("");
|
||||
return FString(field.S.size(), (const UTF8CHAR*)(field.S.data()));
|
||||
}
|
||||
|
||||
bool UlxAnimationStepLibrary::AnimationStepGetBool(const FlxAnimationStep& step, const FString& name) {
|
||||
FlxAnimationField field = FindAnimationField(step, name);
|
||||
if (field.Type != ElxAnimValueType::BOOLEAN) return false;
|
||||
if (field.Type != SimpleDynamicTag::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 = (SimpleDynamicTag)Decoder.read_uint8();
|
||||
switch (result.Type) {
|
||||
case SimpleDynamicTag::STRING: {
|
||||
result.S = Decoder.read_string_view();
|
||||
break;
|
||||
}
|
||||
case SimpleDynamicTag::NUMBER: {
|
||||
result.X = Decoder.read_double();
|
||||
break;
|
||||
}
|
||||
case SimpleDynamicTag::BOOLEAN: {
|
||||
result.X = Decoder.read_bool() ? 1.0 : 0.0;
|
||||
break;
|
||||
}
|
||||
case SimpleDynamicTag::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 = SimpleDynamicTag::UNINITIALIZED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
FString FlxAnimationStepDecoder::DebugString(bool injectidle, bool persistentonly, uint64 hash, std::string_view body) {
|
||||
FString result;
|
||||
FlxAnimationStepDecoder decoder(body);
|
||||
result.Appendf(TEXT("Hash=%016llx"), hash);
|
||||
if (injectidle) {
|
||||
result.Appendf(TEXT(" action=idle"));
|
||||
}
|
||||
while (!decoder.AtEOF()) {
|
||||
FlxAnimationField field = decoder.ReadField();
|
||||
if (persistentonly && !field.Persistent) continue;
|
||||
result.Append(TEXT(" "));
|
||||
result.Append(FString(field.Name.size(), (const UTF8CHAR*)field.Name.data()));
|
||||
result.Append(field.Persistent ? TEXT("=") : TEXT(":"));
|
||||
switch (field.Type) {
|
||||
case SimpleDynamicTag::STRING:
|
||||
result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data()));
|
||||
break;
|
||||
case SimpleDynamicTag::NUMBER:
|
||||
result.Appendf(TEXT("%lf"), field.X);
|
||||
break;
|
||||
case SimpleDynamicTag::BOOLEAN:
|
||||
result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false"));
|
||||
break;
|
||||
case SimpleDynamicTag::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_uint64();
|
||||
@@ -138,65 +249,16 @@ FlxAnimationStepView FlxAnimQueueDecoder::ReadStep() {
|
||||
return result;
|
||||
}
|
||||
|
||||
FlxAnimationField FlxAnimationStepDecoder::ReadField() {
|
||||
FlxAnimationField result;
|
||||
result.Name = Decoder.read_string_view();
|
||||
result.Persistent = Decoder.read_bool();
|
||||
result.Type = (ElxAnimValueType)Decoder.read_uint8();
|
||||
switch (result.Type) {
|
||||
case ElxAnimValueType::STRING: {
|
||||
result.S = Decoder.read_string_view();
|
||||
break;
|
||||
}
|
||||
case ElxAnimValueType::NUMBER: {
|
||||
result.X = Decoder.read_double();
|
||||
break;
|
||||
}
|
||||
case ElxAnimValueType::BOOLEAN: {
|
||||
result.X = Decoder.read_bool() ? 1.0 : 0.0;
|
||||
break;
|
||||
}
|
||||
case ElxAnimValueType::XYZ: {
|
||||
result.X = Decoder.read_double();
|
||||
result.Y = Decoder.read_double();
|
||||
result.Z = Decoder.read_double();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Decoder.set_at_eof();
|
||||
result.Type = ElxAnimValueType::BOOLEAN;
|
||||
result.X = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
uint64 FlxAnimQueueDecoder::PeekHash() {
|
||||
int64_t read_count = Decoder.total_reads();
|
||||
uint64 result = Decoder.read_uint64();
|
||||
Decoder.unread_to(read_count);
|
||||
return result;
|
||||
}
|
||||
|
||||
FString FlxAnimationStepDecoder::DebugString(uint64 hash, std::string_view body) {
|
||||
FString result;
|
||||
FlxAnimationStepDecoder decoder(body);
|
||||
result.Appendf(TEXT("Hash=%016llx"), hash);
|
||||
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 ElxAnimValueType::STRING:
|
||||
result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data()));
|
||||
break;
|
||||
case ElxAnimValueType::NUMBER:
|
||||
result.Appendf(TEXT("%lf"), field.X);
|
||||
break;
|
||||
case ElxAnimValueType::BOOLEAN:
|
||||
result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false"));
|
||||
break;
|
||||
case ElxAnimValueType::XYZ:
|
||||
result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -204,42 +266,62 @@ FString FlxAnimQueueDecoder::DebugString(std::string_view queue) {
|
||||
FlxAnimQueueDecoder decoder(queue);
|
||||
while (!decoder.AtEOF()) {
|
||||
FlxAnimationStepView step = decoder.ReadStep();
|
||||
FString stepdebug = FlxAnimationStepDecoder::DebugString(step.Hash, step.Body);
|
||||
FString stepdebug = FlxAnimationStepDecoder::DebugString(false, false, step.Hash, step.Body);
|
||||
result.Appendf(TEXT("%s\n"), *stepdebug);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
FlxAnimTracker::FlxAnimTracker() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
void FlxAnimTracker::Clear() {
|
||||
AQ.Empty();
|
||||
FirstSeqno = 0;
|
||||
HashToSeqno.Empty();
|
||||
UnstartedSeqno = 0;
|
||||
PlaybackMode = ElxAnimationMode::INVALID;
|
||||
AbortedHashes.Empty();
|
||||
Changed = true;
|
||||
}
|
||||
|
||||
void FlxAnimTracker::FinishedAnimation(uint64 hash) {
|
||||
for (int i = 0; i < AQ.Num(); i++) {
|
||||
if (AQ[i].Hash == hash) {
|
||||
AQ[i].Finished = true;
|
||||
Changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlxAnimTracker::SkipToEnd() {
|
||||
for (int i = 0; i < AQ.Num(); i++) {
|
||||
if (!AQ[i].Finished) {
|
||||
AQ[i].Finished = true;
|
||||
Changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlxAnimTracker::Update(std::string_view encqueue) {
|
||||
// Check for an exact match. If the most recent entry
|
||||
// in encqueue has the same hash as the most recent
|
||||
// entry in AQ, then that's an exact match. Or,
|
||||
// if both are empty, that's also an exact match.
|
||||
// In case of exact match, abort early, there's no
|
||||
// further work needed.
|
||||
check(!encqueue.empty());
|
||||
|
||||
// If the first hash matches, we don't bother updating at all.
|
||||
//
|
||||
if (encqueue.empty()) {
|
||||
if (AQ.IsEmpty()) {
|
||||
FlxAnimQueueDecoder decoder(encqueue);
|
||||
if (!AQ.IsEmpty()) {
|
||||
if (decoder.PeekHash() == AQ.Last().Hash) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!AQ.IsEmpty()) {
|
||||
FlxStringDecoder qdecoder(encqueue);
|
||||
uint64 hash = qdecoder.read_uint64();
|
||||
if (hash == 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<uint64, int32> HashToIndex;
|
||||
for (int i = 0; i < AQ.Num(); i++) {
|
||||
HashToIndex.Emplace(AQ[i].Hash, i);
|
||||
}
|
||||
|
||||
// Search for a Luprex animation record that matches
|
||||
@@ -249,108 +331,59 @@ void FlxAnimTracker::Update(std::string_view encqueue) {
|
||||
// At the same time, push all non-matching records
|
||||
// after the matching record onto a stack of new records.
|
||||
//
|
||||
FlxAnimQueueDecoder decoder(encqueue);
|
||||
TArray<FlxAnimationStepView> newsteps;
|
||||
int32 matchingseqno = -1;
|
||||
int32 matchingindex = -1;
|
||||
while (!decoder.AtEOF()) {
|
||||
FlxAnimationStepView step = decoder.ReadStep();
|
||||
int32* stepseq = HashToSeqno.Find(step.Hash);
|
||||
if (stepseq == nullptr) {
|
||||
int32* indexp = HashToIndex.Find(step.Hash);
|
||||
if (indexp == nullptr) {
|
||||
newsteps.Emplace(step);
|
||||
} else {
|
||||
matchingseqno = *stepseq;
|
||||
matchingindex = *indexp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all animations after the most recent matching
|
||||
// record. If we remove a 'started' animation, add that
|
||||
// animation to the list of aborted animations.
|
||||
// record.
|
||||
//
|
||||
int32 nremove = (FirstSeqno + AQ.Num()) - (matchingseqno + 1);
|
||||
int32 nremove = (AQ.Num() - (matchingindex + 1));
|
||||
check((nremove >= 0) && (nremove <= AQ.Num()));
|
||||
for (int32 i = 0; i < nremove; i++) {
|
||||
uint64 lasthash = AQ.Last().Hash;
|
||||
int32 seqno = FirstSeqno + AQ.Num() - 1;
|
||||
HashToSeqno.Remove(lasthash);
|
||||
if (seqno < UnstartedSeqno) {
|
||||
AbortedHashes.Emplace(lasthash);
|
||||
}
|
||||
AQ.PopLast();
|
||||
}
|
||||
|
||||
// If we aborted a 'started' animation, we have to fix
|
||||
// up the Unstarted animation pointer.
|
||||
//
|
||||
// Note: this could leave the Unstarted pointer at
|
||||
// seqno less than FirstSeqno. We will fix that state
|
||||
// of affairs up later.
|
||||
//
|
||||
if (UnstartedSeqno > (FirstSeqno + AQ.Num())) {
|
||||
UnstartedSeqno = matchingseqno;
|
||||
PlaybackMode = ElxAnimationMode::BlendToFinal;
|
||||
}
|
||||
|
||||
// Transfer the new animations onto the queue.
|
||||
//
|
||||
while (!newsteps.IsEmpty()) {
|
||||
FlxAnimationStepView step = newsteps.Pop();
|
||||
int32 seqno = FirstSeqno + AQ.Num();
|
||||
AQ.EmplaceLast(step.Hash, step.Body);
|
||||
HashToSeqno.Emplace(step.Hash, seqno);
|
||||
|
||||
}
|
||||
|
||||
// If there are too many animations in AQ, discard
|
||||
// any very old ones.
|
||||
//
|
||||
if (AQ.Num() > 10) {
|
||||
int ndiscard = AQ.Num() - 10;
|
||||
for (int i = 0; i < ndiscard; i++) {
|
||||
uint64 hash = AQ.First().Hash;
|
||||
HashToSeqno.Remove(hash);
|
||||
AQ.PopFirst();
|
||||
FirstSeqno += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If UnstartedSeqno is before the live range,
|
||||
// then the whole queue has to be replayed. Don't
|
||||
// do that: just fast skip to the end.
|
||||
// TODO: this is hardwired to keep 10. Instead, we
|
||||
// should keep the number specified in the queue.
|
||||
//
|
||||
if (UnstartedSeqno <= FirstSeqno) {
|
||||
if (AQ.Num() == 0) {
|
||||
UnstartedSeqno = FirstSeqno;
|
||||
} else {
|
||||
UnstartedSeqno = FirstSeqno + AQ.Num() - 1;
|
||||
int limit = 10;
|
||||
int ndiscard = AQ.Num() - limit;
|
||||
if (ndiscard > 0) {
|
||||
for (int i = 0; i < ndiscard; i++) {
|
||||
AQ.PopFirst();
|
||||
}
|
||||
PlaybackMode = ElxAnimationMode::WarpToFinal;
|
||||
}
|
||||
}
|
||||
|
||||
TArray<uint64> FlxAnimTracker::GetAborted() {
|
||||
TArray<uint64> result;
|
||||
result = std::move(AbortedHashes);
|
||||
AbortedHashes.Empty();
|
||||
FlxAnimationStep FlxAnimTracker::GetCurrentAnimation() {
|
||||
FlxAnimationStep result;
|
||||
for (int i = 0; i < AQ.Num(); i++) {
|
||||
if (!AQ[i].Finished) {
|
||||
return AQ[i];
|
||||
}
|
||||
}
|
||||
result = AQ.Last();
|
||||
result.Hash = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
ElxAnimationMode FlxAnimTracker::GetNextStep(FlxAnimationStep &step) {
|
||||
int offset = UnstartedSeqno - FirstSeqno;
|
||||
if (offset < AQ.Num()) {
|
||||
step = AQ[offset];
|
||||
return PlaybackMode;
|
||||
} else {
|
||||
step.Hash = 0;
|
||||
step.Body.Empty();
|
||||
return ElxAnimationMode::INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void FlxAnimTracker::StartedStep(uint64 hash) {
|
||||
int offset = UnstartedSeqno - FirstSeqno;
|
||||
check(offset < AQ.Num());
|
||||
check(AQ[offset].Hash == hash);
|
||||
UnstartedSeqno += 1;
|
||||
PlaybackMode = ElxAnimationMode::StartAnimation;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,46 +5,6 @@
|
||||
#include "Containers/Deque.h"
|
||||
#include "AnimQueue.generated.h"
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//
|
||||
// This is copied over from Luprex source. Not ideal.
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
|
||||
enum class ElxAnimValueType {
|
||||
STRING,
|
||||
NUMBER,
|
||||
BOOLEAN,
|
||||
XYZ,
|
||||
INVALID
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//
|
||||
// Playback Modes
|
||||
//
|
||||
// There are three different ways to play an animation:
|
||||
//
|
||||
// 1. Start Animation. This is the normal way to play an animation.
|
||||
//
|
||||
// 2. Warp to Final. Skip the actual animation, and jump
|
||||
// instantaneously to the final state of the animation.
|
||||
//
|
||||
// 3. Blend to Final. Skip the actual animation, and blend
|
||||
// smoothly to the final state of the animation. If the current
|
||||
// state is very far from the final state, then maybe
|
||||
// jump instantaneously instead.
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxAnimationMode : uint8 {
|
||||
StartAnimation,
|
||||
WarpToFinal,
|
||||
BlendToFinal,
|
||||
INVALID,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//
|
||||
// An single animation step.
|
||||
@@ -61,13 +21,19 @@ struct INTEGRATION_API FlxAnimationStep {
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
bool Finished;
|
||||
|
||||
UPROPERTY()
|
||||
uint64 Hash;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<uint8> Body;
|
||||
|
||||
FlxAnimationStep() : Hash(0), Body() {}
|
||||
UPROPERTY()
|
||||
FString Blueprint;
|
||||
|
||||
FlxAnimationStep() : Finished(false), Hash(0), Body(), Blueprint() {}
|
||||
FlxAnimationStep(uint64 h, std::string_view b);
|
||||
|
||||
// Unpack an AnimStep into a UObject
|
||||
@@ -78,6 +44,14 @@ public:
|
||||
// routine tries to find a string property "color" in the
|
||||
// UObject, and then it sets that property to "blue."
|
||||
//
|
||||
// The prefix is prepended to the key names. For example,
|
||||
// if one of the key-value pairs is "color=blue", and the
|
||||
// prefix is "aq", then the property "aqColor=blue" will be
|
||||
// stored in the UObject.
|
||||
//
|
||||
// All properties of the UObject starting with the specified
|
||||
// prefix are cleared before unpacking the animation step.
|
||||
//
|
||||
// Returns true if all of the key-value pairs in the
|
||||
// animation step could be unpacked into fields of the UObject.
|
||||
// This could fail, for instance, if the UObject just doesn't
|
||||
@@ -85,16 +59,23 @@ public:
|
||||
// fail if there's a type mismatch. For example, "color=blue"
|
||||
// cannot be stored in a property "color" of type int.
|
||||
//
|
||||
// The prefix is prepended to the key names. For example,
|
||||
// if one of the key-value pairs is "color=blue", and the
|
||||
// prefix is "aq", then the property "aqColor=blue" will be
|
||||
// stored in the UObject.
|
||||
// Automatically injects a boolean property "idle" representing
|
||||
// whether the property's finished flag is set.
|
||||
//
|
||||
// If pre-clear is true, then all properties of the UObject
|
||||
// starting with the specified prefix are cleared before
|
||||
// unpacking the animation step.
|
||||
bool Unpack(const FString& prefix, UObject* into) const;
|
||||
|
||||
// Auto-Execute
|
||||
//
|
||||
bool Unpack(const FString& prefix, UObject* into, bool preclear = true) const;
|
||||
// These functions automatically update certain actor
|
||||
// properties:
|
||||
//
|
||||
// AutoUpdateXYZ(AActor *actor); // uses 'xyz' keyword
|
||||
// AutoUpdateFacing(AActor *actor); // uses 'facing' keyword.
|
||||
// AutoUpdatePlane(FName *plane); // uses 'plane' keyword
|
||||
//
|
||||
void AutoUpdateXYZ(AActor *actor) const;
|
||||
void AutoUpdateFacing(AActor *actor) const;
|
||||
void AutoUpdatePlane(FName *plane) const;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
@@ -114,10 +95,15 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex)
|
||||
static FString AnimationStepDebugString(const FlxAnimationStep& step);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = Luprex)
|
||||
static void UnpackAnimationStep(const FlxAnimationStep& step,
|
||||
const FString& VariableNamePrefix, UObject* into);
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "into"), Category = Luprex)
|
||||
static void UnpackAnimationStep(UObject* into, const FlxAnimationStep& step, const FString& VariableNamePrefix = TEXT("aq"));
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex")
|
||||
static bool AnimationStepEqual(const FlxAnimationStep &StepA, const FlxAnimationStep &StepB);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex")
|
||||
static bool AnimationStepIsIdle(const FlxAnimationStep &step);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex")
|
||||
static FVector AnimationStepGetVector(const FlxAnimationStep& step, const FString& name);
|
||||
|
||||
@@ -129,18 +115,13 @@ public:
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex")
|
||||
static bool AnimationStepGetBool(const FlxAnimationStep& step, const FString& name);
|
||||
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//
|
||||
// Exposing functions to blueprints.
|
||||
//
|
||||
////////////////////////////////////////////////
|
||||
|
||||
//UFUNCTION(BlueprintCallable)
|
||||
//void Unpack(const FString& prefix, UObject* into, bool preclear = true);
|
||||
|
||||
struct FlxAnimationStepView {
|
||||
uint64 Hash;
|
||||
std::string_view Body;
|
||||
@@ -166,7 +147,7 @@ struct FlxAnimationStepView {
|
||||
struct FlxAnimationField {
|
||||
std::string_view Name;
|
||||
bool Persistent;
|
||||
ElxAnimValueType Type;
|
||||
SimpleDynamicTag Type;
|
||||
double X, Y, Z;
|
||||
std::string_view S;
|
||||
};
|
||||
@@ -184,21 +165,38 @@ struct FlxAnimationField {
|
||||
|
||||
class FlxAnimQueueDecoder {
|
||||
private:
|
||||
FlxStringDecoder Decoder;
|
||||
FlxStreamBuffer Decoder;
|
||||
|
||||
// These values are immediately read from the header.
|
||||
//
|
||||
int SizeLimit;
|
||||
int ActualSize;
|
||||
|
||||
public:
|
||||
// Initialize the FlxAnimQueueDecoder with the encoded animation queue.
|
||||
//
|
||||
FlxAnimQueueDecoder(std::string_view s) : Decoder(s) {}
|
||||
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 the end of the string.
|
||||
//
|
||||
bool AtEOF() { return Decoder.at_eof(); }
|
||||
bool AtEOF() { return Decoder.empty(); }
|
||||
|
||||
// Read one animation step.
|
||||
//
|
||||
FlxAnimationStepView ReadStep();
|
||||
|
||||
// Peek at the hash of the next animation step.
|
||||
//
|
||||
uint64 PeekHash();
|
||||
|
||||
// Convert an AnimQueue to an FString.
|
||||
//
|
||||
static FString DebugString(std::string_view s);
|
||||
@@ -217,7 +215,7 @@ public:
|
||||
|
||||
class FlxAnimationStepDecoder {
|
||||
private:
|
||||
FlxStringDecoder Decoder;
|
||||
FlxStreamBuffer Decoder;
|
||||
|
||||
public:
|
||||
// Initialize the FlxAnimationStepDecoder from the FlxAnimationStepView.
|
||||
@@ -226,7 +224,7 @@ public:
|
||||
|
||||
// Return true if the parser has reached the end of the string.
|
||||
//
|
||||
bool AtEOF() { return Decoder.at_eof(); }
|
||||
bool AtEOF() { return Decoder.empty(); }
|
||||
|
||||
// Read one field.
|
||||
//
|
||||
@@ -234,7 +232,7 @@ public:
|
||||
|
||||
// Convert an AnimStep to an FString.
|
||||
//
|
||||
static FString DebugString(uint64 hash, std::string_view body);
|
||||
static FString DebugString(bool injectidle, bool persistentonly, uint64 hash, std::string_view body);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
@@ -257,26 +255,11 @@ public:
|
||||
//
|
||||
TDeque<FlxAnimationStep> AQ;
|
||||
|
||||
// The sequence number of the first item in AQ.
|
||||
// True if something has recently changed.
|
||||
//
|
||||
int32 FirstSeqno;
|
||||
|
||||
// Map from hash to sequence number.
|
||||
//
|
||||
TMap<uint64, int32> HashToSeqno;
|
||||
|
||||
// The sequence number of the first unstarted animation.
|
||||
//
|
||||
int32 UnstartedSeqno;
|
||||
|
||||
// Indicates whether the unstarted animation should be played or otherwise.
|
||||
//
|
||||
ElxAnimationMode PlaybackMode;
|
||||
|
||||
// Array of recently-aborted hash values.
|
||||
//
|
||||
TArray<uint64> AbortedHashes;
|
||||
bool Changed;
|
||||
|
||||
private:
|
||||
public:
|
||||
// Construct a tracker.
|
||||
//
|
||||
@@ -284,6 +267,10 @@ public:
|
||||
//
|
||||
FlxAnimTracker();
|
||||
|
||||
// Clear everything, reset to the initial state.
|
||||
//
|
||||
void Clear();
|
||||
|
||||
// Update from the specified animation queue.
|
||||
//
|
||||
// After the update is done, AQ will be a copy
|
||||
@@ -291,24 +278,39 @@ public:
|
||||
//
|
||||
void Update(std::string_view encqueue);
|
||||
|
||||
// Fetch the aborted hash values.
|
||||
// Get the current animation step.
|
||||
//
|
||||
// This gets the array of aborted hashes and clears
|
||||
// the stored array.
|
||||
//
|
||||
TArray<uint64> GetAborted();
|
||||
// Get the current animation step. This is the step that the
|
||||
// blueprint should currently be playing.
|
||||
//
|
||||
FlxAnimationStep GetCurrentAnimation();
|
||||
|
||||
// Get the next unstarted animation step.
|
||||
// Declare that an animation is finished.
|
||||
//
|
||||
// Get the next animation step. Returns the next step and the
|
||||
// playback mode. If the playback mode is INVALID then there is
|
||||
// no next step to play
|
||||
//
|
||||
ElxAnimationMode GetNextStep(FlxAnimationStep& step);
|
||||
// The blueprint uses this function call to indicate that it
|
||||
// is done playing the specified animation. This will cause the
|
||||
// animation to be marked as finished, which in turn causes
|
||||
// 'GetCurrentStep' to advance to the next animation.
|
||||
//
|
||||
void FinishedAnimation(uint64 Hash);
|
||||
|
||||
// Declare that an animation has been started.
|
||||
// Skip to the end of the animation queue.
|
||||
//
|
||||
// This is equivalent to calling 'FinishedHash' on every
|
||||
// animation in the entire queue.
|
||||
//
|
||||
// After starting an animation, you should call this.
|
||||
void SkipToEnd();
|
||||
|
||||
// Clear the 'Changed' flag.
|
||||
//
|
||||
void StartedStep(uint64 Hash);
|
||||
void ClearChanged() { Changed = false; }
|
||||
|
||||
// Get the 'Changed' flag.
|
||||
//
|
||||
// The changed flag is set to true whenever the Luprex animation
|
||||
// queue changes from its previous state. The changed flag is also
|
||||
// set to true whenever 'SetFinished' marks an animation as finished.
|
||||
// The changed flag can only be set to false by 'ClearChanged,' above.
|
||||
//
|
||||
bool IsChanged() const { return Changed; }
|
||||
};
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
#include "IntegrationGameModeBase.h"
|
||||
#include "lpx-drvutil.hpp"
|
||||
#include "lpx-paths.hpp"
|
||||
#include "DebugPrint.h"
|
||||
#include "Tangible.h"
|
||||
#include "TangibleManager.h"
|
||||
#include "TangibleInterface.h"
|
||||
#include "CommonTypes.h"
|
||||
#include "AnimQueue.h"
|
||||
#include <string>
|
||||
@@ -16,6 +16,7 @@ using namespace CommonTypes;
|
||||
|
||||
AIntegrationGameModeBase::AIntegrationGameModeBase()
|
||||
{
|
||||
TangibleManager = NewObject<UlxTangibleManager>();
|
||||
EngineSeconds = 0.0;
|
||||
NextThreadTrigger = 1.0;
|
||||
//PrimaryActorTick.bCanEverTick = true; // Probably wrong
|
||||
@@ -47,6 +48,11 @@ void AIntegrationGameModeBase::ResetToInitialState()
|
||||
{
|
||||
Playing = false;
|
||||
|
||||
if (TangibleManager != nullptr) {
|
||||
TangibleManager->ConditionalBeginDestroy();
|
||||
TangibleManager = nullptr;
|
||||
}
|
||||
|
||||
// Shut down the thread
|
||||
LuprexUpdateTask.Shutdown();
|
||||
|
||||
@@ -104,67 +110,73 @@ void AIntegrationGameModeBase::MaybeTriggerUpdateTask(float deltaseconds) {
|
||||
}
|
||||
}
|
||||
|
||||
//#pragma optimize( "", off )
|
||||
//void SetLocal(UObject* obj, const char *name, int value) {
|
||||
// FString sname((const UTF8CHAR *)name);
|
||||
// FName nname(sname);
|
||||
// UClass* uclass = obj->GetClass();
|
||||
// FProperty* fprop = FindFProperty<FProperty>(uclass, nname);
|
||||
// FStructProperty* sprop = FindFProperty<FStructProperty>(uclass, nname);
|
||||
//}
|
||||
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
void AIntegrationGameModeBase::UpdateTangibles() {
|
||||
double radius = 1000.0; // Hardwired for now.
|
||||
using TanArray = UlxTangibleManager::TanArray;
|
||||
if (!Playing) return;
|
||||
FlxLockedWrapper w(LockableWrapper);
|
||||
int64 actor = w.GetActor();
|
||||
TangibleManager.SetActor(actor);
|
||||
TangibleManager.SetNear(w.GetNear(actor, 100, 100, 100));
|
||||
for (int64 id : TangibleManager.GetNear()) {
|
||||
TangibleManager.MakeTangible(id);
|
||||
int64 player = w.GetActor();
|
||||
IdView nearids = w.GetNear(player, radius, radius, radius);
|
||||
TangibleManager->UpdateNearAccordingToLuprex(nearids);
|
||||
TanArray alltans = TangibleManager->GetAllTangibles();
|
||||
IdArray allids = TangibleManager->GetIds(alltans);
|
||||
StringViewVec allqueues = w.GetAnimationQueues(allids);
|
||||
for (int i = 0; i < alltans.Num(); i++) {
|
||||
alltans[i]->UpdateAnimationQueue(allqueues[i]);
|
||||
}
|
||||
// Update animation queues of live tangibles.
|
||||
IdArray tanids = TangibleManager.GetLive();
|
||||
StringViewVec aqueues = w.GetAnimationQueues(tanids);
|
||||
for (int i = 0; i < tanids.Num(); i++) {
|
||||
uint64_t tanid = tanids[i];
|
||||
std::string_view aqueue = aqueues[i];
|
||||
UlxTangible* t = TangibleManager.GetTangible(tanid);
|
||||
check(t != nullptr);
|
||||
t->AnimTracker.Update(aqueue);
|
||||
TangibleManager->RecalcNearAccordingToUnreal(player, radius);
|
||||
TangibleManager->DeleteFarawayTangibles();
|
||||
}
|
||||
|
||||
TArray<uint64> aborted = t->AnimTracker.GetAborted();
|
||||
for (uint64 hash : aborted) {
|
||||
IlxTangibleInterface::Execute_AbortAnimation(t->Actor, hash);
|
||||
}
|
||||
FlxAnimationStep step;
|
||||
ElxAnimationMode mode = t->AnimTracker.GetNextStep(step);
|
||||
if (mode != ElxAnimationMode::INVALID) {
|
||||
bool started = IlxTangibleInterface::Execute_StartAnimation(t->Actor, mode, step);
|
||||
if (started) {
|
||||
t->AnimTracker.StartedStep(step.Hash);
|
||||
}
|
||||
}
|
||||
void AIntegrationGameModeBase::ExecuteDebuggingCommand(const FString &fs) {
|
||||
if (fs == "\\invokeplayer") {
|
||||
DPrint(TEXT("Trying to invoke 'myfunction' in lua"));
|
||||
FlxLockedWrapper w(LockableWrapper);
|
||||
FlxStreamBuffer sb;
|
||||
sb.write_string("myfunction");
|
||||
sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER);
|
||||
sb.write_double(3.0);
|
||||
sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING);
|
||||
sb.write_string("Howdy");
|
||||
sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR);
|
||||
sb.write_fvector(FVector(2,3,4));
|
||||
std::string_view datapk = sb.view();
|
||||
int64 player = w.GetActor();
|
||||
w->play_invoke_engio(w.Get(), player, datapk.size(), datapk.data());
|
||||
} else {
|
||||
ConsoleOutput.AppendLine(TEXT("Unknown Command"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AIntegrationGameModeBase::ConsoleSendInput(const FString& fs)
|
||||
{
|
||||
FlxLockedWrapper w(LockableWrapper);
|
||||
if (w->engine != nullptr)
|
||||
{
|
||||
const TCHAR* fstchar = *fs;
|
||||
if (sizeof(TCHAR) == 2)
|
||||
{
|
||||
ConsoleOutput.AppendLine(FString("> ") + fs);
|
||||
std::u16string_view fsview((const char16_t*)fstchar, fs.Len());
|
||||
std::string utf8 = drvutil::utf16_to_utf8(fsview);
|
||||
utf8 = utf8 + "\n";
|
||||
w->play_recv_incoming(w.Get(), 0, utf8.size(), utf8.c_str());
|
||||
ConsoleOutput.AppendLine(FString("> ") + fs);
|
||||
// This is a bad way to do this. The problem is that if some
|
||||
// lua code contains '\\', we'll catch it instead of passing it
|
||||
// through. There's no simple solution, though.
|
||||
if (fs[0] == '\\') {
|
||||
ExecuteDebuggingCommand(fs);
|
||||
} else {
|
||||
const TCHAR* fstchar = *fs;
|
||||
if (sizeof(TCHAR) == 2)
|
||||
{
|
||||
std::u16string_view fsview((const char16_t*)fstchar, fs.Len());
|
||||
std::string utf8 = drvutil::utf16_to_utf8(fsview);
|
||||
utf8 = utf8 + "\n";
|
||||
w->play_recv_incoming(w.Get(), 0, utf8.size(), utf8.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AIntegrationGameModeBase::Tick(float deltaseconds)
|
||||
{
|
||||
Super::Tick(deltaseconds);
|
||||
@@ -209,22 +221,25 @@ void AIntegrationGameModeBase::BeginPlay()
|
||||
if (w->play_initialize != nullptr)
|
||||
{
|
||||
drvutil::ostringstream srcpak;
|
||||
std::string srcpakerr = drvutil::package_lua_source("c:\\Luprex", &srcpak);
|
||||
std::string srcpakerr = drvutil::package_lua_source(LUPREX_ROOT_PATH, &srcpak);
|
||||
if (!srcpakerr.empty())
|
||||
{
|
||||
DPrint(srcpakerr.c_str());
|
||||
}
|
||||
std::string_view srcpakv = srcpak.view();
|
||||
char* argv[1];
|
||||
argv[0] = const_cast<char*>("lpxserver");
|
||||
w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), "");
|
||||
if (w->error[0])
|
||||
else
|
||||
{
|
||||
DPrint(w->error);
|
||||
}
|
||||
if (w->engine != nullptr) {
|
||||
DPrint("Luprex initialize success");
|
||||
Playing = true;
|
||||
std::string_view srcpakv = srcpak.view();
|
||||
char* argv[1];
|
||||
argv[0] = const_cast<char*>("lpxserver");
|
||||
w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), "");
|
||||
if (w->error[0])
|
||||
{
|
||||
DPrint(w->error);
|
||||
}
|
||||
if (w->engine != nullptr) {
|
||||
DPrint("Luprex initialize success");
|
||||
Playing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +252,8 @@ void AIntegrationGameModeBase::BeginPlay()
|
||||
}
|
||||
|
||||
// Initialize the tangible manager.
|
||||
TangibleManager.Init(GetWorld(), ClassTangibleActor);
|
||||
TangibleManager = NewObject<UlxTangibleManager>();
|
||||
TangibleManager->Init(GetWorld(), ClassTangibleActor);
|
||||
}
|
||||
|
||||
void AIntegrationGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
@@ -245,4 +261,3 @@ void AIntegrationGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
ResetToInitialState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ public:
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Luprex")
|
||||
TSubclassOf<AActor> ClassTangibleActor;
|
||||
|
||||
// Execute a debugging command, typed on the GUI.
|
||||
void ExecuteDebuggingCommand(const FString &fs);
|
||||
|
||||
// Transfer console output from the Luprex engine to unreal.
|
||||
void UpdateConsoleOutput();
|
||||
|
||||
@@ -57,7 +60,7 @@ public:
|
||||
virtual uint32 Run() override;
|
||||
|
||||
UPROPERTY()
|
||||
FTangibleManager TangibleManager;
|
||||
UlxTangibleManager *TangibleManager;
|
||||
|
||||
// This stores the entire text currently visible in the console.
|
||||
FlxConsoleOutput ConsoleOutput;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "LockedWrapper.h"
|
||||
#include "DebugPrint.h"
|
||||
#include "lpx-drvutil.hpp"
|
||||
#include "lpx-paths.hpp"
|
||||
|
||||
using namespace CommonTypes;
|
||||
|
||||
@@ -10,7 +11,9 @@ void FlxLockedWrapper::InitWrapper() {
|
||||
// Already initialized.
|
||||
return;
|
||||
}
|
||||
void* DLL = FPlatformProcess::GetDllHandle(TEXT("c:\\Luprex\\build\\visual\\luprexlib.dll"));
|
||||
FString dll((const UTF8CHAR*)LUPREX_DLL_PATH);
|
||||
DebugPrint::DPrint(dll);
|
||||
void* DLL = FPlatformProcess::GetDllHandle(*dll);
|
||||
if (DLL != nullptr) {
|
||||
using InitFn = void (*)(EngineWrapper*);
|
||||
InitFn init = (InitFn)FPlatformProcess::GetDllExport(DLL, TEXT("init_engine_wrapper"));
|
||||
@@ -43,7 +46,7 @@ int64 FlxLockedWrapper::GetActor() {
|
||||
IdView FlxLockedWrapper::GetNear(int64 id, double rx, double ry, double rz) {
|
||||
uint32 size;
|
||||
int64* data;
|
||||
Lockable.Wrapper.get_tangibles_near(Get(), id, rx, ry, rz, &size, &data);
|
||||
Lockable.Wrapper.get_tangibles_near(Get(), id, rx, ry, rz, &size, (int64_t**)&data);
|
||||
return IdView(data, size);
|
||||
}
|
||||
|
||||
@@ -64,7 +67,7 @@ StringViewVec FlxLockedWrapper::GetAnimationQueues(IdView ids) {
|
||||
const char** StrBuf = Lockable.AQStrBuffer.GetData();
|
||||
|
||||
// Get the animation queues into the static buffers.
|
||||
Lockable.Wrapper.get_animation_queues(Get(), num, ids.GetData(), LenBuf, StrBuf);
|
||||
Lockable.Wrapper.get_animation_queues(Get(), num, (const int64_t *)ids.GetData(), LenBuf, StrBuf);
|
||||
|
||||
// Transfer data from static buffers into an array of string_view
|
||||
StringViewVec result;
|
||||
@@ -73,4 +76,4 @@ StringViewVec FlxLockedWrapper::GetAnimationQueues(IdView ids) {
|
||||
result[i] = std::string_view(StrBuf[i], LenBuf[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,13 @@ THIRD_PARTY_INCLUDES_START
|
||||
THIRD_PARTY_INCLUDES_END
|
||||
#undef UI
|
||||
|
||||
#ifdef __linux__
|
||||
#else
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -241,6 +245,26 @@ public:
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef __linux__
|
||||
inline static void strerror_helper(int status, int errnum, char errbuf[256]) {
|
||||
if (status != 0) {
|
||||
snprintf(errbuf, 256, "unknown errno %d", errnum);
|
||||
}
|
||||
}
|
||||
|
||||
inline static void strerror_helper(const char *result, int errnum, char errbuf[256]) {
|
||||
if (result != errbuf) {
|
||||
snprintf(errbuf, 256, "%s", result);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string strerror_str(int errnum) {
|
||||
char buf[256];
|
||||
auto rval = strerror_r(errnum, buf, 256);
|
||||
strerror_helper(rval, errnum, buf);
|
||||
return buf;
|
||||
}
|
||||
#else
|
||||
static std::string strerror_str(int errnum) {
|
||||
char buf[256];
|
||||
int status = strerror_s(buf, 256, errnum);
|
||||
@@ -250,6 +274,8 @@ static std::string strerror_str(int errnum) {
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static FSocket* OpenConnection(ISocketSubsystem *subsys, const std::string& host, const std::string& port, std::string& err)
|
||||
{
|
||||
@@ -401,6 +427,12 @@ static SSL_CTX* SSLNewContext(int verify, const SSL_METHOD *method, BIO *tracebi
|
||||
return ctx;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) {
|
||||
check(SSL_CTX_set_default_verify_paths(ctx) == 1);
|
||||
return "";
|
||||
}
|
||||
#else
|
||||
static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) {
|
||||
HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT");
|
||||
|
||||
@@ -427,6 +459,7 @@ static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) {
|
||||
CertCloseStore(hStore, 0);
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
static std::string SSLUseCertificateString(SSL_CTX* ctx, const char* str) {
|
||||
SSLClearErrors();
|
||||
@@ -747,7 +780,7 @@ void FLpxChannel::Advance()
|
||||
AdvanceReadWrite();
|
||||
break;
|
||||
default:
|
||||
checkf(false, L"EChanState is invalid");
|
||||
checkf(false, TEXT("EChanState is invalid"));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
34
Source/Integration/SampleActorComponent.cpp
Normal file
34
Source/Integration/SampleActorComponent.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SampleActorComponent.h"
|
||||
|
||||
// Sets default values for this component's properties
|
||||
USampleActorComponent::USampleActorComponent()
|
||||
{
|
||||
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
|
||||
// off to improve performance if you don't need them.
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
// Called when the game starts
|
||||
void USampleActorComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// ...
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Called every frame
|
||||
void USampleActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
28
Source/Integration/SampleActorComponent.h
Normal file
28
Source/Integration/SampleActorComponent.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "SampleActorComponent.generated.h"
|
||||
|
||||
|
||||
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
|
||||
class INTEGRATION_API USampleActorComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets default values for this component's properties
|
||||
USampleActorComponent();
|
||||
|
||||
protected:
|
||||
// Called when the game starts
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
public:
|
||||
// Called every frame
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
|
||||
};
|
||||
@@ -1,26 +1,2 @@
|
||||
#include "StringDecoder.h"
|
||||
|
||||
FlxStringDecoder::FlxStringDecoder(std::string_view s) {
|
||||
Text = s.data();
|
||||
Size = s.size();
|
||||
ErrBeyondEOF = false;
|
||||
ErrStringTooLong = false;
|
||||
}
|
||||
|
||||
std::string_view FlxStringDecoder::read_string_view() {
|
||||
size_t length = read_length();
|
||||
if (length > Size) {
|
||||
ErrBeyondEOF = true;
|
||||
return std::string_view();
|
||||
}
|
||||
std::string_view result(Text, length);
|
||||
Text += length;
|
||||
Size -= length;
|
||||
return result;
|
||||
}
|
||||
|
||||
void FlxStringDecoder::set_at_eof() {
|
||||
Text += Size;
|
||||
Size = 0;
|
||||
|
||||
}
|
||||
@@ -1,116 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "lpx-basewriter.hpp"
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//
|
||||
// FlxStringDecoder
|
||||
//
|
||||
// This class is used to decipher 8-bit strings that
|
||||
// contain packed ints, strings, and other data.
|
||||
// The typical example of usage is to decipher the
|
||||
// serialized animation queues fed to us by Luprex.
|
||||
//
|
||||
// The FlxStringDecoder doesn't make a copy of the string
|
||||
// you pass in, instead, it stores a pointer to it.
|
||||
// So be sure not to free the string until you're
|
||||
// done analyzing it.
|
||||
//
|
||||
// You'll note that some of the function names are
|
||||
// lowercase, with underscores. That's because they're
|
||||
// inherited from Luprex classes, and luprex classes
|
||||
// use that naming convention. There's not any easy
|
||||
// workaround for that.
|
||||
//
|
||||
/////////////////////////////////////////////////////
|
||||
#include "lpx-basebuffer.hpp"
|
||||
|
||||
|
||||
class FlxStringDecoder : public BaseReader<FlxStringDecoder> {
|
||||
using FlxSimpleDynamic = SimpleDynamic<std::string>;
|
||||
|
||||
class FlxStreamBufferCore {
|
||||
private:
|
||||
const char* Text;
|
||||
size_t Size;
|
||||
bool err_eof_on_read_;
|
||||
bool err_string_too_long_;
|
||||
bool err_integer_truncated_;
|
||||
protected:
|
||||
|
||||
public:
|
||||
// You can check and clear these error flags at will.
|
||||
//
|
||||
bool ErrBeyondEOF;
|
||||
bool ErrStringTooLong;
|
||||
|
||||
public:
|
||||
// This function is required by BaseReader.
|
||||
// It's not meant for end users.
|
||||
//
|
||||
void read_bytes_into(char* buffer, size_t size) {
|
||||
if (size > Size) {
|
||||
memset(buffer, 0, size);
|
||||
ErrBeyondEOF = true;
|
||||
set_at_eof();
|
||||
}
|
||||
else {
|
||||
memcpy(buffer, Text, size);
|
||||
Text += size;
|
||||
Size -= size;
|
||||
}
|
||||
}
|
||||
|
||||
// This function is required by BaseReader.
|
||||
// It's not meant for end users.
|
||||
//
|
||||
void raise_string_too_long() {
|
||||
ErrStringTooLong = true;
|
||||
}
|
||||
|
||||
public:
|
||||
// The type returned by read_string.
|
||||
//
|
||||
using read_string_type = std::string;
|
||||
|
||||
// Initialize the string decoder with a text to analyze.
|
||||
//
|
||||
FlxStringDecoder(std::string_view s);
|
||||
|
||||
// Get the size of the remaining text.
|
||||
//
|
||||
size_t get_size() { return Size; }
|
||||
void *basebuffer_malloc(size_t size) { return malloc(size); }
|
||||
void basebuffer_free(void *p) { free(p); }
|
||||
void clear_error_flags() { err_eof_on_read_ = err_string_too_long_ = err_integer_truncated_ = false; }
|
||||
void raise_eof_on_read() { err_eof_on_read_ = true; }
|
||||
void raise_string_too_long() { err_string_too_long_ = true; }
|
||||
void raise_integer_truncated() { err_integer_truncated_ = true; }
|
||||
|
||||
// Return true if the remaining text is empty.
|
||||
//
|
||||
bool at_eof() { return Size == 0; }
|
||||
|
||||
// Move the cursor to EOF.
|
||||
//
|
||||
void set_at_eof();
|
||||
|
||||
// Read a string as a string_view
|
||||
//
|
||||
// This reads a string from the source, returning
|
||||
// it as a string_view that points into the buffer.
|
||||
// Naturally, if you release the buffer, the
|
||||
// string_view is invalidated.
|
||||
//
|
||||
// This is considerably faster than read_string.
|
||||
//
|
||||
std::string_view read_string_view();
|
||||
|
||||
// Inherited Methods:
|
||||
//
|
||||
// The following methods are inherited from BaseReader:
|
||||
//
|
||||
// uint8_t read_uint8();
|
||||
// uint16_t read_uint16();
|
||||
// uint32_t read_uint32();
|
||||
// uint64_t read_uint64();
|
||||
// int8_t read_int8();
|
||||
// int16_t read_int16();
|
||||
// int32_t read_int32();
|
||||
// int64_t read_int64();
|
||||
// bool read_bool();
|
||||
// char read_char();
|
||||
// float read_float();
|
||||
// double read_double();
|
||||
// size_t read_length();
|
||||
// std::string read_string_limit(uint64_t size);
|
||||
// std::string read_string();
|
||||
//
|
||||
bool get_err_eof_on_read() const { return err_eof_on_read_; }
|
||||
bool get_err_string_too_long() const { return err_string_too_long_; }
|
||||
bool get_err_integer_truncated() const { return err_integer_truncated_; }
|
||||
bool any_error() const { return err_eof_on_read_ || err_string_too_long_ || err_integer_truncated_; }
|
||||
};
|
||||
|
||||
class FlxStreamBuffer : public BaseBuffer<FlxStreamBufferCore, std::string> {
|
||||
public:
|
||||
using BaseBuffer::BaseBuffer;
|
||||
|
||||
void write_fvector(const FVector &xyz) {
|
||||
write_double(xyz.X);
|
||||
write_double(xyz.Y);
|
||||
write_double(xyz.Z);
|
||||
}
|
||||
|
||||
FVector read_fvector() {
|
||||
double x = read_double();
|
||||
double y = read_double();
|
||||
double z = read_double();
|
||||
return FVector(x, y, z);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,4 +2,132 @@
|
||||
|
||||
|
||||
#include "Tangible.h"
|
||||
#include "TangibleManager.h"
|
||||
|
||||
UlxTangible::UlxTangible()
|
||||
{
|
||||
Manager = nullptr;
|
||||
TangibleId = -1;
|
||||
CurrentActor = nullptr;
|
||||
ActorBlueprint = nullptr;
|
||||
NearAccordingToLuprex = false;
|
||||
NearAccordingToUnreal = false;
|
||||
}
|
||||
|
||||
void UlxTangible::Init(UlxTangibleManager* tm, int64 id)
|
||||
{
|
||||
Manager = tm;
|
||||
TangibleId = id;
|
||||
}
|
||||
|
||||
bool UlxTangible::BlueprintIsTangible(TSubclassOf<AActor> bp) {
|
||||
if (bp == nullptr) return true;
|
||||
return bp->ImplementsInterface(UlxTangibleInterface::StaticClass());
|
||||
}
|
||||
|
||||
void UlxTangible::SetActorBlueprintClass(TSubclassOf<AActor> bp) {
|
||||
// If we're already of the right class, do nothing.
|
||||
if (ActorBlueprint == bp) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity check the blueprint. Nullptr is allowed.
|
||||
check(BlueprintIsTangible(bp));
|
||||
|
||||
// If there's already an actor, delete it.
|
||||
if (CurrentActor != nullptr) {
|
||||
// Remove the tangible component. This is probably
|
||||
// unnecessary, but it makes it more likely that we'll
|
||||
// catch bugs early.
|
||||
UlxTangibleComponent* comp = CurrentActor->GetComponentByClass<UlxTangibleComponent>();
|
||||
if (comp != nullptr) {
|
||||
comp->DestroyComponent();
|
||||
}
|
||||
|
||||
// Now destroy the actor itself. According to various
|
||||
// documents I've read online, it may be necessary to take
|
||||
// further steps to delete the object. Not clear.
|
||||
CurrentActor->Destroy();
|
||||
}
|
||||
|
||||
// Update the blueprint reference.
|
||||
ActorBlueprint = bp;
|
||||
|
||||
// Now create a new actor, unless the BP is nullptr.
|
||||
if (ActorBlueprint != nullptr) {
|
||||
// Create the actor.
|
||||
FActorSpawnParameters params;
|
||||
FVector location(0, 0, 0);
|
||||
FRotator rotation(0, 0, 0);
|
||||
UWorld* w = Manager->GetWorld();
|
||||
AActor* a = w->SpawnActor(ActorBlueprint, &location, &rotation, params);
|
||||
|
||||
// Insert a TangibleComponent into the actor.
|
||||
UActorComponent* ac = a->AddComponentByClass(UlxTangibleComponent::StaticClass(), false, FTransform::Identity, false);
|
||||
UlxTangibleComponent* tc = Cast<UlxTangibleComponent>(ac);
|
||||
check(tc != nullptr);
|
||||
|
||||
// Make the tangible point to the actor and vice versa.
|
||||
tc->Tangible = this;
|
||||
CurrentActor = a;
|
||||
}
|
||||
}
|
||||
|
||||
void UlxTangible::UpdateAnimationQueue(std::string_view aq) {
|
||||
AnimTracker.Update(aq);
|
||||
int limit = 3;
|
||||
while (AnimTracker.IsChanged()) {
|
||||
if (limit == 0) break;
|
||||
limit -= 1;
|
||||
AnimTracker.ClearChanged();
|
||||
IlxTangibleInterface::Execute_AnimationStateChanged(GetActor());
|
||||
}
|
||||
}
|
||||
|
||||
FVector UlxTangible::GetLocation() const {
|
||||
if (CurrentActor == nullptr) {
|
||||
return FVector(0,0,0);
|
||||
} else {
|
||||
return CurrentActor->GetActorLocation();
|
||||
}
|
||||
}
|
||||
|
||||
void UlxTangible::Destroy() {
|
||||
SetActorBlueprintClass(nullptr);
|
||||
Manager = nullptr;
|
||||
TangibleId = -1;
|
||||
CurrentActor = nullptr;
|
||||
ActorBlueprint = nullptr;
|
||||
AnimTracker.Clear();
|
||||
NearAccordingToLuprex = false;
|
||||
NearAccordingToUnreal = false;
|
||||
}
|
||||
|
||||
static UlxTangible *GetActorTangible(AActor *actor) {
|
||||
UlxTangibleComponent* comp = actor->GetComponentByClass<UlxTangibleComponent>();
|
||||
check(comp != nullptr);
|
||||
UlxTangible *result = comp->Tangible.Get();
|
||||
check(result != nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
void UlxTangible::GetCurrentAnimation(AActor *target, FlxAnimationStep &step) {
|
||||
step = GetActorTangible(target)->AnimTracker.GetCurrentAnimation();
|
||||
}
|
||||
|
||||
void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step, bool autoxyz, bool autofacing, bool autoplane) {
|
||||
UlxTangible *tan = GetActorTangible(target);
|
||||
if (autoxyz) step.AutoUpdateXYZ(target);
|
||||
if (autofacing) step.AutoUpdateFacing(target);
|
||||
if (autoplane) step.AutoUpdatePlane(&(tan->Plane));
|
||||
tan->AnimTracker.FinishedAnimation(step.Hash);
|
||||
}
|
||||
|
||||
FString UlxTangible::GetTangiblePlane(AActor* target) {
|
||||
return GetActorTangible(target)->Plane.ToString();
|
||||
}
|
||||
|
||||
void UlxTangible::SetTangiblePlane(AActor* target, const FString& plane) {
|
||||
GetActorTangible(target)->Plane = FName(plane);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,27 +3,203 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "AnimQueue.h"
|
||||
#include "TangibleInterface.h"
|
||||
#include "Tangible.generated.h"
|
||||
|
||||
|
||||
class UlxTangibleManager;
|
||||
|
||||
// This class does not need to be modified.
|
||||
UINTERFACE(Blueprintable)
|
||||
class UlxTangibleInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* IlxTangibleInterface
|
||||
*
|
||||
* This class implements the interface that an Actor must implement
|
||||
* in order for that Actor to be usable as a Tangible.
|
||||
*
|
||||
*/
|
||||
|
||||
class INTEGRATION_API IlxTangibleInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
|
||||
public:
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality")
|
||||
bool AnimationStateChanged();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* UlxTangible
|
||||
*
|
||||
* The Tangible stores all the data we need for a tangible,
|
||||
* such as the animation queue and so forth.
|
||||
*
|
||||
* From time to time, a tangible can change its blueprint class.
|
||||
* To do that, we have to delete and recreate the actor. This
|
||||
* is all set up so that it is possible to do that.
|
||||
*
|
||||
* The tangible has a place to store an Actor pointer. This
|
||||
* actor pointer is allowed to be nullptr, especially in the
|
||||
* case that the blueprint hasn't been set yet.
|
||||
*
|
||||
* This also serves as a repository for blueprint functions
|
||||
* that operate on tangible actors.
|
||||
*
|
||||
*/
|
||||
|
||||
UCLASS()
|
||||
class INTEGRATION_API UlxTangible : public UObject
|
||||
{
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
AActor* Actor;
|
||||
UlxTangible();
|
||||
|
||||
// My Tangible Manager.
|
||||
UPROPERTY()
|
||||
TObjectPtr<UlxTangibleManager> Manager;
|
||||
|
||||
// The tangible ID.
|
||||
UPROPERTY()
|
||||
uint64 TangibleId;
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<AActor> CurrentActor;
|
||||
|
||||
// The blueprint class of the actor.
|
||||
UPROPERTY()
|
||||
TSubclassOf<AActor> ActorBlueprint;
|
||||
|
||||
// Animation tracker
|
||||
FlxAnimTracker AnimTracker;
|
||||
|
||||
void Init(AActor* a) {
|
||||
Actor = a;
|
||||
}
|
||||
// Current Plane.
|
||||
FName Plane;
|
||||
|
||||
// True if luprex thinks this object is Near the player.
|
||||
bool NearAccordingToLuprex;
|
||||
|
||||
// True if unreal thinks this object is Near the player.
|
||||
bool NearAccordingToUnreal;
|
||||
|
||||
public:
|
||||
// Initialize a new tangible.
|
||||
//
|
||||
// This links the tangible to its TangibleManager and
|
||||
// sets the tangible's ID.
|
||||
//
|
||||
void Init(UlxTangibleManager *tm, int64 id);
|
||||
|
||||
// Destroy a tangible.
|
||||
//
|
||||
// Delete the actor associated with the tangible, if any,
|
||||
// and clean up everything else.
|
||||
//
|
||||
void Destroy();
|
||||
|
||||
// Get the actor associated with this tangible.
|
||||
//
|
||||
// Note that this may return nullptr: it is valid for a
|
||||
// tangible to have no actor associated with it. This
|
||||
// is most commonly the case if the blueprint class of
|
||||
// the tangible is set to nullptr.
|
||||
//
|
||||
// Also bear in mind that if a tangible changes its blueprint
|
||||
// class, then the actor will have to be deleted and
|
||||
// recreated. In that case, GetActor will return a different
|
||||
// pointer than before the blueprint change.
|
||||
//
|
||||
AActor* GetActor() const { return CurrentActor.Get(); }
|
||||
|
||||
// Get the location of the tangible.
|
||||
//
|
||||
// Note that if the actor is nullptr, the location is always
|
||||
// at the origin.
|
||||
//
|
||||
FVector GetLocation() const;
|
||||
|
||||
// Check a blueprint class to see if it is valid as a Tangible.
|
||||
//
|
||||
// In order for a blueprint class to be used as a tangible,
|
||||
// it must implement the interface IlxTangibleInterface.
|
||||
// This function also returns true for nullptr.
|
||||
//
|
||||
static bool BlueprintIsTangible(TSubclassOf<AActor> bp);
|
||||
|
||||
// Change the blueprint class of the tangible.
|
||||
//
|
||||
// This requires the deletion and recreation of the Actor.
|
||||
// The blueprint class must satisfy 'BlueprintIsTangible' above.
|
||||
//
|
||||
// It is legal to pass in nullptr for the blueprint class.
|
||||
// Whenever the blueprint class is nullptr, the Actor is
|
||||
// also nullptr. Ie, a tangible will have no Actor associated
|
||||
// with it if its blueprint class is nullptr.
|
||||
//
|
||||
void SetActorBlueprintClass(TSubclassOf<AActor> bp);
|
||||
|
||||
// Update the animation queue from Luprex.
|
||||
//
|
||||
// This reads the animation queue, and then based on
|
||||
// what is new in the animation queue, it calls into
|
||||
// the Actor's TangibleInterface, calling methods such
|
||||
// as 'StartAnimation' and 'AbortAnimation' as necessary.
|
||||
//
|
||||
// If the animation queue specifies a blueprint change,
|
||||
// this function will eventually implement that by calling
|
||||
// SetActorBlueprintClass above. This is not implemented
|
||||
// yet.
|
||||
//
|
||||
void UpdateAnimationQueue(std::string_view aq);
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex)
|
||||
static void GetCurrentAnimation(AActor *target, FlxAnimationStep &step);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex)
|
||||
static void FinishedAnimation(AActor *target, const FlxAnimationStep &step,
|
||||
bool AutoUpdateXYZ = true, bool AutoUpdateFacing = true, bool AutoUpdatePlane = true);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex)
|
||||
static FString GetTangiblePlane(AActor* target);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex)
|
||||
static void SetTangiblePlane(AActor* target, const FString& plane);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* UlxTangibleComponent
|
||||
*
|
||||
* The TangibleComponent holds a pointer to the Tangible.
|
||||
* This is the only purpose it serves: to make it possible to
|
||||
* have the Actor point to its corresponding Tangible.
|
||||
*
|
||||
* The TangibleComponent is procedurally inserted into the Actor.
|
||||
* The TangibleComponent is not visible to blueprints.
|
||||
*
|
||||
*/
|
||||
|
||||
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
|
||||
class INTEGRATION_API UlxTangibleComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UlxTangibleComponent() : Tangible(nullptr) {}
|
||||
|
||||
// The actor that we're a part of.
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UlxTangible> Tangible;
|
||||
};
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "TangibleInterface.h"
|
||||
|
||||
// Add default functionality here for any IlxTangibleInterface functions that are not pure virtual.
|
||||
@@ -1,31 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "AnimQueue.h"
|
||||
#include "TangibleInterface.generated.h"
|
||||
|
||||
// This class does not need to be modified.
|
||||
UINTERFACE(Blueprintable)
|
||||
class UlxTangibleInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class INTEGRATION_API IlxTangibleInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
|
||||
public:
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "Luprex")
|
||||
bool StartAnimation(ElxAnimationMode mode, const FlxAnimationStep& step);
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "Luprex")
|
||||
bool AbortAnimation(int64 hash);
|
||||
};
|
||||
@@ -2,27 +2,25 @@
|
||||
|
||||
|
||||
#include "TangibleManager.h"
|
||||
#include "TangibleInterface.h"
|
||||
#include "Tangible.h"
|
||||
#include "DebugPrint.h"
|
||||
|
||||
using namespace DebugPrint;
|
||||
using TanArray = UlxTangibleManager::TanArray;
|
||||
using IdArray = UlxTangibleManager::IdArray;
|
||||
|
||||
FTangibleManager::FTangibleManager() {
|
||||
UlxTangibleManager::UlxTangibleManager() {
|
||||
World = nullptr;
|
||||
ClassTangibleActor = nullptr;
|
||||
Actor = 0;
|
||||
Near = IdView();
|
||||
}
|
||||
|
||||
void FTangibleManager::Init(UWorld *world, UClass* tanact) {
|
||||
void UlxTangibleManager::Init(UWorld* world, UClass* tanact) {
|
||||
World = world;
|
||||
ClassTangibleActor = tanact;
|
||||
Actor = 0;
|
||||
Near = IdView();
|
||||
}
|
||||
|
||||
UlxTangible *FTangibleManager::GetTangible(int64 id) {
|
||||
UlxTangible **p = IdToTangible.Find(id);
|
||||
UlxTangible* UlxTangibleManager::GetTangible(int64 id) const {
|
||||
UlxTangible*const* p = IdToTangible.Find(id);
|
||||
if (p == nullptr) {
|
||||
return nullptr;
|
||||
} else {
|
||||
@@ -30,31 +28,90 @@ UlxTangible *FTangibleManager::GetTangible(int64 id) {
|
||||
}
|
||||
}
|
||||
|
||||
UlxTangible *FTangibleManager::MakeTangible(int64 id) {
|
||||
UlxTangible *& p = IdToTangible.FindOrAdd(id);
|
||||
if (p == nullptr) {
|
||||
FVector location(0,0,0);
|
||||
FRotator rotation(0, 0, 0);
|
||||
FActorSpawnParameters params;
|
||||
AActor* a = World->SpawnActor(ClassTangibleActor, &location, &rotation, params);
|
||||
check(a != nullptr);
|
||||
check(a->GetClass()->ImplementsInterface(UlxTangibleInterface::StaticClass()));
|
||||
p = NewObject<UlxTangible>();
|
||||
p->Init(a);
|
||||
#pragma optimize("", off)
|
||||
UlxTangible* UlxTangibleManager::MakeTangible(int64 id) {
|
||||
UlxTangible*& t = IdToTangible.FindOrAdd(id);
|
||||
if (t == nullptr) {
|
||||
t = NewObject<UlxTangible>();
|
||||
t->Init(this, id);
|
||||
|
||||
// TODO: fix this. The blueprint needs to be assigned
|
||||
// during animation queue parsing, based on the animation
|
||||
// queue keyword 'bp'.
|
||||
t->SetActorBlueprintClass(ClassTangibleActor);
|
||||
}
|
||||
return p;
|
||||
return t;
|
||||
}
|
||||
|
||||
void FTangibleManager::DeleteTangible(int64 id) {
|
||||
void UlxTangibleManager::DeleteTangible(int64 id) {
|
||||
// IMPLEMENT ME
|
||||
}
|
||||
|
||||
FTangibleManager::IdArray FTangibleManager::GetLive() {
|
||||
IdArray result;
|
||||
TanArray UlxTangibleManager::GetAllTangibles() const {
|
||||
TanArray result;
|
||||
result.SetNum(IdToTangible.Num());
|
||||
int next = 0;
|
||||
for (auto &pair : IdToTangible) {
|
||||
result[next++] = pair.Key;
|
||||
for (auto& pair : IdToTangible) {
|
||||
result[next++] = pair.Value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma optimize("", off)
|
||||
void UlxTangibleManager::UpdateNearAccordingToLuprex(IdView near) {
|
||||
// Clear all the 'NearAccordingToLuprex' flags.
|
||||
for (const auto& pair : IdToTangible) {
|
||||
pair.Value->NearAccordingToLuprex = false;
|
||||
}
|
||||
// For every ID on the list, create it if it doesn't exist,
|
||||
// mark it, and return it.
|
||||
for (int64 id : near) {
|
||||
UlxTangible* tan = MakeTangible(id);
|
||||
tan->NearAccordingToLuprex = true;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma optimize("", off)
|
||||
void UlxTangibleManager::RecalcNearAccordingToUnreal(int64 player, double radius) {
|
||||
UlxTangible *p = GetTangible(player);
|
||||
check (p != nullptr);
|
||||
FVector playerpos = p->CurrentActor->GetActorLocation();
|
||||
FName playerplane = p->Plane;
|
||||
double radiussq = radius * radius;
|
||||
for (const auto& pair : IdToTangible) {
|
||||
UlxTangible *tan = pair.Value;
|
||||
if (tan->Plane != playerplane) {
|
||||
tan->NearAccordingToUnreal = false;
|
||||
} else {
|
||||
FVector pos = tan->GetLocation();
|
||||
double distsq = FVector::DistSquared(pos, playerpos);
|
||||
tan->NearAccordingToUnreal = (distsq <= radiussq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UlxTangibleManager::DeleteFarawayTangibles() {
|
||||
// Make a list of tangibles that need to be deleted.
|
||||
TanArray faraway;
|
||||
for (const auto& pair : IdToTangible) {
|
||||
UlxTangible *tan = pair.Value;
|
||||
if (!(tan->NearAccordingToLuprex || tan->NearAccordingToUnreal)) {
|
||||
faraway.Add(tan);
|
||||
}
|
||||
}
|
||||
|
||||
for (UlxTangible *tan : faraway) {
|
||||
IdToTangible.Remove(tan->TangibleId);
|
||||
tan->Destroy(); // Remove the actor from the scene.
|
||||
}
|
||||
}
|
||||
|
||||
IdArray UlxTangibleManager::GetIds(const TanArray &arr) {
|
||||
IdArray result;
|
||||
result.SetNum(arr.Num());
|
||||
for (int i = 0; i < arr.Num(); i++) {
|
||||
result[i] = arr[i]->TangibleId;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,62 +8,77 @@
|
||||
#include "Tangible.h"
|
||||
#include "TangibleManager.generated.h"
|
||||
|
||||
USTRUCT()
|
||||
struct INTEGRATION_API FTangibleManager
|
||||
UCLASS()
|
||||
class INTEGRATION_API UlxTangibleManager : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Import these types into our Namespace.
|
||||
using IdArray = CommonTypes::IdArray;
|
||||
// Types used frequently.
|
||||
using IdView = CommonTypes::IdView;
|
||||
using IdArray = CommonTypes::IdArray;
|
||||
using TanArray = TArray<UlxTangible*>;
|
||||
|
||||
// A pointer to the UWorld.
|
||||
UWorld* World;
|
||||
// A pointer to our world.
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UWorld> World;
|
||||
|
||||
// A pointer to uclass TangibleActor.
|
||||
// A pointer to uclass TangibleActor. This is the class
|
||||
// of actors that we create (for now).
|
||||
UPROPERTY()
|
||||
TSubclassOf<AActor> ClassTangibleActor;
|
||||
|
||||
// Given a tangible ID, look up actor pointer (or NULL if actor was deleted)
|
||||
// Given a tangible ID, look up the TangibleComponent of that actor.
|
||||
UPROPERTY()
|
||||
TMap<int64, UlxTangible*> IdToTangible;
|
||||
|
||||
// Actor tangible Id.
|
||||
int64 Actor;
|
||||
|
||||
// Tangibles near the actor.
|
||||
IdView Near;
|
||||
|
||||
public:
|
||||
FTangibleManager();
|
||||
UlxTangibleManager();
|
||||
|
||||
// Initialize the tangible manager.
|
||||
//
|
||||
void Init(UWorld *world, UClass* tanact);
|
||||
|
||||
// Get a pointer to our world.
|
||||
//
|
||||
UWorld* GetWorld() const override { return World.Get(); }
|
||||
|
||||
// Get the tangible if it exists, otherwise return NULL
|
||||
UlxTangible* GetTangible(int64 id);
|
||||
//
|
||||
UlxTangible* GetTangible(int64 id) const;
|
||||
|
||||
// Get the tangible if it exists, otherwise create it.
|
||||
//
|
||||
UlxTangible* MakeTangible(int64 id);
|
||||
|
||||
// Delete the tangible.
|
||||
//
|
||||
void DeleteTangible(int64 id);
|
||||
|
||||
// Get/Set the Id of the actor.
|
||||
//
|
||||
int64 GetActor() const { return Actor; };
|
||||
void SetActor(int64 id) { Actor = id; }
|
||||
// Get an array of all tangibles.
|
||||
//
|
||||
TanArray GetAllTangibles() const;
|
||||
|
||||
// Update the 'NearAccordingToLuprex' flags.
|
||||
//
|
||||
// Also creates stub tangibles for every Id in the list.
|
||||
//
|
||||
void UpdateNearAccordingToLuprex(IdView near);
|
||||
|
||||
// Get/Set the list of tangibles near the player, according to Luprex.
|
||||
// Recalculate the 'NearAccordingToUnreal' flags.
|
||||
//
|
||||
IdView GetNear() const { return Near; }
|
||||
void SetNear(IdView near) { Near = near; }
|
||||
void RecalcNearAccordingToUnreal(int64 player, double radius);
|
||||
|
||||
// Delete Far Tangibles.
|
||||
//
|
||||
// Any tangible whose 'NearAccordingToLuprex' and 'NearAccordingToUnreal'
|
||||
// flags are both false is deleted. You probably want to update both
|
||||
// flags by calling the two routines above before calling this.
|
||||
//
|
||||
void DeleteFarawayTangibles();
|
||||
|
||||
// Get the Live list.
|
||||
// Given an array of tangibles, return an array of tangible Ids.
|
||||
//
|
||||
// Efficiency note: this makes a copy of the array.
|
||||
//
|
||||
IdArray GetLive();
|
||||
static IdArray GetIds(const TanArray &tans);
|
||||
};
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#include "c:/luprex/ext/base-writer.hpp"
|
||||
@@ -1 +0,0 @@
|
||||
#include "c:/luprex/cpp/drv/drvutil.cpp"
|
||||
@@ -1 +0,0 @@
|
||||
#include "c:/luprex/cpp/drv/drvutil.hpp"
|
||||
@@ -1 +0,0 @@
|
||||
#include "c:/luprex/cpp/core/enginewrapper.hpp"
|
||||
27
luprex-install.sh
Executable file
27
luprex-install.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
#
|
||||
# Install symbolic links to the Luprex source and the Luprex DLL.
|
||||
#
|
||||
# These symbolic links are needed to be able to build integration.
|
||||
#
|
||||
#
|
||||
|
||||
if [ -d c:/luprex ] ; then
|
||||
LUPREX=c:/luprex
|
||||
DLL=$LUPREX/build/visual/luprexlib.dll
|
||||
else
|
||||
LUPREX=$HOME/luprex
|
||||
DLL=$LUPREX/build/linux/luprexlib.so
|
||||
fi
|
||||
|
||||
rm -f Source/Integration/lpx-*.hpp
|
||||
rm -f Source/Integration/lpx-*.cpp
|
||||
|
||||
echo '#include "'$LUPREX'/ext/base-buffer.hpp"' > Source/Integration/lpx-basebuffer.hpp
|
||||
echo '#include "'$LUPREX'/cpp/drv/drvutil.hpp"' > Source/Integration/lpx-drvutil.hpp
|
||||
echo '#include "'$LUPREX'/cpp/drv/drvutil.cpp"' > Source/Integration/lpx-drvutil.cpp
|
||||
echo '#include "'$LUPREX'/cpp/core/enginewrapper.hpp"' > Source/Integration/lpx-enginewrapper.hpp
|
||||
echo '#define LUPREX_DLL_PATH "'$DLL'"' > Source/Integration/lpx-paths.hpp
|
||||
echo '#define LUPREX_ROOT_PATH "'$LUPREX'"' >> Source/Integration/lpx-paths.hpp
|
||||
|
||||
Reference in New Issue
Block a user