543 lines
20 KiB
C++
543 lines
20 KiB
C++
// Portage UE 5.5 - voir LPOutdoorRoomSubsystem.h
|
|
|
|
#include "LPOutdoorRoomSubsystem.h"
|
|
|
|
#include "Blueprint/UserWidget.h"
|
|
#include "Components/BoxComponent.h"
|
|
#include "Components/CapsuleComponent.h"
|
|
#include "Components/SceneComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/GameInstance.h"
|
|
#include "Engine/World.h"
|
|
#include "EngineUtils.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "GameFramework/Character.h"
|
|
#include "GameFramework/InputSettings.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "GameFramework/PlayerInput.h"
|
|
#include "LPKeyboardSubsystem.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "LifePlay.h"
|
|
#include "UObject/UnrealType.h"
|
|
|
|
namespace
|
|
{
|
|
// Interieurs : 0,2 a 0,4 ; exterieurs : 0,01. Le seuil laisse une marge des deux cotes.
|
|
constexpr double OutdoorScaleZ = 0.05;
|
|
const FName RoomPropertyName(TEXT("Room"));
|
|
const FName CeilingPropertyName(TEXT("WallT"));
|
|
// S-F4 : plancher des personnages (BoxComponent du Blueprint Room) et echelle Z de reference d'un interieur.
|
|
const FName FloorPropertyName(TEXT("Box"));
|
|
constexpr double IndoorScaleZ = 0.2;
|
|
// S-F4b : decors exterieurs (fnt_ext*) dont la collision simple englobe la zone de marche ; examines toutes les 0,5 s.
|
|
const TCHAR* const OutdoorDecorPrefix = TEXT("fnt_ext");
|
|
constexpr float DecorScanSeconds = 0.5f;
|
|
// S-F5 : seuil d'enfoncement sous le dessus de Box, examens immobiles exiges, depart de la descente de la capsule (bas a
|
|
// 30 cm au-dessus de Box : le plan de marche fnt_nav01, 9 cm au-dessus de Box, est trouve ; un banc ou un etal ne l'est pas).
|
|
constexpr double SunkDepth = 15.0;
|
|
constexpr int32 SunkScans = 3;
|
|
constexpr double LiftProbe = 30.0;
|
|
// S-F5j : examens immobiles exiges pour le joueur (0 ; 0,5 ; 1 ; 1,5 s).
|
|
constexpr int32 PlayerSunkScans = 4;
|
|
// S-F5k : distance minimale entre deux personnes relevees, et ecart applique le long du grand axe de la piece.
|
|
constexpr double LiftSpacing = 50.0;
|
|
// S-F5p : apres l'entree dans une piece exterieure, balayage a chaque tick pendant FastScanSeconds ; saut de position au-dela de
|
|
// JumpDistance entre deux balayages = personne posee par le Blueprint (teleportation).
|
|
constexpr float FastScanSeconds = 3.f;
|
|
constexpr double JumpDistance = 50.0;
|
|
// Memes menus que LPDirectControlSubsystem (MenuClasses), dont la boite de scene Scene_C.
|
|
const TCHAR* const PlayerMenuClasses[] = {
|
|
TEXT("Journal_C"), TEXT("MainMenu_C"), TEXT("CityMenu_C"), TEXT("StatMenu_C"),
|
|
TEXT("ContactMenu_C"), TEXT("Inventory_C"), TEXT("CharGen_C"), TEXT("ModManager_C"),
|
|
TEXT("BodyShapeMenu_C"), TEXT("Scene_C")
|
|
};
|
|
|
|
template <typename T>
|
|
T* GetObjectProperty(const UObject* Container, FName Name)
|
|
{
|
|
const FObjectPropertyBase* Prop = Container ? CastField<FObjectPropertyBase>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
|
return Prop ? Cast<T>(Prop->GetObjectPropertyValue_InContainer(Container)) : nullptr;
|
|
}
|
|
|
|
bool ReadBoolProperty(const UObject* Container, const TCHAR* Name)
|
|
{
|
|
const FBoolProperty* Prop = Container ? CastField<FBoolProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
|
return Prop && Prop->GetPropertyValue_InContainer(Container);
|
|
}
|
|
|
|
// S-F5j : le joueur pilote-t-il (une touche de MoveForward / MoveRight enfoncee) ou un menu, ou la boite de scene, est-il a l'ecran ?
|
|
bool PlayerBusy(const UGameInstance* GI, UWorld* World)
|
|
{
|
|
const APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
|
const UInputSettings* Settings = UInputSettings::GetInputSettings();
|
|
if (!GI || !PC || !PC->PlayerInput || !Settings)
|
|
{
|
|
return true;
|
|
}
|
|
for (const FInputAxisKeyMapping& Map : Settings->GetAxisMappings())
|
|
{
|
|
if ((Map.AxisName == FName(TEXT("MoveForward")) || Map.AxisName == FName(TEXT("MoveRight")))
|
|
&& (Map.Key.IsDigital() ? PC->PlayerInput->IsPressed(Map.Key) : !FMath::IsNearlyZero(PC->PlayerInput->GetKeyValue(Map.Key))))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if (const ULPKeyboardSubsystem* Keys = GI->GetSubsystem<ULPKeyboardSubsystem>())
|
|
{
|
|
if (Keys->IsMenuOpen())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
for (TObjectIterator<UUserWidget> It; It; ++It)
|
|
{
|
|
const UUserWidget* Widget = *It;
|
|
if (!Widget || Widget->HasAnyFlags(RF_ClassDefaultObject) || Widget->GetGameInstance() != GI || !Widget->IsInViewport())
|
|
{
|
|
continue;
|
|
}
|
|
const ESlateVisibility Visibility = Widget->GetVisibility();
|
|
if (Visibility == ESlateVisibility::Collapsed || Visibility == ESlateVisibility::Hidden)
|
|
{
|
|
continue;
|
|
}
|
|
const FString ClassName = Widget->GetClass()->GetName();
|
|
for (const TCHAR* Menu : PlayerMenuClasses)
|
|
{
|
|
if (ClassName == Menu)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// GI.PersonsUsedThisScene contient-il cet acteur (personne prise par la scene en cours) ?
|
|
bool UsedInScene(const UObject* GI, const AActor* Person)
|
|
{
|
|
const FArrayProperty* Prop = GI ? CastField<FArrayProperty>(GI->GetClass()->FindPropertyByName(TEXT("PersonsUsedThisScene"))) : nullptr;
|
|
const FObjectPropertyBase* Inner = Prop ? CastField<FObjectPropertyBase>(Prop->Inner) : nullptr;
|
|
if (!Inner)
|
|
{
|
|
return false;
|
|
}
|
|
FScriptArrayHelper Array(Prop, Prop->ContainerPtrToValuePtr<void>(GI));
|
|
for (int32 Index = 0; Index < Array.Num(); ++Index)
|
|
{
|
|
if (Inner->GetObjectPropertyValue(Array.GetRawPtr(Index)) == Person)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ULPOutdoorRoomSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
|
{
|
|
Super::Initialize(Collection);
|
|
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPOutdoorRoomSubsystem::Tick));
|
|
}
|
|
|
|
void ULPOutdoorRoomSubsystem::Deinitialize()
|
|
{
|
|
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
|
|
RestoreFloor();
|
|
RestoreDecor();
|
|
Super::Deinitialize();
|
|
}
|
|
|
|
bool ULPOutdoorRoomSubsystem::Tick(float DeltaTime)
|
|
{
|
|
const AActor* Room = GetObjectProperty<AActor>(GetGameInstance(), RoomPropertyName);
|
|
USceneComponent* Root = IsValid(Room) ? Room->GetRootComponent() : nullptr;
|
|
USceneComponent* Ceiling = IsValid(Room) ? GetObjectProperty<USceneComponent>(Room, CeilingPropertyName) : nullptr;
|
|
|
|
// Plafond masque precedemment mais qui n'est plus celui de la piece courante : le rendre.
|
|
if (HiddenCeiling.IsValid() && HiddenCeiling.Get() != Ceiling)
|
|
{
|
|
HiddenCeiling->SetHiddenInGame(false);
|
|
HiddenCeiling.Reset();
|
|
}
|
|
UBoxComponent* Floor = IsValid(Room) ? GetObjectProperty<UBoxComponent>(Room, FloorPropertyName) : nullptr;
|
|
if (ThickenedFloor.IsValid() && ThickenedFloor.Get() != Floor)
|
|
{
|
|
RestoreFloor();
|
|
}
|
|
if (!Root)
|
|
{
|
|
return true;
|
|
}
|
|
const bool bOutdoor = Root->GetRelativeScale3D().Z < OutdoorScaleZ;
|
|
if (Floor)
|
|
{
|
|
UpdateFloor(Floor, bOutdoor);
|
|
}
|
|
if (bOutdoor)
|
|
{
|
|
// S-F5p : nouvelle piece exterieure (echelle differente, ou retour de l'interieur) : balayage rapide, positions oubliees.
|
|
const FVector Scale = Root->GetRelativeScale3D();
|
|
if (!Scale.Equals(OutdoorScale))
|
|
{
|
|
OutdoorScale = Scale;
|
|
FastScanTime = FastScanSeconds;
|
|
LastPositions.Reset();
|
|
}
|
|
const bool bFast = FastScanTime > 0.f;
|
|
FastScanTime = FMath::Max(0.f, FastScanTime - DeltaTime);
|
|
DecorScanDelay += DeltaTime;
|
|
if (DecorScanDelay >= DecorScanSeconds)
|
|
{
|
|
DecorScanDelay = 0.f;
|
|
IgnoreOutdoorDecor(Room);
|
|
LiftSunkPersons(Room, Floor);
|
|
}
|
|
else if (bFast)
|
|
{
|
|
LiftSunkPersons(Room, Floor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutdoorScale = FVector::ZeroVector;
|
|
FastScanTime = 0.f;
|
|
LastPositions.Reset();
|
|
if (IgnoredDecor.Num() > 0)
|
|
{
|
|
RestoreDecor();
|
|
}
|
|
SunkPersons.Reset();
|
|
}
|
|
if (!Ceiling)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bOutdoor && !Ceiling->bHiddenInGame)
|
|
{
|
|
Ceiling->SetHiddenInGame(true);
|
|
HiddenCeiling = Ceiling;
|
|
UE_LOG(LogLifePlay, Log, TEXT("Piece exterieure (echelle Z %.3f) : plafond WallT masque"), Root->GetRelativeScale3D().Z);
|
|
}
|
|
else if (!bOutdoor && HiddenCeiling.Get() == Ceiling)
|
|
{
|
|
Ceiling->SetHiddenInGame(false);
|
|
HiddenCeiling.Reset();
|
|
UE_LOG(LogLifePlay, Log, TEXT("Piece interieure (echelle Z %.3f) : plafond WallT rendu visible"), Root->GetRelativeScale3D().Z);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ULPOutdoorRoomSubsystem::UpdateFloor(UBoxComponent* Floor, const bool bOutdoor)
|
|
{
|
|
if (!bOutdoor)
|
|
{
|
|
if (ThickenedFloor.Get() == Floor)
|
|
{
|
|
RestoreFloor();
|
|
}
|
|
return;
|
|
}
|
|
if (ThickenedFloor.Get() == Floor)
|
|
{
|
|
return;
|
|
}
|
|
// Box n'a pas d'echelle propre dans Room : son epaisseur monde vaut 2 x demi-hauteur x echelle Z de la racine.
|
|
const double ScaleZ = Floor->GetComponentScale().Z;
|
|
if (ScaleZ <= UE_KINDA_SMALL_NUMBER || !FMath::IsNearlyEqual(Floor->GetRelativeScale3D().Z, 1.0))
|
|
{
|
|
return;
|
|
}
|
|
const FVector Extent = Floor->GetUnscaledBoxExtent();
|
|
const double TargetZ = Extent.Z * IndoorScaleZ / ScaleZ;
|
|
if (TargetZ <= Extent.Z)
|
|
{
|
|
return;
|
|
}
|
|
FloorExtent = Extent;
|
|
FloorLocation = Floor->GetRelativeLocation();
|
|
FloorMobility = Floor->Mobility;
|
|
if (FloorMobility != EComponentMobility::Movable)
|
|
{
|
|
// Un composant Static refuse SetRelativeLocation en jeu.
|
|
Floor->SetMobility(EComponentMobility::Movable);
|
|
}
|
|
// Dessus inchange : relatif Z + demi-hauteur reste egal ; seul le dessous descend.
|
|
Floor->SetBoxExtent(FVector(Extent.X, Extent.Y, TargetZ), true);
|
|
Floor->SetRelativeLocation(FVector(FloorLocation.X, FloorLocation.Y, FloorLocation.Z + Extent.Z - TargetZ));
|
|
ThickenedFloor = Floor;
|
|
UE_LOG(LogLifePlay, Log, TEXT("Piece exterieure (echelle Z %.3f) : plancher Box epaissi a %.1f cm (demi-hauteur %.1f -> %.1f)"),
|
|
ScaleZ, 2.0 * TargetZ * ScaleZ, Extent.Z, TargetZ);
|
|
}
|
|
|
|
void ULPOutdoorRoomSubsystem::RestoreFloor()
|
|
{
|
|
if (UBoxComponent* Floor = ThickenedFloor.Get())
|
|
{
|
|
Floor->SetBoxExtent(FloorExtent, true);
|
|
Floor->SetRelativeLocation(FloorLocation);
|
|
if (Floor->Mobility != FloorMobility)
|
|
{
|
|
Floor->SetMobility(FloorMobility);
|
|
}
|
|
UE_LOG(LogLifePlay, Log, TEXT("Plancher Box rendu a sa demi-hauteur d'origine %.1f"), FloorExtent.Z);
|
|
}
|
|
ThickenedFloor.Reset();
|
|
}
|
|
|
|
void ULPOutdoorRoomSubsystem::IgnoreOutdoorDecor(const AActor* Room)
|
|
{
|
|
// Depuis CHG-145, Room::addObject met chaque objet en WorldDynamic / Pawn Block ; la capsule des personnages bloque
|
|
// WorldDynamic, et la collision simple d'un decor exterieur (un volume, pas le pave) les emprisonne (STG-SF4b-DIAG.md).
|
|
IgnoredDecor.RemoveAll([](const TWeakObjectPtr<UPrimitiveComponent>& Comp) { return !Comp.IsValid(); });
|
|
SolidDecor.RemoveAll([](const TWeakObjectPtr<UPrimitiveComponent>& Comp) { return !Comp.IsValid(); });
|
|
TInlineComponentArray<UStaticMeshComponent*> Meshes;
|
|
Room->GetComponents(Meshes);
|
|
int32 Added = 0;
|
|
for (UStaticMeshComponent* Mesh : Meshes)
|
|
{
|
|
const UStaticMesh* Asset = Mesh ? Mesh->GetStaticMesh() : nullptr;
|
|
if (!Asset || !Asset->GetName().StartsWith(OutdoorDecorPrefix) || IgnoredDecor.Contains(Mesh) || SolidDecor.Contains(Mesh)
|
|
|| Mesh->GetCollisionResponseToChannel(ECC_Pawn) != ECR_Block)
|
|
{
|
|
continue;
|
|
}
|
|
// Variante facades : collision complexe (triangles du pave et des facades), pas de volume piege : le decor reste solide.
|
|
const UBodySetup* Body = Asset->GetBodySetup();
|
|
if (Body && Body->CollisionTraceFlag == CTF_UseComplexAsSimple)
|
|
{
|
|
SolidDecor.Add(Mesh);
|
|
UE_LOG(LogLifePlay, Log, TEXT("Piece exterieure : decor %s (%s) en collision complexe, laisse en Pawn Block"), *Mesh->GetName(), *Asset->GetName());
|
|
continue;
|
|
}
|
|
Mesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore);
|
|
IgnoredDecor.Add(Mesh);
|
|
++Added;
|
|
}
|
|
if (Added > 0)
|
|
{
|
|
UE_LOG(LogLifePlay, Log, TEXT("Piece exterieure : %d decor(s) fnt_ext en Pawn Ignore (%d en tout)"), Added, IgnoredDecor.Num());
|
|
}
|
|
}
|
|
|
|
void ULPOutdoorRoomSubsystem::RestoreDecor()
|
|
{
|
|
int32 Restored = 0;
|
|
for (const TWeakObjectPtr<UPrimitiveComponent>& Comp : IgnoredDecor)
|
|
{
|
|
if (UPrimitiveComponent* Prim = Comp.Get())
|
|
{
|
|
Prim->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block);
|
|
++Restored;
|
|
}
|
|
}
|
|
if (IgnoredDecor.Num() > 0)
|
|
{
|
|
UE_LOG(LogLifePlay, Log, TEXT("Decors fnt_ext rendus en Pawn Block : %d (sur %d, les autres detruits avec la piece)"), Restored, IgnoredDecor.Num());
|
|
}
|
|
IgnoredDecor.Reset();
|
|
SolidDecor.Reset();
|
|
DecorScanDelay = 0.f;
|
|
}
|
|
|
|
void ULPOutdoorRoomSubsystem::LiftSunkPersons(const AActor* Room, const UBoxComponent* Floor)
|
|
{
|
|
const UGameInstance* GI = GetGameInstance();
|
|
UWorld* World = Room ? Room->GetWorld() : nullptr;
|
|
if (!Floor || !World || ReadBoolProperty(GI, TEXT("SexOn")))
|
|
{
|
|
SunkPersons.Reset();
|
|
return;
|
|
}
|
|
const AActor* Player = GetObjectProperty<AActor>(GI, TEXT("PlayerCharacter"));
|
|
// Dessus de Box en monde (Box sans rotation dans Room ; inchange par S-F4).
|
|
const double FloorTop = Floor->GetComponentLocation().Z + Floor->GetScaledBoxExtent().Z;
|
|
TMap<TWeakObjectPtr<AActor>, TPair<FVector, int32>> Seen;
|
|
for (auto Logged = UnplacedLogged.CreateIterator(); Logged; ++Logged)
|
|
{
|
|
if (!Logged->IsValid())
|
|
{
|
|
Logged.RemoveCurrent();
|
|
}
|
|
}
|
|
for (auto Known = LastPositions.CreateIterator(); Known; ++Known)
|
|
{
|
|
if (!Known.Key().IsValid())
|
|
{
|
|
Known.RemoveCurrent();
|
|
}
|
|
}
|
|
for (TActorIterator<ACharacter> It(World); It; ++It)
|
|
{
|
|
ACharacter* Person = *It;
|
|
UCapsuleComponent* Capsule = Person ? Person->GetCapsuleComponent() : nullptr;
|
|
const bool bPlayer = Person == Player;
|
|
if (!Capsule || Person->IsHidden() || (!bPlayer && !Person->GetClass()->GetName().StartsWith(TEXT("Person_C")))
|
|
|| Capsule->GetCollisionEnabled() == ECollisionEnabled::NoCollision
|
|
|| IsValid(GetObjectProperty<UObject>(Person, TEXT("OccupyingFnt"))) || UsedInScene(GI, Person))
|
|
{
|
|
continue;
|
|
}
|
|
const double HalfHeight = Capsule->GetScaledCapsuleHalfHeight();
|
|
const FVector Location = Person->GetActorLocation();
|
|
const double Bottom = Location.Z - HalfHeight;
|
|
// S-F5p : personne posee depuis le balayage precedent (premiere vue dans la piece, ou saut de plus de JumpDistance).
|
|
const FVector* Previous = LastPositions.Find(Person);
|
|
const bool bJustPlaced = !Previous || FVector::Dist(*Previous, Location) > JumpDistance;
|
|
LastPositions.Add(Person, Location);
|
|
if (Bottom >= FloorTop - SunkDepth || Person->GetVelocity().Size2D() > 1.0)
|
|
{
|
|
if (Bottom >= FloorTop - SunkDepth)
|
|
{
|
|
// S-F5m : episode termine (personne a hauteur), une nouvelle ligne sera permise.
|
|
UnplacedLogged.Remove(Person);
|
|
}
|
|
continue;
|
|
}
|
|
// Immobile : meme position (a 1 cm pres) sur SunkScans examens consecutifs.
|
|
const TPair<FVector, int32>* Before = SunkPersons.Find(Person);
|
|
const int32 Scans = (Before && FVector::Dist(Before->Key, Location) < 1.0) ? Before->Value + 1 : 1;
|
|
// S-F5p : une personne qui vient d'etre posee (immobile, garde de vitesse ci-dessus) est relevee des ce premier examen.
|
|
if (!bJustPlaced && Scans < (bPlayer ? PlayerSunkScans : SunkScans))
|
|
{
|
|
Seen.Add(Person, TPair<FVector, int32>(Location, Scans));
|
|
continue;
|
|
}
|
|
// S-F5j : jamais le joueur qui pilote, ni sous un menu ou la boite de scene (examen repris a zero).
|
|
if (bPlayer && PlayerBusy(GI, World))
|
|
{
|
|
continue;
|
|
}
|
|
// S-F5k : autre personne visible a moins de LiftSpacing en 2D ? Alors essayer d'abord un point ecarte le long du grand axe
|
|
// de la piece (du cote oppose a elle), puis de l'autre cote, puis sur place.
|
|
const AActor* Near = nullptr;
|
|
for (TActorIterator<ACharacter> Other(World); Other; ++Other)
|
|
{
|
|
const ACharacter* OtherPerson = *Other;
|
|
const UCapsuleComponent* OtherCapsule = OtherPerson ? OtherPerson->GetCapsuleComponent() : nullptr;
|
|
if (OtherPerson && OtherPerson != Person && OtherCapsule && !OtherPerson->IsHidden()
|
|
&& OtherCapsule->GetCollisionEnabled() != ECollisionEnabled::NoCollision
|
|
&& FVector::Dist2D(OtherPerson->GetActorLocation(), Location) < LiftSpacing)
|
|
{
|
|
Near = OtherPerson;
|
|
break;
|
|
}
|
|
}
|
|
TArray<FVector, TInlineAllocator<3>> Candidates;
|
|
if (Near)
|
|
{
|
|
const FVector Scale = Room->GetRootComponent() ? Room->GetRootComponent()->GetComponentScale() : FVector::OneVector;
|
|
FVector Axis = (FMath::Abs(Scale.Y) >= FMath::Abs(Scale.X)) ? Room->GetActorRightVector() : Room->GetActorForwardVector();
|
|
Axis.Z = 0.0;
|
|
Axis = Axis.GetSafeNormal();
|
|
const double Side = FVector::DotProduct(Location - Near->GetActorLocation(), Axis) >= 0.0 ? 1.0 : -1.0;
|
|
Candidates.Add(Location + Axis * (Side * LiftSpacing));
|
|
Candidates.Add(Location - Axis * (Side * LiftSpacing));
|
|
}
|
|
Candidates.Add(Location);
|
|
// Descente de la capsule depuis le dessus de Box + LiftProbe : premier sol qu'elle rencontre, avec ses propres reponses.
|
|
FCollisionQueryParams Query(TEXT("LPLiftSunk"), false, Person);
|
|
FCollisionResponseParams Responses(Capsule->GetCollisionResponseToChannels());
|
|
FHitResult Hit;
|
|
bool bPlaced = false;
|
|
FVector Target = Location;
|
|
// Descente en un point : vrai si la capsule trouve un sol (ou rien : dessus de Box) sans partir en recouvrement.
|
|
FHitResult InPlace;
|
|
auto TryPlace = [&](const FVector& Candidate) -> bool
|
|
{
|
|
const FVector Start(Candidate.X, Candidate.Y, FloorTop + HalfHeight + LiftProbe);
|
|
const FVector End(Candidate.X, Candidate.Y, FloorTop + HalfHeight - 2.0);
|
|
Hit = FHitResult();
|
|
double TargetZ = FloorTop + HalfHeight + 2.0;
|
|
if (World->SweepSingleByChannel(Hit, Start, End, Capsule->GetComponentQuat(), Capsule->GetCollisionObjectType(), Capsule->GetCollisionShape(), Query, Responses))
|
|
{
|
|
if (Hit.bStartPenetrating)
|
|
{
|
|
if (Candidate.Equals(Location))
|
|
{
|
|
InPlace = Hit;
|
|
}
|
|
return false;
|
|
}
|
|
TargetZ = Hit.Location.Z + 1.0;
|
|
}
|
|
Target = FVector(Candidate.X, Candidate.Y, TargetZ);
|
|
return true;
|
|
};
|
|
for (const FVector& Candidate : Candidates)
|
|
{
|
|
if (TryPlace(Candidate))
|
|
{
|
|
bPlaced = true;
|
|
break;
|
|
}
|
|
}
|
|
// S-F5m (STG-COMMUN8-DIAG A) : point d'arrivee colle a un decor solide (facade en collision complexe depuis COMMUN-6) : la
|
|
// descente part en recouvrement. On essaie alors : S-F5n, le point donne par la normale de depenetration (partie horizontale,
|
|
// profondeur + 2 cm) ; puis 50 cm le long du petit axe vers le centre de la piece (loin de la facade), 50 cm de chaque cote le
|
|
// long du grand axe, et 50 cm le long du petit axe de l'autre cote.
|
|
bool bMoved = false;
|
|
if (!bPlaced)
|
|
{
|
|
TArray<FVector, TInlineAllocator<5>> Escapes;
|
|
FVector Push = InPlace.Normal;
|
|
Push.Z = 0.0;
|
|
if (InPlace.bStartPenetrating && Push.SizeSquared() > 0.01)
|
|
{
|
|
Escapes.Add(Location + Push.GetSafeNormal() * (static_cast<double>(InPlace.PenetrationDepth) + 2.0));
|
|
}
|
|
const FVector Scale = Room->GetRootComponent() ? Room->GetRootComponent()->GetComponentScale() : FVector::OneVector;
|
|
const bool bLongIsY = FMath::Abs(Scale.Y) >= FMath::Abs(Scale.X);
|
|
FVector Long = bLongIsY ? Room->GetActorRightVector() : Room->GetActorForwardVector();
|
|
FVector Short = bLongIsY ? Room->GetActorForwardVector() : Room->GetActorRightVector();
|
|
Long.Z = 0.0;
|
|
Short.Z = 0.0;
|
|
Long = Long.GetSafeNormal();
|
|
Short = Short.GetSafeNormal();
|
|
const double Inward = FVector::DotProduct(Room->GetActorLocation() - Location, Short) >= 0.0 ? 1.0 : -1.0;
|
|
Escapes.Add(Location + Short * (Inward * LiftSpacing));
|
|
Escapes.Add(Location + Long * LiftSpacing);
|
|
Escapes.Add(Location - Long * LiftSpacing);
|
|
Escapes.Add(Location - Short * (Inward * LiftSpacing));
|
|
for (const FVector& Candidate : Escapes)
|
|
{
|
|
if (TryPlace(Candidate))
|
|
{
|
|
bPlaced = true;
|
|
bMoved = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!bPlaced)
|
|
{
|
|
if (!UnplacedLogged.Contains(Person))
|
|
{
|
|
UnplacedLogged.Add(Person);
|
|
UE_LOG(LogLifePlay, Log, TEXT("Piece exterieure : %s%s enfonce de %.1f cm, aucun point libre (depart dans %s, S-F5m)"),
|
|
bPlayer ? TEXT("joueur ") : TEXT(""), *Person->GetName(), FloorTop - Bottom,
|
|
InPlace.GetComponent() ? *InPlace.GetComponent()->GetName() : TEXT("-"));
|
|
}
|
|
continue;
|
|
}
|
|
UnplacedLogged.Remove(Person);
|
|
Person->SetActorLocation(Target, false, nullptr, ETeleportType::TeleportPhysics);
|
|
++LiftCount;
|
|
UE_LOG(LogLifePlay, Log, TEXT("Piece exterieure : %s%s releve de %.1f cm (bas de capsule %.1f, dessus Box %.1f, sol %s) ; %d releve(s) en tout"),
|
|
bPlayer ? TEXT("joueur ") : TEXT(""), *Person->GetName(), Target.Z - Location.Z, Bottom, FloorTop, Hit.GetComponent() ? *Hit.GetComponent()->GetName() : TEXT("Box"), LiftCount);
|
|
if (bMoved)
|
|
{
|
|
UE_LOG(LogLifePlay, Log, TEXT("Piece exterieure : %s ecarte de %.1f cm (depart dans %s, S-F5m)"),
|
|
*Person->GetName(), FVector::Dist2D(Target, Location), InPlace.GetComponent() ? *InPlace.GetComponent()->GetName() : TEXT("-"));
|
|
}
|
|
else if (Near)
|
|
{
|
|
UE_LOG(LogLifePlay, Log, TEXT("Piece exterieure : %s ecarte de %.1f cm (voisin %s a %.1f cm, S-F5k)"),
|
|
*Person->GetName(), FVector::Dist2D(Target, Location), *Near->GetName(), FVector::Dist2D(Near->GetActorLocation(), Location));
|
|
}
|
|
}
|
|
SunkPersons = MoveTemp(Seen);
|
|
}
|