188 lines
6.3 KiB
C++
188 lines
6.3 KiB
C++
|
|
// Portage UE 5.5 - voir LPDataCompat.h
|
||
|
|
|
||
|
|
#include "LPDataCompat.h"
|
||
|
|
|
||
|
|
#include "Kismet/KismetStringLibrary.h"
|
||
|
|
#include "LifePlay.h"
|
||
|
|
#include "LPStringCompat.h"
|
||
|
|
#include "Misc/CommandLine.h"
|
||
|
|
#include "UObject/UnrealType.h"
|
||
|
|
|
||
|
|
namespace
|
||
|
|
{
|
||
|
|
/** Champ chaine d'une structure Blueprint par son nom d'auteur (les noms internes portent un suffixe GUID). */
|
||
|
|
const FStrProperty* FindStructString(const UStruct* Struct, const TCHAR* AuthoredName)
|
||
|
|
{
|
||
|
|
for (TFieldIterator<FStrProperty> It(Struct); It; ++It)
|
||
|
|
{
|
||
|
|
if (It->GetAuthoredName() == AuthoredName)
|
||
|
|
{
|
||
|
|
return *It;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Valeurs du champ AuthoredName de chaque element du tableau de structures ArrayName de GI, dans l'ordre. */
|
||
|
|
TArray<FString> ReadStructArrayField(const UObject* GI, const TCHAR* ArrayName, const TCHAR* AuthoredName)
|
||
|
|
{
|
||
|
|
TArray<FString> Values;
|
||
|
|
const FArrayProperty* ArrayProp = GI ? CastField<FArrayProperty>(GI->GetClass()->FindPropertyByName(ArrayName)) : nullptr;
|
||
|
|
const FStructProperty* Inner = ArrayProp ? CastField<FStructProperty>(ArrayProp->Inner) : nullptr;
|
||
|
|
const FStrProperty* Field = Inner ? FindStructString(Inner->Struct, AuthoredName) : nullptr;
|
||
|
|
if (!Field)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Error, TEXT("WhatWhereToArray : champ %s.%s introuvable sur %s"), ArrayName, AuthoredName, *GetNameSafe(GI));
|
||
|
|
return Values;
|
||
|
|
}
|
||
|
|
FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(GI));
|
||
|
|
Values.Reserve(Helper.Num());
|
||
|
|
for (int32 i = 0; i < Helper.Num(); ++i)
|
||
|
|
{
|
||
|
|
Values.Add(Field->GetPropertyValue_InContainer(Helper.GetRawPtr(i)));
|
||
|
|
}
|
||
|
|
return Values;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** GetArrayItem du Blueprint : valeur par defaut hors bornes. */
|
||
|
|
FString ItemOrEmpty(const TArray<FString>& Array, int32 Index)
|
||
|
|
{
|
||
|
|
return Array.IsValidIndex(Index) ? Array[Index] : FString();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** SwitchString du Blueprint (insensible a la casse) : vrai si Value egale un des cas. */
|
||
|
|
bool MatchesAnyCase(const FString& Value, std::initializer_list<const TCHAR*> Cases)
|
||
|
|
{
|
||
|
|
for (const TCHAR* Case : Cases)
|
||
|
|
{
|
||
|
|
if (UKismetStringLibrary::EqualEqual_StriStri(Value, Case))
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TArray<FString> ULPDataCompat::ComputeWhatWhere(const UObject* GI, const FString& Line)
|
||
|
|
{
|
||
|
|
// Partie gauche / droite de la ligne, avec gestion des ':' supplementaires (rejoints par ':').
|
||
|
|
const TArray<FString> Parts = ULPStringCompat::ParseIntoArray(Line, TEXT(":"), true);
|
||
|
|
const bool bWhat = UKismetStringLibrary::EqualEqual_StriStri(ItemOrEmpty(Parts, 0), TEXT("WHAT"));
|
||
|
|
FString SecondPart;
|
||
|
|
if (Parts.Num() > 2)
|
||
|
|
{
|
||
|
|
TArray<FString> Dup = Parts;
|
||
|
|
Dup.RemoveAt(0);
|
||
|
|
SecondPart = UKismetStringLibrary::JoinStringArray(Dup, TEXT(":"));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
SecondPart = ItemOrEmpty(Parts, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ClearWhiteSpacesFromArray : Trim (debut) puis TrimTrailing, chaque element conserve.
|
||
|
|
TArray<FString> Cleaned;
|
||
|
|
for (const FString& Item : ULPStringCompat::ParseIntoArray(SecondPart, TEXT(","), true))
|
||
|
|
{
|
||
|
|
Cleaned.Add(UKismetStringLibrary::TrimTrailing(UKismetStringLibrary::Trim(Item)));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!UKismetStringLibrary::EqualEqual_StriStri(ItemOrEmpty(Cleaned, 0), TEXT("all")))
|
||
|
|
{
|
||
|
|
return Cleaned;
|
||
|
|
}
|
||
|
|
|
||
|
|
TArray<FString> FinalArray;
|
||
|
|
if (bWhat)
|
||
|
|
{
|
||
|
|
for (const FString& ActionUID : ReadStructArrayField(GI, TEXT("ActionDetail"), TEXT("AmenityUID")))
|
||
|
|
{
|
||
|
|
if (MatchesAnyCase(ActionUID, { TEXT("sleep"), TEXT("sleep_hotel"), TEXT("nap"), TEXT("nap_hotel"), TEXT("masturbate"),
|
||
|
|
TEXT("bathe"), TEXT("go_to_the_bathroom"), TEXT("check_on_my_family") }))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (ULPStringCompat::Contains(ActionUID, TEXT("_HARDCODE"), false, true))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
FinalArray.Add(ActionUID);
|
||
|
|
}
|
||
|
|
FinalArray.Remove(TEXT("sleep"));
|
||
|
|
FinalArray.Remove(TEXT("Sleep"));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
for (const FString& Amenity : ReadStructArrayField(GI, TEXT("AmenityAction"), TEXT("Amenity")))
|
||
|
|
{
|
||
|
|
if (!MatchesAnyCase(Amenity, { TEXT("sexwork"), TEXT("crime"), TEXT("modeling"), TEXT("prison") }))
|
||
|
|
{
|
||
|
|
FinalArray.Add(Amenity);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Elements "-xxx" : retrait de "xxx" (partie droite du premier '-', recherche insensible a la casse).
|
||
|
|
for (const FString& Item : Cleaned)
|
||
|
|
{
|
||
|
|
FString Left, Right;
|
||
|
|
if (ULPStringCompat::Split(Item, TEXT("-"), Left, Right, ESearchCase::IgnoreCase, ESearchDir::FromStart))
|
||
|
|
{
|
||
|
|
FinalArray.Remove(Right);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return FinalArray;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPDataCompat::WhatWhereToArray(UObject* Target, const FString& Line, TArray<FString>& Array)
|
||
|
|
{
|
||
|
|
Array = ComputeWhatWhere(Target, Line);
|
||
|
|
|
||
|
|
static const bool bVerify = FParse::Param(FCommandLine::Get(), TEXT("LPVerifyWhatWhere"));
|
||
|
|
if (!bVerify || !Target)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Verification : execution de la fonction Blueprint d'origine de GI et comparaison exacte (ordre et casse).
|
||
|
|
UFunction* Original = Target->FindFunction(TEXT("WhatWhereToArray"));
|
||
|
|
const FStrProperty* LineProp = Original ? CastField<FStrProperty>(Original->FindPropertyByName(TEXT("Line"))) : nullptr;
|
||
|
|
const FArrayProperty* ArrayProp = Original ? CastField<FArrayProperty>(Original->FindPropertyByName(TEXT("Array"))) : nullptr;
|
||
|
|
if (!LineProp || !ArrayProp)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Error, TEXT("WhatWhereToArray : fonction Blueprint d'origine introuvable, verification impossible"));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
uint8* Params = static_cast<uint8*>(FMemory_Alloca(Original->ParmsSize));
|
||
|
|
FMemory::Memzero(Params, Original->ParmsSize);
|
||
|
|
for (TFieldIterator<FProperty> It(Original); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
|
||
|
|
{
|
||
|
|
It->InitializeValue_InContainer(Params);
|
||
|
|
}
|
||
|
|
LineProp->SetPropertyValue_InContainer(Params, Line);
|
||
|
|
Target->ProcessEvent(Original, Params);
|
||
|
|
const TArray<FString>& Expected = *ArrayProp->ContainerPtrToValuePtr<TArray<FString>>(Params);
|
||
|
|
|
||
|
|
static int32 Calls = 0, Mismatches = 0;
|
||
|
|
++Calls;
|
||
|
|
bool bSame = Expected.Num() == Array.Num();
|
||
|
|
for (int32 i = 0; bSame && i < Array.Num(); ++i)
|
||
|
|
{
|
||
|
|
bSame = Expected[i].Equals(Array[i], ESearchCase::CaseSensitive);
|
||
|
|
}
|
||
|
|
if (!bSame)
|
||
|
|
{
|
||
|
|
++Mismatches;
|
||
|
|
UE_LOG(LogLifePlay, Error, TEXT("WhatWhereToArray ECART #%d | '%s' : Blueprint %d element(s) [%s], C++ %d element(s) [%s]"),
|
||
|
|
Mismatches, *Line, Expected.Num(), *FString::Join(Expected, TEXT(",")).Left(300), Array.Num(), *FString::Join(Array, TEXT(",")).Left(300));
|
||
|
|
}
|
||
|
|
if (Calls % 500 == 0 || !bSame)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Display, TEXT("WhatWhereToArray verification : %d appel(s), %d ecart(s)"), Calls, Mismatches);
|
||
|
|
}
|
||
|
|
for (TFieldIterator<FProperty> It(Original); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
|
||
|
|
{
|
||
|
|
It->DestroyValue_InContainer(Params);
|
||
|
|
}
|
||
|
|
}
|