// Voir LPPersonPropGuardSubsystem.h. #include "LPPersonPropGuardSubsystem.h" #include "Components/SkeletalMeshComponent.h" #include "Components/StaticMeshComponent.h" #include "Engine/GameInstance.h" #include "Engine/World.h" #include "EngineUtils.h" #include "LifePlay.h" #include "UObject/UnrealType.h" namespace LPPersonPropGuard { /** Props d'anim (spawnProp) : template NODE_AddStaticMeshComponent-0 — pas les SMC vetements *SM. */ bool IsAnimPropComponent(const UActorComponent* Comp) { if (!Comp) { return false; } const FString Name = Comp->GetName(); return Name.Contains(TEXT("NODE_AddStaticMeshComponent"), ESearchCase::CaseSensitive); } void DestroyProp(UStaticMeshComponent* SMC) { if (!IsValid(SMC)) { return; } SMC->SetVisibility(false, false); SMC->SetHiddenInGame(true, false); SMC->SetStaticMesh(nullptr); SMC->DestroyComponent(); } FArrayProperty* PropsArray(AActor* Person) { return Person ? CastField(Person->GetClass()->FindPropertyByName(TEXT("Props"))) : nullptr; } void ClearPropsArrayEntries(AActor* Person) { FArrayProperty* Arr = PropsArray(Person); if (!Arr || !CastField(Arr->Inner)) { return; } FScriptArrayHelper Helper(Arr, Arr->ContainerPtrToValuePtr(Person)); for (int32 Index = Helper.Num() - 1; Index >= 0; --Index) { UObject* Entry = *reinterpret_cast(Helper.GetRawPtr(Index)); if (UStaticMeshComponent* SMC = Cast(Entry)) { DestroyProp(SMC); } Helper.RemoveValues(Index); } } } void ULPPersonPropGuardSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); TickHandle = FTSTicker::GetCoreTicker().AddTicker( FTickerDelegate::CreateUObject(this, &ULPPersonPropGuardSubsystem::Tick), 0.2f); } void ULPPersonPropGuardSubsystem::Deinitialize() { FTSTicker::GetCoreTicker().RemoveTicker(TickHandle); Super::Deinitialize(); } bool ULPPersonPropGuardSubsystem::Tick(float DeltaTime) { (void)DeltaTime; UGameInstance* GI = GetGameInstance(); UWorld* World = GI ? GI->GetWorld() : nullptr; if (!World || !World->IsGameWorld()) { return true; } for (TActorIterator It(World); It; ++It) { AActor* Candidate = *It; if (!IsValid(Candidate) || Candidate->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject)) { continue; } if (Candidate->GetClass()->GetName() != TEXT("Person_C")) { continue; } if (Candidate->GetGameInstance() != GI) { continue; } GuardPerson(Candidate); } return true; } USkeletalMeshComponent* ULPPersonPropGuardSubsystem::GetPersonMesh(AActor* Person) { if (!Person) { return nullptr; } if (const FObjectPropertyBase* Prop = CastField(Person->GetClass()->FindPropertyByName(TEXT("Mesh")))) { if (USkeletalMeshComponent* FromProp = Cast(Prop->GetObjectPropertyValue_InContainer(Person))) { return FromProp; } } TInlineComponentArray Meshes(Person); return Meshes.Num() > 0 ? Meshes[0] : nullptr; } void ULPPersonPropGuardSubsystem::CallClearProps(AActor* Person) { if (!Person) { return; } if (UFunction* Fn = Person->FindFunction(TEXT("clearProps"))) { Person->ProcessEvent(Fn, nullptr); } } void ULPPersonPropGuardSubsystem::ForceHidePropComponents(USkeletalMeshComponent* Mesh) { if (!IsValid(Mesh)) { return; } TArray Children; Mesh->GetChildrenComponents(true, Children); for (USceneComponent* Child : Children) { UStaticMeshComponent* SMC = Cast(Child); if (!SMC || !LPPersonPropGuard::IsAnimPropComponent(SMC)) { continue; } LPPersonPropGuard::DestroyProp(SMC); } } void ULPPersonPropGuardSubsystem::ClearOrphanProps(AActor* Person, USkeletalMeshComponent* Mesh) // instance : log bLoggedOnce { FArrayProperty* Arr = LPPersonPropGuard::PropsArray(Person); if (!Arr || !CastField(Arr->Inner)) { return; } FScriptArrayHelper Helper(Arr, Arr->ContainerPtrToValuePtr(Person)); bool bRemoved = false; for (int32 Index = Helper.Num() - 1; Index >= 0; --Index) { UObject* Entry = *reinterpret_cast(Helper.GetRawPtr(Index)); UStaticMeshComponent* SMC = Cast(Entry); if (!IsValid(SMC)) { Helper.RemoveValues(Index); bRemoved = true; continue; } // spawnProp attache toujours avec un SocketName ; socket None ou invalide = verre colle a l'origine du mesh. const FName Socket = SMC->GetAttachSocketName(); const bool bBadParent = SMC->GetAttachParent() != Mesh; const bool bBadSocket = Socket.IsNone() || (Mesh && !Mesh->DoesSocketExist(Socket)); if (!bBadParent && !bBadSocket) { continue; } LPPersonPropGuard::DestroyProp(SMC); Helper.RemoveValues(Index); bRemoved = true; } if (bRemoved && !bLoggedOnce) { bLoggedOnce = true; UE_LOG(LogLifePlay, Display, TEXT("PersonPropGuard : props orphelines retirees (verre/animation sans socket)")); } } void ULPPersonPropGuardSubsystem::GuardPerson(AActor* Person) { USkeletalMeshComponent* Mesh = GetPersonMesh(Person); const bool bBodyGone = !IsValid(Mesh) || Mesh->GetSkeletalMeshAsset() == nullptr; const bool bHidden = Person->IsHidden(); if (bHidden || bBodyGone) { if (!ClearedWhileInvalid.Contains(Person)) { CallClearProps(Person); // Ceinture : clearProps BP peut etre absent / Props desync ; ne touche pas aux SMC vetements. LPPersonPropGuard::ClearPropsArrayEntries(Person); ForceHidePropComponents(Mesh); ClearedWhileInvalid.Add(Person); if (!bLoggedOnce) { bLoggedOnce = true; UE_LOG(LogLifePlay, Display, TEXT("PersonPropGuard : clearProps (Person cache=%d meshVide=%d) — corrige verre orphelin"), bHidden ? 1 : 0, bBodyGone ? 1 : 0); } } return; } ClearedWhileInvalid.Remove(Person); ClearOrphanProps(Person, Mesh); }