Checking in what I found on my hard drive

This commit is contained in:
2024-01-22 10:53:01 -05:00
26 changed files with 1008 additions and 575 deletions

1
.gitattributes vendored
View File

@@ -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
View File

@@ -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

Binary file not shown.

BIN
Content/NameToMeshTable.uasset LFS Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -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;
}

View File

@@ -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; }
};

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}

View 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);
// ...
}

View 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;
};

View File

@@ -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;
}

View File

@@ -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);
}
};

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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.

View File

@@ -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);
};

View File

@@ -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;
}

View File

@@ -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);
};

View File

@@ -1 +0,0 @@
#include "c:/luprex/ext/base-writer.hpp"

View File

@@ -1 +0,0 @@
#include "c:/luprex/cpp/drv/drvutil.cpp"

View File

@@ -1 +0,0 @@
#include "c:/luprex/cpp/drv/drvutil.hpp"

View File

@@ -1 +0,0 @@
#include "c:/luprex/cpp/core/enginewrapper.hpp"

27
luprex-install.sh Executable file
View 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