added session loss sequence, added energy and stamina to HUD, added home location trigger

This commit is contained in:
2026-06-04 18:59:04 +03:00
parent f2fcd42edf
commit 034694695a
26 changed files with 404 additions and 24 deletions
@@ -3,12 +3,24 @@
#include "SessionLossResolver.h"
#include "DefaultLevelSequenceInstanceData.h"
#include "LevelSequence.h"
#include "LevelSequenceActor.h"
#include "LevelSequencePlayer.h"
#include "LossPresentationConfig.h"
#include "MovieSceneSequencePlaybackSettings.h"
#include "NakedDesireGameInstance.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerStart.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Interactables/ItemPickup.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/SaveSubsystem.h"
const FName USessionLossResolver::HomePlayerStartTag = FName(TEXT("Home"));
void USessionLossResolver::OnWorldBeginPlay(UWorld& InWorld)
{
Super::OnWorldBeginPlay(InWorld);
@@ -74,6 +86,9 @@ void USessionLossResolver::ResolveLoss(ESessionLossCause Cause)
Autosave();
OnSessionLossResolved.Broadcast(Cause, bWentToHoldingCell);
// Presentation: play the cause's cutscene, then teleport the player home on finish.
BeginLossPresentation(Cause);
}
void USessionLossResolver::ResolveSleepLoss()
@@ -117,6 +132,122 @@ void USessionLossResolver::ClearWanted()
// TODO(§7.7 / Phase 6): clear the `wanted` tag once the Wanted attribute exists.
}
void USessionLossResolver::BeginLossPresentation(ESessionLossCause Cause)
{
// SafeReturn = the player walked home under their own power; nothing to present.
if (Cause == ESessionLossCause::SafeReturn)
return;
ULevelSequence* Sequence = nullptr;
if (const ULossPresentationConfig* Config = GetPresentationConfig())
{
if (const TSoftObjectPtr<ULevelSequence>* Found = Config->LossCutscenes.Find(Cause))
{
// Synchronous load is acceptable here: the loss has already resolved and the
// player is stationary, so the brief hitch is hidden by the transition.
Sequence = Found->LoadSynchronous();
}
}
if (!Sequence)
{
// No cutscene authored for this cause — go straight home.
TeleportPlayerHome();
return;
}
// Lock the player out of movement / look while the cutscene owns the view; state is
// restored when the sequence finishes (just before we teleport home).
FMovieSceneSequencePlaybackSettings Settings;
Settings.bDisableMovementInput = true;
Settings.bDisableLookAtInput = true;
// Restore track state on finish so the view/player return to normal before we teleport.
Settings.FinishCompletionStateOverride = EMovieSceneCompletionModeOverride::ForceRestoreState;
ALevelSequenceActor* SequenceActor = nullptr;
ActiveLossSequencePlayer = ULevelSequencePlayer::CreateLevelSequencePlayer(this, Sequence, Settings, SequenceActor);
ActiveLossSequenceActor = SequenceActor;
ActiveLossSequenceActor->bOverrideInstanceData = true;
UDefaultLevelSequenceInstanceData* InstanceData = Cast<UDefaultLevelSequenceInstanceData>(ActiveLossSequenceActor->DefaultInstanceData.Get());
InstanceData->TransformOriginActor = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
if (!ActiveLossSequencePlayer)
{
// Playback couldn't be created — don't strand the player outside.
TeleportPlayerHome();
return;
}
ActiveLossSequencePlayer->OnFinished.AddDynamic(this, &USessionLossResolver::HandleLossCutsceneFinished);
ActiveLossSequencePlayer->Play();
}
void USessionLossResolver::HandleLossCutsceneFinished()
{
if (ActiveLossSequencePlayer)
{
ActiveLossSequencePlayer->OnFinished.RemoveDynamic(this, &USessionLossResolver::HandleLossCutsceneFinished);
}
TeleportPlayerHome();
// Tear down the spawned sequence player/actor now that the cutscene is done.
if (ActiveLossSequenceActor)
{
ActiveLossSequenceActor->Destroy();
}
ActiveLossSequenceActor = nullptr;
ActiveLossSequencePlayer = nullptr;
}
void USessionLossResolver::TeleportPlayerHome()
{
UWorld* World = GetWorld();
if (!World)
return;
APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(World, 0);
if (!PlayerPawn)
return;
// Prefer a PlayerStart tagged "Home"; fall back to the first one in the level.
TArray<AActor*> Starts;
UGameplayStatics::GetAllActorsOfClass(World, APlayerStart::StaticClass(), Starts);
APlayerStart* Home = nullptr;
for (AActor* Actor : Starts)
{
APlayerStart* Start = Cast<APlayerStart>(Actor);
if (!Start)
continue;
if (!Home)
Home = Start;
if (Start->PlayerStartTag == HomePlayerStartTag)
{
Home = Start;
break;
}
}
if (!Home)
{
UE_LOG(LogTemp, Warning, TEXT("USessionLossResolver: no APlayerStart found; cannot teleport player home."));
return;
}
PlayerPawn->TeleportTo(Home->GetActorLocation(), Home->GetActorRotation());
// Kill residual velocity so the character doesn't slide on arrival.
if (const ACharacter* Character = Cast<ACharacter>(PlayerPawn))
{
if (UCharacterMovementComponent* Movement = Character->GetCharacterMovement())
{
Movement->StopMovementImmediately();
}
}
}
void USessionLossResolver::Autosave() const
{
if (const UGameInstance* GameInstance = GetWorld()->GetGameInstance())
@@ -138,4 +269,11 @@ UGlobalSaveGameData* USessionLossResolver::GetSave() const
}
}
return nullptr;
}
ULossPresentationConfig* USessionLossResolver::GetPresentationConfig() const
{
const UNakedDesireGameInstance* GameInstance =
Cast<UNakedDesireGameInstance>(GetWorld() ? GetWorld()->GetGameInstance() : nullptr);
return GameInstance ? GameInstance->LossPresentation : nullptr;
}