Ive been using the previous posts here to compile an ultimate way of messing with FProperties and UProperties.
So far, a header like this works for wildcard parameters:
UFUNCTION(BlueprintCallable,Category="Logging",CustomThunk,meta=(CustomStructureParam="Data",DisplayName="Print This",Keywords="Print log Super this"))
static void PrintThis(ACharacter* Target,UProperty* Data);
//Custom thunk must be used because we need the pointers to the values of the original data. You may add extra parameters to the function call, but it gets trickier.
DECLARE_FUNCTION(execPrintThis)
{
//Author credit to @dirkFist and pedro_clericuzzi on epicgames forums
P_GET_OBJECT(ACharacter,Target);//This goes into the stack, or pointer, or property, or whatever. idk. but it grabs the value of the currently-selected parameters and assigns it to a variable in-scope. in this case, Target. This allows you to essentially SKIP to the next parameter whilst maintaining the integrity of the first. theres all kinds of P_GET_X, but since Target SHOULD be an object, we will use Object.
//P_FINISH; = SKIP TO NEXT PARAMETER IF YOU DONT USE IT. IF YOU DO USE IT, WILL REMOVE THE PARAMETER FROM THE FUNCTION CALL. USE THIS AFTER YOURE FINISHED MESSING WITH THE PARAMETER IF YOU HAVENT USED P_GET_
//idk what the rest of this code does besides what the original author comments state.
// Steps into the stack, walking to the next property in it
Stack.Step(Stack.Object,NULL);
// Grab the last property found when we walked the stack
// This does not contains the property value, only its type information
FProperty* StructProperty = CastField<FProperty>(Stack.MostRecentProperty);
// Grab the base address where the struct actually stores its data
// This is where the property value is truly stored
void* StructPtr = Stack.MostRecentPropertyAddress;
// We need this to wrap up the stack
P_FINISH;
PrintThis_impl(StructProperty,StructPtr,Target);//Call the REAL function
}
This is the CPP:
void USuperPrinterBPLibrary::PrintThis_impl(FProperty* Property, void* StructPtr,ACharacter* Target)
{
/*
FProperty = Property provided
StructPtr = a pointer to the address of Property. (this doesnt make sense since FProperty IS a pointer. Perhaps its pointing to the value of FProperty rather than the FProperty itself?
*/
UE_LOG(AVSP,Log,TEXT("PROPERTY AUTHORED NAME: %s"),*Property->GetAuthoredName());
//check if what was provided is a struct property, ie, a Struct.
if(FStructProperty* StructProperty=CastField<FStructProperty>(Property))
{
//go into the struct properties and iterate over every property.
for(TFieldIterator<FProperty> PropertyIt(StructProperty->Struct);PropertyIt;++PropertyIt)
{
UE_LOG(AVSP,Log,TEXT("VARIABLE NAME: %s"),*PropertyIt->GetAuthoredName());
//iterate over all struct properties, again?
for(int32 ArrayIndex=0;ArrayIndex<PropertyIt->ArrayDim;ArrayIndex++)
{
//grabs the pointer where the property is stored
//why void? wtf is going on here? I think its assuming the properties has multiple properties within itself, hence the iteration loop for it. Why not make this a function?
void* ValuePtr = PropertyIt->ContainerPtrToValuePtr<void>(StructPtr,ArrayIndex);
//parse the property into its property subtypes
ParseProperty(*PropertyIt,ValuePtr,Target);
}
}
}//this is my modification to the original post. The original code could not accommodate non-struct values. This allows you to pass in regular objects, like the player itself. Works like a charm, but i have no idea how it works and i loosely based this code from the code up above.
else
{
UE_LOG(AVSP,Warning,TEXT("Param isnt a struct. Parsing property"));
//Parse property
ParseProperty(Property,StructPtr,Target);
}
}
finally, the parser:
void USuperPrinterBPLibrary::ParseProperty(FProperty* Property,void* ValuePtr,ACharacter* Target)
{
//why tf are we initializing variables here? ig it aint hurting anything
float FloatValue;
int32 IntValue;
bool BoolValue;
FString StringValue;
FName NameValue;
FText TextValue;
//If property is Numeric
if(FNumericProperty* NumericProperty=CastField<FNumericProperty>(Property))
{
if(NumericProperty->IsFloatingPoint())
{
FloatValue = NumericProperty->GetFloatingPointPropertyValue(ValuePtr);
UE_LOG(AVSP, Log, TEXT("FLOAT VALUE: '%f'"), FloatValue);
}else if(NumericProperty->IsInteger())
{
IntValue=NumericProperty->GetSignedIntPropertyValue(ValuePtr);
UE_LOG(AVSP,Log,TEXT("INTERGER VALUE: '%i'"),IntValue);
}
//If property is Boolean
}else if(FBoolProperty* BoolProperty = CastField<FBoolProperty>(Property))
{
BoolValue = BoolProperty->GetPropertyValue(ValuePtr);
UE_LOG(AVSP,Log,TEXT("BOOL VALUE: %s"),((BoolValue)?TEXT("True"):TEXT("False")));
}
//If property is an FName
else if(FNameProperty* NameProperty = CastField<FNameProperty>(Property))
{
NameValue = NameProperty->GetPropertyValue(ValuePtr);
UE_LOG(AVSP,Log,TEXT("NAME VALUE: %s"),*NameValue.ToString());
//If property is FString
}else if(FStrProperty* StringProperty = CastField<FStrProperty>(Property))
{
StringValue = StringProperty->GetPropertyValue(ValuePtr);
UE_LOG(AVSP, Log, TEXT("STRING VALUE:'%s'"), *StringValue);
//If property is Text
}else if(FTextProperty* TextProperty = CastField<FTextProperty>(Property))
{
TextValue=TextProperty->GetPropertyValue(ValuePtr);
UE_LOG(AVSP,Log,TEXT("TEXT VALUE: '%s'"),*TextValue.ToString());
//If property is an Array
}else if(FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property))
{
FScriptArrayHelper Helper(ArrayProperty,ValuePtr);
for(int32 i=0,n=Helper.Num();i<n;i++)
{
UE_LOG(AVSP,Log,TEXT("ARRAY: %i"),i);
ParseProperty(ArrayProperty->Inner,Helper.GetRawPtr(i),Target);
}
//my addition to the code; It can detect objects themselves. Atm, its hardcoded to cast to a Character and print out the Character's location. It needs to be able to get ALL properties of the character class, but i dont know how to get all the nested properties of a property this way.
}else if(FObjectProperty* ObjectProperty= CastField<FObjectProperty>(Property))
{
//Name of obj passed through
UE_LOG(AVSP,Warning,TEXT("OBJECT NAME: %s"),*ObjectProperty->GetPropertyValue(ValuePtr).GetName());
//Object type followed by address of object
UE_LOG(AVSP,Warning,TEXT("OBJECT FULL NAME AFTER PROPERTY: %s"),*ObjectProperty->GetPropertyValue(ValuePtr).GetFullName());
//Object type
UE_LOG(AVSP,Warning,TEXT("OBJECT'S CLASS NAME: %s"),*ObjectProperty->GetPropertyValue(ValuePtr).GetClass()->GetFName().ToString());
//Name of property
UE_LOG(AVSP,Warning,TEXT("OBJECT AUTHORED NAME: %s"),*ObjectProperty->GetAuthoredName());
//ObjectProperty followed by the property's static location/origin
UE_LOG(AVSP,Warning,TEXT("OBJECT FULL NAME: '%s'"),*ObjectProperty->GetFullName());
if(AActor* Actor = Cast<AActor>(ObjectProperty->GetPropertyValue(ValuePtr)))
{
UE_LOG(AVSP,Warning,TEXT("ACTOR LOCATION: %s"),*Actor->GetActorLocation().ToString());
}
}
else if(Property)
{
PrintThis_impl(Property,ValuePtr,Target);
}
}
This code 100% works but its too limited in design. I would really appreciate it if someone who is an expert with Properties shines a light here. The two problems I face are; how do you call a function that uses UProperty when UProperties are deprecated? How do you convert a FProperty to UProperty to call this function within CPP? And, how do you get nested properties from a property?