LifePlay-Revival/Source/LifePlay/LPDirectControlSubsystem.cpp
edujiro5972 5e901bef31 Proximité : LPDirectControlSubsystem (marche ZQSD/WASD, touche E), DefaultInput, index furniture.lpnear
Phase Proximité (CHG-157, 166, 167).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:49:08 +02:00

1112 lines
32 KiB
C++

// Marche et invites de proximite. Voir LPDirectControlSubsystem.h.
//
// Releve UI/Action : le bouton dont AmenityUID est vide appelle GI.TakeActionWithBuilding
// avec le texte du bouton et un StreetMapBuilding. Le bouton d'une action chargee appelle
// GI.TakeModActionWithBuilding avec la structure ActionDetail, le batiment, Multiple et
// Interaction. C'est ce second appel qui porte l'identifiant.
#include "LPDirectControlSubsystem.h"
#include "Animation/AnimInstance.h"
#include "Animation/AnimationAsset.h"
#include "Blueprint/UserWidget.h"
#include "Camera/CameraActor.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/GameInstance.h"
#include "Engine/GameViewportClient.h"
#include "Engine/StaticMesh.h"
#include "Engine/World.h"
#include "EngineUtils.h"
#include "Framework/Application/SlateApplication.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/InputSettings.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerInput.h"
#include "LPKeyboardSubsystem.h"
#include "LifePlay.h"
#include "Misc/FileHelper.h"
#include "UObject/TextProperty.h"
#include "Misc/Paths.h"
#include "Styling/CoreStyle.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Text/STextBlock.h"
namespace
{
const FName ForwardAxis(TEXT("MoveForward"));
const FName RightAxis(TEXT("MoveRight"));
const FName InteractAction(TEXT("Interact"));
const float Reach = 150.f;
const TCHAR* MenuClasses[] = {
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")
};
struct FPrompt
{
FString Label;
FString ActionId;
FString MeshPrefix;
bool bPerson = false;
};
struct FPromptLive
{
TWeakObjectPtr<ULPDirectControlSubsystem> Owner;
TSharedPtr<SWidget> Root;
TSharedPtr<SVerticalBox> Rows;
TArray<TSharedPtr<STextBlock>> Labels;
double LastFire = 0.0;
};
FPromptLive& PromptState()
{
static FPromptLive State;
return State;
}
TArray<FPrompt>& PersonPrompts()
{
static TArray<FPrompt> Prompts;
return Prompts;
}
TArray<FPrompt>& MeshPrompts()
{
static TArray<FPrompt> Prompts;
return Prompts;
}
TArray<FPrompt>& Shown()
{
static TArray<FPrompt> Prompts;
return Prompts;
}
const FProperty* FindAuthored(const UStruct* Struct, const TCHAR* Name)
{
if (!Struct)
{
return nullptr;
}
for (TFieldIterator<FProperty> It(Struct); It; ++It)
{
if (It->GetAuthoredName() == Name || It->GetName() == Name)
{
return *It;
}
}
return nullptr;
}
FString DirectFieldText(const UStruct* Struct, const void* Data, const TCHAR* Name)
{
FString Out;
if (const FProperty* Prop = FindAuthored(Struct, Name))
{
Prop->ExportTextItem_Direct(Out, Prop->ContainerPtrToValuePtr<void>(Data), nullptr, nullptr, PPF_None);
}
return Out.TrimQuotes();
}
void SetString(UObject* Object, const TCHAR* Name, const FString& Value)
{
if (FStrProperty* Prop = Object ? CastField<FStrProperty>(Object->GetClass()->FindPropertyByName(Name)) : nullptr)
{
Prop->SetPropertyValue_InContainer(Object, Value);
}
}
void SetObject(UObject* Object, const TCHAR* Name, UObject* Value)
{
if (FObjectPropertyBase* Prop = Object ? CastField<FObjectPropertyBase>(Object->GetClass()->FindPropertyByName(Name)) : nullptr)
{
Prop->SetObjectPropertyValue_InContainer(Object, Value);
}
}
AActor* DirectActor(const UObject* Object, const TCHAR* Name)
{
const FObjectPropertyBase* Prop = Object ? CastField<FObjectPropertyBase>(Object->GetClass()->FindPropertyByName(Name)) : nullptr;
return Prop ? Cast<AActor>(Prop->GetObjectPropertyValue_InContainer(Object)) : nullptr;
}
bool KeyDown(const APlayerController* PC, const FKey& Key)
{
return PC && PC->IsInputKeyDown(Key);
}
bool ModifiersMatch(const APlayerController* PC, const FInputActionKeyMapping& Map)
{
const bool bCtrl = KeyDown(PC, EKeys::LeftControl) || KeyDown(PC, EKeys::RightControl);
const bool bShift = KeyDown(PC, EKeys::LeftShift) || KeyDown(PC, EKeys::RightShift);
const bool bAlt = KeyDown(PC, EKeys::LeftAlt) || KeyDown(PC, EKeys::RightAlt);
const bool bCmd = KeyDown(PC, EKeys::LeftCommand) || KeyDown(PC, EKeys::RightCommand);
return Map.bCtrl == bCtrl && Map.bShift == bShift && Map.bAlt == bAlt && Map.bCmd == bCmd;
}
float AxisValue(const APlayerController* PC, FName Axis)
{
const UInputSettings* Settings = UInputSettings::GetInputSettings();
if (!PC || !PC->PlayerInput || !Settings)
{
return 0.f;
}
float Value = 0.f;
for (const FInputAxisKeyMapping& Map : Settings->GetAxisMappings())
{
if (Map.AxisName == Axis)
{
const float Down = Map.Key.IsDigital()
? (PC->PlayerInput->IsPressed(Map.Key) ? 1.f : 0.f)
: PC->PlayerInput->GetKeyValue(Map.Key);
Value += Down * Map.Scale;
}
}
return FMath::Clamp(Value, -1.f, 1.f);
}
bool InteractPressed(const APlayerController* PC)
{
const UInputSettings* Settings = UInputSettings::GetInputSettings();
if (!PC || !PC->PlayerInput || !Settings)
{
return false;
}
for (const FInputActionKeyMapping& Map : Settings->GetActionMappings())
{
if (Map.ActionName == InteractAction && ModifiersMatch(PC, Map) && PC->PlayerInput->WasJustPressed(Map.Key))
{
return true;
}
}
return false;
}
FString ReadEnabledSignature(const UObject* GI)
{
const UScriptStruct* Struct = nullptr;
const FArrayProperty* Prop = GI ? CastField<FArrayProperty>(GI->GetClass()->FindPropertyByName(TEXT("ModEnableds"))) : nullptr;
const FStructProperty* Inner = Prop ? CastField<FStructProperty>(Prop->Inner) : nullptr;
Struct = Inner ? Inner->Struct : nullptr;
if (!Prop || !Struct)
{
return FString();
}
FString Signature;
FScriptArrayHelper Helper(Prop, Prop->ContainerPtrToValuePtr<void>(GI));
for (int32 Index = 0; Index < Helper.Num(); ++Index)
{
Signature += DirectFieldText(Struct, Helper.GetRawPtr(Index), TEXT("UniqueID"));
Signature += TEXT("=");
Signature += DirectFieldText(Struct, Helper.GetRawPtr(Index), TEXT("IsEnabled"));
Signature += TEXT(";");
}
return Signature;
}
bool ModuleEnabled(const UObject* GI, const FString& Module)
{
const FArrayProperty* Prop = GI ? CastField<FArrayProperty>(GI->GetClass()->FindPropertyByName(TEXT("ModEnableds"))) : nullptr;
const FStructProperty* Inner = Prop ? CastField<FStructProperty>(Prop->Inner) : nullptr;
const UScriptStruct* Struct = Inner ? Inner->Struct : nullptr;
if (!Prop || !Struct)
{
return true;
}
FScriptArrayHelper Helper(Prop, Prop->ContainerPtrToValuePtr<void>(GI));
if (Helper.Num() == 0)
{
return true;
}
for (int32 Index = 0; Index < Helper.Num(); ++Index)
{
if (DirectFieldText(Struct, Helper.GetRawPtr(Index), TEXT("UniqueID")).Equals(Module, ESearchCase::IgnoreCase))
{
return DirectFieldText(Struct, Helper.GetRawPtr(Index), TEXT("IsEnabled")).Equals(TEXT("True"), ESearchCase::IgnoreCase);
}
}
return false;
}
FString ModuleOf(const FString& File, const FString& Root)
{
FString Rel = File;
FString Base = Root;
FPaths::NormalizeFilename(Rel);
FPaths::NormalizeFilename(Base);
if (!Base.EndsWith(TEXT("/")))
{
Base += TEXT("/");
}
if (Rel.StartsWith(Base, ESearchCase::IgnoreCase))
{
Rel.RightChopInline(Base.Len());
}
FString Module, Rest;
return Rel.Split(TEXT("/"), &Module, &Rest) ? Module : FString();
}
void ReadKeys(const FString& Text, TMap<FString, FString>& Out)
{
TArray<FString> Lines;
Text.ParseIntoArrayLines(Lines, true);
for (const FString& Raw : Lines)
{
const FString Line = Raw.TrimStartAndEnd();
FString Key, Value;
if (Line.StartsWith(TEXT("//")) || !Line.Split(TEXT(":"), &Key, &Value))
{
continue;
}
Out.Add(Key.TrimStartAndEnd().ToUpper(), Value.TrimStartAndEnd());
}
}
bool WhereHasInteraction(const FString& Where)
{
TArray<FString> Parts;
Where.ParseIntoArray(Parts, TEXT(","), true);
for (const FString& Part : Parts)
{
if (Part.TrimStartAndEnd().Equals(TEXT("interaction"), ESearchCase::IgnoreCase))
{
return true;
}
}
return false;
}
void AddUnique(TArray<FPrompt>& Dest, const FPrompt& Prompt)
{
for (const FPrompt& Have : Dest)
{
if (Have.ActionId.Equals(Prompt.ActionId, ESearchCase::IgnoreCase)
&& Have.MeshPrefix.Equals(Prompt.MeshPrefix, ESearchCase::IgnoreCase))
{
return;
}
}
Dest.Add(Prompt);
}
const void* FindActionDetail(const UObject* GI, const FString& ActionId, const UScriptStruct*& OutStruct)
{
OutStruct = nullptr;
const FArrayProperty* Prop = GI ? CastField<FArrayProperty>(GI->GetClass()->FindPropertyByName(TEXT("ActionDetail"))) : nullptr;
const FStructProperty* Inner = Prop ? CastField<FStructProperty>(Prop->Inner) : nullptr;
if (!Inner || !Inner->Struct)
{
return nullptr;
}
OutStruct = Inner->Struct;
const TCHAR* IdNames[] = { TEXT("AmenityUID"), TEXT("ActionUID"), TEXT("UID") };
const FProperty* IdField = nullptr;
for (const TCHAR* Name : IdNames)
{
if ((IdField = FindAuthored(OutStruct, Name)) != nullptr)
{
break;
}
}
if (!IdField)
{
return nullptr;
}
FScriptArrayHelper Helper(Prop, Prop->ContainerPtrToValuePtr<void>(GI));
for (int32 Index = 0; Index < Helper.Num(); ++Index)
{
FString Id;
IdField->ExportTextItem_Direct(Id, IdField->ContainerPtrToValuePtr<void>(Helper.GetRawPtr(Index)), nullptr, nullptr, PPF_None);
if (Id.TrimQuotes().Equals(ActionId, ESearchCase::IgnoreCase))
{
return Helper.GetRawPtr(Index);
}
}
return nullptr;
}
bool ActionLoaded(const UObject* GI, const FString& ActionId)
{
const UScriptStruct* Struct = nullptr;
if (FindActionDetail(GI, ActionId, Struct))
{
return true;
}
const FArrayProperty* Prop = GI ? CastField<FArrayProperty>(GI->GetClass()->FindPropertyByName(TEXT("ActionDetail"))) : nullptr;
const FStructProperty* Inner = Prop ? CastField<FStructProperty>(Prop->Inner) : nullptr;
if (!Inner)
{
return true;
}
FScriptArrayHelper Helper(Prop, Prop->ContainerPtrToValuePtr<void>(GI));
return Helper.Num() == 0;
}
void LogParmsOnce(const UFunction* Function)
{
static TSet<FName> Logged;
if (!Function || Logged.Contains(Function->GetFName()))
{
return;
}
Logged.Add(Function->GetFName());
for (TFieldIterator<FProperty> It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
{
UE_LOG(LogLifePlay, Display, TEXT("Interaction : %s.%s (%s)"),
*Function->GetName(), *It->GetAuthoredName(), *It->GetClass()->GetName());
}
}
FProperty* FindParm(UFunction* Function, const TCHAR* Name)
{
for (TFieldIterator<FProperty> It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
{
if (It->GetAuthoredName() == Name || It->GetName() == Name)
{
return *It;
}
}
return nullptr;
}
void CopyBuilding(UObject* GI, UFunction* Function, uint8* Params)
{
FStructProperty* Dest = CastField<FStructProperty>(FindParm(Function, TEXT("Building")));
const UObject* Street = DirectActor(GI, TEXT("StreetMapStatic"));
const FStructProperty* Source = Street ? CastField<FStructProperty>(Street->GetClass()->FindPropertyByName(TEXT("CurrentPlayerBuilding"))) : nullptr;
if (!Dest || !Source || Dest->Struct != Source->Struct)
{
return;
}
Dest->CopyCompleteValue(Dest->ContainerPtrToValuePtr<void>(Params), Source->ContainerPtrToValuePtr<void>(Street));
}
void CallAction(UObject* GI, const FPrompt& Prompt, AActor* Target)
{
if (!GI)
{
return;
}
SetString(GI, TEXT("CurrentActionUID"), Prompt.ActionId);
if (Prompt.bPerson)
{
SetObject(GI, TEXT("Interacting"), Target);
SetObject(GI, TEXT("InteractionTarget"), Target);
}
UFunction* Function = GI->FindFunction(TEXT("TakeModActionWithBuilding"));
const UScriptStruct* DetailStruct = nullptr;
const void* Detail = FindActionDetail(GI, Prompt.ActionId, DetailStruct);
const FStructProperty* ActionParm = Function ? CastField<FStructProperty>(FindParm(Function, TEXT("Action"))) : nullptr;
if (!Function || !Detail || !ActionParm || ActionParm->Struct != DetailStruct)
{
Function = GI->FindFunction(TEXT("TakeActionWithBuilding"));
Detail = nullptr;
}
if (!Function)
{
UE_LOG(LogLifePlay, Warning, TEXT("Interaction : TakeModActionWithBuilding et TakeActionWithBuilding introuvables pour %s"), *Prompt.ActionId);
return;
}
LogParmsOnce(Function);
uint8* Params = static_cast<uint8*>(FMemory_Alloca(Function->ParmsSize));
FMemory::Memzero(Params, Function->ParmsSize);
for (TFieldIterator<FProperty> It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
{
It->InitializeValue_InContainer(Params);
}
if (Detail)
{
ActionParm->CopyCompleteValue(ActionParm->ContainerPtrToValuePtr<void>(Params), Detail);
if (FBoolProperty* Multiple = CastField<FBoolProperty>(FindParm(Function, TEXT("Multiple"))))
{
Multiple->SetPropertyValue_InContainer(Params, false);
}
if (FBoolProperty* Interaction = CastField<FBoolProperty>(FindParm(Function, TEXT("Interaction"))))
{
Interaction->SetPropertyValue_InContainer(Params, Prompt.bPerson);
}
}
else if (FTextProperty* ActionText = CastField<FTextProperty>(FindParm(Function, TEXT("Action"))))
{
ActionText->SetPropertyValue_InContainer(Params, FText::FromString(Prompt.Label));
}
CopyBuilding(GI, Function, Params);
UE_LOG(LogLifePlay, Display, TEXT("Interaction : %s « %s » (%s) vers %s"),
*Function->GetName(), *Prompt.Label, *Prompt.ActionId, *GetNameSafe(Target));
GI->ProcessEvent(Function, Params);
for (TFieldIterator<FProperty> It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
{
It->DestroyValue_InContainer(Params);
}
}
bool ScenePlaying(const UObject* GI)
{
const FStrProperty* Prop = GI ? CastField<FStrProperty>(GI->GetClass()->FindPropertyByName(TEXT("CurrentScene"))) : nullptr;
if (!Prop)
{
return false;
}
const FString Scene = Prop->GetPropertyValue_InContainer(GI).TrimStartAndEnd();
return !Scene.IsEmpty() && !Scene.Equals(TEXT("None"), ESearchCase::IgnoreCase);
}
bool MenuOnScreen(const UGameInstance* GI)
{
if (!GI)
{
return false;
}
if (const ULPKeyboardSubsystem* Keys = GI->GetSubsystem<ULPKeyboardSubsystem>())
{
if (Keys->IsMenuOpen())
{
return true;
}
}
for (TObjectIterator<UUserWidget> It; It; ++It)
{
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 : MenuClasses)
{
if (ClassName == Menu)
{
return true;
}
}
}
if (!FSlateApplication::IsInitialized())
{
return false;
}
TSharedPtr<SWidget> Focused = FSlateApplication::Get().GetKeyboardFocusedWidget();
return Focused.IsValid() && Focused->GetTypeAsString().Contains(TEXT("Editable"));
}
float FlatDistance(const FVector& A, const FVector& B)
{
return FVector::Dist2D(A, B);
}
FString MeshName(const UStaticMeshComponent* Mesh)
{
const UStaticMesh* Asset = Mesh ? Mesh->GetStaticMesh() : nullptr;
return Asset ? Asset->GetName() : FString();
}
void CollectPrompts(const UObject* GI, const FString& Mesh, bool bPerson, TArray<FPrompt>& Out)
{
if (bPerson)
{
for (const FPrompt& Prompt : PersonPrompts())
{
if (ActionLoaded(GI, Prompt.ActionId))
{
Out.Add(Prompt);
}
}
return;
}
int32 Best = 0;
for (const FPrompt& Prompt : MeshPrompts())
{
if (Mesh.StartsWith(Prompt.MeshPrefix, ESearchCase::IgnoreCase))
{
Best = FMath::Max(Best, Prompt.MeshPrefix.Len());
}
}
for (const FPrompt& Prompt : MeshPrompts())
{
if (Prompt.MeshPrefix.Len() == Best && Mesh.StartsWith(Prompt.MeshPrefix, ESearchCase::IgnoreCase) && ActionLoaded(GI, Prompt.ActionId))
{
Out.Add(Prompt);
}
}
}
void PaintSelection(int32 Selected)
{
for (int32 Index = 0; Index < PromptState().Labels.Num(); ++Index)
{
if (PromptState().Labels[Index].IsValid())
{
const bool bOn = Index == Selected;
PromptState().Labels[Index]->SetColorAndOpacity(FSlateColor(bOn ? FLinearColor(1.f, 0.9f, 0.4f) : FLinearColor::White));
}
}
}
}
void ULPDirectControlSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPDirectControlSubsystem::Tick), 0.f);
}
void ULPDirectControlSubsystem::Deinitialize()
{
HidePrompts();
RestoreAnim();
if (FollowView)
{
FollowView->Destroy();
FollowView = nullptr;
}
if (PromptState().Owner.Get() == this)
{
PromptState().Owner = nullptr;
}
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
Super::Deinitialize();
}
bool ULPDirectControlSubsystem::Tick(float DeltaTime)
{
MenuDelay += DeltaTime;
if (MenuDelay >= 0.25f)
{
MenuDelay = 0.f;
bMenuBlocks = MenuOnScreen(GetGameInstance());
}
IndexDelay += DeltaTime;
if (IndexDelay >= 1.f)
{
IndexDelay = 0.f;
RefreshIndex();
}
TargetDelay += DeltaTime;
if (TargetDelay >= 0.5f)
{
TargetDelay = 0.f;
RefreshTarget();
}
if (bMenuBlocks || ScenePlaying(GetGameInstance()))
{
if (!Shown().IsEmpty() || bPromptOpen)
{
Shown().Reset();
CurrentTarget = nullptr;
ShownSignature.Reset();
bPromptOpen = false;
HidePrompts();
}
}
else
{
Walk(DeltaTime);
PollInteract();
}
return true;
}
void ULPDirectControlSubsystem::RefreshIndex()
{
UObject* GI = GetGameInstance();
const FString Signature = ReadEnabledSignature(GI);
if (bIndexed && Signature == LastEnabled)
{
return;
}
bIndexed = true;
LastEnabled = Signature;
PersonPrompts().Reset();
MeshPrompts().Reset();
const FString Root = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir() / TEXT("Modules"));
TArray<FString> Actions;
IFileManager::Get().FindFilesRecursive(Actions, *Root, TEXT("*.lpaction"), true, false);
for (const FString& File : Actions)
{
const FString Module = ModuleOf(File, Root);
if (!ModuleEnabled(GI, Module))
{
continue;
}
FString Text;
TMap<FString, FString> Keys;
if (!FFileHelper::LoadFileToString(Text, *File))
{
continue;
}
ReadKeys(Text, Keys);
if (!WhereHasInteraction(Keys.FindRef(TEXT("WHERE"))))
{
continue;
}
FPrompt Prompt;
Prompt.Label = Keys.FindRef(TEXT("ACTION_NAME"));
Prompt.ActionId = Keys.FindRef(TEXT("ACTION_UNIQUEID"));
Prompt.bPerson = true;
if (!Prompt.Label.IsEmpty() && !Prompt.ActionId.IsEmpty())
{
AddUnique(PersonPrompts(), Prompt);
}
}
TArray<FString> Nears;
IFileManager::Get().FindFilesRecursive(Nears, *Root, TEXT("*.lpnear"), true, false);
for (const FString& File : Nears)
{
if (!ModuleEnabled(GI, ModuleOf(File, Root)))
{
continue;
}
FString Text;
if (!FFileHelper::LoadFileToString(Text, *File))
{
continue;
}
TArray<FString> Lines;
Text.ParseIntoArrayLines(Lines, true);
FPrompt Current;
auto Keep = [&Current]()
{
if (!Current.MeshPrefix.IsEmpty() && !Current.Label.IsEmpty() && !Current.ActionId.IsEmpty())
{
AddUnique(MeshPrompts(), Current);
}
};
for (const FString& Raw : Lines)
{
const FString Line = Raw.TrimStartAndEnd();
FString Key, Value;
if (Line.IsEmpty() || Line.StartsWith(TEXT("//")) || !Line.Split(TEXT(":"), &Key, &Value))
{
continue;
}
Key = Key.TrimStartAndEnd().ToUpper();
Value = Value.TrimStartAndEnd();
if (Key == TEXT("TARGET"))
{
Keep();
Current = FPrompt();
}
else if (Key == TEXT("MESH"))
{
Current.MeshPrefix = Value;
}
else if (Key == TEXT("PROMPT"))
{
Current.Label = Value;
}
else if (Key == TEXT("ACTION"))
{
Current.ActionId = Value;
}
}
Keep();
}
PersonPrompts().Sort([](const FPrompt& A, const FPrompt& B) { return A.Label < B.Label; });
UE_LOG(LogLifePlay, Display, TEXT("Interaction : %d invite(s) personne, %d invite(s) meuble"), PersonPrompts().Num(), MeshPrompts().Num());
}
void ULPDirectControlSubsystem::RefreshTarget()
{
UObject* GI = GetGameInstance();
AActor* Player = DirectActor(GI, TEXT("PlayerCharacter"));
AActor* Room = DirectActor(GI, TEXT("Room"));
Shown().Reset();
CurrentTarget = nullptr;
bCurrentIsPerson = false;
if (!Player || !Room || bMenuBlocks || ScenePlaying(GI))
{
ShownSignature.Reset();
HidePrompts();
return;
}
const FVector Origin = Player->GetActorLocation();
float Best = Reach;
AActor* Person = nullptr;
if (UWorld* World = GetWorld())
{
for (TActorIterator<ACharacter> It(World); It; ++It)
{
ACharacter* Who = *It;
const UCapsuleComponent* Cap = Who ? Who->GetCapsuleComponent() : nullptr;
if (!Who || Who == Player || !Cap || Cap->GetCollisionEnabled() == ECollisionEnabled::NoCollision)
{
continue;
}
const float Distance = FlatDistance(Origin, Who->GetActorLocation());
if (Distance < Best)
{
Best = Distance;
Person = Who;
}
}
}
UStaticMeshComponent* Furniture = nullptr;
float FurnitureDistance = Reach;
TArray<AActor*> Actors;
Actors.Add(Room);
Room->GetAttachedActors(Actors, false, true);
for (AActor* Actor : Actors)
{
if (!Actor)
{
continue;
}
TInlineComponentArray<UStaticMeshComponent*> Meshes(Actor);
for (UStaticMeshComponent* Mesh : Meshes)
{
const FString Name = MeshName(Mesh);
if (Name.IsEmpty())
{
continue;
}
bool bKnown = false;
for (const FPrompt& Prompt : MeshPrompts())
{
bKnown |= Name.StartsWith(Prompt.MeshPrefix, ESearchCase::IgnoreCase);
}
if (!bKnown)
{
continue;
}
const FVector Point = Mesh->Bounds.GetBox().GetClosestPointTo(Origin);
const float Distance = FlatDistance(Origin, Point);
if (Distance < FurnitureDistance)
{
FurnitureDistance = Distance;
Furniture = Mesh;
}
}
}
const bool bPersonWins = Person && (!Furniture || Best <= FurnitureDistance);
if (bPersonWins)
{
CurrentTarget = Person;
bCurrentIsPerson = true;
CollectPrompts(GI, FString(), true, Shown());
}
else if (Furniture)
{
CurrentTarget = Furniture->GetOwner();
CollectPrompts(GI, MeshName(Furniture), false, Shown());
}
if (Shown().IsEmpty())
{
CurrentTarget = nullptr;
ShownSignature.Reset();
if (bPromptOpen)
{
bPromptOpen = false;
HidePrompts();
}
return;
}
FString Signature = GetNameSafe(CurrentTarget.Get());
for (const FPrompt& Prompt : Shown())
{
Signature += TEXT("|") + Prompt.ActionId;
}
if (Signature != ShownSignature)
{
ShownSignature = Signature;
Selected = 0;
if (bPromptOpen)
{
ShowPrompts();
}
}
}
void ULPDirectControlSubsystem::Walk(float DeltaTime)
{
UObject* GI = GetGameInstance();
APawn* Pawn = Cast<APawn>(DirectActor(GI, TEXT("PlayerCharacter")));
UWorld* World = GetWorld();
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
if (!Pawn || !PC)
{
return;
}
ACharacter* Character = Cast<ACharacter>(Pawn);
if (Character)
{
if (UCharacterMovementComponent* Move = Character->GetCharacterMovement())
{
if (!Move->bRunPhysicsWithNoController)
{
Move->bRunPhysicsWithNoController = true;
UE_LOG(LogLifePlay, Display, TEXT("Marche : physique sans controleur, vitesse %.0f, mode %d"), Move->MaxWalkSpeed, static_cast<int32>(Move->MovementMode));
}
if (Move->MaxWalkSpeed < 10.f)
{
Move->MaxWalkSpeed = 180.f;
}
if (Move->MovementMode == MOVE_None)
{
Move->SetMovementMode(MOVE_Walking);
}
if (!Move->bOrientRotationToMovement)
{
Move->bOrientRotationToMovement = true;
Character->bUseControllerRotationYaw = false;
}
}
}
const float Forward = AxisValue(PC, ForwardAxis);
const float Right = AxisValue(PC, RightAxis);
const bool bMoving = !FMath::IsNearlyZero(Forward) || !FMath::IsNearlyZero(Right);
if (bMoving)
{
const FRotator Yaw(0.f, PC->GetControlRotation().Yaw, 0.f);
const FVector ForwardDir = FRotationMatrix(Yaw).GetUnitAxis(EAxis::X);
const FVector RightDir = FRotationMatrix(Yaw).GetUnitAxis(EAxis::Y);
Pawn->AddMovementInput(ForwardDir, Forward);
Pawn->AddMovementInput(RightDir, Right);
}
(void)DeltaTime;
(void)Character;
}
void ULPDirectControlSubsystem::FollowCamera(APawn* Pawn, APlayerController* PC, float DeltaTime, bool bMoving)
{
if (!Pawn || !PC)
{
return;
}
AActor* View = PC->GetViewTarget();
const bool bBird = View && View->GetClass()->GetName().Contains(TEXT("Birdview"));
if (bMoving && bBird)
{
BirdView = View;
if (!FollowView)
{
FActorSpawnParameters Params;
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
Params.ObjectFlags |= RF_Transient;
FollowView = Pawn->GetWorld()->SpawnActor<ACameraActor>(Pawn->GetActorLocation(), PC->GetControlRotation(), Params);
if (UCameraComponent* Camera = FollowView ? FollowView->GetCameraComponent() : nullptr)
{
Camera->SetFieldOfView(70.f);
}
UE_LOG(LogLifePlay, Display, TEXT("Marche : camera troisieme personne derriere le personnage"));
}
if (FollowView)
{
PC->SetViewTargetWithBlend(FollowView, 0.35f);
}
}
if (!FollowView || PC->GetViewTarget() != FollowView)
{
return;
}
const FRotator Control = PC->GetControlRotation();
const FRotator Aim(FMath::Clamp(Control.Pitch, -55.f, -12.f), Control.Yaw, 0.f);
const FVector LookAt = Pawn->GetActorLocation() + FVector(0.f, 0.f, 50.f);
FVector Desired = LookAt - Aim.Vector() * 340.f;
FHitResult Hit;
FCollisionQueryParams Query(SCENE_QUERY_STAT(WalkCamera), false, Pawn);
if (Pawn->GetWorld()->SweepSingleByChannel(Hit, LookAt, Desired, FQuat::Identity, ECC_Camera, FCollisionShape::MakeSphere(12.f), Query))
{
Desired = Hit.Location + Hit.Normal * 12.f;
}
const FVector Next = FMath::VInterpTo(FollowView->GetActorLocation(), Desired, DeltaTime, 8.f);
FollowView->SetActorLocationAndRotation(Next, (LookAt - Next).Rotation());
}
void ULPDirectControlSubsystem::WalkAnim(ACharacter* Character, float Speed)
{
USkeletalMeshComponent* Mesh = Character ? Character->GetMesh() : nullptr;
if (!Mesh)
{
return;
}
if (Speed < 20.f)
{
RestoreAnim();
return;
}
bool bMale = false;
if (const FBoolProperty* Male = CastField<FBoolProperty>(Character->GetClass()->FindPropertyByName(TEXT("IsMale"))))
{
bMale = Male->GetPropertyValue_InContainer(Character);
}
const TCHAR* Path = bMale
? TEXT("/Game/Heavy/New2/Anims2/walk_M.walk_M")
: TEXT("/Game/Heavy/New2/Anims2/walk_F.walk_F");
UAnimationAsset* WalkCycle = LoadObject<UAnimationAsset>(nullptr, Path);
if (!WalkCycle)
{
return;
}
if (!bAnimCaptured)
{
SavedAnimMode = static_cast<uint8>(Mesh->GetAnimationMode());
SavedAnimClass = Mesh->GetAnimClass();
SavedSingleAnim = Mesh->AnimationData.AnimToPlay;
bAnimCaptured = true;
}
if (Mesh->GetAnimationMode() != EAnimationMode::AnimationSingleNode || Mesh->AnimationData.AnimToPlay != WalkCycle)
{
Mesh->PlayAnimation(WalkCycle, true);
UE_LOG(LogLifePlay, Display, TEXT("Marche : animation %s"), *WalkCycle->GetName());
}
Mesh->SetPlayRate(FMath::Clamp(Speed / 160.f, 0.75f, 1.5f));
}
void ULPDirectControlSubsystem::RestoreAnim()
{
if (!bAnimCaptured)
{
return;
}
bAnimCaptured = false;
ACharacter* Character = Cast<ACharacter>(DirectActor(GetGameInstance(), TEXT("PlayerCharacter")));
USkeletalMeshComponent* Mesh = Character ? Character->GetMesh() : nullptr;
if (!Mesh)
{
return;
}
const EAnimationMode::Type Mode = static_cast<EAnimationMode::Type>(SavedAnimMode);
if (Mode == EAnimationMode::AnimationBlueprint && SavedAnimClass)
{
Mesh->SetAnimInstanceClass(SavedAnimClass);
Mesh->SetAnimationMode(EAnimationMode::AnimationBlueprint);
}
else if (SavedSingleAnim)
{
Mesh->PlayAnimation(SavedSingleAnim, true);
}
else
{
Mesh->SetAnimationMode(Mode);
}
Mesh->SetPlayRate(1.f);
}
void ULPDirectControlSubsystem::PollInteract()
{
UWorld* World = GetWorld();
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
if (!PC || !InteractPressed(PC) || Shown().IsEmpty())
{
return;
}
if (!bPromptOpen)
{
bPromptOpen = true;
ShowPrompts();
return;
}
ActivatePrompt(Selected);
}
void ULPDirectControlSubsystem::HighlightPrompt(int32 Index)
{
if (Shown().IsValidIndex(Index))
{
Selected = Index;
PaintSelection(Selected);
}
}
void ULPDirectControlSubsystem::ActivatePrompt(int32 Index)
{
if (!Shown().IsValidIndex(Index) || FPlatformTime::Seconds() - PromptState().LastFire < 0.4)
{
return;
}
PromptState().LastFire = FPlatformTime::Seconds();
Selected = Index;
PaintSelection(Selected);
CallAction(GetGameInstance(), Shown()[Index], CurrentTarget.Get());
}
void ULPDirectControlSubsystem::ShowPrompts()
{
if (!GEngine || !GEngine->GameViewport || !FSlateApplication::IsInitialized())
{
return;
}
PromptState().Owner = this;
if (!PromptState().Root.IsValid())
{
TSharedPtr<SVerticalBox> Rows;
TSharedRef<SWidget> Root = SNew(SOverlay)
.Visibility(EVisibility::SelfHitTestInvisible)
+ SOverlay::Slot().HAlign(HAlign_Center).VAlign(VAlign_Bottom).Padding(FMargin(0.f, 0.f, 0.f, 72.f))
[
SNew(SBorder)
.Padding(FMargin(10.f))
.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
[
SAssignNew(Rows, SVerticalBox)
]
];
PromptState().Rows = Rows;
PromptState().Root = Root;
GEngine->GameViewport->AddViewportWidgetContent(Root, 50);
}
PromptState().Rows->ClearChildren();
PromptState().Labels.Reset();
for (int32 Index = 0; Index < Shown().Num(); ++Index)
{
const FString Line = FString::Printf(TEXT("E %s"), *Shown()[Index].Label);
TSharedPtr<STextBlock> Label;
PromptState().Rows->AddSlot().AutoHeight().Padding(0.f, 2.f)
[
SNew(SButton)
.IsFocusable(false)
.OnHovered_Lambda([Index]()
{
if (ULPDirectControlSubsystem* Sub = PromptState().Owner.Get())
{
Sub->HighlightPrompt(Index);
}
})
.OnClicked_Lambda([Index]()
{
if (ULPDirectControlSubsystem* Sub = PromptState().Owner.Get())
{
Sub->ActivatePrompt(Index);
}
return FReply::Handled();
})
[
SAssignNew(Label, STextBlock)
.Text(FText::FromString(Line))
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 16))
]
];
PromptState().Labels.Add(Label);
}
Selected = FMath::Clamp(Selected, 0, Shown().Num() - 1);
PaintSelection(Selected);
}
void ULPDirectControlSubsystem::HidePrompts()
{
if (PromptState().Root.IsValid() && GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->RemoveViewportWidgetContent(PromptState().Root.ToSharedRef());
}
PromptState().Root.Reset();
PromptState().Rows.Reset();
PromptState().Labels.Reset();
}