LifePlay-Revival/Source/LifePlay/LPCityTraceSubsystem.cpp
edujiro5972 56dfcfc3d2 LifePlay UE 5.8 : état de développement au 2026-10-20
Tickets : PUB-20261020-T1

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 17:35:10 +02:00

304 lines
9.3 KiB
C++

// Lot STM1 (STM1-SPEC-01, T2). Voir LPCityTraceSubsystem.h.
#include "LPCityTraceSubsystem.h"
#include "LifePlay.h"
#if !UE_BUILD_SHIPPING
#include "Components/StaticMeshComponent.h"
#include "Engine/GameInstance.h"
#include "Engine/StaticMesh.h"
#include "GameFramework/Actor.h"
#include "HAL/IConsoleManager.h"
#include "HAL/PlatformTime.h"
#include "UObject/UnrealType.h"
namespace LPCityTrace
{
// Noms etablis par les dumps des graphes du GI (lots STB, STE, L6f ; STM1-CART-02 § 1).
const TCHAR* const CommandName = TEXT("LP.CityTrace");
// Valeur d'une propriete en texte lisible : FText et FString tels quels, le reste par ExportText.
FString ValueToString(const FProperty* Prop, const void* ValuePtr)
{
if (!Prop || !ValuePtr)
{
return FString();
}
if (const FTextProperty* TextProp = CastField<FTextProperty>(Prop))
{
return TextProp->GetPropertyValue(ValuePtr).ToString();
}
if (const FStrProperty* StrProp = CastField<FStrProperty>(Prop))
{
return StrProp->GetPropertyValue(ValuePtr);
}
if (const FNameProperty* NameProp = CastField<FNameProperty>(Prop))
{
return NameProp->GetPropertyValue(ValuePtr).ToString();
}
if (const FObjectPropertyBase* ObjectProp = CastField<FObjectPropertyBase>(Prop))
{
const UObject* Object = ObjectProp->GetObjectPropertyValue(ValuePtr);
return Object ? Object->GetPathName() : FString(TEXT("None"));
}
FString Out;
Prop->ExportTextItem_Direct(Out, ValuePtr, nullptr, nullptr, PPF_None);
return Out;
}
// Membre d'une structure Blueprint : son nom porte un suffixe de GUID (DisplayName_2_EBDEAF...), cherche par prefixe.
const FProperty* FindMemberByPrefix(const UStruct* Struct, const TCHAR* Prefix)
{
if (!Struct)
{
return nullptr;
}
for (TFieldIterator<FProperty> It(Struct); It; ++It)
{
if (It->GetName().StartsWith(Prefix, ESearchCase::IgnoreCase))
{
return *It;
}
}
return nullptr;
}
FString Member(const UStruct* Struct, const void* StructPtr, const TCHAR* Prefix)
{
const FProperty* Prop = FindMemberByPrefix(Struct, Prefix);
return Prop ? ValueToString(Prop, Prop->ContainerPtrToValuePtr<void>(StructPtr)) : FString(TEXT("?"));
}
// Une ville (structure City) en une ligne : nom affiche, donnees StreetMap, maillage.
FString DescribeCity(const UStruct* Struct, const void* StructPtr)
{
return FString::Printf(TEXT("'%s' sm='%s' mesh='%s'"),
*Member(Struct, StructPtr, TEXT("DisplayName_")),
*Member(Struct, StructPtr, TEXT("StreetMapName_")),
*Member(Struct, StructPtr, TEXT("StaticMeshName_")));
}
}
void ULPCityTraceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
StartSeconds = FPlatformTime::Seconds();
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPCityTraceSubsystem::Tick), 0.25f);
// Une instance de jeu par PIE : la commande n'est enregistree que par la premiere.
if (!IConsoleManager::Get().FindConsoleObject(LPCityTrace::CommandName))
{
ConsoleCommand = IConsoleManager::Get().RegisterConsoleCommand(LPCityTrace::CommandName,
TEXT("LifePlay (STM1) : ecrit au journal l'etat du choix de la ville ([LPCity])."),
FConsoleCommandDelegate::CreateUObject(this, &ULPCityTraceSubsystem::DumpNow), ECVF_Default);
}
UE_LOG(LogLPCity, Log, TEXT("[LPCity] trace active (releve toutes les 0,25 s, ecrit au changement)"));
}
void ULPCityTraceSubsystem::Deinitialize()
{
if (TickHandle.IsValid())
{
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
TickHandle.Reset();
}
if (ConsoleCommand)
{
IConsoleManager::Get().UnregisterConsoleObject(ConsoleCommand);
ConsoleCommand = nullptr;
}
Super::Deinitialize();
}
bool ULPCityTraceSubsystem::Tick(float DeltaTime)
{
Sample(false);
return true;
}
void ULPCityTraceSubsystem::DumpNow()
{
Sample(true);
}
void ULPCityTraceSubsystem::Sample(bool bForce)
{
const UGameInstance* GI = GetGameInstance();
if (!GI)
{
return;
}
const UClass* GIClass = GI->GetClass();
auto Missing = [this](const UObject* Owner, const TCHAR* Name)
{
const FString Key = FString::Printf(TEXT("%s.%s"), Owner ? *Owner->GetClass()->GetName() : TEXT("?"), Name);
bool bAlreadyReported = false;
ReportedMissing.Add(Key, &bAlreadyReported);
if (!bAlreadyReported)
{
UE_LOG(LogLPCity, Log, TEXT("[LPCity] PROPRIETE ABSENTE %s"), *Key);
}
};
// T2a : liste des villes (GI.Cities, tableau de City).
const FArrayProperty* CitiesProp = CastField<FArrayProperty>(GIClass->FindPropertyByName(TEXT("Cities")));
const FStructProperty* CityInner = CitiesProp ? CastField<FStructProperty>(CitiesProp->Inner) : nullptr;
if (!CityInner)
{
Missing(GI, TEXT("Cities"));
}
else
{
FScriptArrayHelper Cities(CitiesProp, CitiesProp->ContainerPtrToValuePtr<void>(GI));
const int32 Count = Cities.Num();
if (Count > 0 && (bForce || Count != LastCityCount))
{
LastCityCount = Count;
int32 LondonIndex = INDEX_NONE;
for (int32 Index = 0; Index < Count; ++Index)
{
if (LPCityTrace::Member(CityInner->Struct, Cities.GetRawPtr(Index), TEXT("StreetMapName_")).Contains(TEXT("/CentralLondon")))
{
LondonIndex = Index;
break;
}
}
const FString LondonText = LondonIndex == INDEX_NONE ? FString(TEXT("absent")) : FString::FromInt(LondonIndex);
UE_LOG(LogLPCity, Log, TEXT("[LPCity] CITIES n=%d [0]=%s ; CentralLondon a l'indice %s"),
Count, *LPCityTrace::DescribeCity(CityInner->Struct, Cities.GetRawPtr(0)), *LondonText);
}
}
// T2b : ville sauvegardee (GI.SaveGameInstance.City) et correspondance, comme GI.GetCityByName :
// nom affiche (DisplayName) compare sans tenir compte de la casse.
const FObjectPropertyBase* SaveProp = CastField<FObjectPropertyBase>(GIClass->FindPropertyByName(TEXT("SaveGameInstance")));
const UObject* Save = SaveProp ? SaveProp->GetObjectPropertyValue_InContainer(GI) : nullptr;
if (!SaveProp)
{
Missing(GI, TEXT("SaveGameInstance"));
}
else if (Save)
{
const FProperty* SaveCityProp = Save->GetClass()->FindPropertyByName(TEXT("City"));
if (!SaveCityProp)
{
Missing(Save, TEXT("City"));
}
else
{
const FString SavedCity = LPCityTrace::ValueToString(SaveCityProp, SaveCityProp->ContainerPtrToValuePtr<void>(Save));
FString Match = TEXT("liste des villes illisible");
if (CityInner)
{
Match = TEXT("AUCUNE correspondance (GetCityByName rendra la City par defaut)");
FScriptArrayHelper Cities(CitiesProp, CitiesProp->ContainerPtrToValuePtr<void>(GI));
for (int32 Index = 0; Index < Cities.Num(); ++Index)
{
const FString DisplayName = LPCityTrace::Member(CityInner->Struct, Cities.GetRawPtr(Index), TEXT("DisplayName_"));
if (DisplayName.Equals(SavedCity, ESearchCase::IgnoreCase))
{
Match = FString::Printf(TEXT("trouvee indice %d ('%s')"), Index, *DisplayName);
break;
}
}
}
const FString Line = FString::Printf(TEXT("ville='%s' -> %s"), *SavedCity, *Match);
if (bForce || Line != LastSave)
{
LastSave = Line;
UE_LOG(LogLPCity, Log, TEXT("[LPCity] SAUVEGARDE %s"), *Line);
}
}
}
// T2c : ville demandee (GI.CityToMoveTo).
const FStructProperty* RequestProp = CastField<FStructProperty>(GIClass->FindPropertyByName(TEXT("CityToMoveTo")));
if (!RequestProp)
{
Missing(GI, TEXT("CityToMoveTo"));
}
else
{
const FString Line = LPCityTrace::DescribeCity(RequestProp->Struct, RequestProp->ContainerPtrToValuePtr<void>(GI));
if (bForce || Line != LastRequest)
{
LastRequest = Line;
UE_LOG(LogLPCity, Log, TEXT("[LPCity] DEMANDE %s"), *Line);
}
}
// T2d et T2e : donnees StreetMap et maillage poses sur l'acteur de la carte (GI.StreetMapStatic).
const FObjectPropertyBase* StreetProp = CastField<FObjectPropertyBase>(GIClass->FindPropertyByName(TEXT("StreetMapStatic")));
const AActor* Street = StreetProp ? Cast<AActor>(StreetProp->GetObjectPropertyValue_InContainer(GI)) : nullptr;
if (!StreetProp)
{
Missing(GI, TEXT("StreetMapStatic"));
}
if (!Street)
{
return;
}
const FObjectPropertyBase* MapProp = CastField<FObjectPropertyBase>(Street->GetClass()->FindPropertyByName(TEXT("StreetMap")));
if (!MapProp)
{
Missing(Street, TEXT("StreetMap"));
}
else
{
const UObject* Map = MapProp->GetObjectPropertyValue_InContainer(Street);
const FString Line = Map ? Map->GetPathName() : FString(TEXT("None"));
if (bForce || Line != LastStreetMap)
{
LastStreetMap = Line;
UE_LOG(LogLPCity, Log, TEXT("[LPCity] STREETMAP %s"), *Line);
}
}
const FObjectPropertyBase* MeshCompProp = CastField<FObjectPropertyBase>(Street->GetClass()->FindPropertyByName(TEXT("StaticMeshComponent")));
if (!MeshCompProp)
{
Missing(Street, TEXT("StaticMeshComponent"));
}
else
{
const UStaticMeshComponent* MeshComp = Cast<UStaticMeshComponent>(MeshCompProp->GetObjectPropertyValue_InContainer(Street));
const UStaticMesh* Mesh = MeshComp ? MeshComp->GetStaticMesh() : nullptr;
const FString Line = Mesh ? Mesh->GetPathName() : FString(TEXT("None"));
if (bForce || Line != LastPose)
{
LastPose = Line;
UE_LOG(LogLPCity, Log, TEXT("[LPCity] POSE %s (t=%.1f s)"), *Line, FPlatformTime::Seconds() - StartSeconds);
}
}
}
#else
void ULPCityTraceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
}
void ULPCityTraceSubsystem::Deinitialize()
{
Super::Deinitialize();
}
bool ULPCityTraceSubsystem::Tick(float DeltaTime)
{
return false;
}
void ULPCityTraceSubsystem::Sample(bool bForce)
{
}
void ULPCityTraceSubsystem::DumpNow()
{
}
#endif