// 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 Owner; TSharedPtr Root; TSharedPtr Rows; TArray> Labels; double LastFire = 0.0; }; FPromptLive& PromptState() { static FPromptLive State; return State; } TArray& PersonPrompts() { static TArray Prompts; return Prompts; } TArray& MeshPrompts() { static TArray Prompts; return Prompts; } TArray& Shown() { static TArray Prompts; return Prompts; } const FProperty* FindAuthored(const UStruct* Struct, const TCHAR* Name) { if (!Struct) { return nullptr; } for (TFieldIterator 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(Data), nullptr, nullptr, PPF_None); } return Out.TrimQuotes(); } void SetString(UObject* Object, const TCHAR* Name, const FString& Value) { if (FStrProperty* Prop = Object ? CastField(Object->GetClass()->FindPropertyByName(Name)) : nullptr) { Prop->SetPropertyValue_InContainer(Object, Value); } } void SetObject(UObject* Object, const TCHAR* Name, UObject* Value) { if (FObjectPropertyBase* Prop = Object ? CastField(Object->GetClass()->FindPropertyByName(Name)) : nullptr) { Prop->SetObjectPropertyValue_InContainer(Object, Value); } } AActor* DirectActor(const UObject* Object, const TCHAR* Name) { const FObjectPropertyBase* Prop = Object ? CastField(Object->GetClass()->FindPropertyByName(Name)) : nullptr; return Prop ? Cast(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(GI->GetClass()->FindPropertyByName(TEXT("ModEnableds"))) : nullptr; const FStructProperty* Inner = Prop ? CastField(Prop->Inner) : nullptr; Struct = Inner ? Inner->Struct : nullptr; if (!Prop || !Struct) { return FString(); } FString Signature; FScriptArrayHelper Helper(Prop, Prop->ContainerPtrToValuePtr(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(GI->GetClass()->FindPropertyByName(TEXT("ModEnableds"))) : nullptr; const FStructProperty* Inner = Prop ? CastField(Prop->Inner) : nullptr; const UScriptStruct* Struct = Inner ? Inner->Struct : nullptr; if (!Prop || !Struct) { return true; } FScriptArrayHelper Helper(Prop, Prop->ContainerPtrToValuePtr(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& Out) { TArray 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 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& 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(GI->GetClass()->FindPropertyByName(TEXT("ActionDetail"))) : nullptr; const FStructProperty* Inner = Prop ? CastField(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(GI)); for (int32 Index = 0; Index < Helper.Num(); ++Index) { FString Id; IdField->ExportTextItem_Direct(Id, IdField->ContainerPtrToValuePtr(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(GI->GetClass()->FindPropertyByName(TEXT("ActionDetail"))) : nullptr; const FStructProperty* Inner = Prop ? CastField(Prop->Inner) : nullptr; if (!Inner) { return true; } FScriptArrayHelper Helper(Prop, Prop->ContainerPtrToValuePtr(GI)); return Helper.Num() == 0; } void LogParmsOnce(const UFunction* Function) { static TSet Logged; if (!Function || Logged.Contains(Function->GetFName())) { return; } Logged.Add(Function->GetFName()); for (TFieldIterator 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 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(FindParm(Function, TEXT("Building"))); const UObject* Street = DirectActor(GI, TEXT("StreetMapStatic")); const FStructProperty* Source = Street ? CastField(Street->GetClass()->FindPropertyByName(TEXT("CurrentPlayerBuilding"))) : nullptr; if (!Dest || !Source || Dest->Struct != Source->Struct) { return; } Dest->CopyCompleteValue(Dest->ContainerPtrToValuePtr(Params), Source->ContainerPtrToValuePtr(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(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(FMemory_Alloca(Function->ParmsSize)); FMemory::Memzero(Params, Function->ParmsSize); for (TFieldIterator It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It) { It->InitializeValue_InContainer(Params); } if (Detail) { ActionParm->CopyCompleteValue(ActionParm->ContainerPtrToValuePtr(Params), Detail); if (FBoolProperty* Multiple = CastField(FindParm(Function, TEXT("Multiple")))) { Multiple->SetPropertyValue_InContainer(Params, false); } if (FBoolProperty* Interaction = CastField(FindParm(Function, TEXT("Interaction")))) { Interaction->SetPropertyValue_InContainer(Params, Prompt.bPerson); } } else if (FTextProperty* ActionText = CastField(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 It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It) { It->DestroyValue_InContainer(Params); } } bool ScenePlaying(const UObject* GI) { const FStrProperty* Prop = GI ? CastField(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()) { if (Keys->IsMenuOpen()) { return true; } } for (TObjectIterator 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 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& 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 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 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 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 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 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 Actors; Actors.Add(Room); Room->GetAttachedActors(Actors, false, true); for (AActor* Actor : Actors) { if (!Actor) { continue; } TInlineComponentArray 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(DirectActor(GI, TEXT("PlayerCharacter"))); UWorld* World = GetWorld(); APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr; if (!Pawn || !PC) { return; } ACharacter* Character = Cast(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(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(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(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(nullptr, Path); if (!WalkCycle) { return; } if (!bAnimCaptured) { SavedAnimMode = static_cast(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(DirectActor(GetGameInstance(), TEXT("PlayerCharacter"))); USkeletalMeshComponent* Mesh = Character ? Character->GetMesh() : nullptr; if (!Mesh) { return; } const EAnimationMode::Type Mode = static_cast(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 Rows; TSharedRef 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 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(); }