// 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 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 ReadStructArrayField(const UObject* GI, const TCHAR* ArrayName, const TCHAR* AuthoredName) { TArray Values; const FArrayProperty* ArrayProp = GI ? CastField(GI->GetClass()->FindPropertyByName(ArrayName)) : nullptr; const FStructProperty* Inner = ArrayProp ? CastField(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(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& 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 Cases) { for (const TCHAR* Case : Cases) { if (UKismetStringLibrary::EqualEqual_StriStri(Value, Case)) { return true; } } return false; } } TArray ULPDataCompat::ComputeWhatWhere(const UObject* GI, const FString& Line) { // Partie gauche / droite de la ligne, avec gestion des ':' supplementaires (rejoints par ':'). const TArray Parts = ULPStringCompat::ParseIntoArray(Line, TEXT(":"), true); const bool bWhat = UKismetStringLibrary::EqualEqual_StriStri(ItemOrEmpty(Parts, 0), TEXT("WHAT")); FString SecondPart; if (Parts.Num() > 2) { TArray Dup = Parts; Dup.RemoveAt(0); SecondPart = UKismetStringLibrary::JoinStringArray(Dup, TEXT(":")); } else { SecondPart = ItemOrEmpty(Parts, 1); } // ClearWhiteSpacesFromArray : Trim (debut) puis TrimTrailing, chaque element conserve. TArray 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 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& 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(Original->FindPropertyByName(TEXT("Line"))) : nullptr; const FArrayProperty* ArrayProp = Original ? CastField(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(FMemory_Alloca(Original->ParmsSize)); FMemory::Memzero(Params, Original->ParmsSize); for (TFieldIterator It(Original); It && It->HasAnyPropertyFlags(CPF_Parm); ++It) { It->InitializeValue_InContainer(Params); } LineProp->SetPropertyValue_InContainer(Params, Line); Target->ProcessEvent(Original, Params); const TArray& Expected = *ArrayProp->ContainerPtrToValuePtr>(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 It(Original); It && It->HasAnyPropertyFlags(CPF_Parm); ++It) { It->DestroyValue_InContainer(Params); } }