Charge le plugin via AdditionalPluginDirectories, conserve ShowCouple et les commandlets anim côté jeu, et documente les lots S0–S8 (CHG-212 à CHG-220). Co-authored-by: Cursor <cursoragent@cursor.com>
805 lines
22 KiB
C++
805 lines
22 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();
|
|
}
|