LifePlay-Revival/Source/LifePlay/LPShowCoupleSubsystem.cpp
edujiro5972 8084622450 LifePlay UE 5.8 : état de développement au 2026-10-19
Tickets : REP1-T4

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 22:57:42 +02:00

824 lines
23 KiB
C++

// Show a deux - voir LPShowCoupleSubsystem.h
#include "LPShowCoupleSubsystem.h"
#include "Animation/AnimMontage.h"
#include "Animation/AnimSequence.h"
#include "Animation/AnimSingleNodeInstance.h"
#include "Components/CapsuleComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "EngineUtils.h"
#include "Misc/PackageName.h"
#include "Components/BoxComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/PrimitiveComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "DrawDebugHelpers.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerController.h"
#include "HAL/IConsoleManager.h"
#include "LifePlay.h"
#include "LPCoupleSim.h"
#include "UObject/UnrealType.h"
static TAutoConsoleVariable<int32> CVarShowCouple(
TEXT("lp.ShowCouple"),
1,
TEXT("1 : boite du couple pendant le show. 0 : ancien chemin du Blueprint."),
ECVF_Default);
namespace
{
constexpr double ShowReach = 400.0;
bool IsBedMesh(const UStaticMesh* Mesh)
{
if (!Mesh)
{
return false;
}
static TSet<FName> Beds;
static TSet<FName> Others;
const FName Name = Mesh->GetFName();
if (Beds.Contains(Name))
{
return true;
}
if (Others.Contains(Name))
{
return false;
}
const FString MeshName = Mesh->GetName();
const bool bBed = MeshName.Contains(TEXT("bed"), ESearchCase::IgnoreCase)
|| MeshName.Contains(TEXT("fnt_ext011"))
|| MeshName.Equals(TEXT("fnt_new045"))
|| MeshName.Equals(TEXT("fnt_new235"));
(bBed ? Beds : Others).Add(Name);
return bBed;
}
ACharacter* AsCharacter(UObject* Object)
{
return Cast<ACharacter>(Object);
}
AActor* ObjectActor(const UObject* Container, const TCHAR* Name)
{
const FObjectPropertyBase* Prop = Container ? CastField<FObjectPropertyBase>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
return Prop ? Cast<AActor>(Prop->GetObjectPropertyValue_InContainer(Container)) : nullptr;
}
ACharacter* ObjectProperty(const UObject* Container, const TCHAR* Name)
{
return Cast<ACharacter>(ObjectActor(Container, Name));
}
void ArrayProperty(const UObject* Container, const TCHAR* Name, TArray<ACharacter*>& Out)
{
const FArrayProperty* ArrayProp = Container ? CastField<FArrayProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
const FObjectPropertyBase* Inner = ArrayProp ? CastField<FObjectPropertyBase>(ArrayProp->Inner) : nullptr;
if (!Inner)
{
return;
}
FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(Container));
for (int32 Index = 0; Index < Helper.Num(); ++Index)
{
if (ACharacter* Who = AsCharacter(Inner->GetObjectPropertyValue(Helper.GetRawPtr(Index))))
{
Out.Add(Who);
}
}
}
bool CapsuleOff(const ACharacter* Who)
{
const UCapsuleComponent* Cap = Who ? Who->GetCapsuleComponent() : nullptr;
return Cap && Cap->GetCollisionEnabled() == ECollisionEnabled::NoCollision;
}
void AddUniqueAnim(TArray<UAnimSequence*>& Out, UAnimSequence* Anim)
{
if (Anim)
{
Out.AddUnique(Anim);
}
}
UAnimSequence* SequenceFromMontage(const UAnimMontage* Montage)
{
if (Montage && Montage->SlotAnimTracks.Num() > 0 && Montage->SlotAnimTracks[0].AnimTrack.AnimSegments.Num() > 0)
{
return Cast<UAnimSequence>(Montage->SlotAnimTracks[0].AnimTrack.AnimSegments[0].GetAnimReference());
}
return nullptr;
}
UAnimSequence* PlayingSequence(const USkeletalMeshComponent* Mesh)
{
if (!Mesh)
{
return nullptr;
}
if (UAnimSequence* Seq = Cast<UAnimSequence>(Mesh->AnimationData.AnimToPlay))
{
return Seq;
}
const UAnimInstance* Instance = Mesh->GetAnimInstance();
if (UAnimSequence* Seq = SequenceFromMontage(Instance ? Instance->GetCurrentActiveMontage() : nullptr))
{
return Seq;
}
if (UAnimSingleNodeInstance* Single = Cast<UAnimSingleNodeInstance>(const_cast<UAnimInstance*>(Instance)))
{
return Cast<UAnimSequence>(Single->GetCurrentAsset());
}
return nullptr;
}
bool IsIdleClip(const UAnimSequence* Anim)
{
return Anim && Anim->GetName().Contains(TEXT("idle"), ESearchCase::IgnoreCase);
}
UAnimSequence* PairSibling(const UAnimSequence* Anim)
{
if (!Anim)
{
return nullptr;
}
const FString Name = Anim->GetName();
FString Other;
if (Name.EndsWith(TEXT("_F")))
{
Other = Name.LeftChop(2) + TEXT("_M");
}
else if (Name.EndsWith(TEXT("_M")))
{
Other = Name.LeftChop(2) + TEXT("_F");
}
else if (Name.EndsWith(TEXT("_0")))
{
Other = Name.LeftChop(2) + TEXT("_1");
}
else if (Name.EndsWith(TEXT("_1")))
{
Other = Name.LeftChop(2) + TEXT("_0");
}
else
{
return nullptr;
}
const FString Folder = FPaths::GetPath(Anim->GetOutermost()->GetName());
const FString Path = Folder + TEXT("/") + Other + TEXT(".") + Other;
return LoadObject<UAnimSequence>(nullptr, *Path);
}
void CompletePair(TArray<UAnimSequence*>& Out)
{
TArray<UAnimSequence*> Extra;
for (const UAnimSequence* Anim : Out)
{
AddUniqueAnim(Extra, PairSibling(Anim));
}
for (UAnimSequence* Anim : Extra)
{
Out.AddUnique(Anim);
}
}
void KeepShowClips(TArray<UAnimSequence*>& Clips)
{
Clips.RemoveAll([](const UAnimSequence* Anim) { return IsIdleClip(Anim); });
CompletePair(Clips);
}
void ReadAnimProperty(const FProperty* Prop, const void* Container, TArray<UAnimSequence*>& Out)
{
if (const FArrayProperty* ArrayProp = CastField<FArrayProperty>(Prop))
{
const FObjectPropertyBase* Inner = CastField<FObjectPropertyBase>(ArrayProp->Inner);
if (!Inner)
{
return;
}
FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(Container));
for (int32 Index = 0; Index < Helper.Num(); ++Index)
{
UObject* Value = Inner->GetObjectPropertyValue(Helper.GetRawPtr(Index));
AddUniqueAnim(Out, Cast<UAnimSequence>(Value));
AddUniqueAnim(Out, SequenceFromMontage(Cast<UAnimMontage>(Value)));
}
}
else if (const FObjectPropertyBase* ObjectProp = CastField<FObjectPropertyBase>(Prop))
{
UObject* Value = ObjectProp->GetObjectPropertyValue_InContainer(Container);
AddUniqueAnim(Out, Cast<UAnimSequence>(Value));
AddUniqueAnim(Out, SequenceFromMontage(Cast<UAnimMontage>(Value)));
}
}
void CollectShowAnims(const UObject* GI, const ACharacter* Lead, const ACharacter* Partner, TArray<UAnimSequence*>& Out, FString& Label)
{
TArray<UAnimSequence*> FromShow;
if (const FStructProperty* Last = GI ? CastField<FStructProperty>(GI->GetClass()->FindPropertyByName(TEXT("LastAnimation"))) : nullptr)
{
if (Last->Struct)
{
const void* Data = Last->ContainerPtrToValuePtr<void>(GI);
for (TFieldIterator<FProperty> It(Last->Struct); It; ++It)
{
ReadAnimProperty(*It, Data, FromShow);
}
}
}
KeepShowClips(FromShow);
if (FromShow.Num() == 0)
{
auto FromActor = [&](const ACharacter* Who)
{
if (!Who)
{
return;
}
TInlineComponentArray<USkeletalMeshComponent*> Meshes;
Who->GetComponents(Meshes);
for (const USkeletalMeshComponent* Mesh : Meshes)
{
AddUniqueAnim(FromShow, PlayingSequence(Mesh));
}
};
FromActor(Lead);
FromActor(Partner);
KeepShowClips(FromShow);
}
Out = MoveTemp(FromShow);
for (const UAnimSequence* Anim : Out)
{
if (!Label.IsEmpty())
{
Label += TEXT("+");
}
Label += Anim->GetName();
}
}
}
void ULPShowCoupleSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
PostTickHandle = FWorldDelegates::OnWorldPostActorTick.AddUObject(this, &ULPShowCoupleSubsystem::AfterActors);
}
void ULPShowCoupleSubsystem::Deinitialize()
{
FWorldDelegates::OnWorldPostActorTick.Remove(PostTickHandle);
Release();
Super::Deinitialize();
}
void ULPShowCoupleSubsystem::AfterActors(UWorld* World, ELevelTick TickType, float DeltaSeconds)
{
(void)TickType;
(void)DeltaSeconds;
if (!World || World != GetWorld() || !World->IsGameWorld())
{
return;
}
if (CVarShowCouple.GetValueOnGameThread() == 0)
{
OpenUseFurniture();
LetSittersPass(World);
Release();
return;
}
UGameInstance* GI = GetGameInstance();
TArray<ACharacter*> Found;
ArrayProperty(GI, TEXT("SexPersons"), Found);
if (Found.Num() < 2)
{
Found.Reset();
if (ACharacter* A = ObjectProperty(GI, TEXT("AP1")))
{
Found.Add(A);
}
if (ACharacter* B = ObjectProperty(GI, TEXT("AP2")))
{
Found.Add(B);
}
}
ACharacter* Player = nullptr;
if (const APlayerController* PC = World->GetFirstPlayerController())
{
Player = Cast<ACharacter>(PC->GetPawn());
}
ACharacter* NextLead = nullptr;
ACharacter* NextPartner = nullptr;
for (ACharacter* Who : Found)
{
if (!IsValid(Who) || !CapsuleOff(Who))
{
continue;
}
if (!NextLead)
{
NextLead = (Player && Found.Contains(Player) && CapsuleOff(Player)) ? Player : Who;
}
}
if (NextLead)
{
double Best = ShowReach * ShowReach;
for (ACharacter* Who : Found)
{
if (!IsValid(Who) || Who == NextLead || !CapsuleOff(Who))
{
continue;
}
const double Dist = FVector::DistSquared(NextLead->GetActorLocation(), Who->GetActorLocation());
if (Dist <= Best)
{
Best = Dist;
NextPartner = Who;
}
}
}
if (!NextLead || !NextPartner)
{
OpenUseFurniture();
LetSittersPass(World);
Release();
return;
}
if (!bEngaged || Lead.Get() != NextLead || Partner.Get() != NextPartner)
{
Release();
Engage(NextLead, NextPartner);
}
Weld();
BlockRoomPawn();
TArray<UAnimSequence*> Anims;
FString Label;
CollectShowAnims(GI, NextLead, NextPartner, Anims, Label);
FString Key;
for (const UAnimSequence* Anim : Anims)
{
if (!Key.IsEmpty())
{
Key += TEXT("+");
}
Key += Anim->GetName();
}
if (!FitToBodies() && !Key.IsEmpty() && Key != FittedPair && Key != RejectedPair)
{
if (FitBox(Anims, Label))
{
FittedPair = Key;
}
else
{
RejectedPair = Key;
}
}
KeepTogether();
if (UBoxComponent* Drawn = Box.Get())
{
DrawDebugBox(World, Drawn->GetComponentLocation(), Drawn->GetScaledBoxExtent(), Drawn->GetComponentQuat(), FColor::Green, false, -1.f, 0, 2.f);
}
}
void ULPShowCoupleSubsystem::Engage(ACharacter* InLead, ACharacter* InPartner)
{
Lead = InLead;
Partner = InPartner;
bEngaged = true;
LastSafe = InLead->GetActorLocation();
SavedPartnerMove = LPCoupleSim::SaveAndFreeze(InPartner);
UE_LOG(LogLifePlay, Warning, TEXT("Show couple : branche (%s + %s)"), *GetNameSafe(InLead), *GetNameSafe(InPartner));
}
bool ULPShowCoupleSubsystem::IsUseFurniture(const UStaticMesh* Mesh)
{
if (!Mesh)
{
return false;
}
const FName Name = Mesh->GetFName();
if (UseFurniture.Contains(Name))
{
return true;
}
if (SolidFurniture.Contains(Name))
{
return false;
}
const TCHAR* Actions[] = { TEXT("sit"), TEXT("lie"), TEXT("use"), TEXT("work") };
bool bUse = false;
for (const TCHAR* Action : Actions)
{
const FString Path = FString::Printf(TEXT("/Game/Heavy/New2/Daily/%s-f-0-%s"), *Mesh->GetName(), Action);
if (FPackageName::DoesPackageExist(Path))
{
bUse = true;
break;
}
}
(bUse ? UseFurniture : SolidFurniture).Add(Name);
return bUse;
}
void ULPShowCoupleSubsystem::OpenUseFurniture()
{
AActor* Room = ObjectActor(GetGameInstance(), TEXT("Room"));
if (!Room)
{
return;
}
TArray<AActor*> Actors;
Actors.Add(Room);
TArray<AActor*> Attached;
Room->GetAttachedActors(Attached, true, true);
Actors.Append(Attached);
int32 Opened = 0;
for (AActor* Actor : Actors)
{
TInlineComponentArray<UStaticMeshComponent*> Meshes;
Actor->GetComponents(Meshes);
for (UStaticMeshComponent* Comp : Meshes)
{
if (!Comp || !IsUseFurniture(Comp->GetStaticMesh()))
{
continue;
}
if (Comp->GetCollisionResponseToChannel(ECC_Pawn) == ECR_Ignore)
{
continue;
}
Comp->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore);
++Opened;
}
}
if (Opened > 0)
{
UE_LOG(LogLifePlay, Warning, TEXT("Meubles assis ou couches : %d ouverts au passage"), Opened);
}
}
void ULPShowCoupleSubsystem::LetSittersPass(UWorld* World)
{
TSet<UCapsuleComponent*> Sitting;
for (TActorIterator<ACharacter> It(World); It; ++It)
{
ACharacter* Who = *It;
UCapsuleComponent* Cap = Who ? Who->GetCapsuleComponent() : nullptr;
if (!Cap || Cap->GetCollisionEnabled() == ECollisionEnabled::NoCollision)
{
continue;
}
const UAnimSequence* Anim = PlayingSequence(Who->GetMesh());
const FString Name = Anim ? Anim->GetName() : FString();
const bool bPose = Name.Contains(TEXT("-sit")) || Name.Contains(TEXT("-lie")) || Name.Contains(TEXT("-use"))
|| Name.Contains(TEXT("-work")) || Name.Contains(TEXT("sitting"), ESearchCase::IgnoreCase);
if (!bPose)
{
continue;
}
Sitting.Add(Cap);
bool bSeen = false;
for (const FSavedSeat& Saved : SavedSeats)
{
if (Saved.Cap.Get() == Cap)
{
bSeen = true;
break;
}
}
if (bSeen)
{
continue;
}
SavedSeats.Add({ Cap, Cap->GetCollisionResponseToChannel(ECC_WorldDynamic) });
Cap->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Ignore);
}
for (int32 Index = SavedSeats.Num() - 1; Index >= 0; --Index)
{
UCapsuleComponent* Cap = SavedSeats[Index].Cap.Get();
if (Cap && Sitting.Contains(Cap))
{
continue;
}
if (Cap && Cap->GetCollisionEnabled() != ECollisionEnabled::NoCollision)
{
Cap->SetCollisionResponseToChannel(ECC_WorldDynamic, SavedSeats[Index].Response);
}
SavedSeats.RemoveAt(Index);
}
}
void ULPShowCoupleSubsystem::BlockRoomPawn()
{
AActor* Room = ObjectActor(GetGameInstance(), TEXT("Room"));
if (!Room)
{
return;
}
TArray<AActor*> Actors;
TSet<AActor*> Seen;
TArray<AActor*> Pending;
Pending.Add(Room);
for (TFieldIterator<FProperty> It(Room->GetClass()); It; ++It)
{
if (const FArrayProperty* ArrayProp = CastField<FArrayProperty>(*It))
{
const FObjectPropertyBase* Inner = CastField<FObjectPropertyBase>(ArrayProp->Inner);
if (!Inner)
{
continue;
}
FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(Room));
for (int32 Index = 0; Index < Helper.Num(); ++Index)
{
if (AActor* Other = Cast<AActor>(Inner->GetObjectPropertyValue(Helper.GetRawPtr(Index))))
{
Pending.Add(Other);
}
}
}
else if (const FObjectPropertyBase* ObjectProp = CastField<FObjectPropertyBase>(*It))
{
if (AActor* Other = Cast<AActor>(ObjectProp->GetObjectPropertyValue_InContainer(Room)))
{
Pending.Add(Other);
}
}
}
while (Pending.Num() > 0)
{
AActor* Actor = Pending.Pop(EAllowShrinking::No);
if (!Actor || Seen.Contains(Actor) || Cast<ACharacter>(Actor))
{
continue;
}
bool bInRoom = Actor == Room || Actor->GetOwner() == Room;
for (AActor* Cursor = Actor; Cursor && !bInRoom; Cursor = Cursor->GetAttachParentActor())
{
bInRoom = Cursor == Room;
}
if (!bInRoom && FVector::DistSquared(Actor->GetActorLocation(), Room->GetActorLocation()) > FMath::Square(4000.0))
{
continue;
}
Seen.Add(Actor);
Actors.Add(Actor);
TArray<AActor*> Attached;
Actor->GetAttachedActors(Attached, true, true);
Pending.Append(Attached);
}
int32 AlreadyBlock = 0;
int32 Overlap = 0;
int32 Flipped = 0;
FString Names;
for (AActor* Actor : Actors)
{
TInlineComponentArray<UPrimitiveComponent*> Prims;
Actor->GetComponents(Prims);
for (UPrimitiveComponent* Prim : Prims)
{
if (!Prim || Prim->GetCollisionEnabled() == ECollisionEnabled::NoCollision || Cast<USkeletalMeshComponent>(Prim))
{
continue;
}
const ECollisionResponse Response = Prim->GetCollisionResponseToChannel(ECC_Pawn);
if (Response == ECR_Block)
{
++AlreadyBlock;
continue;
}
if (Response == ECR_Overlap)
{
++Overlap;
}
if (Response != ECR_Ignore)
{
continue;
}
bool bSeen = false;
for (const FSavedPawn& Saved : SavedFurniture)
{
if (Saved.Comp.Get() == Prim)
{
bSeen = true;
break;
}
}
if (bSeen)
{
continue;
}
SavedFurniture.Add({ Prim, ECR_Ignore });
Prim->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block);
++Flipped;
if (Flipped <= 6)
{
if (!Names.IsEmpty())
{
Names += TEXT(", ");
}
Names += FString::Printf(TEXT("%s/%s"), *GetNameSafe(Actor), *Prim->GetName());
}
}
}
if (!bPawnLogged || Flipped > 0)
{
bPawnLogged = true;
UE_LOG(LogLifePlay, Warning, TEXT("Show couple : Pawn chambre Block=%d Overlap=%d Ignore->Block=%d %s"),
AlreadyBlock, Overlap, Flipped, *Names);
}
}
void ULPShowCoupleSubsystem::Release()
{
if (!bEngaged && SavedFurniture.Num() == 0 && !Box.IsValid())
{
return;
}
LPCoupleSim::Unweld(Partner.Get());
LPCoupleSim::RestoreMove(SavedPartnerMove);
for (const FSavedPawn& Saved : SavedFurniture)
{
if (UPrimitiveComponent* Prim = Saved.Comp.Get())
{
Prim->SetCollisionResponseToChannel(ECC_Pawn, Saved.Response);
}
}
SavedFurniture.Reset();
if (UBoxComponent* Old = Box.Get())
{
Old->DestroyComponent();
}
Box.Reset();
FittedPair.Reset();
RejectedPair.Reset();
bPawnLogged = false;
Lead.Reset();
Partner.Reset();
if (bEngaged)
{
UE_LOG(LogLifePlay, Warning, TEXT("Show couple : ancien chemin"));
}
bEngaged = false;
}
void ULPShowCoupleSubsystem::Weld()
{
ACharacter* Who = Lead.Get();
ACharacter* Other = Partner.Get();
if (!Who || !Other)
{
return;
}
LPCoupleSim::AttachPartner(Who, Other);
const double Half = LPCoupleSim::CapsuleHalfOf(Who);
LPCoupleSim::PlaceBodyMesh(Who, Half);
LPCoupleSim::PlaceBodyMesh(Other, Half);
}
bool ULPShowCoupleSubsystem::FitToBodies()
{
ACharacter* Who = Lead.Get();
ACharacter* Other = Partner.Get();
if (!Who || !Other)
{
return false;
}
// Voie "corps vivants" : os affiches des deux meshes. Geometrie partagee avec la sandbox
// (LPCoupleSim.h) ; cette voie ne recale pas le fond de boite sur la plante des pieds.
const LPCoupleSim::FBoxFitParams Params;
FBox Bounds(ForceInit);
LPCoupleSim::AccumulateLiveBody(Who->GetMesh(), Params, Bounds);
LPCoupleSim::AccumulateLiveBody(Other->GetMesh(), Params, Bounds);
const LPCoupleSim::FBoxFit Fit = LPCoupleSim::FitFromLiveBounds(Bounds, Params);
if (!Fit.bValid)
{
return false;
}
Box = LPCoupleSim::EnsureBox(Who, Box.Get(), TEXT("LPShowCoupleBox"), true);
LPCoupleSim::ApplyBoxFit(Box.Get(), Fit);
if (!LoggedExtent.Equals(Fit.Extent, 5.0))
{
UE_LOG(LogLifePlay, Warning, TEXT("Show couple : boite pose centre %s etendue %s"), *Fit.Center.ToString(), *Fit.Extent.ToString());
LoggedExtent = Fit.Extent;
}
return true;
}
bool ULPShowCoupleSubsystem::FitBox(const TArray<UAnimSequence*>& Anims, const FString& Label)
{
ACharacter* Who = Lead.Get();
if (!Who || Anims.Num() == 0)
{
return false;
}
// Voie "animations" : meme mesure et meme padding que FitCoupleBox de la sandbox (LPCoupleSim.h).
// Le fond de boite est recale sur la plante des pieds du porteur.
LPCoupleSim::FBoxFitParams Params;
Params.CapsuleHalf = LPCoupleSim::CapsuleHalfOf(Who);
Params.FloorLocalZ = -Params.CapsuleHalf;
const FTransform ToActor = LPCoupleSim::MeshToActor(Params.CapsuleHalf);
FBox Bounds(ForceInit);
for (const UAnimSequence* Anim : Anims)
{
LPCoupleSim::AccumulateAnimBounds(Anim, ToActor, Bounds);
}
const LPCoupleSim::FBoxFit Fit = LPCoupleSim::FitFromAnimBounds(Bounds, Params);
if (!Fit.bValid)
{
UE_LOG(LogLifePlay, Error, TEXT("Show couple : boite non mesuree (%s)"), *Label);
return false;
}
Box = LPCoupleSim::EnsureBox(Who, Box.Get(), TEXT("LPShowCoupleBox"), true);
LPCoupleSim::ApplyBoxFit(Box.Get(), Fit);
UE_LOG(LogLifePlay, Warning, TEXT("Show couple : boite %s centre %s etendue %s"), *Label, *Fit.Center.ToString(), *Fit.Extent.ToString());
return true;
}
void ULPShowCoupleSubsystem::KeepTogether()
{
ACharacter* Who = Lead.Get();
ACharacter* Other = Partner.Get();
UBoxComponent* Volume = Box.Get();
UWorld* World = GetWorld();
if (!Who || !Other || !Volume || !World)
{
return;
}
const FVector Now = Who->GetActorLocation();
const FVector Delta = Now - LastSafe;
if (Delta.IsNearlyZero())
{
return;
}
// Meme resolveur que la sandbox (LPCoupleSim.h), avec les reglages du jeu : balayage multiple,
// 3 passes, depenetration, tous les PNJ ignores, lits franchissables, coque de la chambre ignoree.
LPCoupleSim::FSweepOptions Options = LPCoupleSim::FSweepOptions::Show();
Options.IsWalkableSupport = [](const UPrimitiveComponent* Comp)
{
const UStaticMeshComponent* MeshComp = Cast<UStaticMeshComponent>(Comp);
return MeshComp && IsBedMesh(MeshComp->GetStaticMesh());
};
Options.IsIgnoredShell = [](const AActor* Actor)
{
const FString Name = Actor ? Actor->GetName() : FString();
return Name == TEXT("Wall") || Name.StartsWith(TEXT("Wall_"));
};
Options.OnIgnoredShell = [this](const AActor* Actor)
{
if (LastBlocker == TEXT("coque"))
{
return;
}
if (const UStaticMeshComponent* Shell = Actor->FindComponentByClass<UStaticMeshComponent>())
{
UE_LOG(LogLifePlay, Warning, TEXT("Show couple : coque %s ignoree, maille %s, boite %s"),
*Actor->GetName(), *GetNameSafe(Shell->GetStaticMesh()), *Shell->Bounds.GetBox().ToString());
}
LastBlocker = TEXT("coque");
};
const LPCoupleSim::FSweepResult Move = LPCoupleSim::ResolveCoupleMove(
World, Volume, Who, Other, LastSafe, Delta, Options);
if (!Move.Blocker.IsEmpty() && Move.Blocker != LastBlocker)
{
UE_LOG(LogLifePlay, Warning, TEXT("Show couple : bloque par %s normal %s profondeur %.0f"),
*Move.Blocker, *Move.BlockerNormal.ToString(), Move.BlockerDepth);
LastBlocker = Move.Blocker;
}
if (!Move.Location.Equals(Now, LPCoupleSim::MoveEpsilon))
{
Who->SetActorLocation(Move.Location, false, nullptr, ETeleportType::TeleportPhysics);
}
LastSafe = Who->GetActorLocation();
}
void ULPShowCoupleSubsystem::NotifyCoupleTeleported()
{
// Meme grandeur que KeepTogether ecrit dans LastSafe : la place du meneur. La prochaine image de
// KeepTogether balaie depuis la place libre choisie par CAM, plus depuis un meuble.
ACharacter* Who = Lead.Get();
if (!bEngaged || !Who || !Partner.Get() || !GetWorld())
{
return;
}
const FVector Now = Who->GetActorLocation();
if (!(Now - LastSafe).IsNearlyZero())
{
UE_LOG(LogLifePlay, Warning, TEXT("Show couple : place reprise apres teleportation %s (ecart %.0f)"),
*Now.ToString(), FVector::Dist(LastSafe, Now));
}
LastSafe = Now;
LastBlocker.Reset();
}