Compare commits
26 Commits
main
...
ability-sy
Author | SHA1 | Date | |
---|---|---|---|
57b53b9c0c | |||
c0b033fb22 | |||
2c4a71b343 | |||
51306c57a9 | |||
e0545d6323 | |||
6c053954c8 | |||
8917782a44 | |||
7a9b1b82b2 | |||
0332ef5789 | |||
fe68d8be31 | |||
57be5728b7 | |||
1c148494ff | |||
bac7ff7498 | |||
fe8ee2e867 | |||
a43fc18456 | |||
b9b7e5344b | |||
7415b74e8f | |||
313cf68f61 | |||
c1fd9b2c95 | |||
01eda48d38 | |||
2a6162c728 | |||
49843cdecf | |||
38ae567d20 | |||
0eb6d52ca8 | |||
6814732c4b | |||
799cfa4303 |
29
Plugins/CommonLoadingScreen/CommonLoadingScreen.uplugin
Normal file
29
Plugins/CommonLoadingScreen/CommonLoadingScreen.uplugin
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0",
|
||||
"FriendlyName": "CommonLoadingScreen",
|
||||
"Description": "Loading screen manager handling creation and display of a project-specified loading screen widget",
|
||||
"Category": "Gameplay",
|
||||
"CreatedBy": "Epic Games, Inc.",
|
||||
"CreatedByURL": "https://www.epicgames.com",
|
||||
"DocsURL": "",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "",
|
||||
"CanContainContent": false,
|
||||
"IsBetaVersion": false,
|
||||
"IsExperimentalVersion": false,
|
||||
"Installed": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "CommonLoadingScreen",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
},
|
||||
{
|
||||
"Name": "CommonStartupLoadingScreen",
|
||||
"Type": "ClientOnly",
|
||||
"LoadingPhase": "PreLoadingScreen"
|
||||
}
|
||||
]
|
||||
}
|
BIN
Plugins/CommonLoadingScreen/Resources/Icon128.png
Normal file
BIN
Plugins/CommonLoadingScreen/Resources/Icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,57 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class CommonLoadingScreen : ModuleRules
|
||||
{
|
||||
public CommonLoadingScreen(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"InputCore",
|
||||
"PreLoadScreen",
|
||||
"RenderCore",
|
||||
"DeveloperSettings",
|
||||
"UMG"
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
IMPLEMENT_MODULE(FDefaultModuleImpl, CommonLoadingScreen)
|
@ -0,0 +1,12 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "CommonLoadingScreenSettings.h"
|
||||
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonLoadingScreenSettings)
|
||||
|
||||
UCommonLoadingScreenSettings::UCommonLoadingScreenSettings()
|
||||
{
|
||||
CategoryName = TEXT("Game");
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/DeveloperSettingsBackedByCVars.h"
|
||||
#include "UObject/SoftObjectPath.h"
|
||||
|
||||
#include "CommonLoadingScreenSettings.generated.h"
|
||||
|
||||
class UObject;
|
||||
|
||||
/**
|
||||
* Settings for a loading screen system.
|
||||
*/
|
||||
UCLASS(config=Game, defaultconfig, meta=(DisplayName="Common Loading Screen"))
|
||||
class UCommonLoadingScreenSettings : public UDeveloperSettingsBackedByCVars
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UCommonLoadingScreenSettings();
|
||||
|
||||
public:
|
||||
|
||||
// The widget to load for the loading screen.
|
||||
UPROPERTY(config, EditAnywhere, Category=Display, meta=(MetaClass="/Script/UMG.UserWidget"))
|
||||
FSoftClassPath LoadingScreenWidget;
|
||||
|
||||
// The z-order of the loading screen widget in the viewport stack
|
||||
UPROPERTY(config, EditAnywhere, Category=Display)
|
||||
int32 LoadingScreenZOrder = 10000;
|
||||
|
||||
// How long to hold the loading screen up after other loading finishes (in seconds) to
|
||||
// try to give texture streaming a chance to avoid blurriness
|
||||
//
|
||||
// Note: This is not normally applied in the editor for iteration time, but can be
|
||||
// enabled via HoldLoadingScreenAdditionalSecsEvenInEditor
|
||||
UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s, ConsoleVariable="CommonLoadingScreen.HoldLoadingScreenAdditionalSecs"))
|
||||
float HoldLoadingScreenAdditionalSecs = 2.0f;
|
||||
|
||||
// The interval in seconds beyond which the loading screen is considered permanently hung (if non-zero).
|
||||
UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s))
|
||||
float LoadingScreenHeartbeatHangDuration = 0.0f;
|
||||
|
||||
// The interval in seconds between each log of what is keeping a loading screen up (if non-zero).
|
||||
UPROPERTY(config, EditAnywhere, Category=Configuration, meta=(ForceUnits=s))
|
||||
float LogLoadingScreenHeartbeatInterval = 5.0f;
|
||||
|
||||
// When true, the reason the loading screen is shown or hidden will be printed to the log every frame.
|
||||
UPROPERTY(Transient, EditAnywhere, Category=Debugging, meta=(ConsoleVariable="CommonLoadingScreen.LogLoadingScreenReasonEveryFrame"))
|
||||
bool LogLoadingScreenReasonEveryFrame = 0;
|
||||
|
||||
// Force the loading screen to be displayed (useful for debugging)
|
||||
UPROPERTY(Transient, EditAnywhere, Category=Debugging, meta=(ConsoleVariable="CommonLoadingScreen.AlwaysShow"))
|
||||
bool ForceLoadingScreenVisible = false;
|
||||
|
||||
// Should we apply the additional HoldLoadingScreenAdditionalSecs delay even in the editor
|
||||
// (useful when iterating on loading screens)
|
||||
UPROPERTY(Transient, EditAnywhere, Category=Debugging)
|
||||
bool HoldLoadingScreenAdditionalSecsEvenInEditor = false;
|
||||
|
||||
// Should we apply the additional HoldLoadingScreenAdditionalSecs delay even in the editor
|
||||
// (useful when iterating on loading screens)
|
||||
UPROPERTY(config, EditAnywhere, Category=Configuration)
|
||||
bool ForceTickLoadingScreenEvenInEditor = true;
|
||||
};
|
||||
|
@ -0,0 +1,647 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "LoadingScreenManager.h"
|
||||
|
||||
#include "HAL/ThreadHeartBeat.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "GameFramework/GameStateBase.h"
|
||||
#include "GameFramework/WorldSettings.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
|
||||
#include "LoadingProcessInterface.h"
|
||||
|
||||
#include "Framework/Application/IInputProcessor.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
|
||||
#include "PreLoadScreen.h"
|
||||
#include "PreLoadScreenManager.h"
|
||||
|
||||
#include "ShaderPipelineCache.h"
|
||||
#include "CommonLoadingScreenSettings.h"
|
||||
|
||||
//@TODO: Used as the placeholder widget in error cases, should probably create a wrapper that at least centers it/etc...
|
||||
#include "Widgets/Images/SThrobber.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(LoadingScreenManager)
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogLoadingScreen, Log, All);
|
||||
DEFINE_LOG_CATEGORY(LogLoadingScreen);
|
||||
|
||||
//@TODO: Why can GetLocalPlayers() have nullptr entries? Can it really?
|
||||
//@TODO: Test with PIE mode set to simulate and decide how much (if any) loading screen action should occur
|
||||
//@TODO: Allow other things implementing ILoadingProcessInterface besides GameState/PlayerController (and owned components) to register as interested parties
|
||||
//@TODO: ChangeMusicSettings (either here or using the LoadingScreenVisibilityChanged delegate)
|
||||
//@TODO: Studio analytics (FireEvent_PIEFinishedLoading / tracking PIE startup time for regressions, either here or using the LoadingScreenVisibilityChanged delegate)
|
||||
|
||||
// Profiling category for loading screens
|
||||
CSV_DEFINE_CATEGORY(LoadingScreen, true);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool ILoadingProcessInterface::ShouldShowLoadingScreen(UObject* TestObject, FString& OutReason)
|
||||
{
|
||||
if (TestObject != nullptr)
|
||||
{
|
||||
if (ILoadingProcessInterface* LoadObserver = Cast<ILoadingProcessInterface>(TestObject))
|
||||
{
|
||||
FString ObserverReason;
|
||||
if (LoadObserver->ShouldShowLoadingScreen(/*out*/ ObserverReason))
|
||||
{
|
||||
if (ensureMsgf(!ObserverReason.IsEmpty(), TEXT("%s failed to set a reason why it wants to show the loading screen"), *GetPathNameSafe(TestObject)))
|
||||
{
|
||||
OutReason = ObserverReason;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace LoadingScreenCVars
|
||||
{
|
||||
// CVars
|
||||
static float HoldLoadingScreenAdditionalSecs = 2.0f;
|
||||
static FAutoConsoleVariableRef CVarHoldLoadingScreenUpAtLeastThisLongInSecs(
|
||||
TEXT("CommonLoadingScreen.HoldLoadingScreenAdditionalSecs"),
|
||||
HoldLoadingScreenAdditionalSecs,
|
||||
TEXT("How long to hold the loading screen up after other loading finishes (in seconds) to try to give texture streaming a chance to avoid blurriness"),
|
||||
ECVF_Default | ECVF_Preview);
|
||||
|
||||
static bool LogLoadingScreenReasonEveryFrame = false;
|
||||
static FAutoConsoleVariableRef CVarLogLoadingScreenReasonEveryFrame(
|
||||
TEXT("CommonLoadingScreen.LogLoadingScreenReasonEveryFrame"),
|
||||
LogLoadingScreenReasonEveryFrame,
|
||||
TEXT("When true, the reason the loading screen is shown or hidden will be printed to the log every frame."),
|
||||
ECVF_Default);
|
||||
|
||||
static bool ForceLoadingScreenVisible = false;
|
||||
static FAutoConsoleVariableRef CVarForceLoadingScreenVisible(
|
||||
TEXT("CommonLoadingScreen.AlwaysShow"),
|
||||
ForceLoadingScreenVisible,
|
||||
TEXT("Force the loading screen to show."),
|
||||
ECVF_Default);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// FLoadingScreenInputPreProcessor
|
||||
|
||||
// Input processor to throw in when loading screen is shown
|
||||
// This will capture any inputs, so active menus under the loading screen will not interact
|
||||
class FLoadingScreenInputPreProcessor : public IInputProcessor
|
||||
{
|
||||
public:
|
||||
FLoadingScreenInputPreProcessor() { }
|
||||
virtual ~FLoadingScreenInputPreProcessor() { }
|
||||
|
||||
bool CanEatInput() const
|
||||
{
|
||||
return !GIsEditor;
|
||||
}
|
||||
|
||||
//~IInputProcess interface
|
||||
virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) override { }
|
||||
|
||||
virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return CanEatInput(); }
|
||||
virtual bool HandleKeyUpEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { return CanEatInput(); }
|
||||
virtual bool HandleAnalogInputEvent(FSlateApplication& SlateApp, const FAnalogInputEvent& InAnalogInputEvent) override { return CanEatInput(); }
|
||||
virtual bool HandleMouseMoveEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
|
||||
virtual bool HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
|
||||
virtual bool HandleMouseButtonUpEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
|
||||
virtual bool HandleMouseButtonDoubleClickEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override { return CanEatInput(); }
|
||||
virtual bool HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent) override { return CanEatInput(); }
|
||||
virtual bool HandleMotionDetectedEvent(FSlateApplication& SlateApp, const FMotionEvent& MotionEvent) override { return CanEatInput(); }
|
||||
//~End of IInputProcess interface
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// ULoadingScreenManager
|
||||
|
||||
void ULoadingScreenManager::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
FCoreUObjectDelegates::PreLoadMapWithContext.AddUObject(this, &ThisClass::HandlePreLoadMap);
|
||||
FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &ThisClass::HandlePostLoadMap);
|
||||
|
||||
const UGameInstance* LocalGameInstance = GetGameInstance();
|
||||
check(LocalGameInstance);
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::Deinitialize()
|
||||
{
|
||||
StopBlockingInput();
|
||||
|
||||
RemoveWidgetFromViewport();
|
||||
|
||||
FCoreUObjectDelegates::PreLoadMap.RemoveAll(this);
|
||||
FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this);
|
||||
|
||||
// We are done, so do not attempt to tick us again
|
||||
SetTickableTickType(ETickableTickType::Never);
|
||||
}
|
||||
|
||||
bool ULoadingScreenManager::ShouldCreateSubsystem(UObject* Outer) const
|
||||
{
|
||||
// Only clients have loading screens
|
||||
const UGameInstance* GameInstance = CastChecked<UGameInstance>(Outer);
|
||||
const bool bIsServerWorld = GameInstance->IsDedicatedServerInstance();
|
||||
return !bIsServerWorld;
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::Tick(float DeltaTime)
|
||||
{
|
||||
UpdateLoadingScreen();
|
||||
|
||||
TimeUntilNextLogHeartbeatSeconds = FMath::Max(TimeUntilNextLogHeartbeatSeconds - DeltaTime, 0.0);
|
||||
}
|
||||
|
||||
ETickableTickType ULoadingScreenManager::GetTickableTickType() const
|
||||
{
|
||||
if (IsTemplate())
|
||||
{
|
||||
return ETickableTickType::Never;
|
||||
}
|
||||
return ETickableTickType::Conditional;
|
||||
}
|
||||
|
||||
bool ULoadingScreenManager::IsTickable() const
|
||||
{
|
||||
// Don't tick if we don't have a game viewport client, this catches cases that ShouldCreateSubsystem does not
|
||||
UGameInstance* GameInstance = GetGameInstance();
|
||||
return (GameInstance && GameInstance->GetGameViewportClient());
|
||||
}
|
||||
|
||||
TStatId ULoadingScreenManager::GetStatId() const
|
||||
{
|
||||
RETURN_QUICK_DECLARE_CYCLE_STAT(ULoadingScreenManager, STATGROUP_Tickables);
|
||||
}
|
||||
|
||||
UWorld* ULoadingScreenManager::GetTickableGameObjectWorld() const
|
||||
{
|
||||
return GetGameInstance()->GetWorld();
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::RegisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface)
|
||||
{
|
||||
ExternalLoadingProcessors.Add(Interface.GetObject());
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::UnregisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface)
|
||||
{
|
||||
ExternalLoadingProcessors.Remove(Interface.GetObject());
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::HandlePreLoadMap(const FWorldContext& WorldContext, const FString& MapName)
|
||||
{
|
||||
if (WorldContext.OwningGameInstance == GetGameInstance())
|
||||
{
|
||||
bCurrentlyInLoadMap = true;
|
||||
|
||||
// Update the loading screen immediately if the engine is initialized
|
||||
if (GEngine->IsInitialized())
|
||||
{
|
||||
UpdateLoadingScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::HandlePostLoadMap(UWorld* World)
|
||||
{
|
||||
if ((World != nullptr) && (World->GetGameInstance() == GetGameInstance()))
|
||||
{
|
||||
bCurrentlyInLoadMap = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::UpdateLoadingScreen()
|
||||
{
|
||||
bool bLogLoadingScreenStatus = LoadingScreenCVars::LogLoadingScreenReasonEveryFrame;
|
||||
|
||||
if (ShouldShowLoadingScreen())
|
||||
{
|
||||
const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();
|
||||
|
||||
// If we don't make it to the specified checkpoint in the given time will trigger the hang detector so we can better determine where progress stalled.
|
||||
FThreadHeartBeat::Get().MonitorCheckpointStart(GetFName(), Settings->LoadingScreenHeartbeatHangDuration);
|
||||
|
||||
ShowLoadingScreen();
|
||||
|
||||
if ((Settings->LogLoadingScreenHeartbeatInterval > 0.0f) && (TimeUntilNextLogHeartbeatSeconds <= 0.0))
|
||||
{
|
||||
bLogLoadingScreenStatus = true;
|
||||
TimeUntilNextLogHeartbeatSeconds = Settings->LogLoadingScreenHeartbeatInterval;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HideLoadingScreen();
|
||||
|
||||
FThreadHeartBeat::Get().MonitorCheckpointEnd(GetFName());
|
||||
}
|
||||
|
||||
if (bLogLoadingScreenStatus)
|
||||
{
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("Loading screen showing: %d. Reason: %s"), bCurrentlyShowingLoadingScreen ? 1 : 0, *DebugReasonForShowingOrHidingLoadingScreen);
|
||||
}
|
||||
}
|
||||
|
||||
bool ULoadingScreenManager::CheckForAnyNeedToShowLoadingScreen()
|
||||
{
|
||||
// Start out with 'unknown' reason in case someone forgets to put a reason when changing this in the future.
|
||||
DebugReasonForShowingOrHidingLoadingScreen = TEXT("Reason for Showing/Hiding LoadingScreen is unknown!");
|
||||
|
||||
const UGameInstance* LocalGameInstance = GetGameInstance();
|
||||
|
||||
if (LoadingScreenCVars::ForceLoadingScreenVisible)
|
||||
{
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommonLoadingScreen.AlwaysShow is true"));
|
||||
return true;
|
||||
}
|
||||
|
||||
const FWorldContext* Context = LocalGameInstance->GetWorldContext();
|
||||
if (Context == nullptr)
|
||||
{
|
||||
// We don't have a world context right now... better show a loading screen
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("The game instance has a null WorldContext"));
|
||||
return true;
|
||||
}
|
||||
|
||||
UWorld* World = Context->World();
|
||||
if (World == nullptr)
|
||||
{
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have no world (FWorldContext's World() is null)"));
|
||||
return true;
|
||||
}
|
||||
|
||||
AGameStateBase* GameState = World->GetGameState<AGameStateBase>();
|
||||
if (GameState == nullptr)
|
||||
{
|
||||
// The game state has not yet replicated.
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("GameState hasn't yet replicated (it's null)"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bCurrentlyInLoadMap)
|
||||
{
|
||||
// Show a loading screen if we are in LoadMap
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("bCurrentlyInLoadMap is true"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Context->TravelURL.IsEmpty())
|
||||
{
|
||||
// Show a loading screen when pending travel
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We have pending travel (the TravelURL is not empty)"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Context->PendingNetGame != nullptr)
|
||||
{
|
||||
// Connecting to another server
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are connecting to another server (PendingNetGame != nullptr)"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!World->HasBegunPlay())
|
||||
{
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("World hasn't begun play"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (World->IsInSeamlessTravel())
|
||||
{
|
||||
// Show a loading screen during seamless travel
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("We are in seamless travel"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ask the game state if it needs a loading screen
|
||||
if (ILoadingProcessInterface::ShouldShowLoadingScreen(GameState, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ask any game state components if they need a loading screen
|
||||
for (UActorComponent* TestComponent : GameState->GetComponents())
|
||||
{
|
||||
if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ask any of the external loading processors that may have been registered. These might be actors or components
|
||||
// that were registered by game code to tell us to keep the loading screen up while perhaps something finishes
|
||||
// streaming in.
|
||||
for (const TWeakInterfacePtr<ILoadingProcessInterface>& Processor : ExternalLoadingProcessors)
|
||||
{
|
||||
if (ILoadingProcessInterface::ShouldShowLoadingScreen(Processor.GetObject(), /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check each local player
|
||||
bool bFoundAnyLocalPC = false;
|
||||
bool bMissingAnyLocalPC = false;
|
||||
|
||||
for (ULocalPlayer* LP : LocalGameInstance->GetLocalPlayers())
|
||||
{
|
||||
if (LP != nullptr)
|
||||
{
|
||||
if (APlayerController* PC = LP->PlayerController)
|
||||
{
|
||||
bFoundAnyLocalPC = true;
|
||||
|
||||
// Ask the PC itself if it needs a loading screen
|
||||
if (ILoadingProcessInterface::ShouldShowLoadingScreen(PC, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ask any PC components if they need a loading screen
|
||||
for (UActorComponent* TestComponent : PC->GetComponents())
|
||||
{
|
||||
if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bMissingAnyLocalPC = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient();
|
||||
const bool bIsInSplitscreen = GameViewportClient->GetCurrentSplitscreenConfiguration() != ESplitScreenType::None;
|
||||
|
||||
// In splitscreen we need all player controllers to be present
|
||||
if (bIsInSplitscreen && bMissingAnyLocalPC)
|
||||
{
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("At least one missing local player controller in splitscreen"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// And in non-splitscreen we need at least one player controller to be present
|
||||
if (!bIsInSplitscreen && !bFoundAnyLocalPC)
|
||||
{
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("Need at least one local player controller"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Victory! The loading screen can go away now
|
||||
DebugReasonForShowingOrHidingLoadingScreen = TEXT("(nothing wants to show it anymore)");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ULoadingScreenManager::ShouldShowLoadingScreen()
|
||||
{
|
||||
const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();
|
||||
|
||||
// Check debugging commands that force the state one way or another
|
||||
#if !UE_BUILD_SHIPPING
|
||||
static bool bCmdLineNoLoadingScreen = FParse::Param(FCommandLine::Get(), TEXT("NoLoadingScreen"));
|
||||
if (bCmdLineNoLoadingScreen)
|
||||
{
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString(TEXT("CommandLine has 'NoLoadingScreen'"));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Can't show a loading screen if there's no game viewport
|
||||
UGameInstance* LocalGameInstance = GetGameInstance();
|
||||
if (LocalGameInstance->GetGameViewportClient() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for a need to show the loading screen
|
||||
const bool bNeedToShowLoadingScreen = CheckForAnyNeedToShowLoadingScreen();
|
||||
|
||||
// Keep the loading screen up a bit longer if desired
|
||||
bool bWantToForceShowLoadingScreen = false;
|
||||
if (bNeedToShowLoadingScreen)
|
||||
{
|
||||
// Still need to show it
|
||||
TimeLoadingScreenLastDismissed = -1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't *need* to show the screen anymore, but might still want to for a bit
|
||||
const double CurrentTime = FPlatformTime::Seconds();
|
||||
const bool bCanHoldLoadingScreen = (!GIsEditor || Settings->HoldLoadingScreenAdditionalSecsEvenInEditor);
|
||||
const double HoldLoadingScreenAdditionalSecs = bCanHoldLoadingScreen ? LoadingScreenCVars::HoldLoadingScreenAdditionalSecs : 0.0;
|
||||
|
||||
if (TimeLoadingScreenLastDismissed < 0.0)
|
||||
{
|
||||
TimeLoadingScreenLastDismissed = CurrentTime;
|
||||
}
|
||||
const double TimeSinceScreenDismissed = CurrentTime - TimeLoadingScreenLastDismissed;
|
||||
|
||||
// hold for an extra X seconds, to cover up streaming
|
||||
if ((HoldLoadingScreenAdditionalSecs > 0.0) && (TimeSinceScreenDismissed < HoldLoadingScreenAdditionalSecs))
|
||||
{
|
||||
// Make sure we're rendering the world at this point, so that textures will actually stream in
|
||||
//@TODO: If bNeedToShowLoadingScreen bounces back true during this window, we won't turn this off again...
|
||||
UGameViewportClient* GameViewportClient = GetGameInstance()->GetGameViewportClient();
|
||||
GameViewportClient->bDisableWorldRendering = false;
|
||||
|
||||
DebugReasonForShowingOrHidingLoadingScreen = FString::Printf(TEXT("Keeping loading screen up for an additional %.2f seconds to allow texture streaming"), HoldLoadingScreenAdditionalSecs);
|
||||
bWantToForceShowLoadingScreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
return bNeedToShowLoadingScreen || bWantToForceShowLoadingScreen;
|
||||
}
|
||||
|
||||
bool ULoadingScreenManager::IsShowingInitialLoadingScreen() const
|
||||
{
|
||||
FPreLoadScreenManager* PreLoadScreenManager = FPreLoadScreenManager::Get();
|
||||
return (PreLoadScreenManager != nullptr) && PreLoadScreenManager->HasValidActivePreLoadScreen();
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::ShowLoadingScreen()
|
||||
{
|
||||
if (bCurrentlyShowingLoadingScreen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Unable to show loading screen if the engine is still loading with its loading screen.
|
||||
if (FPreLoadScreenManager::Get() && FPreLoadScreenManager::Get()->HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TimeLoadingScreenShown = FPlatformTime::Seconds();
|
||||
|
||||
bCurrentlyShowingLoadingScreen = true;
|
||||
|
||||
CSV_EVENT(LoadingScreen, TEXT("Show"));
|
||||
|
||||
const UCommonLoadingScreenSettings* Settings = GetDefault<UCommonLoadingScreenSettings>();
|
||||
|
||||
if (IsShowingInitialLoadingScreen())
|
||||
{
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("Showing loading screen when 'IsShowingInitialLoadingScreen()' is true."));
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("Showing loading screen when 'IsShowingInitialLoadingScreen()' is false."));
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen);
|
||||
|
||||
UGameInstance* LocalGameInstance = GetGameInstance();
|
||||
|
||||
// Eat input while the loading screen is displayed
|
||||
StartBlockingInput();
|
||||
|
||||
LoadingScreenVisibilityChanged.Broadcast(/*bIsVisible=*/ true);
|
||||
|
||||
// Create the loading screen widget
|
||||
TSubclassOf<UUserWidget> LoadingScreenWidgetClass = Settings->LoadingScreenWidget.TryLoadClass<UUserWidget>();
|
||||
if (UUserWidget* UserWidget = UUserWidget::CreateWidgetInstance(*LocalGameInstance, LoadingScreenWidgetClass, NAME_None))
|
||||
{
|
||||
LoadingScreenWidget = UserWidget->TakeWidget();
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogLoadingScreen, Error, TEXT("Failed to load the loading screen widget %s, falling back to placeholder."), *Settings->LoadingScreenWidget.ToString());
|
||||
LoadingScreenWidget = SNew(SThrobber);
|
||||
}
|
||||
|
||||
// Add to the viewport at a high ZOrder to make sure it is on top of most things
|
||||
UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient();
|
||||
GameViewportClient->AddViewportWidgetContent(LoadingScreenWidget.ToSharedRef(), Settings->LoadingScreenZOrder);
|
||||
|
||||
ChangePerformanceSettings(/*bEnableLoadingScreen=*/ true);
|
||||
|
||||
if (!GIsEditor || Settings->ForceTickLoadingScreenEvenInEditor)
|
||||
{
|
||||
// Tick Slate to make sure the loading screen is displayed immediately
|
||||
FSlateApplication::Get().Tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::HideLoadingScreen()
|
||||
{
|
||||
if (!bCurrentlyShowingLoadingScreen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopBlockingInput();
|
||||
|
||||
if (IsShowingInitialLoadingScreen())
|
||||
{
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("Hiding loading screen when 'IsShowingInitialLoadingScreen()' is true."));
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("Hiding loading screen when 'IsShowingInitialLoadingScreen()' is false."));
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("%s"), *DebugReasonForShowingOrHidingLoadingScreen);
|
||||
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("Garbage Collecting before dropping load screen"));
|
||||
GEngine->ForceGarbageCollection(true);
|
||||
|
||||
RemoveWidgetFromViewport();
|
||||
|
||||
ChangePerformanceSettings(/*bEnableLoadingScreen=*/ false);
|
||||
|
||||
// Let observers know that the loading screen is done
|
||||
LoadingScreenVisibilityChanged.Broadcast(/*bIsVisible=*/ false);
|
||||
}
|
||||
|
||||
CSV_EVENT(LoadingScreen, TEXT("Hide"));
|
||||
|
||||
const double LoadingScreenDuration = FPlatformTime::Seconds() - TimeLoadingScreenShown;
|
||||
UE_LOG(LogLoadingScreen, Log, TEXT("LoadingScreen was visible for %.2fs"), LoadingScreenDuration);
|
||||
|
||||
bCurrentlyShowingLoadingScreen = false;
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::RemoveWidgetFromViewport()
|
||||
{
|
||||
UGameInstance* LocalGameInstance = GetGameInstance();
|
||||
if (LoadingScreenWidget.IsValid())
|
||||
{
|
||||
if (UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient())
|
||||
{
|
||||
GameViewportClient->RemoveViewportWidgetContent(LoadingScreenWidget.ToSharedRef());
|
||||
}
|
||||
LoadingScreenWidget.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::StartBlockingInput()
|
||||
{
|
||||
if (!InputPreProcessor.IsValid())
|
||||
{
|
||||
InputPreProcessor = MakeShareable<FLoadingScreenInputPreProcessor>(new FLoadingScreenInputPreProcessor());
|
||||
FSlateApplication::Get().RegisterInputPreProcessor(InputPreProcessor, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::StopBlockingInput()
|
||||
{
|
||||
if (InputPreProcessor.IsValid())
|
||||
{
|
||||
FSlateApplication::Get().UnregisterInputPreProcessor(InputPreProcessor);
|
||||
InputPreProcessor.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void ULoadingScreenManager::ChangePerformanceSettings(bool bEnabingLoadingScreen)
|
||||
{
|
||||
UGameInstance* LocalGameInstance = GetGameInstance();
|
||||
UGameViewportClient* GameViewportClient = LocalGameInstance->GetGameViewportClient();
|
||||
|
||||
FShaderPipelineCache::SetBatchMode(bEnabingLoadingScreen ? FShaderPipelineCache::BatchMode::Fast : FShaderPipelineCache::BatchMode::Background);
|
||||
|
||||
// Don't bother drawing the 3D world while we're loading
|
||||
GameViewportClient->bDisableWorldRendering = bEnabingLoadingScreen;
|
||||
|
||||
// Make sure to prioritize streaming in levels if the loading screen is up
|
||||
if (UWorld* ViewportWorld = GameViewportClient->GetWorld())
|
||||
{
|
||||
if (AWorldSettings* WorldSettings = ViewportWorld->GetWorldSettings(false, false))
|
||||
{
|
||||
WorldSettings->bHighPriorityLoadingLocal = bEnabingLoadingScreen;
|
||||
}
|
||||
}
|
||||
|
||||
if (bEnabingLoadingScreen)
|
||||
{
|
||||
// Set a new hang detector timeout multiplier when the loading screen is visible.
|
||||
double HangDurationMultiplier;
|
||||
if (!GConfig || !GConfig->GetDouble(TEXT("Core.System"), TEXT("LoadingScreenHangDurationMultiplier"), /*out*/ HangDurationMultiplier, GEngineIni))
|
||||
{
|
||||
HangDurationMultiplier = 1.0;
|
||||
}
|
||||
FThreadHeartBeat::Get().SetDurationMultiplier(HangDurationMultiplier);
|
||||
|
||||
// Do not report hitches while the loading screen is up
|
||||
FGameThreadHitchHeartBeat::Get().SuspendHeartBeat();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restore the hang detector timeout when we hide the loading screen
|
||||
FThreadHeartBeat::Get().SetDurationMultiplier(1.0);
|
||||
|
||||
// Resume reporting hitches now that the loading screen is down
|
||||
FGameThreadHitchHeartBeat::Get().ResumeHeartBeat();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
|
||||
#include "LoadingProcessInterface.generated.h"
|
||||
|
||||
/** Interface for things that might cause loading to happen which requires a loading screen to be displayed */
|
||||
UINTERFACE(BlueprintType)
|
||||
class COMMONLOADINGSCREEN_API ULoadingProcessInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
class COMMONLOADINGSCREEN_API ILoadingProcessInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Checks to see if this object implements the interface, and if so asks whether or not we should
|
||||
// be currently showing a loading screen
|
||||
static bool ShouldShowLoadingScreen(UObject* TestObject, FString& OutReason);
|
||||
|
||||
virtual bool ShouldShowLoadingScreen(FString& OutReason) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
@ -0,0 +1,47 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "LoadingProcessTask.h"
|
||||
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "LoadingScreenManager.h"
|
||||
#include "UObject/ScriptInterface.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(LoadingProcessTask)
|
||||
|
||||
/*static*/ ULoadingProcessTask* ULoadingProcessTask::CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason)
|
||||
{
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||
UGameInstance* GameInstance = World ? World->GetGameInstance() : nullptr;
|
||||
ULoadingScreenManager* LoadingScreenManager = GameInstance ? GameInstance->GetSubsystem<ULoadingScreenManager>() : nullptr;
|
||||
|
||||
if (LoadingScreenManager)
|
||||
{
|
||||
ULoadingProcessTask* NewLoadingTask = NewObject<ULoadingProcessTask>(LoadingScreenManager);
|
||||
NewLoadingTask->SetShowLoadingScreenReason(ShowLoadingScreenReason);
|
||||
|
||||
LoadingScreenManager->RegisterLoadingProcessor(NewLoadingTask);
|
||||
|
||||
return NewLoadingTask;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ULoadingProcessTask::Unregister()
|
||||
{
|
||||
ULoadingScreenManager* LoadingScreenManager = Cast<ULoadingScreenManager>(GetOuter());
|
||||
LoadingScreenManager->UnregisterLoadingProcessor(this);
|
||||
}
|
||||
|
||||
void ULoadingProcessTask::SetShowLoadingScreenReason(const FString& InReason)
|
||||
{
|
||||
Reason = InReason;
|
||||
}
|
||||
|
||||
bool ULoadingProcessTask::ShouldShowLoadingScreen(FString& OutReason) const
|
||||
{
|
||||
OutReason = Reason;
|
||||
return true;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "LoadingProcessInterface.h"
|
||||
#include "UObject/Object.h"
|
||||
|
||||
#include "LoadingProcessTask.generated.h"
|
||||
|
||||
struct FFrame;
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class COMMONLOADINGSCREEN_API ULoadingProcessTask : public UObject, public ILoadingProcessInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, meta=(WorldContext = "WorldContextObject"))
|
||||
static ULoadingProcessTask* CreateLoadingScreenProcessTask(UObject* WorldContextObject, const FString& ShowLoadingScreenReason);
|
||||
|
||||
public:
|
||||
ULoadingProcessTask() { }
|
||||
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void Unregister();
|
||||
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void SetShowLoadingScreenReason(const FString& InReason);
|
||||
|
||||
virtual bool ShouldShowLoadingScreen(FString& OutReason) const override;
|
||||
|
||||
FString Reason;
|
||||
};
|
@ -0,0 +1,127 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "Tickable.h"
|
||||
#include "UObject/WeakInterfacePtr.h"
|
||||
|
||||
#include "LoadingScreenManager.generated.h"
|
||||
|
||||
template <typename InterfaceType> class TScriptInterface;
|
||||
|
||||
class FSubsystemCollectionBase;
|
||||
class IInputProcessor;
|
||||
class ILoadingProcessInterface;
|
||||
class SWidget;
|
||||
class UObject;
|
||||
class UWorld;
|
||||
struct FFrame;
|
||||
struct FWorldContext;
|
||||
|
||||
/**
|
||||
* Handles showing/hiding the loading screen
|
||||
*/
|
||||
UCLASS()
|
||||
class COMMONLOADINGSCREEN_API ULoadingScreenManager : public UGameInstanceSubsystem, public FTickableGameObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
//~USubsystem interface
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
|
||||
//~End of USubsystem interface
|
||||
|
||||
//~FTickableObjectBase interface
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
virtual ETickableTickType GetTickableTickType() const override;
|
||||
virtual bool IsTickable() const override;
|
||||
virtual TStatId GetStatId() const override;
|
||||
virtual UWorld* GetTickableGameObjectWorld() const override;
|
||||
//~End of FTickableObjectBase interface
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category=LoadingScreen)
|
||||
FString GetDebugReasonForShowingOrHidingLoadingScreen() const
|
||||
{
|
||||
return DebugReasonForShowingOrHidingLoadingScreen;
|
||||
}
|
||||
|
||||
/** Returns True when the loading screen is currently being shown */
|
||||
bool GetLoadingScreenDisplayStatus() const
|
||||
{
|
||||
return bCurrentlyShowingLoadingScreen;
|
||||
}
|
||||
|
||||
/** Called when the loading screen visibility changes */
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnLoadingScreenVisibilityChangedDelegate, bool);
|
||||
FORCEINLINE FOnLoadingScreenVisibilityChangedDelegate& OnLoadingScreenVisibilityChangedDelegate() { return LoadingScreenVisibilityChanged; }
|
||||
|
||||
void RegisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface);
|
||||
void UnregisterLoadingProcessor(TScriptInterface<ILoadingProcessInterface> Interface);
|
||||
|
||||
private:
|
||||
void HandlePreLoadMap(const FWorldContext& WorldContext, const FString& MapName);
|
||||
void HandlePostLoadMap(UWorld* World);
|
||||
|
||||
/** Determines if we should show or hide the loading screen. Called every frame. */
|
||||
void UpdateLoadingScreen();
|
||||
|
||||
/** Returns true if we need to be showing the loading screen. */
|
||||
bool CheckForAnyNeedToShowLoadingScreen();
|
||||
|
||||
/** Returns true if we want to be showing the loading screen (if we need to or are artificially forcing it on for other reasons). */
|
||||
bool ShouldShowLoadingScreen();
|
||||
|
||||
/** Returns true if we are in the initial loading flow before this screen should be used */
|
||||
bool IsShowingInitialLoadingScreen() const;
|
||||
|
||||
/** Shows the loading screen. Sets up the loading screen widget on the viewport */
|
||||
void ShowLoadingScreen();
|
||||
|
||||
/** Hides the loading screen. The loading screen widget will be destroyed */
|
||||
void HideLoadingScreen();
|
||||
|
||||
/** Removes the widget from the viewport */
|
||||
void RemoveWidgetFromViewport();
|
||||
|
||||
/** Prevents input from being used in-game while the loading screen is visible */
|
||||
void StartBlockingInput();
|
||||
|
||||
/** Resumes in-game input, if blocking */
|
||||
void StopBlockingInput();
|
||||
|
||||
void ChangePerformanceSettings(bool bEnabingLoadingScreen);
|
||||
|
||||
private:
|
||||
/** Delegate broadcast when the loading screen visibility changes */
|
||||
FOnLoadingScreenVisibilityChangedDelegate LoadingScreenVisibilityChanged;
|
||||
|
||||
/** A reference to the loading screen widget we are displaying (if any) */
|
||||
TSharedPtr<SWidget> LoadingScreenWidget;
|
||||
|
||||
/** Input processor to eat all input while the loading screen is shown */
|
||||
TSharedPtr<IInputProcessor> InputPreProcessor;
|
||||
|
||||
/** External loading processors, components maybe actors that delay the loading. */
|
||||
TArray<TWeakInterfacePtr<ILoadingProcessInterface>> ExternalLoadingProcessors;
|
||||
|
||||
/** The reason why the loading screen is up (or not) */
|
||||
FString DebugReasonForShowingOrHidingLoadingScreen;
|
||||
|
||||
/** The time when we started showing the loading screen */
|
||||
double TimeLoadingScreenShown = 0.0;
|
||||
|
||||
/** The time the loading screen most recently wanted to be dismissed (might still be up due to a min display duration requirement) **/
|
||||
double TimeLoadingScreenLastDismissed = -1.0;
|
||||
|
||||
/** The time until the next log for why the loading screen is still up */
|
||||
double TimeUntilNextLogHeartbeatSeconds = 0.0;
|
||||
|
||||
/** True when we are between PreLoadMap and PostLoadMap */
|
||||
bool bCurrentlyInLoadMap = false;
|
||||
|
||||
/** True when the loading screen is currently being shown */
|
||||
bool bCurrentlyShowingLoadingScreen = false;
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class CommonStartupLoadingScreen : ModuleRules
|
||||
{
|
||||
public CommonStartupLoadingScreen(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"MoviePlayer",
|
||||
"PreLoadScreen",
|
||||
"DeveloperSettings"
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "CommonPreLoadScreen.h"
|
||||
|
||||
#include "Misc/App.h"
|
||||
#include "SCommonPreLoadingScreenWidget.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "CommonPreLoadingScreen"
|
||||
|
||||
void FCommonPreLoadScreen::Init()
|
||||
{
|
||||
if (!GIsEditor && FApp::CanEverRender())
|
||||
{
|
||||
EngineLoadingWidget = SNew(SCommonPreLoadingScreenWidget);
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,20 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PreLoadScreenBase.h"
|
||||
|
||||
class SWidget;
|
||||
|
||||
class FCommonPreLoadScreen : public FPreLoadScreenBase
|
||||
{
|
||||
public:
|
||||
|
||||
/*** IPreLoadScreen Implementation ***/
|
||||
virtual void Init() override;
|
||||
virtual EPreLoadScreenTypes GetPreLoadScreenType() const override { return EPreLoadScreenTypes::EngineLoadingScreen; }
|
||||
virtual TSharedPtr<SWidget> GetWidget() override { return EngineLoadingWidget; }
|
||||
private:
|
||||
|
||||
TSharedPtr<SWidget> EngineLoadingWidget;
|
||||
};
|
@ -0,0 +1,62 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "CommonPreLoadScreen.h"
|
||||
#include "Misc/App.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "PreLoadScreenManager.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FCommonLoadingScreenModule"
|
||||
|
||||
class FCommonStartupLoadingScreenModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
bool IsGameModule() const override;
|
||||
|
||||
private:
|
||||
void OnPreLoadScreenManagerCleanUp();
|
||||
|
||||
TSharedPtr<FCommonPreLoadScreen> PreLoadingScreen;
|
||||
};
|
||||
|
||||
|
||||
void FCommonStartupLoadingScreenModule::StartupModule()
|
||||
{
|
||||
// No need to load these assets on dedicated servers.
|
||||
// Still want to load them in commandlets so cook catches them
|
||||
if (!IsRunningDedicatedServer())
|
||||
{
|
||||
PreLoadingScreen = MakeShared<FCommonPreLoadScreen>();
|
||||
PreLoadingScreen->Init();
|
||||
|
||||
if (!GIsEditor && FApp::CanEverRender() && FPreLoadScreenManager::Get())
|
||||
{
|
||||
FPreLoadScreenManager::Get()->RegisterPreLoadScreen(PreLoadingScreen);
|
||||
FPreLoadScreenManager::Get()->OnPreLoadScreenManagerCleanUp.AddRaw(this, &FCommonStartupLoadingScreenModule::OnPreLoadScreenManagerCleanUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FCommonStartupLoadingScreenModule::OnPreLoadScreenManagerCleanUp()
|
||||
{
|
||||
//Once the PreLoadScreenManager is cleaning up, we can get rid of all our resources too
|
||||
PreLoadingScreen.Reset();
|
||||
ShutdownModule();
|
||||
}
|
||||
|
||||
void FCommonStartupLoadingScreenModule::ShutdownModule()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool FCommonStartupLoadingScreenModule::IsGameModule() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FCommonStartupLoadingScreenModule, CommonStartupLoadingScreen)
|
@ -0,0 +1,32 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "SCommonPreLoadingScreenWidget.h"
|
||||
|
||||
#include "Widgets/Layout/SBorder.h"
|
||||
|
||||
class FReferenceCollector;
|
||||
|
||||
#define LOCTEXT_NAMESPACE "SCommonPreLoadingScreenWidget"
|
||||
|
||||
void SCommonPreLoadingScreenWidget::Construct(const FArguments& InArgs)
|
||||
{
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SBorder)
|
||||
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
|
||||
.BorderBackgroundColor(FLinearColor::Black)
|
||||
.Padding(0)
|
||||
];
|
||||
}
|
||||
|
||||
void SCommonPreLoadingScreenWidget::AddReferencedObjects(FReferenceCollector& Collector)
|
||||
{
|
||||
//WidgetAssets.AddReferencedObjects(Collector);
|
||||
}
|
||||
|
||||
FString SCommonPreLoadingScreenWidget::GetReferencerName() const
|
||||
{
|
||||
return TEXT("SCommonPreLoadingScreenWidget");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,26 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "UObject/GCObject.h"
|
||||
#include "Widgets/Accessibility/SlateWidgetAccessibleTypes.h"
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
|
||||
class FReferenceCollector;
|
||||
|
||||
class SCommonPreLoadingScreenWidget : public SCompoundWidget, public FGCObject
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SCommonPreLoadingScreenWidget) {}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
//~ Begin FGCObject interface
|
||||
virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
|
||||
virtual FString GetReferencerName() const override;
|
||||
//~ End FGCObject interface
|
||||
|
||||
private:
|
||||
|
||||
};
|
38
Plugins/CommonUser/CommonUser.uplugin
Normal file
38
Plugins/CommonUser/CommonUser.uplugin
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0",
|
||||
"FriendlyName": "CommonUser",
|
||||
"Description": "Provides gameplay code and blueprint wrappers for online and platform operations.",
|
||||
"Category": "Gameplay",
|
||||
"CreatedBy": "Epic Games, Inc.",
|
||||
"CreatedByURL": "https://www.epicgames.com",
|
||||
"DocsURL": "",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "",
|
||||
"CanContainContent": false,
|
||||
"IsBetaVersion": false,
|
||||
"IsExperimentalVersion": false,
|
||||
"Installed": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "CommonUser",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "OnlineSubsystem",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "OnlineSubsystemUtils",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "OnlineServices",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
BIN
Plugins/CommonUser/Resources/Icon128.png
Normal file
BIN
Plugins/CommonUser/Resources/Icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
71
Plugins/CommonUser/Source/CommonUser/CommonUser.Build.cs
Normal file
71
Plugins/CommonUser/Source/CommonUser/CommonUser.Build.cs
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class CommonUser : ModuleRules
|
||||
{
|
||||
public CommonUser(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
bool bUseOnlineSubsystemV1 = true;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreOnline",
|
||||
"GameplayTags",
|
||||
"OnlineSubsystemUtils",
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
if (bUseOnlineSubsystemV1)
|
||||
{
|
||||
PublicDependencyModuleNames.Add("OnlineSubsystem");
|
||||
}
|
||||
else
|
||||
{
|
||||
PublicDependencyModuleNames.Add("OnlineServicesInterface");
|
||||
}
|
||||
|
||||
PublicDefinitions.Add("COMMONUSER_OSSV1=" + (bUseOnlineSubsystemV1 ? "1" : "0"));
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreOnline",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"ApplicationCore",
|
||||
"InputCore",
|
||||
// ... add private dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AsyncAction_CommonUserInitialize.h"
|
||||
|
||||
#include "GenericPlatform/GenericPlatformInputDeviceMapper.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(AsyncAction_CommonUserInitialize)
|
||||
|
||||
UAsyncAction_CommonUserInitialize* UAsyncAction_CommonUserInitialize::InitializeForLocalPlay(UCommonUserSubsystem* Target, int32 LocalPlayerIndex, FInputDeviceId PrimaryInputDevice, bool bCanUseGuestLogin)
|
||||
{
|
||||
if (!PrimaryInputDevice.IsValid())
|
||||
{
|
||||
// Set to default device
|
||||
PrimaryInputDevice = IPlatformInputDeviceMapper::Get().GetDefaultInputDevice();
|
||||
}
|
||||
|
||||
UAsyncAction_CommonUserInitialize* Action = NewObject<UAsyncAction_CommonUserInitialize>();
|
||||
|
||||
Action->RegisterWithGameInstance(Target);
|
||||
|
||||
if (Target && Action->IsRegistered())
|
||||
{
|
||||
Action->Subsystem = Target;
|
||||
|
||||
Action->Params.RequestedPrivilege = ECommonUserPrivilege::CanPlay;
|
||||
Action->Params.LocalPlayerIndex = LocalPlayerIndex;
|
||||
Action->Params.PrimaryInputDevice = PrimaryInputDevice;
|
||||
Action->Params.bCanUseGuestLogin = bCanUseGuestLogin;
|
||||
Action->Params.bCanCreateNewLocalPlayer = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Action->SetReadyToDestroy();
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
UAsyncAction_CommonUserInitialize* UAsyncAction_CommonUserInitialize::LoginForOnlinePlay(UCommonUserSubsystem* Target, int32 LocalPlayerIndex)
|
||||
{
|
||||
UAsyncAction_CommonUserInitialize* Action = NewObject<UAsyncAction_CommonUserInitialize>();
|
||||
|
||||
Action->RegisterWithGameInstance(Target);
|
||||
|
||||
if (Target && Action->IsRegistered())
|
||||
{
|
||||
Action->Subsystem = Target;
|
||||
|
||||
Action->Params.RequestedPrivilege = ECommonUserPrivilege::CanPlayOnline;
|
||||
Action->Params.LocalPlayerIndex = LocalPlayerIndex;
|
||||
Action->Params.bCanCreateNewLocalPlayer = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Action->SetReadyToDestroy();
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UAsyncAction_CommonUserInitialize::HandleFailure()
|
||||
{
|
||||
const UCommonUserInfo* UserInfo = nullptr;
|
||||
if (Subsystem.IsValid())
|
||||
{
|
||||
UserInfo = Subsystem->GetUserInfoForLocalPlayerIndex(Params.LocalPlayerIndex);
|
||||
}
|
||||
HandleInitializationComplete(UserInfo, false, NSLOCTEXT("CommonUser", "LoginFailedEarly", "Unable to start login process"), Params.RequestedPrivilege, Params.OnlineContext);
|
||||
}
|
||||
|
||||
void UAsyncAction_CommonUserInitialize::HandleInitializationComplete(const UCommonUserInfo* UserInfo, bool bSuccess, FText Error, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext OnlineContext)
|
||||
{
|
||||
if (ShouldBroadcastDelegates())
|
||||
{
|
||||
OnInitializationComplete.Broadcast(UserInfo, bSuccess, Error, RequestedPrivilege, OnlineContext);
|
||||
}
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
void UAsyncAction_CommonUserInitialize::Activate()
|
||||
{
|
||||
if (Subsystem.IsValid())
|
||||
{
|
||||
Params.OnUserInitializeComplete.BindUFunction(this, GET_FUNCTION_NAME_CHECKED(UAsyncAction_CommonUserInitialize, HandleInitializationComplete));
|
||||
bool bSuccess = Subsystem->TryToInitializeUser(Params);
|
||||
|
||||
if (!bSuccess)
|
||||
{
|
||||
// Call failure next frame
|
||||
FTimerManager* TimerManager = GetTimerManager();
|
||||
|
||||
if (TimerManager)
|
||||
{
|
||||
TimerManager->SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &UAsyncAction_CommonUserInitialize::HandleFailure));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,120 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
#include "CommonUserBasicPresence.h"
|
||||
#include "CommonSessionSubsystem.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "CommonUserTypes.h"
|
||||
|
||||
|
||||
#if COMMONUSER_OSSV1
|
||||
#include "OnlineSubsystemUtils.h"
|
||||
#include "Interfaces/OnlinePresenceInterface.h"
|
||||
#else
|
||||
#include "Online/OnlineServicesEngineUtils.h"
|
||||
#include "Online/Presence.h"
|
||||
#endif
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogUserBasicPresence, Log, All);
|
||||
DEFINE_LOG_CATEGORY(LogUserBasicPresence);
|
||||
|
||||
UCommonUserBasicPresence::UCommonUserBasicPresence()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void UCommonUserBasicPresence::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
UCommonSessionSubsystem* CommonSession = Collection.InitializeDependency<UCommonSessionSubsystem>();
|
||||
if(ensure(CommonSession))
|
||||
{
|
||||
CommonSession->OnSessionInformationChangedEvent.AddUObject(this, &UCommonUserBasicPresence::OnNotifySessionInformationChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void UCommonUserBasicPresence::Deinitialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
FString UCommonUserBasicPresence::SessionStateToBackendKey(ECommonSessionInformationState SessionStatus)
|
||||
{
|
||||
switch (SessionStatus)
|
||||
{
|
||||
case ECommonSessionInformationState::OutOfGame:
|
||||
return PresenceStatusMainMenu;
|
||||
break;
|
||||
case ECommonSessionInformationState::Matchmaking:
|
||||
return PresenceStatusMatchmaking;
|
||||
break;
|
||||
case ECommonSessionInformationState::InGame:
|
||||
return PresenceStatusInGame;
|
||||
break;
|
||||
default:
|
||||
UE_LOG(LogUserBasicPresence, Error, TEXT("UCommonUserBasicPresence::SessionStateToBackendKey: Found unknown enum value %d"), (uint8)SessionStatus);
|
||||
return TEXT("Unknown");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void UCommonUserBasicPresence::OnNotifySessionInformationChanged(ECommonSessionInformationState SessionStatus, const FString& GameMode, const FString& MapName)
|
||||
{
|
||||
if (bEnableSessionsBasedPresence && !GetGameInstance()->IsDedicatedServerInstance())
|
||||
{
|
||||
// trim the map name since its a URL
|
||||
FString MapNameTruncated = MapName;
|
||||
if (!MapNameTruncated.IsEmpty())
|
||||
{
|
||||
int LastIndexOfSlash = 0;
|
||||
MapNameTruncated.FindLastChar('/', LastIndexOfSlash);
|
||||
MapNameTruncated = MapNameTruncated.RightChop(LastIndexOfSlash + 1);
|
||||
}
|
||||
|
||||
#if COMMONUSER_OSSV1
|
||||
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
|
||||
if(OnlineSub)
|
||||
{
|
||||
IOnlinePresencePtr Presence = OnlineSub->GetPresenceInterface();
|
||||
if(Presence)
|
||||
{
|
||||
FOnlineUserPresenceStatus UpdatedPresence;
|
||||
UpdatedPresence.State = EOnlinePresenceState::Online; // We'll only send the presence update if the user has a valid UniqueNetId, so we can assume they are Online
|
||||
UpdatedPresence.StatusStr = *SessionStateToBackendKey(SessionStatus);
|
||||
UpdatedPresence.Properties.Emplace(PresenceKeyGameMode, GameMode);
|
||||
UpdatedPresence.Properties.Emplace(PresenceKeyMapName, MapNameTruncated);
|
||||
|
||||
for (const ULocalPlayer* LocalPlayer : GetGameInstance()->GetLocalPlayers())
|
||||
{
|
||||
if (LocalPlayer && LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId() != nullptr)
|
||||
{
|
||||
Presence->SetPresence(*LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId(), UpdatedPresence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
UE::Online::IOnlineServicesPtr OnlineServices = UE::Online::GetServices(GetWorld());
|
||||
check(OnlineServices);
|
||||
UE::Online::IPresencePtr Presence = OnlineServices->GetPresenceInterface();
|
||||
if(Presence)
|
||||
{
|
||||
for (const ULocalPlayer* LocalPlayer : GetGameInstance()->GetLocalPlayers())
|
||||
{
|
||||
if (LocalPlayer && LocalPlayer->GetPreferredUniqueNetId().IsV2())
|
||||
{
|
||||
UE::Online::FPartialUpdatePresence::Params UpdateParams;
|
||||
UpdateParams.LocalAccountId = LocalPlayer->GetPreferredUniqueNetId().GetV2();
|
||||
UpdateParams.Mutations.StatusString.Emplace(*SessionStateToBackendKey(SessionStatus));
|
||||
UpdateParams.Mutations.UpdatedProperties.Emplace(PresenceKeyGameMode, GameMode);
|
||||
UpdateParams.Mutations.UpdatedProperties.Emplace(PresenceKeyMapName, MapNameTruncated);
|
||||
|
||||
Presence->PartialUpdatePresence(MoveTemp(UpdateParams));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "CommonUserModule.h"
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FCommonUserModule"
|
||||
|
||||
void FCommonUserModule::StartupModule()
|
||||
{
|
||||
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||
}
|
||||
|
||||
void FCommonUserModule::ShutdownModule()
|
||||
{
|
||||
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||
// we call this function before unloading the module.
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FCommonUserModule, CommonUser)
|
2684
Plugins/CommonUser/Source/CommonUser/Private/CommonUserSubsystem.cpp
Normal file
2684
Plugins/CommonUser/Source/CommonUser/Private/CommonUserSubsystem.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,17 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "CommonUserTypes.h"
|
||||
#include "OnlineError.h"
|
||||
|
||||
void FOnlineResultInformation::FromOnlineError(const FOnlineErrorType& InOnlineError)
|
||||
{
|
||||
#if COMMONUSER_OSSV1
|
||||
bWasSuccessful = InOnlineError.WasSuccessful();
|
||||
ErrorId = InOnlineError.GetErrorCode();
|
||||
ErrorText = InOnlineError.GetErrorMessage();
|
||||
#else
|
||||
bWasSuccessful = InOnlineError != UE::Online::Errors::Success();
|
||||
ErrorId = InOnlineError.GetErrorId();
|
||||
ErrorText = InOnlineError.GetText();
|
||||
#endif
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommonUserSubsystem.h"
|
||||
#include "Engine/CancellableAsyncAction.h"
|
||||
|
||||
#include "AsyncAction_CommonUserInitialize.generated.h"
|
||||
|
||||
enum class ECommonUserOnlineContext : uint8;
|
||||
enum class ECommonUserPrivilege : uint8;
|
||||
struct FInputDeviceId;
|
||||
|
||||
class FText;
|
||||
class UObject;
|
||||
struct FFrame;
|
||||
|
||||
/**
|
||||
* Async action to handle different functions for initializing users
|
||||
*/
|
||||
UCLASS()
|
||||
class COMMONUSER_API UAsyncAction_CommonUserInitialize : public UCancellableAsyncAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initializes a local player with the common user system, which includes doing platform-specific login and privilege checks.
|
||||
* When the process has succeeded or failed, it will broadcast the OnInitializationComplete delegate.
|
||||
*
|
||||
* @param LocalPlayerIndex Desired index of ULocalPlayer in Game Instance, 0 will be primary player and 1+ for local multiplayer
|
||||
* @param PrimaryInputDevice Primary input device for the user, if invalid will use the system default
|
||||
* @param bCanUseGuestLogin If true, this player can be a guest without a real system net id
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UAsyncAction_CommonUserInitialize* InitializeForLocalPlay(UCommonUserSubsystem* Target, int32 LocalPlayerIndex, FInputDeviceId PrimaryInputDevice, bool bCanUseGuestLogin);
|
||||
|
||||
/**
|
||||
* Attempts to log an existing user into the platform-specific online backend to enable full online play
|
||||
* When the process has succeeded or failed, it will broadcast the OnInitializationComplete delegate.
|
||||
*
|
||||
* @param LocalPlayerIndex Index of existing LocalPlayer in Game Instance
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser, meta = (BlueprintInternalUseOnly = "true"))
|
||||
static UAsyncAction_CommonUserInitialize* LoginForOnlinePlay(UCommonUserSubsystem* Target, int32 LocalPlayerIndex);
|
||||
|
||||
/** Call when initialization succeeds or fails */
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FCommonUserOnInitializeCompleteMulticast OnInitializationComplete;
|
||||
|
||||
/** Fail and send callbacks if needed */
|
||||
void HandleFailure();
|
||||
|
||||
/** Wrapper delegate, will pass on to OnInitializationComplete if appropriate */
|
||||
UFUNCTION()
|
||||
virtual void HandleInitializationComplete(const UCommonUserInfo* UserInfo, bool bSuccess, FText Error, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext OnlineContext);
|
||||
|
||||
protected:
|
||||
/** Actually start the initialization */
|
||||
virtual void Activate() override;
|
||||
|
||||
TWeakObjectPtr<UCommonUserSubsystem> Subsystem;
|
||||
FCommonUserInitializeParams Params;
|
||||
};
|
@ -0,0 +1,471 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommonUserTypes.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "UObject/ObjectPtr.h"
|
||||
#include "UObject/StrongObjectPtr.h"
|
||||
#include "UObject/PrimaryAssetId.h"
|
||||
#include "UObject/WeakObjectPtr.h"
|
||||
#include "PartyBeaconClient.h"
|
||||
#include "PartyBeaconHost.h"
|
||||
#include "PartyBeaconState.h"
|
||||
|
||||
class APlayerController;
|
||||
class AOnlineBeaconHost;
|
||||
class ULocalPlayer;
|
||||
namespace ETravelFailure { enum Type : int; }
|
||||
struct FOnlineResultInformation;
|
||||
|
||||
#if COMMONUSER_OSSV1
|
||||
#include "Interfaces/OnlineSessionInterface.h"
|
||||
#include "OnlineSessionSettings.h"
|
||||
#else
|
||||
#include "Online/Lobbies.h"
|
||||
#include "Online/OnlineAsyncOpHandle.h"
|
||||
#endif // COMMONUSER_OSSV1
|
||||
|
||||
#include "CommonSessionSubsystem.generated.h"
|
||||
|
||||
class UWorld;
|
||||
class FCommonSession_OnlineSessionSettings;
|
||||
|
||||
#if COMMONUSER_OSSV1
|
||||
class FCommonOnlineSearchSettingsOSSv1;
|
||||
using FCommonOnlineSearchSettings = FCommonOnlineSearchSettingsOSSv1;
|
||||
#else
|
||||
class FCommonOnlineSearchSettingsOSSv2;
|
||||
using FCommonOnlineSearchSettings = FCommonOnlineSearchSettingsOSSv2;
|
||||
#endif // COMMONUSER_OSSV1
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// UCommonSession_HostSessionRequest
|
||||
|
||||
/** Specifies the online features and connectivity that should be used for a game session */
|
||||
UENUM(BlueprintType)
|
||||
enum class ECommonSessionOnlineMode : uint8
|
||||
{
|
||||
Offline,
|
||||
LAN,
|
||||
Online
|
||||
};
|
||||
|
||||
/** A request object that stores the parameters used when hosting a gameplay session */
|
||||
UCLASS(BlueprintType)
|
||||
class COMMONUSER_API UCommonSession_HostSessionRequest : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Indicates if the session is a full online session or a different type */
|
||||
UPROPERTY(BlueprintReadWrite, Category=Session)
|
||||
ECommonSessionOnlineMode OnlineMode;
|
||||
|
||||
/** True if this request should create a player-hosted lobbies if available */
|
||||
UPROPERTY(BlueprintReadWrite, Category = Session)
|
||||
bool bUseLobbies;
|
||||
|
||||
/** True if this request should create a lobby with enabled voice chat in available */
|
||||
UPROPERTY(BlueprintReadWrite, Category = Session)
|
||||
bool bUseLobbiesVoiceChat;
|
||||
|
||||
/** True if this request should create a session that will appear in the user's presence information */
|
||||
UPROPERTY(BlueprintReadWrite, Category = Session)
|
||||
bool bUsePresence;
|
||||
|
||||
/** String used during matchmaking to specify what type of game mode this is */
|
||||
UPROPERTY(BlueprintReadWrite, Category=Session)
|
||||
FString ModeNameForAdvertisement;
|
||||
|
||||
/** The map that will be loaded at the start of gameplay, this needs to be a valid Primary Asset top-level map */
|
||||
UPROPERTY(BlueprintReadWrite, Category=Session, meta=(AllowedTypes="World"))
|
||||
FPrimaryAssetId MapID;
|
||||
|
||||
/** Extra arguments passed as URL options to the game */
|
||||
UPROPERTY(BlueprintReadWrite, Category=Session)
|
||||
TMap<FString, FString> ExtraArgs;
|
||||
|
||||
/** Maximum players allowed per gameplay session */
|
||||
UPROPERTY(BlueprintReadWrite, Category=Session)
|
||||
int32 MaxPlayerCount = 16;
|
||||
|
||||
public:
|
||||
/** Returns the maximum players that should actually be used, could be overridden in child classes */
|
||||
virtual int32 GetMaxPlayers() const;
|
||||
|
||||
/** Returns the full map name that will be used during gameplay */
|
||||
virtual FString GetMapName() const;
|
||||
|
||||
/** Constructs the full URL that will be passed to ServerTravel */
|
||||
virtual FString ConstructTravelURL() const;
|
||||
|
||||
/** Returns true if this request is valid, returns false and logs errors if it is not */
|
||||
virtual bool ValidateAndLogErrors(FText& OutError) const;
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// UCommonSession_SearchResult
|
||||
|
||||
/** A result object returned from the online system that describes a joinable game session */
|
||||
UCLASS(BlueprintType)
|
||||
class COMMONUSER_API UCommonSession_SearchResult : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Returns an internal description of the session, not meant to be human readable */
|
||||
UFUNCTION(BlueprintCallable, Category=Session)
|
||||
FString GetDescription() const;
|
||||
|
||||
/** Gets an arbitrary string setting, bFoundValue will be false if the setting does not exist */
|
||||
UFUNCTION(BlueprintPure, Category=Sessions)
|
||||
void GetStringSetting(FName Key, FString& Value, bool& bFoundValue) const;
|
||||
|
||||
/** Gets an arbitrary integer setting, bFoundValue will be false if the setting does not exist */
|
||||
UFUNCTION(BlueprintPure, Category = Sessions)
|
||||
void GetIntSetting(FName Key, int32& Value, bool& bFoundValue) const;
|
||||
|
||||
/** The number of private connections that are available */
|
||||
UFUNCTION(BlueprintPure, Category=Sessions)
|
||||
int32 GetNumOpenPrivateConnections() const;
|
||||
|
||||
/** The number of publicly available connections that are available */
|
||||
UFUNCTION(BlueprintPure, Category=Sessions)
|
||||
int32 GetNumOpenPublicConnections() const;
|
||||
|
||||
/** The maximum number of publicly available connections that could be available, including already filled connections */
|
||||
UFUNCTION(BlueprintPure, Category = Sessions)
|
||||
int32 GetMaxPublicConnections() const;
|
||||
|
||||
/** Ping to the search result, MAX_QUERY_PING is unreachable */
|
||||
UFUNCTION(BlueprintPure, Category=Sessions)
|
||||
int32 GetPingInMs() const;
|
||||
|
||||
public:
|
||||
/** Pointer to the platform-specific implementation */
|
||||
#if COMMONUSER_OSSV1
|
||||
FOnlineSessionSearchResult Result;
|
||||
#else
|
||||
TSharedPtr<const UE::Online::FLobby> Lobby;
|
||||
#endif // COMMONUSER_OSSV1
|
||||
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// UCommonSession_SearchSessionRequest
|
||||
|
||||
/** Delegates called when a session search completes */
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FCommonSession_FindSessionsFinished, bool bSucceeded, const FText& ErrorMessage);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCommonSession_FindSessionsFinishedDynamic, bool, bSucceeded, FText, ErrorMessage);
|
||||
|
||||
/** Request object describing a session search, this object will be updated once the search has completed */
|
||||
UCLASS(BlueprintType)
|
||||
class COMMONUSER_API UCommonSession_SearchSessionRequest : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Indicates if the this is looking for full online games or a different type like LAN */
|
||||
UPROPERTY(BlueprintReadWrite, Category = Session)
|
||||
ECommonSessionOnlineMode OnlineMode;
|
||||
|
||||
/** True if this request should look for player-hosted lobbies if they are available, false will only search for registered server sessions */
|
||||
UPROPERTY(BlueprintReadWrite, Category = Session)
|
||||
bool bUseLobbies;
|
||||
|
||||
/** List of all found sessions, will be valid when OnSearchFinished is called */
|
||||
UPROPERTY(BlueprintReadOnly, Category=Session)
|
||||
TArray<TObjectPtr<UCommonSession_SearchResult>> Results;
|
||||
|
||||
/** Native Delegate called when a session search completes */
|
||||
FCommonSession_FindSessionsFinished OnSearchFinished;
|
||||
|
||||
/** Called by subsystem to execute finished delegates */
|
||||
void NotifySearchFinished(bool bSucceeded, const FText& ErrorMessage);
|
||||
|
||||
private:
|
||||
/** Delegate called when a session search completes */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Search Finished", AllowPrivateAccess = true))
|
||||
FCommonSession_FindSessionsFinishedDynamic K2_OnSearchFinished;
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// CommonSessionSubsystem Events
|
||||
|
||||
/**
|
||||
* Event triggered when the local user has requested to join a session from an external source, for example from a platform overlay.
|
||||
* Generally, the game should transition the player into the session.
|
||||
* @param LocalPlatformUserId the local user id that accepted the invitation. This is a platform user id because the user might not be signed in yet.
|
||||
* @param RequestedSession the requested session. Can be null if there was an error processing the request.
|
||||
* @param RequestedSessionResult result of the requested session processing
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_ThreeParams(FCommonSessionOnUserRequestedSession, const FPlatformUserId& /*LocalPlatformUserId*/, UCommonSession_SearchResult* /*RequestedSession*/, const FOnlineResultInformation& /*RequestedSessionResult*/);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FCommonSessionOnUserRequestedSession_Dynamic, const FPlatformUserId&, LocalPlatformUserId, UCommonSession_SearchResult*, RequestedSession, const FOnlineResultInformation&, RequestedSessionResult);
|
||||
|
||||
/**
|
||||
* Event triggered when a session join has completed, after joining the underlying session and before traveling to the server if it was successful.
|
||||
* The event parameters indicate if this was successful, or if there was an error that will stop it from traveling.
|
||||
* @param Result result of the session join
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FCommonSessionOnJoinSessionComplete, const FOnlineResultInformation& /*Result*/);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCommonSessionOnJoinSessionComplete_Dynamic, const FOnlineResultInformation&, Result);
|
||||
|
||||
/**
|
||||
* Event triggered when a session creation for hosting has completed, right before it travels to the map.
|
||||
* The event parameters indicate if this was successful, or if there was an error that will stop it from traveling.
|
||||
* @param Result result of the session join
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FCommonSessionOnCreateSessionComplete, const FOnlineResultInformation& /*Result*/);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCommonSessionOnCreateSessionComplete_Dynamic, const FOnlineResultInformation&, Result);
|
||||
|
||||
/**
|
||||
* Event triggered when the local user has requested to destroy a session from an external source, for example from a platform overlay.
|
||||
* The game should transition the player out of the session.
|
||||
* @param LocalPlatformUserId the local user id that made the destroy request. This is a platform user id because the user might not be signed in yet.
|
||||
* @param SessionName the name identifier for the session.
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FCommonSessionOnDestroySessionRequested, const FPlatformUserId& /*LocalPlatformUserId*/, const FName& /*SessionName*/);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCommonSessionOnDestroySessionRequested_Dynamic, const FPlatformUserId&, LocalPlatformUserId, const FName&, SessionName);
|
||||
|
||||
/**
|
||||
* Event triggered when a session join has completed, after resolving the connect string and prior to the client traveling.
|
||||
* @param URL resolved connection string for the session with any additional arguments
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FCommonSessionOnPreClientTravel, FString& /*URL*/);
|
||||
|
||||
/**
|
||||
* Event triggered at different points in the session ecosystem that represent a user-presentable state of the session.
|
||||
* This should not be used for online functionality (use OnCreateSessionComplete or OnJoinSessionComplete for those) but for features such as rich presence
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class ECommonSessionInformationState : uint8
|
||||
{
|
||||
OutOfGame,
|
||||
Matchmaking,
|
||||
InGame
|
||||
};
|
||||
DECLARE_MULTICAST_DELEGATE_ThreeParams(FCommonSessionOnSessionInformationChanged, ECommonSessionInformationState /*SessionStatus*/, const FString& /*GameMode*/, const FString& /*MapName*/);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FCommonSessionOnSessionInformationChanged_Dynamic, ECommonSessionInformationState, SessionStatus, const FString&, GameMode, const FString&, MapName);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// UCommonSessionSubsystem
|
||||
|
||||
/**
|
||||
* Game subsystem that handles requests for hosting and joining online games.
|
||||
* One subsystem is created for each game instance and can be accessed from blueprints or C++ code.
|
||||
* If a game-specific subclass exists, this base subsystem will not be created.
|
||||
*/
|
||||
UCLASS(BlueprintType, Config=Engine)
|
||||
class COMMONUSER_API UCommonSessionSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UCommonSessionSubsystem() { }
|
||||
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
|
||||
|
||||
/** Creates a host session request with default options for online games, this can be modified after creation */
|
||||
UFUNCTION(BlueprintCallable, Category = Session)
|
||||
virtual UCommonSession_HostSessionRequest* CreateOnlineHostSessionRequest();
|
||||
|
||||
/** Creates a session search object with default options to look for default online games, this can be modified after creation */
|
||||
UFUNCTION(BlueprintCallable, Category = Session)
|
||||
virtual UCommonSession_SearchSessionRequest* CreateOnlineSearchSessionRequest();
|
||||
|
||||
/** Creates a new online game using the session request information, if successful this will start a hard map transfer */
|
||||
UFUNCTION(BlueprintCallable, Category=Session)
|
||||
virtual void HostSession(APlayerController* HostingPlayer, UCommonSession_HostSessionRequest* Request);
|
||||
|
||||
/** Starts a process to look for existing sessions or create a new one if no viable sessions are found */
|
||||
UFUNCTION(BlueprintCallable, Category=Session)
|
||||
virtual void QuickPlaySession(APlayerController* JoiningOrHostingPlayer, UCommonSession_HostSessionRequest* Request);
|
||||
|
||||
/** Starts process to join an existing session, if successful this will connect to the specified server */
|
||||
UFUNCTION(BlueprintCallable, Category=Session)
|
||||
virtual void JoinSession(APlayerController* JoiningPlayer, UCommonSession_SearchResult* Request);
|
||||
|
||||
/** Queries online system for the list of joinable sessions matching the search request */
|
||||
UFUNCTION(BlueprintCallable, Category=Session)
|
||||
virtual void FindSessions(APlayerController* SearchingPlayer, UCommonSession_SearchSessionRequest* Request);
|
||||
|
||||
/** Clean up any active sessions, called from cases like returning to the main menu */
|
||||
UFUNCTION(BlueprintCallable, Category=Session)
|
||||
virtual void CleanUpSessions();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Events
|
||||
|
||||
/** Native Delegate when a local user has accepted an invite */
|
||||
FCommonSessionOnUserRequestedSession OnUserRequestedSessionEvent;
|
||||
/** Event broadcast when a local user has accepted an invite */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On User Requested Session"))
|
||||
FCommonSessionOnUserRequestedSession_Dynamic K2_OnUserRequestedSessionEvent;
|
||||
|
||||
/** Native Delegate when a JoinSession call has completed */
|
||||
FCommonSessionOnJoinSessionComplete OnJoinSessionCompleteEvent;
|
||||
/** Event broadcast when a JoinSession call has completed */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Join Session Complete"))
|
||||
FCommonSessionOnJoinSessionComplete_Dynamic K2_OnJoinSessionCompleteEvent;
|
||||
|
||||
/** Native Delegate when a CreateSession call has completed */
|
||||
FCommonSessionOnCreateSessionComplete OnCreateSessionCompleteEvent;
|
||||
/** Event broadcast when a CreateSession call has completed */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Create Session Complete"))
|
||||
FCommonSessionOnCreateSessionComplete_Dynamic K2_OnCreateSessionCompleteEvent;
|
||||
|
||||
/** Native Delegate when the presentable session information has changed */
|
||||
FCommonSessionOnSessionInformationChanged OnSessionInformationChangedEvent;
|
||||
/** Event broadcast when the presentable session information has changed */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Session Information Changed"))
|
||||
FCommonSessionOnSessionInformationChanged_Dynamic K2_OnSessionInformationChangedEvent;
|
||||
|
||||
/** Native Delegate when a platform session destroy has been requested */
|
||||
FCommonSessionOnDestroySessionRequested OnDestroySessionRequestedEvent;
|
||||
/** Event broadcast when a platform session destroy has been requested */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Leave Session Requested"))
|
||||
FCommonSessionOnDestroySessionRequested_Dynamic K2_OnDestroySessionRequestedEvent;
|
||||
|
||||
/** Native Delegate for modifying the connect URL prior to a client travel */
|
||||
FCommonSessionOnPreClientTravel OnPreClientTravelEvent;
|
||||
|
||||
// Config settings, these can overridden in child classes or config files
|
||||
|
||||
/** Sets the default value of bUseLobbies for session search and host requests */
|
||||
UPROPERTY(Config)
|
||||
bool bUseLobbiesDefault = true;
|
||||
|
||||
/** Sets the default value of bUseLobbiesVoiceChat for session host requests */
|
||||
UPROPERTY(Config)
|
||||
bool bUseLobbiesVoiceChatDefault = false;
|
||||
|
||||
/** Enables reservation beacon flow prior to server travel when creating or joining a game session */
|
||||
UPROPERTY(Config)
|
||||
bool bUseBeacons = true;
|
||||
|
||||
protected:
|
||||
// Functions called during the process of creating or joining a session, these can be overidden for game-specific behavior
|
||||
|
||||
/** Called to fill in a session request from quick play host settings, can be overridden for game-specific behavior */
|
||||
virtual TSharedRef<FCommonOnlineSearchSettings> CreateQuickPlaySearchSettings(UCommonSession_HostSessionRequest* Request, UCommonSession_SearchSessionRequest* QuickPlayRequest);
|
||||
|
||||
/** Called when a quick play search finishes, can be overridden for game-specific behavior */
|
||||
virtual void HandleQuickPlaySearchFinished(bool bSucceeded, const FText& ErrorMessage, TWeakObjectPtr<APlayerController> JoiningOrHostingPlayer, TStrongObjectPtr<UCommonSession_HostSessionRequest> HostRequest);
|
||||
|
||||
/** Called when traveling to a session fails */
|
||||
virtual void TravelLocalSessionFailure(UWorld* World, ETravelFailure::Type FailureType, const FString& ReasonString);
|
||||
|
||||
/** Called when a new session is either created or fails to be created */
|
||||
virtual void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);
|
||||
|
||||
/** Called to finalize session creation */
|
||||
virtual void FinishSessionCreation(bool bWasSuccessful);
|
||||
|
||||
/** Called after traveling to the new hosted session map */
|
||||
virtual void HandlePostLoadMap(UWorld* World);
|
||||
|
||||
protected:
|
||||
// Internal functions for initializing and handling results from the online systems
|
||||
|
||||
void BindOnlineDelegates();
|
||||
void CreateOnlineSessionInternal(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request);
|
||||
void FindSessionsInternal(APlayerController* SearchingPlayer, const TSharedRef<FCommonOnlineSearchSettings>& InSearchSettings);
|
||||
void JoinSessionInternal(ULocalPlayer* LocalPlayer, UCommonSession_SearchResult* Request);
|
||||
void InternalTravelToSession(const FName SessionName);
|
||||
void NotifyUserRequestedSession(const FPlatformUserId& PlatformUserId, UCommonSession_SearchResult* RequestedSession, const FOnlineResultInformation& RequestedSessionResult);
|
||||
void NotifyJoinSessionComplete(const FOnlineResultInformation& Result);
|
||||
void NotifyCreateSessionComplete(const FOnlineResultInformation& Result);
|
||||
void NotifySessionInformationUpdated(ECommonSessionInformationState SessionStatusStr, const FString& GameMode = FString(), const FString& MapName = FString());
|
||||
void NotifyDestroySessionRequested(const FPlatformUserId& PlatformUserId, const FName& SessionName);
|
||||
void SetCreateSessionError(const FText& ErrorText);
|
||||
|
||||
#if COMMONUSER_OSSV1
|
||||
void BindOnlineDelegatesOSSv1();
|
||||
void CreateOnlineSessionInternalOSSv1(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request);
|
||||
void FindSessionsInternalOSSv1(ULocalPlayer* LocalPlayer);
|
||||
void JoinSessionInternalOSSv1(ULocalPlayer* LocalPlayer, UCommonSession_SearchResult* Request);
|
||||
TSharedRef<FCommonOnlineSearchSettings> CreateQuickPlaySearchSettingsOSSv1(UCommonSession_HostSessionRequest* Request, UCommonSession_SearchSessionRequest* QuickPlayRequest);
|
||||
void CleanUpSessionsOSSv1();
|
||||
|
||||
void HandleSessionFailure(const FUniqueNetId& NetId, ESessionFailure::Type FailureType);
|
||||
void HandleSessionUserInviteAccepted(const bool bWasSuccessful, const int32 LocalUserIndex, FUniqueNetIdPtr AcceptingUserId, const FOnlineSessionSearchResult& SearchResult);
|
||||
void OnStartSessionComplete(FName SessionName, bool bWasSuccessful);
|
||||
void OnRegisterLocalPlayerComplete_CreateSession(const FUniqueNetId& PlayerId, EOnJoinSessionCompleteResult::Type Result);
|
||||
void OnUpdateSessionComplete(FName SessionName, bool bWasSuccessful);
|
||||
void OnEndSessionComplete(FName SessionName, bool bWasSuccessful);
|
||||
void OnDestroySessionComplete(FName SessionName, bool bWasSuccessful);
|
||||
void OnDestroySessionRequested(int32 LocalUserNum, FName SessionName);
|
||||
void OnFindSessionsComplete(bool bWasSuccessful);
|
||||
void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
|
||||
void OnRegisterJoiningLocalPlayerComplete(const FUniqueNetId& PlayerId, EOnJoinSessionCompleteResult::Type Result);
|
||||
void FinishJoinSession(EOnJoinSessionCompleteResult::Type Result);
|
||||
|
||||
#else
|
||||
void BindOnlineDelegatesOSSv2();
|
||||
void CreateOnlineSessionInternalOSSv2(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request);
|
||||
void FindSessionsInternalOSSv2(ULocalPlayer* LocalPlayer);
|
||||
void JoinSessionInternalOSSv2(ULocalPlayer* LocalPlayer, UCommonSession_SearchResult* Request);
|
||||
TSharedRef<FCommonOnlineSearchSettings> CreateQuickPlaySearchSettingsOSSv2(UCommonSession_HostSessionRequest* HostRequest, UCommonSession_SearchSessionRequest* SearchRequest);
|
||||
void CleanUpSessionsOSSv2();
|
||||
|
||||
/** Process a join request originating from the online service */
|
||||
void OnSessionJoinRequested(const UE::Online::FUILobbyJoinRequested& EventParams);
|
||||
|
||||
/** Get the local user id for a given controller */
|
||||
UE::Online::FAccountId GetAccountId(APlayerController* PlayerController) const;
|
||||
/** Get the lobby id for a given session name */
|
||||
UE::Online::FLobbyId GetLobbyId(const FName SessionName) const;
|
||||
/** Event handle for UI lobby join requested */
|
||||
UE::Online::FOnlineEventDelegateHandle LobbyJoinRequestedHandle;
|
||||
#endif // COMMONUSER_OSSV1
|
||||
|
||||
void CreateHostReservationBeacon();
|
||||
void ConnectToHostReservationBeacon();
|
||||
void DestroyHostReservationBeacon();
|
||||
|
||||
protected:
|
||||
/** The travel URL that will be used after session operations are complete */
|
||||
FString PendingTravelURL;
|
||||
|
||||
/** Most recent result information for a session creation attempt, stored here to allow storing error codes for later */
|
||||
FOnlineResultInformation CreateSessionResult;
|
||||
|
||||
/** True if we want to cancel the session after it is created */
|
||||
bool bWantToDestroyPendingSession = false;
|
||||
|
||||
/** True if this is a dedicated server, which doesn't require a LocalPlayer to create a session */
|
||||
bool bIsDedicatedServer = false;
|
||||
|
||||
/** Settings for the current search */
|
||||
TSharedPtr<FCommonOnlineSearchSettings> SearchSettings;
|
||||
|
||||
/** General beacon listener for registering beacons with */
|
||||
UPROPERTY(Transient)
|
||||
TWeakObjectPtr<AOnlineBeaconHost> BeaconHostListener;
|
||||
/** State of the beacon host */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UPartyBeaconState> ReservationBeaconHostState;
|
||||
/** Beacon controlling access to this game. */
|
||||
UPROPERTY(Transient)
|
||||
TWeakObjectPtr<APartyBeaconHost> ReservationBeaconHost;
|
||||
/** Common class object for beacon communication */
|
||||
UPROPERTY(Transient)
|
||||
TWeakObjectPtr<APartyBeaconClient> ReservationBeaconClient;
|
||||
|
||||
/** Number of teams for beacon reservation */
|
||||
UPROPERTY(Config)
|
||||
int32 BeaconTeamCount = 2;
|
||||
/** Size of a team for beacon reservation */
|
||||
UPROPERTY(Config)
|
||||
int32 BeaconTeamSize = 8;
|
||||
/** Max number of beacon reservations */
|
||||
UPROPERTY(Config)
|
||||
int32 BeaconMaxReservations = 16;
|
||||
};
|
@ -0,0 +1,59 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
#pragma once
|
||||
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "CommonUserBasicPresence.generated.h"
|
||||
|
||||
class UCommonSessionSubsystem;
|
||||
enum class ECommonSessionInformationState : uint8;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// UCommonUserBasicPresence
|
||||
|
||||
/**
|
||||
* This subsystem plugs into the session subsystem and pushes its information to the presence interface.
|
||||
* It is not intended to be a full featured rich presence implementation, but can be used as a proof-of-concept
|
||||
* for pushing information from the session subsystem to the presence system
|
||||
*/
|
||||
UCLASS(BlueprintType, Config = Engine)
|
||||
class COMMONUSER_API UCommonUserBasicPresence : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UCommonUserBasicPresence();
|
||||
|
||||
|
||||
/** Implement this for initialization of instances of the system */
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
|
||||
/** Implement this for deinitialization of instances of the system */
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
/** False is a general purpose killswitch to stop this class from pushing presence*/
|
||||
UPROPERTY(Config)
|
||||
bool bEnableSessionsBasedPresence = false;
|
||||
|
||||
/** Maps the presence status "In-game" to a backend key*/
|
||||
UPROPERTY(Config)
|
||||
FString PresenceStatusInGame;
|
||||
|
||||
/** Maps the presence status "Main Menu" to a backend key*/
|
||||
UPROPERTY(Config)
|
||||
FString PresenceStatusMainMenu;
|
||||
|
||||
/** Maps the presence status "Matchmaking" to a backend key*/
|
||||
UPROPERTY(Config)
|
||||
FString PresenceStatusMatchmaking;
|
||||
|
||||
/** Maps the "Game Mode" rich presence entry to a backend key*/
|
||||
UPROPERTY(Config)
|
||||
FString PresenceKeyGameMode;
|
||||
|
||||
/** Maps the "Map Name" rich presence entry to a backend key*/
|
||||
UPROPERTY(Config)
|
||||
FString PresenceKeyMapName;
|
||||
|
||||
void OnNotifySessionInformationChanged(ECommonSessionInformationState SessionStatus, const FString& GameMode, const FString& MapName);
|
||||
FString SessionStateToBackendKey(ECommonSessionInformationState SessionStatus);
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Modules/ModuleInterface.h"
|
||||
|
||||
class FCommonUserModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
@ -0,0 +1,649 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommonUserTypes.h"
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "GameFramework/OnlineReplStructs.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "UObject/WeakObjectPtr.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "CommonUserSubsystem.generated.h"
|
||||
|
||||
#if COMMONUSER_OSSV1
|
||||
#include "Interfaces/OnlineIdentityInterface.h"
|
||||
#include "OnlineError.h"
|
||||
#else
|
||||
#include "Online/OnlineAsyncOpHandle.h"
|
||||
#endif
|
||||
|
||||
class FNativeGameplayTag;
|
||||
class IOnlineSubsystem;
|
||||
|
||||
/** List of tags used by the common user subsystem */
|
||||
struct COMMONUSER_API FCommonUserTags
|
||||
{
|
||||
// General severity levels and specific system messages
|
||||
|
||||
static FNativeGameplayTag SystemMessage_Error; // SystemMessage.Error
|
||||
static FNativeGameplayTag SystemMessage_Warning; // SystemMessage.Warning
|
||||
static FNativeGameplayTag SystemMessage_Display; // SystemMessage.Display
|
||||
|
||||
/** All attempts to initialize a player failed, user has to do something before trying again */
|
||||
static FNativeGameplayTag SystemMessage_Error_InitializeLocalPlayerFailed; // SystemMessage.Error.InitializeLocalPlayerFailed
|
||||
|
||||
|
||||
// Platform trait tags, it is expected that the game instance or other system calls SetTraitTags with these tags for the appropriate platform
|
||||
|
||||
/** This tag means it is a console platform that directly maps controller IDs to different system users. If false, the same user can have multiple controllers */
|
||||
static FNativeGameplayTag Platform_Trait_RequiresStrictControllerMapping; // Platform.Trait.RequiresStrictControllerMapping
|
||||
|
||||
/** This tag means the platform has a single online user and all players use index 0 */
|
||||
static FNativeGameplayTag Platform_Trait_SingleOnlineUser; // Platform.Trait.SingleOnlineUser
|
||||
};
|
||||
|
||||
/** Logical representation of an individual user, one of these will exist for all initialized local players */
|
||||
UCLASS(BlueprintType)
|
||||
class COMMONUSER_API UCommonUserInfo : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Primary controller input device for this user, they could also have additional secondary devices */
|
||||
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
|
||||
FInputDeviceId PrimaryInputDevice;
|
||||
|
||||
/** Specifies the logical user on the local platform, guest users will point to the primary user */
|
||||
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
|
||||
FPlatformUserId PlatformUser;
|
||||
|
||||
/** If this user is assigned a LocalPlayer, this will match the index in the GameInstance localplayers array once it is fully created */
|
||||
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
|
||||
int32 LocalPlayerIndex = -1;
|
||||
|
||||
/** If true, this user is allowed to be a guest */
|
||||
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
|
||||
bool bCanBeGuest = false;
|
||||
|
||||
/** If true, this is a guest user attached to primary user 0 */
|
||||
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
|
||||
bool bIsGuest = false;
|
||||
|
||||
/** Overall state of the user's initialization process */
|
||||
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
|
||||
ECommonUserInitializationState InitializationState = ECommonUserInitializationState::Invalid;
|
||||
|
||||
/** Returns true if this user has successfully logged in */
|
||||
UFUNCTION(BlueprintCallable, Category = UserInfo)
|
||||
bool IsLoggedIn() const;
|
||||
|
||||
/** Returns true if this user is in the middle of logging in */
|
||||
UFUNCTION(BlueprintCallable, Category = UserInfo)
|
||||
bool IsDoingLogin() const;
|
||||
|
||||
/** Returns the most recently queries result for a specific privilege, will return unknown if never queried */
|
||||
UFUNCTION(BlueprintCallable, Category = UserInfo)
|
||||
ECommonUserPrivilegeResult GetCachedPrivilegeResult(ECommonUserPrivilege Privilege, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Ask about the general availability of a feature, this combines cached results with state */
|
||||
UFUNCTION(BlueprintCallable, Category = UserInfo)
|
||||
ECommonUserAvailability GetPrivilegeAvailability(ECommonUserPrivilege Privilege) const;
|
||||
|
||||
/** Returns the net id for the given context */
|
||||
UFUNCTION(BlueprintCallable, Category = UserInfo)
|
||||
FUniqueNetIdRepl GetNetId(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Returns the user's human readable nickname, this will return the value that was cached during UpdateCachedNetId or SetNickname */
|
||||
UFUNCTION(BlueprintCallable, Category = UserInfo)
|
||||
FString GetNickname(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Modify the user's human readable nickname, this can be used when setting up multiple guests but will get overwritten with the platform nickname for real users */
|
||||
UFUNCTION(BlueprintCallable, Category = UserInfo)
|
||||
void SetNickname(const FString& NewNickname, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game);
|
||||
|
||||
/** Returns an internal debug string for this player */
|
||||
UFUNCTION(BlueprintCallable, Category = UserInfo)
|
||||
FString GetDebugString() const;
|
||||
|
||||
/** Accessor for platform user id */
|
||||
FPlatformUserId GetPlatformUserId() const;
|
||||
|
||||
/** Gets the platform user index for older functions expecting an integer */
|
||||
int32 GetPlatformUserIndex() const;
|
||||
|
||||
// Internal data, only intended to be accessed by online subsystems
|
||||
|
||||
/** Cached data for each online system */
|
||||
struct FCachedData
|
||||
{
|
||||
/** Cached net id per system */
|
||||
FUniqueNetIdRepl CachedNetId;
|
||||
|
||||
/** Cached nickanem, updated whenever net ID might change */
|
||||
FString CachedNickname;
|
||||
|
||||
/** Cached values of various user privileges */
|
||||
TMap<ECommonUserPrivilege, ECommonUserPrivilegeResult> CachedPrivileges;
|
||||
};
|
||||
|
||||
/** Per context cache, game will always exist but others may not */
|
||||
TMap<ECommonUserOnlineContext, FCachedData> CachedDataMap;
|
||||
|
||||
/** Looks up cached data using resolution rules */
|
||||
FCachedData* GetCachedData(ECommonUserOnlineContext Context);
|
||||
const FCachedData* GetCachedData(ECommonUserOnlineContext Context) const;
|
||||
|
||||
/** Updates cached privilege results, will propagate to game if needed */
|
||||
void UpdateCachedPrivilegeResult(ECommonUserPrivilege Privilege, ECommonUserPrivilegeResult Result, ECommonUserOnlineContext Context);
|
||||
|
||||
/** Updates cached privilege results, will propagate to game if needed */
|
||||
void UpdateCachedNetId(const FUniqueNetIdRepl& NewId, ECommonUserOnlineContext Context);
|
||||
|
||||
/** Return the subsystem this is owned by */
|
||||
class UCommonUserSubsystem* GetSubsystem() const;
|
||||
};
|
||||
|
||||
|
||||
/** Delegates when initialization processes succeed or fail */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FCommonUserOnInitializeCompleteMulticast, const UCommonUserInfo*, UserInfo, bool, bSuccess, FText, Error, ECommonUserPrivilege, RequestedPrivilege, ECommonUserOnlineContext, OnlineContext);
|
||||
DECLARE_DYNAMIC_DELEGATE_FiveParams(FCommonUserOnInitializeComplete, const UCommonUserInfo*, UserInfo, bool, bSuccess, FText, Error, ECommonUserPrivilege, RequestedPrivilege, ECommonUserOnlineContext, OnlineContext);
|
||||
|
||||
/** Delegate when a system error message is sent, the game can choose to display it to the user using the type tag */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FCommonUserHandleSystemMessageDelegate, FGameplayTag, MessageType, FText, TitleText, FText, BodyText);
|
||||
|
||||
/** Delegate when a privilege changes, this can be bound to see if online status/etc changes during gameplay */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FCommonUserAvailabilityChangedDelegate, const UCommonUserInfo*, UserInfo, ECommonUserPrivilege, Privilege, ECommonUserAvailability, OldAvailability, ECommonUserAvailability, NewAvailability);
|
||||
|
||||
|
||||
/** Parameter struct for initialize functions, this would normally be filled in by wrapper functions like async nodes */
|
||||
USTRUCT(BlueprintType)
|
||||
struct COMMONUSER_API FCommonUserInitializeParams
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** What local player index to use, can specify one above current if can create player is enabled */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
|
||||
int32 LocalPlayerIndex = 0;
|
||||
|
||||
/** Deprecated method of selecting platform user and input device */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
|
||||
int32 ControllerId = -1;
|
||||
|
||||
/** Primary controller input device for this user, they could also have additional secondary devices */
|
||||
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
|
||||
FInputDeviceId PrimaryInputDevice;
|
||||
|
||||
/** Specifies the logical user on the local platform */
|
||||
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
|
||||
FPlatformUserId PlatformUser;
|
||||
|
||||
/** Generally either CanPlay or CanPlayOnline, specifies what level of privilege is required */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
|
||||
ECommonUserPrivilege RequestedPrivilege = ECommonUserPrivilege::CanPlay;
|
||||
|
||||
/** What specific online context to log in to, game means to login to all relevant ones */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
|
||||
ECommonUserOnlineContext OnlineContext = ECommonUserOnlineContext::Game;
|
||||
|
||||
/** True if this is allowed to create a new local player for initial login */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
|
||||
bool bCanCreateNewLocalPlayer = false;
|
||||
|
||||
/** True if this player can be a guest user without an actual online presence */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
|
||||
bool bCanUseGuestLogin = false;
|
||||
|
||||
/** True if we should not show login errors, the game will be responsible for displaying them */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
|
||||
bool bSuppressLoginErrors = false;
|
||||
|
||||
/** If bound, call this dynamic delegate at completion of login */
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
|
||||
FCommonUserOnInitializeComplete OnUserInitializeComplete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Game subsystem that handles queries and changes to user identity and login status.
|
||||
* One subsystem is created for each game instance and can be accessed from blueprints or C++ code.
|
||||
* If a game-specific subclass exists, this base subsystem will not be created.
|
||||
*/
|
||||
UCLASS(BlueprintType, Config=Engine)
|
||||
class COMMONUSER_API UCommonUserSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UCommonUserSubsystem() { }
|
||||
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
|
||||
|
||||
|
||||
/** BP delegate called when any requested initialization request completes */
|
||||
UPROPERTY(BlueprintAssignable, Category = CommonUser)
|
||||
FCommonUserOnInitializeCompleteMulticast OnUserInitializeComplete;
|
||||
|
||||
/** BP delegate called when the system sends an error/warning message */
|
||||
UPROPERTY(BlueprintAssignable, Category = CommonUser)
|
||||
FCommonUserHandleSystemMessageDelegate OnHandleSystemMessage;
|
||||
|
||||
/** BP delegate called when privilege availability changes for a user */
|
||||
UPROPERTY(BlueprintAssignable, Category = CommonUser)
|
||||
FCommonUserAvailabilityChangedDelegate OnUserPrivilegeChanged;
|
||||
|
||||
/** Send a system message via OnHandleSystemMessage */
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser)
|
||||
virtual void SendSystemMessage(FGameplayTag MessageType, FText TitleText, FText BodyText);
|
||||
|
||||
/** Sets the maximum number of local players, will not destroy existing ones */
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser)
|
||||
virtual void SetMaxLocalPlayers(int32 InMaxLocalPLayers);
|
||||
|
||||
/** Gets the maximum number of local players */
|
||||
UFUNCTION(BlueprintPure, Category = CommonUser)
|
||||
int32 GetMaxLocalPlayers() const;
|
||||
|
||||
/** Gets the current number of local players, will always be at least 1 */
|
||||
UFUNCTION(BlueprintPure, Category = CommonUser)
|
||||
int32 GetNumLocalPlayers() const;
|
||||
|
||||
/** Returns the state of initializing the specified local player */
|
||||
UFUNCTION(BlueprintPure, Category = CommonUser)
|
||||
ECommonUserInitializationState GetLocalPlayerInitializationState(int32 LocalPlayerIndex) const;
|
||||
|
||||
/** Returns the user info for a given local player index in game instance, 0 is always valid in a running game */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
|
||||
const UCommonUserInfo* GetUserInfoForLocalPlayerIndex(int32 LocalPlayerIndex) const;
|
||||
|
||||
/** Deprecated, use PlatformUserId when available */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
|
||||
const UCommonUserInfo* GetUserInfoForPlatformUserIndex(int32 PlatformUserIndex) const;
|
||||
|
||||
/** Returns the primary user info for a given platform user index. Can return null */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
|
||||
const UCommonUserInfo* GetUserInfoForPlatformUser(FPlatformUserId PlatformUser) const;
|
||||
|
||||
/** Returns the user info for a unique net id. Can return null */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
|
||||
const UCommonUserInfo* GetUserInfoForUniqueNetId(const FUniqueNetIdRepl& NetId) const;
|
||||
|
||||
/** Deprecated, use InputDeviceId when available */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
|
||||
const UCommonUserInfo* GetUserInfoForControllerId(int32 ControllerId) const;
|
||||
|
||||
/** Returns the user info for a given input device. Can return null */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
|
||||
const UCommonUserInfo* GetUserInfoForInputDevice(FInputDeviceId InputDevice) const;
|
||||
|
||||
/**
|
||||
* Tries to start the process of creating or updating a local player, including logging in and creating a player controller.
|
||||
* When the process has succeeded or failed, it will broadcast the OnUserInitializeComplete delegate.
|
||||
*
|
||||
* @param LocalPlayerIndex Desired index of LocalPlayer in Game Instance, 0 will be primary player and 1+ for local multiplayer
|
||||
* @param PrimaryInputDevice The physical controller that should be mapped to this user, will use the default device if invalid
|
||||
* @param bCanUseGuestLogin If true, this player can be a guest without a real Unique Net Id
|
||||
*
|
||||
* @returns true if the process was started, false if it failed before properly starting
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser)
|
||||
virtual bool TryToInitializeForLocalPlay(int32 LocalPlayerIndex, FInputDeviceId PrimaryInputDevice, bool bCanUseGuestLogin);
|
||||
|
||||
/**
|
||||
* Starts the process of taking a locally logged in user and doing a full online login including account permission checks.
|
||||
* When the process has succeeded or failed, it will broadcast the OnUserInitializeComplete delegate.
|
||||
*
|
||||
* @param LocalPlayerIndex Index of existing LocalPlayer in Game Instance
|
||||
*
|
||||
* @returns true if the process was started, false if it failed before properly starting
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser)
|
||||
virtual bool TryToLoginForOnlinePlay(int32 LocalPlayerIndex);
|
||||
|
||||
/**
|
||||
* Starts a general user login and initialization process, using the params structure to determine what to log in to.
|
||||
* When the process has succeeded or failed, it will broadcast the OnUserInitializeComplete delegate.
|
||||
* AsyncAction_CommonUserInitialize provides several wrapper functions for using this in an Event graph.
|
||||
*
|
||||
* @returns true if the process was started, false if it failed before properly starting
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser)
|
||||
virtual bool TryToInitializeUser(FCommonUserInitializeParams Params);
|
||||
|
||||
/**
|
||||
* Starts the process of listening for user input for new and existing controllers and logging them.
|
||||
* This will insert a key input handler on the active GameViewportClient and is turned off by calling again with empty key arrays.
|
||||
*
|
||||
* @param AnyUserKeys Listen for these keys for any user, even the default user. Set this for an initial press start screen or empty to disable
|
||||
* @param NewUserKeys Listen for these keys for a new user without a player controller. Set this for splitscreen/local multiplayer or empty to disable
|
||||
* @param Params Params passed to TryToInitializeUser after detecting key input
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser)
|
||||
virtual void ListenForLoginKeyInput(TArray<FKey> AnyUserKeys, TArray<FKey> NewUserKeys, FCommonUserInitializeParams Params);
|
||||
|
||||
/** Attempts to cancel an in-progress initialization attempt, this may not work on all platforms but will disable callbacks */
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser)
|
||||
virtual bool CancelUserInitialization(int32 LocalPlayerIndex);
|
||||
|
||||
/** Logs a player out of any online systems, and optionally destroys the player entirely if it's not the first one */
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser)
|
||||
virtual bool TryToLogOutUser(int32 LocalPlayerIndex, bool bDestroyPlayer = false);
|
||||
|
||||
/** Resets the login and initialization state when returning to the main menu after an error */
|
||||
UFUNCTION(BlueprintCallable, Category = CommonUser)
|
||||
virtual void ResetUserState();
|
||||
|
||||
/** Returns true if this this could be a real platform user with a valid identity (even if not currently logged in) */
|
||||
virtual bool IsRealPlatformUserIndex(int32 PlatformUserIndex) const;
|
||||
|
||||
/** Returns true if this this could be a real platform user with a valid identity (even if not currently logged in) */
|
||||
virtual bool IsRealPlatformUser(FPlatformUserId PlatformUser) const;
|
||||
|
||||
/** Converts index to id */
|
||||
virtual FPlatformUserId GetPlatformUserIdForIndex(int32 PlatformUserIndex) const;
|
||||
|
||||
/** Converts id to index */
|
||||
virtual int32 GetPlatformUserIndexForId(FPlatformUserId PlatformUser) const;
|
||||
|
||||
/** Gets the user for an input device */
|
||||
virtual FPlatformUserId GetPlatformUserIdForInputDevice(FInputDeviceId InputDevice) const;
|
||||
|
||||
/** Gets a user's primary input device id */
|
||||
virtual FInputDeviceId GetPrimaryInputDeviceForPlatformUser(FPlatformUserId PlatformUser) const;
|
||||
|
||||
/** Call from game code to set the cached trait tags when platform state or options changes */
|
||||
virtual void SetTraitTags(const FGameplayTagContainer& InTags);
|
||||
|
||||
/** Gets the current tags that affect feature avialability */
|
||||
const FGameplayTagContainer& GetTraitTags() const { return CachedTraitTags; }
|
||||
|
||||
/** Checks if a specific platform/feature tag is enabled */
|
||||
UFUNCTION(BlueprintPure, Category=CommonUser)
|
||||
bool HasTraitTag(const FGameplayTag TraitTag) const { return CachedTraitTags.HasTag(TraitTag); }
|
||||
|
||||
/** Checks to see if we should display a press start/input confirmation screen at startup. Games can call this or check the trait tags directly */
|
||||
UFUNCTION(BlueprintPure, BlueprintPure, Category=CommonUser)
|
||||
virtual bool ShouldWaitForStartInput() const;
|
||||
|
||||
|
||||
// Functions for accessing low-level online system information
|
||||
|
||||
#if COMMONUSER_OSSV1
|
||||
/** Returns OSS interface of specific type, will return null if there is no type */
|
||||
IOnlineSubsystem* GetOnlineSubsystem(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Returns identity interface of specific type, will return null if there is no type */
|
||||
IOnlineIdentity* GetOnlineIdentity(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Returns human readable name of OSS system */
|
||||
FName GetOnlineSubsystemName(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Returns the current online connection status */
|
||||
EOnlineServerConnectionStatus::Type GetConnectionStatus(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
#else
|
||||
/** Get the services provider type, or None if there isn't one. */
|
||||
UE::Online::EOnlineServices GetOnlineServicesProvider(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Returns auth interface of specific type, will return null if there is no type */
|
||||
UE::Online::IAuthPtr GetOnlineAuth(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Returns the current online connection status */
|
||||
UE::Online::EOnlineServicesConnectionStatus GetConnectionStatus(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
#endif
|
||||
|
||||
/** Returns true if we are currently connected to backend servers */
|
||||
bool HasOnlineConnection(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Returns the current login status for a player on the specified online system, only works for real platform users */
|
||||
ELoginStatusType GetLocalUserLoginStatus(FPlatformUserId PlatformUser, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Returns the unique net id for a local platform user */
|
||||
FUniqueNetIdRepl GetLocalUserNetId(FPlatformUserId PlatformUser, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Returns the nickname for a local platform user, this is cached in common user Info */
|
||||
FString GetLocalUserNickname(FPlatformUserId PlatformUser, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
|
||||
/** Convert a user id to a debug string */
|
||||
FString PlatformUserIdToString(FPlatformUserId UserId);
|
||||
|
||||
/** Convert a context to a debug string */
|
||||
FString ECommonUserOnlineContextToString(ECommonUserOnlineContext Context);
|
||||
|
||||
/** Returns human readable string for privilege checks */
|
||||
virtual FText GetPrivilegeDescription(ECommonUserPrivilege Privilege) const;
|
||||
virtual FText GetPrivilegeResultDescription(ECommonUserPrivilegeResult Result) const;
|
||||
|
||||
/**
|
||||
* Starts the process of login for an existing local user, will return false if callback was not scheduled
|
||||
* This activates the low level state machine and does not modify the initialization state on user info
|
||||
*/
|
||||
DECLARE_DELEGATE_FiveParams(FOnLocalUserLoginCompleteDelegate, const UCommonUserInfo* /*UserInfo*/, ELoginStatusType /*NewStatus*/, FUniqueNetIdRepl /*NetId*/, const TOptional<FOnlineErrorType>& /*Error*/, ECommonUserOnlineContext /*Type*/);
|
||||
virtual bool LoginLocalUser(const UCommonUserInfo* UserInfo, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext Context, FOnLocalUserLoginCompleteDelegate OnComplete);
|
||||
|
||||
/** Assign a local player to a specific local user and call callbacks as needed */
|
||||
virtual void SetLocalPlayerUserInfo(ULocalPlayer* LocalPlayer, const UCommonUserInfo* UserInfo);
|
||||
|
||||
/** Resolves a context that has default behavior into a specific context */
|
||||
ECommonUserOnlineContext ResolveOnlineContext(ECommonUserOnlineContext Context) const;
|
||||
|
||||
/** True if there is a separate platform and service interface */
|
||||
bool HasSeparatePlatformContext() const;
|
||||
|
||||
protected:
|
||||
/** Internal structure that caches status and pointers for each online context */
|
||||
struct FOnlineContextCache
|
||||
{
|
||||
#if COMMONUSER_OSSV1
|
||||
/** Pointer to base subsystem, will stay valid as long as game instance does */
|
||||
IOnlineSubsystem* OnlineSubsystem = nullptr;
|
||||
|
||||
/** Cached identity system, this will always be valid */
|
||||
IOnlineIdentityPtr IdentityInterface;
|
||||
|
||||
/** Last connection status that was passed into the HandleNetworkConnectionStatusChanged hander */
|
||||
EOnlineServerConnectionStatus::Type CurrentConnectionStatus = EOnlineServerConnectionStatus::Normal;
|
||||
#else
|
||||
/** Online services, accessor to specific services */
|
||||
UE::Online::IOnlineServicesPtr OnlineServices;
|
||||
/** Cached auth service */
|
||||
UE::Online::IAuthPtr AuthService;
|
||||
/** Login status changed event handle */
|
||||
UE::Online::FOnlineEventDelegateHandle LoginStatusChangedHandle;
|
||||
/** Connection status changed event handle */
|
||||
UE::Online::FOnlineEventDelegateHandle ConnectionStatusChangedHandle;
|
||||
/** Last connection status that was passed into the HandleNetworkConnectionStatusChanged hander */
|
||||
UE::Online::EOnlineServicesConnectionStatus CurrentConnectionStatus = UE::Online::EOnlineServicesConnectionStatus::NotConnected;
|
||||
#endif
|
||||
|
||||
/** Resets state, important to clear all shared ptrs */
|
||||
void Reset()
|
||||
{
|
||||
#if COMMONUSER_OSSV1
|
||||
OnlineSubsystem = nullptr;
|
||||
IdentityInterface.Reset();
|
||||
CurrentConnectionStatus = EOnlineServerConnectionStatus::Normal;
|
||||
#else
|
||||
OnlineServices.Reset();
|
||||
AuthService.Reset();
|
||||
CurrentConnectionStatus = UE::Online::EOnlineServicesConnectionStatus::NotConnected;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
/** Internal structure to represent an in-progress login request */
|
||||
struct FUserLoginRequest : public TSharedFromThis<FUserLoginRequest>
|
||||
{
|
||||
FUserLoginRequest(UCommonUserInfo* InUserInfo, ECommonUserPrivilege InPrivilege, ECommonUserOnlineContext InContext, FOnLocalUserLoginCompleteDelegate&& InDelegate)
|
||||
: UserInfo(TWeakObjectPtr<UCommonUserInfo>(InUserInfo))
|
||||
, DesiredPrivilege(InPrivilege)
|
||||
, DesiredContext(InContext)
|
||||
, Delegate(MoveTemp(InDelegate))
|
||||
{}
|
||||
|
||||
/** Which local user is trying to log on */
|
||||
TWeakObjectPtr<UCommonUserInfo> UserInfo;
|
||||
|
||||
/** Overall state of login request, could come from many sources */
|
||||
ECommonUserAsyncTaskState OverallLoginState = ECommonUserAsyncTaskState::NotStarted;
|
||||
|
||||
/** State of attempt to use platform auth. When started, this immediately transitions to Failed for OSSv1, as we do not support platform auth there. */
|
||||
ECommonUserAsyncTaskState TransferPlatformAuthState = ECommonUserAsyncTaskState::NotStarted;
|
||||
|
||||
/** State of attempt to use AutoLogin */
|
||||
ECommonUserAsyncTaskState AutoLoginState = ECommonUserAsyncTaskState::NotStarted;
|
||||
|
||||
/** State of attempt to use external login UI */
|
||||
ECommonUserAsyncTaskState LoginUIState = ECommonUserAsyncTaskState::NotStarted;
|
||||
|
||||
/** Final privilege to that is requested */
|
||||
ECommonUserPrivilege DesiredPrivilege = ECommonUserPrivilege::Invalid_Count;
|
||||
|
||||
/** State of attempt to request the relevant privilege */
|
||||
ECommonUserAsyncTaskState PrivilegeCheckState = ECommonUserAsyncTaskState::NotStarted;
|
||||
|
||||
/** The final context to log into */
|
||||
ECommonUserOnlineContext DesiredContext = ECommonUserOnlineContext::Invalid;
|
||||
|
||||
/** What online system we are currently logging into */
|
||||
ECommonUserOnlineContext CurrentContext = ECommonUserOnlineContext::Invalid;
|
||||
|
||||
/** User callback for completion */
|
||||
FOnLocalUserLoginCompleteDelegate Delegate;
|
||||
|
||||
/** Most recent/relevant error to display to user */
|
||||
TOptional<FOnlineErrorType> Error;
|
||||
};
|
||||
|
||||
|
||||
/** Create a new user info object */
|
||||
virtual UCommonUserInfo* CreateLocalUserInfo(int32 LocalPlayerIndex);
|
||||
|
||||
/** Deconst wrapper for const getters */
|
||||
FORCEINLINE UCommonUserInfo* ModifyInfo(const UCommonUserInfo* Info) { return const_cast<UCommonUserInfo*>(Info); }
|
||||
|
||||
/** Refresh user info from OSS */
|
||||
virtual void RefreshLocalUserInfo(UCommonUserInfo* UserInfo);
|
||||
|
||||
/** Possibly send privilege availability notification, compares current value to cached old value */
|
||||
virtual void HandleChangedAvailability(UCommonUserInfo* UserInfo, ECommonUserPrivilege Privilege, ECommonUserAvailability OldAvailability);
|
||||
|
||||
/** Updates the cached privilege on a user and notifies delegate */
|
||||
virtual void UpdateUserPrivilegeResult(UCommonUserInfo* UserInfo, ECommonUserPrivilege Privilege, ECommonUserPrivilegeResult Result, ECommonUserOnlineContext Context);
|
||||
|
||||
/** Gets internal data for a type of online system, can return null for service */
|
||||
const FOnlineContextCache* GetContextCache(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
|
||||
FOnlineContextCache* GetContextCache(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game);
|
||||
|
||||
/** Create and set up system objects before delegates are bound */
|
||||
virtual void CreateOnlineContexts();
|
||||
virtual void DestroyOnlineContexts();
|
||||
|
||||
/** Bind online delegates */
|
||||
virtual void BindOnlineDelegates();
|
||||
|
||||
/** Forcibly logs out and deinitializes a single user */
|
||||
virtual void LogOutLocalUser(FPlatformUserId PlatformUser);
|
||||
|
||||
/** Performs the next step of a login request, which could include completing it. Returns true if it's done */
|
||||
virtual void ProcessLoginRequest(TSharedRef<FUserLoginRequest> Request);
|
||||
|
||||
/** Call login on OSS, with platform auth from the platform OSS. Return true if AutoLogin started */
|
||||
virtual bool TransferPlatformAuth(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
|
||||
/** Call AutoLogin on OSS. Return true if AutoLogin started. */
|
||||
virtual bool AutoLogin(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
|
||||
/** Call ShowLoginUI on OSS. Return true if ShowLoginUI started. */
|
||||
virtual bool ShowLoginUI(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
|
||||
/** Call QueryUserPrivilege on OSS. Return true if QueryUserPrivilege started. */
|
||||
virtual bool QueryUserPrivilege(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
|
||||
/** OSS-specific functions */
|
||||
#if COMMONUSER_OSSV1
|
||||
virtual ECommonUserPrivilege ConvertOSSPrivilege(EUserPrivileges::Type Privilege) const;
|
||||
virtual EUserPrivileges::Type ConvertOSSPrivilege(ECommonUserPrivilege Privilege) const;
|
||||
virtual ECommonUserPrivilegeResult ConvertOSSPrivilegeResult(EUserPrivileges::Type Privilege, uint32 Results) const;
|
||||
|
||||
void BindOnlineDelegatesOSSv1();
|
||||
bool AutoLoginOSSv1(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
bool ShowLoginUIOSSv1(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
bool QueryUserPrivilegeOSSv1(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
#else
|
||||
virtual ECommonUserPrivilege ConvertOnlineServicesPrivilege(UE::Online::EUserPrivileges Privilege) const;
|
||||
virtual UE::Online::EUserPrivileges ConvertOnlineServicesPrivilege(ECommonUserPrivilege Privilege) const;
|
||||
virtual ECommonUserPrivilegeResult ConvertOnlineServicesPrivilegeResult(UE::Online::EUserPrivileges Privilege, UE::Online::EPrivilegeResults Results) const;
|
||||
|
||||
void BindOnlineDelegatesOSSv2();
|
||||
void CacheConnectionStatus(ECommonUserOnlineContext Context);
|
||||
bool TransferPlatformAuthOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
bool AutoLoginOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
bool ShowLoginUIOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
bool QueryUserPrivilegeOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
|
||||
TSharedPtr<UE::Online::FAccountInfo> GetOnlineServiceAccountInfo(UE::Online::IAuthPtr AuthService, FPlatformUserId InUserId) const;
|
||||
#endif
|
||||
|
||||
/** Callbacks for OSS functions */
|
||||
#if COMMONUSER_OSSV1
|
||||
virtual void HandleIdentityLoginStatusChanged(int32 PlatformUserIndex, ELoginStatus::Type OldStatus, ELoginStatus::Type NewStatus, const FUniqueNetId& NewId, ECommonUserOnlineContext Context);
|
||||
virtual void HandleUserLoginCompleted(int32 PlatformUserIndex, bool bWasSuccessful, const FUniqueNetId& NetId, const FString& Error, ECommonUserOnlineContext Context);
|
||||
virtual void HandleControllerPairingChanged(int32 PlatformUserIndex, FControllerPairingChangedUserInfo PreviousUser, FControllerPairingChangedUserInfo NewUser);
|
||||
virtual void HandleNetworkConnectionStatusChanged(const FString& ServiceName, EOnlineServerConnectionStatus::Type LastConnectionStatus, EOnlineServerConnectionStatus::Type ConnectionStatus, ECommonUserOnlineContext Context);
|
||||
virtual void HandleOnLoginUIClosed(TSharedPtr<const FUniqueNetId> LoggedInNetId, const int PlatformUserIndex, const FOnlineError& Error, ECommonUserOnlineContext Context);
|
||||
virtual void HandleCheckPrivilegesComplete(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults, ECommonUserPrivilege RequestedPrivilege, TWeakObjectPtr<UCommonUserInfo> CommonUserInfo, ECommonUserOnlineContext Context);
|
||||
#else
|
||||
virtual void HandleAuthLoginStatusChanged(const UE::Online::FAuthLoginStatusChanged& EventParameters, ECommonUserOnlineContext Context);
|
||||
virtual void HandleUserLoginCompletedV2(const UE::Online::TOnlineResult<UE::Online::FAuthLogin>& Result, FPlatformUserId PlatformUser, ECommonUserOnlineContext Context);
|
||||
virtual void HandleOnLoginUIClosedV2(const UE::Online::TOnlineResult<UE::Online::FExternalUIShowLoginUI>& Result, FPlatformUserId PlatformUser, ECommonUserOnlineContext Context);
|
||||
virtual void HandleNetworkConnectionStatusChanged(const UE::Online::FConnectionStatusChanged& EventParameters, ECommonUserOnlineContext Context);
|
||||
virtual void HandleCheckPrivilegesComplete(const UE::Online::TOnlineResult<UE::Online::FQueryUserPrivilege>& Result, TWeakObjectPtr<UCommonUserInfo> CommonUserInfo, UE::Online::EUserPrivileges DesiredPrivilege, ECommonUserOnlineContext Context);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Callback for when an input device (i.e. a gamepad) has been connected or disconnected.
|
||||
*/
|
||||
virtual void HandleInputDeviceConnectionChanged(EInputDeviceConnectionState NewConnectionState, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId);
|
||||
|
||||
virtual void HandleLoginForUserInitialize(const UCommonUserInfo* UserInfo, ELoginStatusType NewStatus, FUniqueNetIdRepl NetId, const TOptional<FOnlineErrorType>& Error, ECommonUserOnlineContext Context, FCommonUserInitializeParams Params);
|
||||
virtual void HandleUserInitializeFailed(FCommonUserInitializeParams Params, FText Error);
|
||||
virtual void HandleUserInitializeSucceeded(FCommonUserInitializeParams Params);
|
||||
|
||||
/** Callback for handling press start/login logic */
|
||||
virtual bool OverrideInputKeyForLogin(FInputKeyEventArgs& EventArgs);
|
||||
|
||||
|
||||
/** Previous override handler, will restore on cancel */
|
||||
FOverrideInputKeyHandler WrappedInputKeyHandler;
|
||||
|
||||
/** List of keys to listen for from any user */
|
||||
TArray<FKey> LoginKeysForAnyUser;
|
||||
|
||||
/** List of keys to listen for a new unmapped user */
|
||||
TArray<FKey> LoginKeysForNewUser;
|
||||
|
||||
/** Params to use for a key-triggered login */
|
||||
FCommonUserInitializeParams ParamsForLoginKey;
|
||||
|
||||
/** Maximum number of local players */
|
||||
int32 MaxNumberOfLocalPlayers = 0;
|
||||
|
||||
/** True if this is a dedicated server, which doesn't require a LocalPlayer */
|
||||
bool bIsDedicatedServer = false;
|
||||
|
||||
/** List of current in progress login requests */
|
||||
TArray<TSharedRef<FUserLoginRequest>> ActiveLoginRequests;
|
||||
|
||||
/** Information about each local user, from local player index to user */
|
||||
UPROPERTY()
|
||||
TMap<int32, TObjectPtr<UCommonUserInfo>> LocalUserInfos;
|
||||
|
||||
/** Cached platform/mode trait tags */
|
||||
FGameplayTagContainer CachedTraitTags;
|
||||
|
||||
/** Do not access this outside of initialization */
|
||||
FOnlineContextCache* DefaultContextInternal = nullptr;
|
||||
FOnlineContextCache* ServiceContextInternal = nullptr;
|
||||
FOnlineContextCache* PlatformContextInternal = nullptr;
|
||||
|
||||
friend UCommonUserInfo;
|
||||
};
|
218
Plugins/CommonUser/Source/CommonUser/Public/CommonUserTypes.h
Normal file
218
Plugins/CommonUser/Source/CommonUser/Public/CommonUserTypes.h
Normal file
@ -0,0 +1,218 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#if COMMONUSER_OSSV1
|
||||
|
||||
// Online Subsystem (OSS v1) includes and forward declares
|
||||
#include "OnlineSubsystemTypes.h"
|
||||
class IOnlineSubsystem;
|
||||
struct FOnlineError;
|
||||
using FOnlineErrorType = FOnlineError;
|
||||
using ELoginStatusType = ELoginStatus::Type;
|
||||
|
||||
#else
|
||||
|
||||
// Online Services (OSS v2) includes and forward declares
|
||||
#include "Online/Connectivity.h"
|
||||
#include "Online/OnlineError.h"
|
||||
namespace UE::Online
|
||||
{
|
||||
enum class ELoginStatus : uint8;
|
||||
enum class EPrivilegeResults : uint32;
|
||||
enum class EUserPrivileges : uint8;
|
||||
using IAuthPtr = TSharedPtr<class IAuth>;
|
||||
using IOnlineServicesPtr = TSharedPtr<class IOnlineServices>;
|
||||
template <typename OpType>
|
||||
class TOnlineResult;
|
||||
struct FAuthLogin;
|
||||
struct FConnectionStatusChanged;
|
||||
struct FExternalUIShowLoginUI;
|
||||
struct FAuthLoginStatusChanged;
|
||||
struct FQueryUserPrivilege;
|
||||
struct FAccountInfo;
|
||||
}
|
||||
using FOnlineErrorType = UE::Online::FOnlineError;
|
||||
using ELoginStatusType = UE::Online::ELoginStatus;
|
||||
|
||||
#endif
|
||||
|
||||
#include "CommonUserTypes.generated.h"
|
||||
|
||||
|
||||
/** Enum specifying where and how to run online queries */
|
||||
UENUM(BlueprintType)
|
||||
enum class ECommonUserOnlineContext : uint8
|
||||
{
|
||||
/** Called from game code, this uses the default system but with special handling that could merge results from multiple contexts */
|
||||
Game,
|
||||
|
||||
/** The default engine online system, this will always exist and will be the same as either Service or Platform */
|
||||
Default,
|
||||
|
||||
/** Explicitly ask for the external service, which may not exist */
|
||||
Service,
|
||||
|
||||
/** Looks for external service first, then falls back to default */
|
||||
ServiceOrDefault,
|
||||
|
||||
/** Explicitly ask for the platform system, which may not exist */
|
||||
Platform,
|
||||
|
||||
/** Looks for platform system first, then falls back to default */
|
||||
PlatformOrDefault,
|
||||
|
||||
/** Invalid system */
|
||||
Invalid
|
||||
};
|
||||
|
||||
/** Enum describing the state of initialization for a specific user */
|
||||
UENUM(BlueprintType)
|
||||
enum class ECommonUserInitializationState : uint8
|
||||
{
|
||||
/** User has not started login process */
|
||||
Unknown,
|
||||
|
||||
/** Player is in the process of acquiring a user id with local login */
|
||||
DoingInitialLogin,
|
||||
|
||||
/** Player is performing the network login, they have already logged in locally */
|
||||
DoingNetworkLogin,
|
||||
|
||||
/** Player failed to log in at all */
|
||||
FailedtoLogin,
|
||||
|
||||
|
||||
/** Player is logged in and has access to online functionality */
|
||||
LoggedInOnline,
|
||||
|
||||
/** Player is logged in locally (either guest or real user), but cannot perform online actions */
|
||||
LoggedInLocalOnly,
|
||||
|
||||
|
||||
/** Invalid state or user */
|
||||
Invalid,
|
||||
};
|
||||
|
||||
/** Enum specifying different privileges and capabilities available to a user */
|
||||
UENUM(BlueprintType)
|
||||
enum class ECommonUserPrivilege : uint8
|
||||
{
|
||||
/** Whether the user can play at all, online or offline */
|
||||
CanPlay,
|
||||
|
||||
/** Whether the user can play in online modes */
|
||||
CanPlayOnline,
|
||||
|
||||
/** Whether the user can use text chat */
|
||||
CanCommunicateViaTextOnline,
|
||||
|
||||
/** Whether the user can use voice chat */
|
||||
CanCommunicateViaVoiceOnline,
|
||||
|
||||
/** Whether the user can access content generated by other users */
|
||||
CanUseUserGeneratedContent,
|
||||
|
||||
/** Whether the user can ever participate in cross-play */
|
||||
CanUseCrossPlay,
|
||||
|
||||
/** Invalid privilege (also the count of valid ones) */
|
||||
Invalid_Count UMETA(Hidden)
|
||||
};
|
||||
|
||||
/** Enum specifying the general availability of a feature or privilege, this combines information from multiple sources */
|
||||
UENUM(BlueprintType)
|
||||
enum class ECommonUserAvailability : uint8
|
||||
{
|
||||
/** State is completely unknown and needs to be queried */
|
||||
Unknown,
|
||||
|
||||
/** This feature is fully available for use right now */
|
||||
NowAvailable,
|
||||
|
||||
/** This might be available after the completion of normal login procedures */
|
||||
PossiblyAvailable,
|
||||
|
||||
/** This feature is not available now because of something like network connectivity but may be available in the future */
|
||||
CurrentlyUnavailable,
|
||||
|
||||
/** This feature will never be available for the rest of this session due to hard account or platform restrictions */
|
||||
AlwaysUnavailable,
|
||||
|
||||
/** Invalid feature */
|
||||
Invalid,
|
||||
};
|
||||
|
||||
/** Enum giving specific reasons why a user may or may not use a certain privilege */
|
||||
UENUM(BlueprintType)
|
||||
enum class ECommonUserPrivilegeResult : uint8
|
||||
{
|
||||
/** State is unknown and needs to be queried */
|
||||
Unknown,
|
||||
|
||||
/** This privilege is fully available for use */
|
||||
Available,
|
||||
|
||||
/** User has not fully logged in */
|
||||
UserNotLoggedIn,
|
||||
|
||||
/** User does not own the game or content */
|
||||
LicenseInvalid,
|
||||
|
||||
/** The game needs to be updated or patched before this will be available */
|
||||
VersionOutdated,
|
||||
|
||||
/** No network connection, this may be resolved by reconnecting */
|
||||
NetworkConnectionUnavailable,
|
||||
|
||||
/** Parental control failure */
|
||||
AgeRestricted,
|
||||
|
||||
/** Account does not have a required subscription or account type */
|
||||
AccountTypeRestricted,
|
||||
|
||||
/** Another account/user restriction such as being banned by the service */
|
||||
AccountUseRestricted,
|
||||
|
||||
/** Other platform-specific failure */
|
||||
PlatformFailure,
|
||||
};
|
||||
|
||||
/** Used to track the progress of different asynchronous operations */
|
||||
enum class ECommonUserAsyncTaskState : uint8
|
||||
{
|
||||
/** The task has not been started */
|
||||
NotStarted,
|
||||
/** The task is currently being processed */
|
||||
InProgress,
|
||||
/** The task has completed successfully */
|
||||
Done,
|
||||
/** The task failed to complete */
|
||||
Failed
|
||||
};
|
||||
|
||||
/** Detailed information about the online error. Effectively a wrapper for FOnlineError. */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FOnlineResultInformation
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Whether the operation was successful or not. If it was successful, the error fields of this struct will not contain extra information. */
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
bool bWasSuccessful = true;
|
||||
|
||||
/** The unique error id. Can be used to compare against specific handled errors. */
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FString ErrorId;
|
||||
|
||||
/** Error text to display to the user. */
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FText ErrorText;
|
||||
|
||||
/**
|
||||
* Initialize this from an FOnlineErrorType
|
||||
* @param InOnlineError the online error to initialize from
|
||||
*/
|
||||
void COMMONUSER_API FromOnlineError(const FOnlineErrorType& InOnlineError);
|
||||
};
|
@ -9,7 +9,7 @@ public class OLSAnimation : ModuleRules
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
|
||||
@ -21,8 +21,15 @@ public class OLSAnimation : ModuleRules
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"AnimGraphRuntime",
|
||||
"GameplayTags"
|
||||
"GameplayTags",
|
||||
"GameplayAbilities",
|
||||
}
|
||||
);
|
||||
|
||||
// Header files path
|
||||
PublicIncludePaths.AddRange(new[] { "OLSAnimation/Public" });
|
||||
|
||||
// Source files path
|
||||
PrivateIncludePaths.AddRange(new[] { "OLSAnimation/Private" });
|
||||
}
|
||||
}
|
@ -17,6 +17,22 @@
|
||||
static TAutoConsoleVariable<bool> CVarAnimInstanceLocomotionDebug(TEXT("a.AnimInstance.Locomotion.Debug"), false, TEXT("Turn on visualization debugging for Locomotion"));
|
||||
#endif
|
||||
|
||||
void UOLSBaseLayerAnimInstance::InitializeWithAbilitySystem(UAbilitySystemComponent* asc)
|
||||
{
|
||||
check(asc);
|
||||
|
||||
GameplayTagPropertyMap.Initialize(this, asc);
|
||||
}
|
||||
|
||||
EDataValidationResult UOLSBaseLayerAnimInstance::IsDataValid(FDataValidationContext& context) const
|
||||
{
|
||||
Super::IsDataValid(context);
|
||||
|
||||
GameplayTagPropertyMap.IsDataValid(this, context);
|
||||
|
||||
return ((context.GetNumErrors() > 0) ? EDataValidationResult::Invalid : EDataValidationResult::Valid);
|
||||
}
|
||||
|
||||
void UOLSBaseLayerAnimInstance::NativeInitializeAnimation()
|
||||
{
|
||||
// Super::NativeInitializeAnimation();
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
#include "Data/OLSAnimationData.h"
|
||||
#include "Data/OLSLocomotionData.h"
|
||||
#include "GameplayEffectTypes.h"
|
||||
#include "OLSBaseLayerAnimInstance.generated.h"
|
||||
|
||||
#if ENABLE_ANIM_DEBUG && ENABLE_VISUAL_LOG
|
||||
@ -24,6 +25,16 @@ class OLSANIMATION_API UOLSBaseLayerAnimInstance : public UAnimInstance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
virtual void InitializeWithAbilitySystem(class UAbilitySystemComponent* asc);
|
||||
|
||||
protected:
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual EDataValidationResult IsDataValid(class FDataValidationContext& context) const override;
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
protected:
|
||||
|
||||
//~ Begin UAnimInstance overrides
|
||||
@ -432,6 +443,14 @@ protected:
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Settings|GameplayTagBindings")
|
||||
FGameplayTag MeleeGameplayTag = FGameplayTag::EmptyTag;
|
||||
|
||||
protected:
|
||||
|
||||
// Gameplay tags that can be mapped to blueprint variables. The variables will automatically update as the tags are added or removed.
|
||||
// These should be used instead of manually querying for the gameplay tags.
|
||||
UPROPERTY(EditDefaultsOnly, Category = "GameplayTags")
|
||||
FGameplayTagBlueprintPropertyMap GameplayTagPropertyMap;
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
|
@ -16,6 +16,10 @@ public class OLSAnimationEditor : ModuleRules
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { "MessageLog" });
|
||||
|
||||
PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public/Nodes"));
|
||||
// Header files path
|
||||
PublicIncludePaths.AddRange(new[] { "OLSAnimationEditor/Public" });
|
||||
|
||||
// Source files path
|
||||
PrivateIncludePaths.AddRange(new[] { "OLSAnimationEditor/Private" });
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/Abilities/OLSAbilityCost.h"
|
||||
|
||||
UOLSAbilityCost::UOLSAbilityCost()
|
||||
{
|
||||
}
|
||||
|
||||
bool UOLSAbilityCost::CheckCost(const UOLSGameplayAbility* ability,
|
||||
const FGameplayAbilitySpecHandle handle,
|
||||
const FGameplayAbilityActorInfo* actorInfo,
|
||||
FGameplayTagContainer* optionalRelevantTags) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void UOLSAbilityCost::ApplyCost(const UOLSGameplayAbility* ability,
|
||||
const FGameplayAbilitySpecHandle handle,
|
||||
const FGameplayAbilityActorInfo* actorInfo,
|
||||
const FGameplayAbilityActivationInfo activationInfo)
|
||||
{
|
||||
}
|
||||
|
||||
bool UOLSAbilityCost::ShouldOnlyApplyCostOnHit() const
|
||||
{
|
||||
return bShouldOnlyApplyCostOnHit;
|
||||
}
|
@ -0,0 +1,575 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/Abilities/OLSGameplayAbility.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySimpleFailureMessage.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "AbilitySystem/Abilities/OLSAbilityCost.h"
|
||||
#include "AbilitySystemBlueprintLibrary.h"
|
||||
#include "AbilitySystemGlobals.h"
|
||||
#include "AbilitySystem/OLSGameplayEffectContext.h"
|
||||
#include "AbilitySystem/Interfaces/OLSAbilitySourceInterface.h"
|
||||
#include "Camera/OLSCameraMode.h"
|
||||
#include "Components/OLSHeroComponent.h"
|
||||
#include "GameFramework/GameplayMessageSubsystem.h"
|
||||
#include "Physics/OLSPhysicalMaterialWithTags.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameplayAbility)
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSGameplayAbility);
|
||||
|
||||
#define ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(FunctionName, ReturnValue) \
|
||||
{ \
|
||||
if (!ensure(IsInstantiated())) \
|
||||
{ \
|
||||
OLS_LOG(LogOLSGameplayAbility, Error, TEXT("%s: " #FunctionName " cannot be called on a non-instanced ability. Check the instancing policy."), *GetPathName()); \
|
||||
return ReturnValue; \
|
||||
} \
|
||||
}
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_ABILITY_SIMPLE_FAILURE_MESSAGE, "Ability.UserFacingSimpleActivateFail.Message");
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_ABILITY_PLAY_MONTAGE_FAILURE_MESSAGE, "Ability.PlayMontageOnActivateFail.Message");
|
||||
|
||||
UOLSGameplayAbility::UOLSGameplayAbility(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo;
|
||||
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
|
||||
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
|
||||
NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ClientOrServer;
|
||||
|
||||
ActivationPolicy = EOLSAbilityActivationPolicy::OnInputTriggered;
|
||||
ActivationGroup = EOLSAbilityActivationGroup::Independent;
|
||||
|
||||
bShouldLogCancellation = false;
|
||||
|
||||
ActiveCameraMode = nullptr;
|
||||
}
|
||||
|
||||
bool UOLSGameplayAbility::CanActivateAbility(const FGameplayAbilitySpecHandle handle,
|
||||
const FGameplayAbilityActorInfo* actorInfo,
|
||||
const FGameplayTagContainer* sourceTags,
|
||||
const FGameplayTagContainer* targetTags,
|
||||
FGameplayTagContainer* optionalRelevantTags) const
|
||||
{
|
||||
if (!actorInfo || !actorInfo->AbilitySystemComponent.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Super::CanActivateAbility(handle, actorInfo, sourceTags, targetTags, optionalRelevantTags))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//@TODO Possibly remove after setting up tag relationships
|
||||
UOLSAbilitySystemComponent* asc = CastChecked<UOLSAbilitySystemComponent>(actorInfo->AbilitySystemComponent.Get());
|
||||
if (asc->IsActivationGroupBlocked(ActivationGroup))
|
||||
{
|
||||
if (optionalRelevantTags)
|
||||
{
|
||||
// @TODO: Implement LyraGameplayTags::Ability_ActivateFail_ActivationGroup.
|
||||
// optionalRelevantTags->AddTag(LyraGameplayTags::Ability_ActivateFail_ActivationGroup);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::SetCanBeCanceled(bool canBeCanceled)
|
||||
{
|
||||
// The ability can not block canceling if it's replaceable.
|
||||
if (!canBeCanceled && (ActivationGroup == EOLSAbilityActivationGroup::Exclusive_Replaceable))
|
||||
{
|
||||
OLS_LOG(LogOLSGameplayAbility, Error,
|
||||
TEXT(
|
||||
"Ability [%s] can not block canceling because its activation group is replaceable."
|
||||
), *GetName());
|
||||
return;
|
||||
}
|
||||
|
||||
Super::SetCanBeCanceled(canBeCanceled);
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilitySpec& spec)
|
||||
{
|
||||
Super::OnGiveAbility(actorInfo, spec);
|
||||
|
||||
K2_OnAbilityAdded();
|
||||
|
||||
TryActivateAbilityOnSpawn(actorInfo, spec);
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::OnRemoveAbility(const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilitySpec& spec)
|
||||
{
|
||||
K2_OnAbilityRemoved();
|
||||
|
||||
Super::OnRemoveAbility(actorInfo, spec);
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
|
||||
const FGameplayAbilityActorInfo* ActorInfo,
|
||||
const FGameplayAbilityActivationInfo ActivationInfo,
|
||||
const FGameplayEventData* TriggerEventData)
|
||||
{
|
||||
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle,
|
||||
const FGameplayAbilityActorInfo* ActorInfo,
|
||||
const FGameplayAbilityActivationInfo ActivationInfo,
|
||||
bool bReplicateEndAbility, bool bWasCancelled)
|
||||
{
|
||||
ClearCameraMode();
|
||||
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
|
||||
}
|
||||
|
||||
bool UOLSGameplayAbility::CheckCost(const FGameplayAbilitySpecHandle handle,
|
||||
const FGameplayAbilityActorInfo* actorInfo,
|
||||
FGameplayTagContainer* optionalRelevantTags) const
|
||||
{
|
||||
if (!Super::CheckCost(handle, actorInfo, optionalRelevantTags) || !actorInfo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify we can afford any additional costs
|
||||
for (const TObjectPtr<UOLSAbilityCost>& additionalCost : AdditionalCosts)
|
||||
{
|
||||
if (additionalCost)
|
||||
{
|
||||
if (!additionalCost->CheckCost(this, handle, actorInfo, /*inout*/ optionalRelevantTags))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::ApplyCost(const FGameplayAbilitySpecHandle handle,
|
||||
const FGameplayAbilityActorInfo* actorInfo,
|
||||
const FGameplayAbilityActivationInfo activationInfo) const
|
||||
{
|
||||
check(actorInfo);
|
||||
|
||||
// Used to determine if the ability actually hit a target (as some costs are only spent on successful attempts)
|
||||
auto determineIfAbilityHitTarget = [&]()
|
||||
{
|
||||
if (actorInfo->IsNetAuthority())
|
||||
{
|
||||
if (UOLSAbilitySystemComponent* ASC = Cast<UOLSAbilitySystemComponent>(actorInfo->AbilitySystemComponent.Get()))
|
||||
{
|
||||
FGameplayAbilityTargetDataHandle targetData;
|
||||
ASC->GetAbilityTargetData(handle, activationInfo, targetData);
|
||||
for (int32 targetDataIdx = 0; targetDataIdx < targetData.Data.Num(); ++targetDataIdx)
|
||||
{
|
||||
if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(targetData, targetDataIdx))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Pay any additional costs
|
||||
bool hasAbilityHitTarget = false;
|
||||
bool hasDeterminedIfAbilityHitTarget = false;
|
||||
for (const TObjectPtr<UOLSAbilityCost>& additionalCost : AdditionalCosts)
|
||||
{
|
||||
if (additionalCost)
|
||||
{
|
||||
if (additionalCost->ShouldOnlyApplyCostOnHit())
|
||||
{
|
||||
if (!hasDeterminedIfAbilityHitTarget)
|
||||
{
|
||||
hasAbilityHitTarget = determineIfAbilityHitTarget();
|
||||
hasDeterminedIfAbilityHitTarget = true;
|
||||
}
|
||||
|
||||
if (!hasAbilityHitTarget)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
additionalCost->ApplyCost(this, handle, actorInfo, activationInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FGameplayEffectContextHandle UOLSGameplayAbility::MakeEffectContext(const FGameplayAbilitySpecHandle handle,
|
||||
const FGameplayAbilityActorInfo* actorInfo) const
|
||||
{
|
||||
FGameplayEffectContextHandle contextHandle = Super::MakeEffectContext(handle, actorInfo);
|
||||
|
||||
FOLSGameplayEffectContext* effectContext = FOLSGameplayEffectContext::ExtractEffectContext(contextHandle);
|
||||
check(effectContext);
|
||||
|
||||
check(actorInfo);
|
||||
|
||||
AActor* effectCauser = nullptr;
|
||||
const IOLSAbilitySourceInterface* abilitySource = nullptr;
|
||||
float sourceLevel = 0.0f;
|
||||
GetAbilitySource(handle, actorInfo, /*out*/ sourceLevel, /*out*/ abilitySource, /*out*/ effectCauser);
|
||||
|
||||
UObject* sourceObject = GetSourceObject(handle, actorInfo);
|
||||
|
||||
AActor* instigator = actorInfo ? actorInfo->OwnerActor.Get() : nullptr;
|
||||
|
||||
effectContext->SetAbilitySource(abilitySource, sourceLevel);
|
||||
effectContext->AddInstigator(instigator, effectCauser);
|
||||
effectContext->AddSourceObject(sourceObject);
|
||||
|
||||
return contextHandle;
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::ApplyAbilityTagsToGameplayEffectSpec(FGameplayEffectSpec& spec,
|
||||
FGameplayAbilitySpec* abilitySpec) const
|
||||
{
|
||||
Super::ApplyAbilityTagsToGameplayEffectSpec(spec, abilitySpec);
|
||||
|
||||
if (const FHitResult* hitResult = spec.GetContext().GetHitResult())
|
||||
{
|
||||
if (const UOLSPhysicalMaterialWithTags* physMatWithTags = Cast<const UOLSPhysicalMaterialWithTags>(hitResult->PhysMaterial.Get()))
|
||||
{
|
||||
spec.CapturedTargetTags.GetSpecTags().AppendTags(physMatWithTags->Tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UOLSGameplayAbility::DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& abilitySystemComponent,
|
||||
const FGameplayTagContainer* sourceTags,
|
||||
const FGameplayTagContainer* targetTags,
|
||||
FGameplayTagContainer* optionalRelevantTags) const
|
||||
{
|
||||
// Specialized version to handle death exclusion and AbilityTags expansion via ASC
|
||||
|
||||
bool isBlocked = false;
|
||||
bool isMissing = false;
|
||||
|
||||
UAbilitySystemGlobals& abilitySystemGlobals = UAbilitySystemGlobals::Get();
|
||||
const FGameplayTag& blockedTag = abilitySystemGlobals.ActivateFailTagsBlockedTag;
|
||||
const FGameplayTag& missingTag = abilitySystemGlobals.ActivateFailTagsMissingTag;
|
||||
|
||||
// Check if any of this ability's tags are currently blocked
|
||||
if (abilitySystemComponent.AreAbilityTagsBlocked(GetAssetTags()))
|
||||
{
|
||||
isBlocked = true;
|
||||
}
|
||||
|
||||
const UOLSAbilitySystemComponent* asc = Cast<UOLSAbilitySystemComponent>(&abilitySystemComponent);
|
||||
static FGameplayTagContainer allRequiredTags;
|
||||
static FGameplayTagContainer allBlockedTags;
|
||||
|
||||
allRequiredTags = ActivationRequiredTags;
|
||||
allBlockedTags = ActivationBlockedTags;
|
||||
|
||||
// Expand our ability tags to add additional required/blocked tags
|
||||
if (asc)
|
||||
{
|
||||
asc->GetAdditionalActivationTagRequirements(GetAssetTags(), allRequiredTags, allBlockedTags);
|
||||
}
|
||||
|
||||
// Check to see the required/blocked tags for this ability
|
||||
if (allBlockedTags.Num() || allRequiredTags.Num())
|
||||
{
|
||||
static FGameplayTagContainer abilitySystemComponentTags;
|
||||
|
||||
abilitySystemComponentTags.Reset();
|
||||
abilitySystemComponent.GetOwnedGameplayTags(abilitySystemComponentTags);
|
||||
|
||||
if (abilitySystemComponentTags.HasAny(allBlockedTags))
|
||||
{
|
||||
// @TODO: Implement LyraGameplayTags::Status_Death.
|
||||
// @TODO: Implement LyraGameplayTags::Ability_ActivateFail_IsDead.
|
||||
// if (optionalRelevantTags && abilitySystemComponentTags.HasTag(LyraGameplayTags::Status_Death))
|
||||
// {
|
||||
// // If player is dead and was rejected due to blocking tags, give that feedback
|
||||
// optionalRelevantTags->AddTag(LyraGameplayTags::Ability_ActivateFail_IsDead);
|
||||
// }
|
||||
|
||||
isBlocked = true;
|
||||
}
|
||||
|
||||
if (!abilitySystemComponentTags.HasAll(allRequiredTags))
|
||||
{
|
||||
isMissing = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceTags != nullptr)
|
||||
{
|
||||
if (SourceBlockedTags.Num() || SourceRequiredTags.Num())
|
||||
{
|
||||
if (sourceTags->HasAny(SourceBlockedTags))
|
||||
{
|
||||
isBlocked = true;
|
||||
}
|
||||
|
||||
if (!sourceTags->HasAll(SourceRequiredTags))
|
||||
{
|
||||
isMissing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetTags != nullptr)
|
||||
{
|
||||
if (TargetBlockedTags.Num() || TargetRequiredTags.Num())
|
||||
{
|
||||
if (targetTags->HasAny(TargetBlockedTags))
|
||||
{
|
||||
isBlocked = true;
|
||||
}
|
||||
|
||||
if (!targetTags->HasAll(TargetRequiredTags))
|
||||
{
|
||||
isMissing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isBlocked)
|
||||
{
|
||||
if (optionalRelevantTags && blockedTag.IsValid())
|
||||
{
|
||||
optionalRelevantTags->AddTag(blockedTag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (isMissing)
|
||||
{
|
||||
if (optionalRelevantTags && missingTag.IsValid())
|
||||
{
|
||||
optionalRelevantTags->AddTag(missingTag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UOLSAbilitySystemComponent* UOLSGameplayAbility::GetOLSAbilitySystemComponentFromActorInfo() const
|
||||
{
|
||||
return (CurrentActorInfo ? Cast<UOLSAbilitySystemComponent>(CurrentActorInfo->AbilitySystemComponent.Get()) : nullptr);
|
||||
}
|
||||
|
||||
AController* UOLSGameplayAbility::GetControllerFromActorInfo() const
|
||||
{
|
||||
if (CurrentActorInfo)
|
||||
{
|
||||
if (AController* controller = CurrentActorInfo->PlayerController.Get())
|
||||
{
|
||||
return controller;
|
||||
}
|
||||
|
||||
// Look for a player controller or pawn in the owner chain.
|
||||
AActor* testActor = CurrentActorInfo->OwnerActor.Get();
|
||||
while (testActor)
|
||||
{
|
||||
if (AController* controller = Cast<AController>(testActor))
|
||||
{
|
||||
return controller;
|
||||
}
|
||||
|
||||
if (APawn* pawn = Cast<APawn>(testActor))
|
||||
{
|
||||
return pawn->GetController();
|
||||
}
|
||||
|
||||
testActor = testActor->GetOwner();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UOLSHeroComponent* UOLSGameplayAbility::GetHeroComponentFromActorInfo() const
|
||||
{
|
||||
return (CurrentActorInfo ? UOLSHeroComponent::FindHeroComponent(CurrentActorInfo->AvatarActor.Get()) : nullptr);
|
||||
}
|
||||
|
||||
bool UOLSGameplayAbility::CanChangeActivationGroup(EOLSAbilityActivationGroup newGroup) const
|
||||
{
|
||||
if (!IsInstantiated() || !IsActive())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ActivationGroup == newGroup)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
UOLSAbilitySystemComponent* asc = GetOLSAbilitySystemComponentFromActorInfo();
|
||||
check(asc);
|
||||
|
||||
if ((ActivationGroup != EOLSAbilityActivationGroup::Exclusive_Blocking) && asc->IsActivationGroupBlocked(newGroup))
|
||||
{
|
||||
// This ability can't change groups if it's blocked (unless it is the one doing the blocking).
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((newGroup == EOLSAbilityActivationGroup::Exclusive_Replaceable) && !CanBeCanceled())
|
||||
{
|
||||
// This ability can't become replaceable if it can't be canceled.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UOLSGameplayAbility::ChangeActivationGroup(EOLSAbilityActivationGroup newGroup)
|
||||
{
|
||||
ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(ChangeActivationGroup, false);
|
||||
|
||||
if (!CanChangeActivationGroup(newGroup))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ActivationGroup != newGroup)
|
||||
{
|
||||
UOLSAbilitySystemComponent* asc = GetOLSAbilitySystemComponentFromActorInfo();
|
||||
check(asc);
|
||||
|
||||
asc->RemoveAbilityFromActivationGroup(ActivationGroup, this);
|
||||
asc->AddAbilityToActivationGroup(newGroup, this);
|
||||
|
||||
ActivationGroup = newGroup;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::SetCameraMode(TSubclassOf<UOLSCameraMode> cameraMode)
|
||||
{
|
||||
ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(SetCameraMode, );
|
||||
|
||||
if (UOLSHeroComponent* heroComponent = GetHeroComponentFromActorInfo())
|
||||
{
|
||||
heroComponent->SetAbilityCameraMode(cameraMode, CurrentSpecHandle);
|
||||
ActiveCameraMode = cameraMode;
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::ClearCameraMode()
|
||||
{
|
||||
ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(ClearCameraMode, );
|
||||
|
||||
if (ActiveCameraMode)
|
||||
{
|
||||
if (UOLSHeroComponent* heroComponent = GetHeroComponentFromActorInfo())
|
||||
{
|
||||
heroComponent->ClearAbilityCameraMode(CurrentSpecHandle);
|
||||
}
|
||||
|
||||
ActiveCameraMode = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
EOLSAbilityActivationPolicy UOLSGameplayAbility::GetActivationPolicy() const
|
||||
{
|
||||
return ActivationPolicy;
|
||||
}
|
||||
|
||||
EOLSAbilityActivationGroup UOLSGameplayAbility::GetActivationGroup() const
|
||||
{
|
||||
return ActivationGroup;
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::TryActivateAbilityOnSpawn(const FGameplayAbilityActorInfo* actorInfo,
|
||||
const FGameplayAbilitySpec& spec) const
|
||||
{
|
||||
// Try to activate if activation policy is on spawn.
|
||||
if (actorInfo && !spec.IsActive() && (ActivationPolicy == EOLSAbilityActivationPolicy::OnSpawn))
|
||||
{
|
||||
UAbilitySystemComponent* asc = actorInfo->AbilitySystemComponent.Get();
|
||||
const AActor* avatarActor = actorInfo->AvatarActor.Get();
|
||||
|
||||
// If avatar actor is torn off or about to die, don't try to activate until we get the new one.
|
||||
if (asc && avatarActor && !avatarActor->GetTearOff() && (avatarActor->GetLifeSpan() <= 0.0f))
|
||||
{
|
||||
const bool isLocalExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalPredicted) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalOnly);
|
||||
const bool iServerExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerOnly) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerInitiated);
|
||||
|
||||
const bool shouldClientActivate = actorInfo->IsLocallyControlled() && isLocalExecution;
|
||||
const bool shouldServerActivate = actorInfo->IsNetAuthority() && iServerExecution;
|
||||
|
||||
if (shouldClientActivate || shouldServerActivate)
|
||||
{
|
||||
asc->TryActivateAbility(spec.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::OnAbilityFailedToActivate(const FGameplayTagContainer& failedReason) const
|
||||
{
|
||||
NativeOnAbilityFailedToActivate(failedReason);
|
||||
K2_OnAbilityFailedToActivate(failedReason);
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::OnPawnAvatarSet()
|
||||
{
|
||||
K2_OnPawnAvatarSet();
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::GetAbilitySource(FGameplayAbilitySpecHandle handle,
|
||||
const FGameplayAbilityActorInfo* actorInfo,
|
||||
float& outSourceLevel,
|
||||
const IOLSAbilitySourceInterface*& outAbilitySource,
|
||||
AActor*& outEffectCauser) const
|
||||
{
|
||||
outSourceLevel = 0.0f;
|
||||
outAbilitySource = nullptr;
|
||||
outEffectCauser = nullptr;
|
||||
|
||||
outEffectCauser = actorInfo->AvatarActor.Get();
|
||||
|
||||
// If we were added by something that's an ability info source, use it
|
||||
UObject* sourceObject = GetSourceObject(handle, actorInfo);
|
||||
|
||||
outAbilitySource = Cast<IOLSAbilitySourceInterface>(sourceObject);
|
||||
}
|
||||
|
||||
void UOLSGameplayAbility::NativeOnAbilityFailedToActivate(const FGameplayTagContainer& failedReason) const
|
||||
{
|
||||
bool simpleFailureFound = false;
|
||||
for (FGameplayTag reason : failedReason)
|
||||
{
|
||||
if (!simpleFailureFound)
|
||||
{
|
||||
if (const FText* pUserFacingMessage = FailureTagToUserFacingMessages.Find(reason))
|
||||
{
|
||||
FOLSAbilitySimpleFailureMessage message;
|
||||
message.PlayerController = GetActorInfo().PlayerController.Get();
|
||||
message.FailureTags = failedReason;
|
||||
message.UserFacingReason = *pUserFacingMessage;
|
||||
|
||||
UGameplayMessageSubsystem& messageSystem = UGameplayMessageSubsystem::Get(GetWorld());
|
||||
messageSystem.BroadcastMessage(TAG_ABILITY_SIMPLE_FAILURE_MESSAGE, message);
|
||||
simpleFailureFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (UAnimMontage* montage = FailureTagToAnimMontage.FindRef(reason))
|
||||
{
|
||||
FOLSAbilityMontageFailureMessage message;
|
||||
message.PlayerController = GetActorInfo().PlayerController.Get();
|
||||
message.AvatarActor = GetActorInfo().AvatarActor.Get();
|
||||
message.FailureTags = failedReason;
|
||||
message.FailureMontage = montage;
|
||||
|
||||
UGameplayMessageSubsystem& messageSystem = UGameplayMessageSubsystem::Get(GetWorld());
|
||||
messageSystem.BroadcastMessage(TAG_ABILITY_PLAY_MONTAGE_FAILURE_MESSAGE, message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/Attributes/OLSAttributeSetBase.h"
|
||||
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
|
||||
UOLSAttributeSetBase::UOLSAttributeSetBase()
|
||||
{
|
||||
}
|
||||
|
||||
UWorld* UOLSAttributeSetBase::GetWorld() const
|
||||
{
|
||||
const UObject* outer = GetOuter();
|
||||
check(outer);
|
||||
|
||||
return outer->GetWorld();
|
||||
}
|
||||
|
||||
UOLSAbilitySystemComponent* UOLSAttributeSetBase::GetOLSAbilitySystemComponent() const
|
||||
{
|
||||
return Cast<UOLSAbilitySystemComponent>(GetOwningAbilitySystemComponent());
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/Attributes/OLSCombatAttributeSet.h"
|
||||
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
UOLSCombatAttributeSet::UOLSCombatAttributeSet()
|
||||
: BaseDamage(0.0f)
|
||||
, BaseHeal(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
void UOLSCombatAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME_CONDITION_NOTIFY(ThisClass, BaseDamage, COND_OwnerOnly, REPNOTIFY_Always);
|
||||
DOREPLIFETIME_CONDITION_NOTIFY(ThisClass, BaseHeal, COND_OwnerOnly, REPNOTIFY_Always);
|
||||
}
|
||||
|
||||
void UOLSCombatAttributeSet::OnRep_BaseDamage(const FGameplayAttributeData& oldValue)
|
||||
{
|
||||
GAMEPLAYATTRIBUTE_REPNOTIFY(ThisClass, BaseDamage, oldValue);
|
||||
}
|
||||
|
||||
void UOLSCombatAttributeSet::OnRep_BaseHeal(const FGameplayAttributeData& oldValue)
|
||||
{
|
||||
GAMEPLAYATTRIBUTE_REPNOTIFY(ThisClass, BaseHeal, oldValue);
|
||||
}
|
@ -0,0 +1,243 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/Attributes/OLSHealthAttributeSet.h"
|
||||
|
||||
#include "GameplayEffectExtension.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "GameFramework/GameplayMessageSubsystem.h"
|
||||
#include "Messages/OLSVerbMessage.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_Damage, "Gameplay.Damage");
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_DamageImmunity, "Gameplay.DamageImmunity");
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_DamageSelfDestruct, "Gameplay.Damage.SelfDestruct");
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_FellOutOfWorld, "Gameplay.Damage.FellOutOfWorld");
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_OLS_Damage_Message, "Lyra.Damage.Message");
|
||||
|
||||
UOLSHealthAttributeSet::UOLSHealthAttributeSet()
|
||||
: Health(100.0f)
|
||||
, MaxHealth(100.0f)
|
||||
{
|
||||
bIsOutOfHealth = false;
|
||||
MaxHealthBeforeAttributeChange = 0.0f;
|
||||
HealthBeforeAttributeChange = 0.0f;
|
||||
}
|
||||
|
||||
void UOLSHealthAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME_CONDITION_NOTIFY(UOLSHealthAttributeSet, Health, COND_None, REPNOTIFY_Always);
|
||||
DOREPLIFETIME_CONDITION_NOTIFY(UOLSHealthAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
|
||||
}
|
||||
|
||||
void UOLSHealthAttributeSet::OnRep_Health(const FGameplayAttributeData& oldValue)
|
||||
{
|
||||
GAMEPLAYATTRIBUTE_REPNOTIFY(UOLSHealthAttributeSet, Health, oldValue);
|
||||
|
||||
// Call the change callback, but without an instigator
|
||||
// This could be changed to an explicit RPC in the future
|
||||
// These events on the client should not be changing attributes
|
||||
|
||||
const float currentHealth = GetHealth();
|
||||
const float estimatedMagnitude = currentHealth - oldValue.GetCurrentValue();
|
||||
|
||||
OnHealthChanged.Broadcast(
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
estimatedMagnitude,
|
||||
oldValue.GetCurrentValue(),
|
||||
currentHealth);
|
||||
|
||||
const bool isCurrentHealthEqualOrBelowZero = (currentHealth <= 0.0f);
|
||||
|
||||
if (!bIsOutOfHealth && isCurrentHealthEqualOrBelowZero)
|
||||
{
|
||||
OnOutOfHealth.Broadcast(nullptr, nullptr, nullptr, estimatedMagnitude, oldValue.GetCurrentValue(), currentHealth);
|
||||
}
|
||||
|
||||
bIsOutOfHealth = isCurrentHealthEqualOrBelowZero;
|
||||
}
|
||||
|
||||
void UOLSHealthAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldValue)
|
||||
{
|
||||
GAMEPLAYATTRIBUTE_REPNOTIFY(UOLSHealthAttributeSet, MaxHealth, OldValue);
|
||||
|
||||
// Call the change callback, but without an instigator
|
||||
// This could be changed to an explicit RPC in the future
|
||||
OnMaxHealthChanged.Broadcast(
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
GetMaxHealth() - OldValue.GetCurrentValue(),
|
||||
OldValue.GetCurrentValue(), GetMaxHealth());
|
||||
}
|
||||
|
||||
bool UOLSHealthAttributeSet::PreGameplayEffectExecute(FGameplayEffectModCallbackData& data)
|
||||
{
|
||||
if (!Super::PreGameplayEffectExecute(data))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle modifying incoming normal damage
|
||||
if (data.EvaluatedData.Attribute == GetDamageAttribute())
|
||||
{
|
||||
if (data.EvaluatedData.Magnitude > 0.0f)
|
||||
{
|
||||
const bool isDamageFromSelfDestruct = data.EffectSpec.GetDynamicAssetTags().HasTagExact(TAG_Gameplay_DamageSelfDestruct);
|
||||
|
||||
if (data.Target.HasMatchingGameplayTag(TAG_Gameplay_DamageImmunity) && !isDamageFromSelfDestruct)
|
||||
{
|
||||
// Do not take away any health.
|
||||
data.EvaluatedData.Magnitude = 0.0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !UE_BUILD_SHIPPING
|
||||
// Check GodMode cheat, unlimited health is checked below
|
||||
// if (data.Target.HasMatchingGameplayTag(LyraGameplayTags::Cheat_GodMode) && !isDamageFromSelfDestruct)
|
||||
// {
|
||||
// // Do not take away any health.
|
||||
// data.EvaluatedData.Magnitude = 0.0f;
|
||||
// return false;
|
||||
// }
|
||||
#endif // #if !UE_BUILD_SHIPPING
|
||||
}
|
||||
}
|
||||
|
||||
// Save the current health
|
||||
HealthBeforeAttributeChange = GetHealth();
|
||||
MaxHealthBeforeAttributeChange = GetMaxHealth();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UOLSHealthAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
|
||||
{
|
||||
Super::PostGameplayEffectExecute(Data);
|
||||
|
||||
const bool isDamageFromSelfDestruct = Data.EffectSpec.GetDynamicAssetTags().HasTagExact(TAG_Gameplay_DamageSelfDestruct);
|
||||
float minimumHealth = 0.0f;
|
||||
|
||||
#if !UE_BUILD_SHIPPING
|
||||
// Godmode and unlimited health stop death unless it's a self destruct
|
||||
// if (!bIsDamageFromSelfDestruct &&
|
||||
// (Data.Target.HasMatchingGameplayTag(LyraGameplayTags::Cheat_GodMode) || Data.Target.HasMatchingGameplayTag(LyraGameplayTags::Cheat_UnlimitedHealth) ))
|
||||
// {
|
||||
// MinimumHealth = 1.0f;
|
||||
// }
|
||||
#endif // #if !UE_BUILD_SHIPPING
|
||||
|
||||
const FGameplayEffectContextHandle& effectContext = Data.EffectSpec.GetEffectContext();
|
||||
AActor* instigator = effectContext.GetOriginalInstigator();
|
||||
AActor* causer = effectContext.GetEffectCauser();
|
||||
|
||||
if (Data.EvaluatedData.Attribute == GetDamageAttribute())
|
||||
{
|
||||
// Send a standardized verb message that other systems can observe
|
||||
if (Data.EvaluatedData.Magnitude > 0.0f)
|
||||
{
|
||||
FOLSVerbMessage message;
|
||||
message.Verb = TAG_OLS_Damage_Message;
|
||||
message.Instigator = Data.EffectSpec.GetEffectContext().GetEffectCauser();
|
||||
message.InstigatorTags = *Data.EffectSpec.CapturedSourceTags.GetAggregatedTags();
|
||||
message.Target = GetOwningActor();
|
||||
message.TargetTags = *Data.EffectSpec.CapturedTargetTags.GetAggregatedTags();
|
||||
//@TODO: Fill out context tags, and any non-ability-system source/instigator tags
|
||||
//@TODO: Determine if it's an opposing team kill, self-own, team kill, etc...
|
||||
message.Magnitude = Data.EvaluatedData.Magnitude;
|
||||
|
||||
UGameplayMessageSubsystem& messageSystem = UGameplayMessageSubsystem::Get(GetWorld());
|
||||
messageSystem.BroadcastMessage(message.Verb, message);
|
||||
}
|
||||
|
||||
// Convert into -Health and then clamp
|
||||
SetHealth(FMath::Clamp(GetHealth() - GetDamage(), minimumHealth, GetMaxHealth()));
|
||||
SetDamage(0.0f);
|
||||
}
|
||||
else if (Data.EvaluatedData.Attribute == GetHealingAttribute())
|
||||
{
|
||||
// Convert into +Health and then clamo
|
||||
SetHealth(FMath::Clamp(GetHealth() + GetHealing(), minimumHealth, GetMaxHealth()));
|
||||
SetHealing(0.0f);
|
||||
}
|
||||
else if (Data.EvaluatedData.Attribute == GetHealthAttribute())
|
||||
{
|
||||
// Clamp and fall into out of health handling below
|
||||
SetHealth(FMath::Clamp(GetHealth(), minimumHealth, GetMaxHealth()));
|
||||
}
|
||||
else if (Data.EvaluatedData.Attribute == GetMaxHealthAttribute())
|
||||
{
|
||||
// TODO clamp current health?
|
||||
|
||||
// Notify on any requested max health changes
|
||||
OnMaxHealthChanged.Broadcast(instigator, causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, MaxHealthBeforeAttributeChange, GetMaxHealth());
|
||||
}
|
||||
|
||||
// If health has actually changed activate callbacks
|
||||
if (GetHealth() != HealthBeforeAttributeChange)
|
||||
{
|
||||
OnHealthChanged.Broadcast(instigator, causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, HealthBeforeAttributeChange, GetHealth());
|
||||
}
|
||||
|
||||
if ((GetHealth() <= 0.0f) && !bIsOutOfHealth)
|
||||
{
|
||||
OnOutOfHealth.Broadcast(instigator, causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, HealthBeforeAttributeChange, GetHealth());
|
||||
}
|
||||
|
||||
// Check health again in case an event above changed it.
|
||||
bIsOutOfHealth = (GetHealth() <= 0.0f);
|
||||
}
|
||||
|
||||
void UOLSHealthAttributeSet::PreAttributeBaseChange(const FGameplayAttribute& attribute, float& newValue) const
|
||||
{
|
||||
Super::PreAttributeBaseChange(attribute, newValue);
|
||||
|
||||
ClampAttribute(attribute, newValue);
|
||||
}
|
||||
|
||||
void UOLSHealthAttributeSet::PreAttributeChange(const FGameplayAttribute& attribute, float& newValue)
|
||||
{
|
||||
Super::PreAttributeChange(attribute, newValue);
|
||||
|
||||
ClampAttribute(attribute, newValue);
|
||||
}
|
||||
|
||||
void UOLSHealthAttributeSet::PostAttributeChange(const FGameplayAttribute& attribute, float oldValue, float newValue)
|
||||
{
|
||||
Super::PostAttributeChange(attribute, oldValue, newValue);
|
||||
|
||||
if (attribute == GetMaxHealthAttribute())
|
||||
{
|
||||
// Make sure current health is not greater than the new max health.
|
||||
if (GetHealth() > newValue)
|
||||
{
|
||||
UOLSAbilitySystemComponent* asc = GetOLSAbilitySystemComponent();
|
||||
check(asc);
|
||||
|
||||
asc->ApplyModToAttribute(GetHealthAttribute(), EGameplayModOp::Override, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (bIsOutOfHealth && (GetHealth() > 0.0f))
|
||||
{
|
||||
bIsOutOfHealth = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHealthAttributeSet::ClampAttribute(const FGameplayAttribute& attribute, float& outNewValue) const
|
||||
{
|
||||
if (attribute == GetHealthAttribute())
|
||||
{
|
||||
// Do not allow health to go negative or above max health.
|
||||
outNewValue = FMath::Clamp(outNewValue, 0.0f, GetMaxHealth());
|
||||
}
|
||||
else if (attribute == GetMaxHealthAttribute())
|
||||
{
|
||||
// Do not allow max health to drop below 1.
|
||||
outNewValue = FMath::Max(outNewValue, 1.0f);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/Effects/OLSGameplayEffect.h"
|
||||
|
||||
#include "AbilitySystemComponent.h"
|
||||
|
||||
void UOLSGameplayEffect::ApplyDynamicGameplayEffect(const FOLSDynamicGameplayEffectData& data)
|
||||
{
|
||||
// Kick out of the call if data's AbilitySystemComponent is invalid.
|
||||
if (!ensureAlwaysMsgf(data.AbilitySystemComponent.IsValid(),
|
||||
TEXT("%s is called but the passed FTowersDynamicGameplayEffectData's AbilitySystemComponent is null."),
|
||||
ANSI_TO_TCHAR(__func__)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Dynamically create the gameplay effect.
|
||||
UGameplayEffect* ge = NewObject<UGameplayEffect>(GetTransientPackage(), data.EffectName);
|
||||
// Dynamic gameplay effects must have an instant duration type.
|
||||
ge->DurationPolicy = EGameplayEffectDurationType::Instant;
|
||||
|
||||
// Add one new modifier to the gameplay effect.
|
||||
const int32 modifierIndex = ge->Modifiers.Num();
|
||||
ge->Modifiers.SetNum(modifierIndex + 1);
|
||||
|
||||
// Grab the modifier at the appropriate index and set its data appropriately.
|
||||
FGameplayModifierInfo& modInfo = ge->Modifiers[modifierIndex];
|
||||
modInfo.ModifierMagnitude = data.EffectMagnitude;
|
||||
modInfo.ModifierOp = data.EffectModifierOpType;
|
||||
modInfo.Attribute = data.AffectedAttribute;
|
||||
|
||||
// Apply the gameplay effect to the TowersCharacter's ability system component.
|
||||
data.AbilitySystemComponent->ApplyGameplayEffectToSelf(ge,
|
||||
data.EffectLevel,
|
||||
data.AbilitySystemComponent->MakeEffectContext());
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/Interfaces/OLSAbilitySourceInterface.h"
|
||||
|
||||
|
||||
// Add default functionality here for any IOLSAbilitySourceInterface functions that are not pure virtual.
|
@ -0,0 +1,4 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/OLSAbilitySimpleFailureMessage.h"
|
814
Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp
Normal file
814
Source/ols/Private/AbilitySystem/OLSAbilitySystemComponent.cpp
Normal file
@ -0,0 +1,814 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
|
||||
#include "AbilitySystemGlobals.h"
|
||||
#include "GameplayCueManager.h"
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSBatchGameplayAbilityInterface.h"
|
||||
#include "AbilitySystem/OLSGlobaAbilitySubsystem.h"
|
||||
#include "AbilitySystem/Abilities/OLSGameplayAbility.h"
|
||||
#include "AnimInstances/OLSBaseLayerAnimInstance.h"
|
||||
#include "DataAssets/OLSAbilityTagRelationshipMappingDataAsset.h"
|
||||
#include "DataAssets/OLSGameDataAsset.h"
|
||||
#include "Systems/OLSAssetManager.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSAbilitySystemComponent);
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_AbilityInputBlocked, "Gameplay.AbilityInputBlocked");
|
||||
|
||||
// Sets default values for this component's properties
|
||||
UOLSAbilitySystemComponent::UOLSAbilitySystemComponent()
|
||||
{
|
||||
static constexpr bool isReplicated = true;
|
||||
SetIsReplicatedByDefault(isReplicated);
|
||||
|
||||
bShouldEnableBatchRPC = true;
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::InitAbilityActorInfo(AActor* ownerActor, AActor* avatarActor)
|
||||
{
|
||||
FGameplayAbilityActorInfo* actorInfo = AbilityActorInfo.Get();
|
||||
check(actorInfo);
|
||||
check(ownerActor);
|
||||
|
||||
// Guard condition to ensure we should clear/init for this new Avatar Actor.
|
||||
const bool hasAvatarChanged = avatarActor && Cast<APawn>(avatarActor) && (avatarActor != actorInfo->AvatarActor);
|
||||
|
||||
Super::InitAbilityActorInfo(ownerActor, avatarActor);
|
||||
|
||||
// Apply the new defaults obtained from the owner's interface.
|
||||
if (hasAvatarChanged)
|
||||
{
|
||||
if (const TObjectPtr<UOLSGlobaAbilitySubsystem> globalAbilitySystem = UWorld::GetSubsystem<UOLSGlobaAbilitySubsystem>(GetWorld()))
|
||||
{
|
||||
globalAbilitySystem->RegisterASC(this);
|
||||
}
|
||||
|
||||
if (const TObjectPtr<UOLSBaseLayerAnimInstance> animInstance = Cast<UOLSBaseLayerAnimInstance>(GetAnimInstanceFromActorInfo()))
|
||||
{
|
||||
animInstance->InitializeWithAbilitySystem(this);
|
||||
}
|
||||
|
||||
TryActivateAbilitiesOnSpawn();
|
||||
}
|
||||
}
|
||||
|
||||
bool UOLSAbilitySystemComponent::ShouldDoServerAbilityRPCBatch() const
|
||||
{
|
||||
return bShouldEnableBatchRPC;
|
||||
}
|
||||
|
||||
float UOLSAbilitySystemComponent::PlayMontage(
|
||||
UGameplayAbility* animatingAbility,
|
||||
FGameplayAbilityActivationInfo activationInfo,
|
||||
UAnimMontage* montage,
|
||||
float playRate,
|
||||
FName startSectionName,
|
||||
float startTimeSeconds)
|
||||
{
|
||||
if (GEnableDefaultPlayMontage)
|
||||
{
|
||||
// Always useful to still allow the default flow, if there are some meaningful changes in the core system
|
||||
// that were not yet reflect in this custom implementation. Can be enabled with CVar "GEnableDefaultPlayMontage".
|
||||
//
|
||||
return Super::PlayMontage(animatingAbility, activationInfo, montage, playRate, startSectionName, startTimeSeconds);
|
||||
}
|
||||
|
||||
float duration = -1.f;
|
||||
|
||||
// This method was re-written just to ensure that the Animation Instance is retrieved from the Actor Info
|
||||
// by default, but also, other scenarios can be supported. Biggest example being an IK Runtime Retarget.
|
||||
//
|
||||
// This virtual "GetAnimInstanceFromActorInfo" provides some flexibility on how the Anim Instance is
|
||||
// retrieved. It can be extended in projects that should support IK Runtime Retargets and also traditional
|
||||
// Anim Instances set in the Actor Info.
|
||||
//
|
||||
const TObjectPtr<UAnimInstance> animInstance = GetAnimInstanceFromActorInfo();
|
||||
if (animInstance && montage)
|
||||
{
|
||||
duration = animInstance->Montage_Play(
|
||||
montage,
|
||||
playRate,
|
||||
EMontagePlayReturnType::MontageLength,
|
||||
startTimeSeconds);
|
||||
if (duration > 0.f)
|
||||
{
|
||||
if (montage->HasRootMotion() && animInstance->GetOwningActor())
|
||||
{
|
||||
UE_LOG(LogRootMotion, Log, TEXT("UAbilitySystemComponent::PlayMontage %s, Role: %s")
|
||||
, *GetNameSafe(montage)
|
||||
, *UEnum::GetValueAsString(TEXT("Engine.ENetRole"), animInstance->GetOwningActor()->GetLocalRole())
|
||||
);
|
||||
}
|
||||
|
||||
LocalAnimMontageInfo.AnimMontage = montage;
|
||||
LocalAnimMontageInfo.AnimatingAbility = animatingAbility;
|
||||
LocalAnimMontageInfo.PlayInstanceId = (LocalAnimMontageInfo.PlayInstanceId < UINT8_MAX ? LocalAnimMontageInfo.PlayInstanceId + 1 : 0);
|
||||
|
||||
if (animatingAbility)
|
||||
{
|
||||
animatingAbility->SetCurrentMontage(montage);
|
||||
}
|
||||
|
||||
// Start at a given Section.
|
||||
if (startSectionName != NAME_None)
|
||||
{
|
||||
animInstance->Montage_JumpToSection(startSectionName, montage);
|
||||
}
|
||||
|
||||
// Replicate for non-owners and for replay recordings
|
||||
// The data we set from GetRepAnimMontageInfo_Mutable() is used both by the server to replicate to clients and by clients to record replays.
|
||||
// We need to set this data for recording clients because there exists network configurations where an abilities montage data will not replicate to some clients (for example: if the client is an autonomous proxy.)
|
||||
if (ShouldRecordMontageReplication())
|
||||
{
|
||||
FGameplayAbilityRepAnimMontage& mutableRepAnimMontageInfo = GetRepAnimMontageInfo_Mutable();
|
||||
SetReplicatedMontageInfo(mutableRepAnimMontageInfo, montage, startSectionName);
|
||||
|
||||
// Update parameters that change during Montage lifetime.
|
||||
AnimMontage_UpdateReplicatedData();
|
||||
}
|
||||
|
||||
// Replicate to non-owners
|
||||
if (IsOwnerActorAuthoritative())
|
||||
{
|
||||
// Force net update on our avatar actor.
|
||||
if (AbilityActorInfo->AvatarActor != nullptr)
|
||||
{
|
||||
AbilityActorInfo->AvatarActor->ForceNetUpdate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this prediction key is rejected, we need to end the preview
|
||||
FPredictionKey predictionKey = GetPredictionKeyForNewAction();
|
||||
if (predictionKey.IsValidKey())
|
||||
{
|
||||
predictionKey.NewRejectedDelegate().BindUObject(this, &ThisClass::OnPredictiveMontageRejected, montage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
UAnimInstance* UOLSAbilitySystemComponent::GetAnimInstanceFromActorInfo() const
|
||||
{
|
||||
if (!AbilityActorInfo.IsValid())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FGameplayAbilityActorInfo* actorInfo = AbilityActorInfo.Get();
|
||||
if (actorInfo->AnimInstance.IsValid() && actorInfo->AnimInstance->IsValidLowLevelFast())
|
||||
{
|
||||
// Return the one that was deliberately set in the Actor Info.
|
||||
return actorInfo->AnimInstance.Get();
|
||||
}
|
||||
|
||||
// Otherwise, let the getter method try to figure out the animation instance.
|
||||
return actorInfo->GetAnimInstance();
|
||||
}
|
||||
|
||||
FActiveGameplayEffectHandle UOLSAbilitySystemComponent::ApplyGameplayEffectClassToSelf(
|
||||
TSubclassOf<UGameplayEffect> effectClass,
|
||||
float level)
|
||||
{
|
||||
FActiveGameplayEffectHandle handle;
|
||||
|
||||
if (IsValid(effectClass))
|
||||
{
|
||||
FGameplayEffectContextHandle contextHandle = MakeEffectContext();
|
||||
contextHandle.AddSourceObject(GetOwner());
|
||||
|
||||
const FGameplayEffectSpecHandle specHandle = MakeOutgoingSpec(effectClass, level, contextHandle);
|
||||
if (specHandle.IsValid())
|
||||
{
|
||||
handle = ApplyGameplayEffectSpecToSelf(*specHandle.Data.Get());
|
||||
|
||||
OLS_LOG(LogOLSAbilitySystemComponent, Verbose, TEXT("[%s] Effect '%s' granted at level %f."),
|
||||
GET_UOBJECT_NAME(GetAvatarActor()), GET_UOBJECT_NAME(effectClass), level);
|
||||
}
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
FGameplayAbilitySpecHandle UOLSAbilitySystemComponent::GiveAbilityFromClass(
|
||||
const TSubclassOf<UGameplayAbility> abilityClass,
|
||||
int32 level,
|
||||
int32 input)
|
||||
{
|
||||
FGameplayAbilitySpecHandle handle;
|
||||
|
||||
if (IsValid(abilityClass))
|
||||
{
|
||||
const FGameplayAbilitySpec newAbilitySpec(FGameplayAbilitySpec(abilityClass, level, input, GetOwner()));
|
||||
handle = GiveAbility(newAbilitySpec);
|
||||
|
||||
OLS_LOG(LogOLSAbilitySystemComponent, Log, TEXT("[%s] Ability '%s' %s at level %d."),
|
||||
GET_UOBJECT_NAME(GetAvatarActor()), GET_UOBJECT_NAME(abilityClass),
|
||||
handle.IsValid() ? TEXT("successfully granted") : TEXT("failed to be granted"), level);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool UOLSAbilitySystemComponent::TryBatchActivateAbility(
|
||||
FGameplayAbilitySpecHandle abilityHandle,
|
||||
bool shouldEndAbilityImmediately)
|
||||
{
|
||||
bool isAbilityActivated = false;
|
||||
if (abilityHandle.IsValid())
|
||||
{
|
||||
OLS_LOG(LogAbilitySystemComponent, Warning, TEXT("Ability handle is invalid!"));
|
||||
return isAbilityActivated;
|
||||
}
|
||||
|
||||
FScopedServerAbilityRPCBatcher batch(this, abilityHandle);
|
||||
isAbilityActivated = TryActivateAbility(abilityHandle, true);
|
||||
|
||||
if (!shouldEndAbilityImmediately)
|
||||
{
|
||||
const FGameplayAbilitySpec* abilitySpec = FindAbilitySpecFromHandle(abilityHandle);
|
||||
if (abilitySpec != nullptr)
|
||||
{
|
||||
UGameplayAbility* ability = abilitySpec->GetPrimaryInstance();
|
||||
if (IsValid(ability) && ability->Implements<UOLSBatchGameplayAbilityInterface>())
|
||||
{
|
||||
IOLSBatchGameplayAbilityInterface::Execute_EndAbilityFromBatch(ability);
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogAbilitySystemComponent, Error,
|
||||
TEXT("%s does not implement Batch Gameplay Ability Interface"), GET_UOBJECT_NAME(ability));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isAbilityActivated;
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::CancelAbilitiesByTags(
|
||||
FGameplayTagContainer abilityTags,
|
||||
FGameplayTagContainer cancelFilterTags)
|
||||
{
|
||||
CancelAbilities(&abilityTags, &cancelFilterTags);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::ExecuteGameplayCueLocal(
|
||||
const FGameplayTag gameplayCueTag,
|
||||
const FGameplayCueParameters& gameplayCueParameters) const
|
||||
{
|
||||
const TObjectPtr<UGameplayCueManager> cueManager = UAbilitySystemGlobals::Get().GetGameplayCueManager();
|
||||
cueManager->HandleGameplayCue(
|
||||
GetOwner(),
|
||||
gameplayCueTag,
|
||||
EGameplayCueEvent::Type::Executed,
|
||||
gameplayCueParameters);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::AddGameplayCueLocally(
|
||||
const FGameplayTag gameplayCueTag,
|
||||
const FGameplayCueParameters& gameplayCueParameters) const
|
||||
{
|
||||
const TObjectPtr<UGameplayCueManager> cueManager = UAbilitySystemGlobals::Get().GetGameplayCueManager();
|
||||
cueManager->HandleGameplayCue(
|
||||
GetOwner(),
|
||||
gameplayCueTag,
|
||||
EGameplayCueEvent::Type::OnActive,
|
||||
gameplayCueParameters);
|
||||
cueManager->HandleGameplayCue(
|
||||
GetOwner(),
|
||||
gameplayCueTag,
|
||||
EGameplayCueEvent::Type::WhileActive,
|
||||
gameplayCueParameters);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::RemoveGameplayCueLocally(
|
||||
const FGameplayTag gameplayCueTag,
|
||||
const FGameplayCueParameters& gameplayCueParameters) const
|
||||
{
|
||||
const TObjectPtr<UGameplayCueManager> cueManager = UAbilitySystemGlobals::Get().GetGameplayCueManager();
|
||||
cueManager->HandleGameplayCue(GetOwner(), gameplayCueTag, EGameplayCueEvent::Type::Removed, gameplayCueParameters);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::TryActivateAbilitiesOnSpawn()
|
||||
{
|
||||
ABILITYLIST_SCOPE_LOCK();
|
||||
for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items)
|
||||
{
|
||||
if (const UOLSGameplayAbility* abilityCDO = Cast<UOLSGameplayAbility>(abilitySpec.Ability))
|
||||
{
|
||||
abilityCDO->TryActivateAbilityOnSpawn(AbilityActorInfo.Get(), abilitySpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::AbilitySpecInputPressed(FGameplayAbilitySpec& spec)
|
||||
{
|
||||
Super::AbilitySpecInputPressed(spec);
|
||||
|
||||
// We don't support UGameplayAbility::bReplicateInputDirectly.
|
||||
// Use replicated events instead so that the WaitInputPress ability task works.
|
||||
if (spec.IsActive())
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
const UGameplayAbility* instance = spec.GetPrimaryInstance();
|
||||
FPredictionKey originalPredictionKey = instance
|
||||
? instance->GetCurrentActivationInfo().GetActivationPredictionKey()
|
||||
: spec.ActivationInfo.GetActivationPredictionKey();
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
// Invoke the InputPressed event. This is not replicated here. If someone is listening, they may replicate the InputPressed event to the server.
|
||||
InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, spec.Handle, originalPredictionKey);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::AbilitySpecInputReleased(FGameplayAbilitySpec& spec)
|
||||
{
|
||||
Super::AbilitySpecInputReleased(spec);
|
||||
|
||||
// We don't support UGameplayAbility::bReplicateInputDirectly.
|
||||
// Use replicated events instead so that the WaitInputRelease ability task works.
|
||||
if (spec.IsActive())
|
||||
{
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
const UGameplayAbility* instance = spec.GetPrimaryInstance();
|
||||
FPredictionKey originalPredictionKey = instance
|
||||
? instance->GetCurrentActivationInfo().GetActivationPredictionKey()
|
||||
: spec.ActivationInfo.GetActivationPredictionKey();
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
// Invoke the InputReleased event. This is not replicated here. If someone is listening, they may replicate the InputReleased event to the server.
|
||||
InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputReleased, spec.Handle, originalPredictionKey);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::NotifyAbilityActivated(const FGameplayAbilitySpecHandle handle,
|
||||
UGameplayAbility* ability)
|
||||
{
|
||||
Super::NotifyAbilityActivated(handle, ability);
|
||||
|
||||
if (UOLSGameplayAbility* olsAbility = Cast<UOLSGameplayAbility>(ability))
|
||||
{
|
||||
AddAbilityToActivationGroup(olsAbility->GetActivationGroup(), olsAbility);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::NotifyAbilityFailed(const FGameplayAbilitySpecHandle handle,
|
||||
UGameplayAbility* ability,
|
||||
const FGameplayTagContainer& failureReason)
|
||||
{
|
||||
Super::NotifyAbilityFailed(handle, ability, failureReason);
|
||||
|
||||
if (APawn* avatar = Cast<APawn>(GetAvatarActor()))
|
||||
{
|
||||
if (!avatar->IsLocallyControlled() && ability->IsSupportedForNetworking())
|
||||
{
|
||||
ClientNotifyAbilityFailed(ability, failureReason);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HandleAbilityFailed(ability, failureReason);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::NotifyAbilityEnded(FGameplayAbilitySpecHandle handle,
|
||||
UGameplayAbility* ability,
|
||||
bool wasCancelled)
|
||||
{
|
||||
Super::NotifyAbilityEnded(handle, ability, wasCancelled);
|
||||
|
||||
if (UOLSGameplayAbility* olsAbility = Cast<UOLSGameplayAbility>(ability))
|
||||
{
|
||||
RemoveAbilityFromActivationGroup(olsAbility->GetActivationGroup(), olsAbility);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& abilityTags,
|
||||
UGameplayAbility* requestingAbility,
|
||||
bool shouldEnableBlockTags,
|
||||
const FGameplayTagContainer& blockTags,
|
||||
bool shouldExecuteCancelTags,
|
||||
const FGameplayTagContainer& cancelTags)
|
||||
{
|
||||
FGameplayTagContainer modifiedBlockTags = blockTags;
|
||||
FGameplayTagContainer modifiedCancelTags = cancelTags;
|
||||
|
||||
if (TagRelationshipMapping)
|
||||
{
|
||||
// Use the mapping to expand the ability tags into block and cancel tag
|
||||
TagRelationshipMapping->GetAbilityTagsToBlockAndCancel(abilityTags, &modifiedBlockTags, &modifiedCancelTags);
|
||||
}
|
||||
|
||||
Super::ApplyAbilityBlockAndCancelTags(abilityTags, requestingAbility, shouldEnableBlockTags, modifiedBlockTags,
|
||||
shouldExecuteCancelTags, modifiedCancelTags);
|
||||
|
||||
//@TODO: Apply any special logic like blocking input or movement
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::HandleChangeAbilityCanBeCanceled(const FGameplayTagContainer& abilityTags,
|
||||
UGameplayAbility* requestingAbility,
|
||||
bool canBeCanceled)
|
||||
{
|
||||
Super::HandleChangeAbilityCanBeCanceled(abilityTags, requestingAbility, canBeCanceled);
|
||||
|
||||
//@TODO: Apply any special logic like blocking input or movement
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::ClientNotifyAbilityFailed_Implementation(const UGameplayAbility* ability,
|
||||
const FGameplayTagContainer& failureReason)
|
||||
{
|
||||
HandleAbilityFailed(ability, failureReason);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::HandleAbilityFailed(const UGameplayAbility* ability,
|
||||
const FGameplayTagContainer& failureReason)
|
||||
{
|
||||
if (const UOLSGameplayAbility* olsAbility = Cast<const UOLSGameplayAbility>(ability))
|
||||
{
|
||||
olsAbility->OnAbilityFailedToActivate(failureReason);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::SetReplicatedMontageInfo(
|
||||
FGameplayAbilityRepAnimMontage& mutableRepAnimMontageInfo,
|
||||
UAnimMontage* newMontageToPlay,
|
||||
const FName& startSectionName)
|
||||
{
|
||||
const uint8 playInstanceId = mutableRepAnimMontageInfo.PlayInstanceId < UINT8_MAX ? mutableRepAnimMontageInfo.PlayInstanceId + 1 : 0;
|
||||
const uint8 sectionIdToPlay = newMontageToPlay->GetSectionIndex(startSectionName) + 1;
|
||||
|
||||
TObjectPtr<UAnimSequenceBase> animation = newMontageToPlay;
|
||||
if (newMontageToPlay->IsDynamicMontage())
|
||||
{
|
||||
animation = newMontageToPlay->GetFirstAnimReference();
|
||||
|
||||
check(!newMontageToPlay->SlotAnimTracks.IsEmpty());
|
||||
mutableRepAnimMontageInfo.SlotName = newMontageToPlay->SlotAnimTracks[0].SlotName;
|
||||
mutableRepAnimMontageInfo.BlendOutTime = newMontageToPlay->GetDefaultBlendInTime();
|
||||
}
|
||||
|
||||
mutableRepAnimMontageInfo.Animation = animation;
|
||||
mutableRepAnimMontageInfo.PlayInstanceId = playInstanceId;
|
||||
|
||||
mutableRepAnimMontageInfo.SectionIdToPlay = 0;
|
||||
if (mutableRepAnimMontageInfo.Animation && startSectionName != NAME_None)
|
||||
{
|
||||
mutableRepAnimMontageInfo.SectionIdToPlay = sectionIdToPlay;
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::CancelAbilitiesByFunc(TShouldCancelAbilityFunc shouldCancelFunc,
|
||||
bool shouldReplicateCancelAbility)
|
||||
{
|
||||
ABILITYLIST_SCOPE_LOCK();
|
||||
for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items)
|
||||
{
|
||||
if (!abilitySpec.IsActive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UOLSGameplayAbility* abilityCDO = Cast<UOLSGameplayAbility>(abilitySpec.Ability);
|
||||
if (!abilityCDO)
|
||||
{
|
||||
OLS_LOG(LogOLSAbilitySystemComponent, Error,
|
||||
TEXT("CancelAbilitiesByFunc: Non-LyraGameplayAbility %s was Granted to ASC. Skipping."),
|
||||
GET_UOBJECT_NAME(abilitySpec.Ability));
|
||||
continue;
|
||||
}
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
ensureMsgf(abilitySpec.Ability->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced, TEXT("CancelAbilitiesByFunc: All Abilities should be Instanced (NonInstanced is being deprecated due to usability issues)."));
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
// Cancel all the spawned instances.
|
||||
TArray<UGameplayAbility*> Instances = abilitySpec.GetAbilityInstances();
|
||||
for (UGameplayAbility* AbilityInstance : Instances)
|
||||
{
|
||||
UOLSGameplayAbility* abilityInstance = CastChecked<UOLSGameplayAbility>(AbilityInstance);
|
||||
|
||||
if (shouldCancelFunc(abilityInstance, abilitySpec.Handle))
|
||||
{
|
||||
if (abilityInstance->CanBeCanceled())
|
||||
{
|
||||
abilityInstance->CancelAbility(abilitySpec.Handle, AbilityActorInfo.Get(), abilityInstance->GetCurrentActivationInfo(), shouldReplicateCancelAbility);
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogOLSAbilitySystemComponent, Error,
|
||||
TEXT("CancelAbilitiesByFunc: Can't cancel ability [%s] because CanBeCanceled is false."),
|
||||
GET_UOBJECT_NAME(abilityInstance));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::CancelInputActivatedAbilities(bool shouldReplicateCancelAbility)
|
||||
{
|
||||
auto shouldCancelFunc = [this](const UOLSGameplayAbility* ability, FGameplayAbilitySpecHandle handle)
|
||||
{
|
||||
const EOLSAbilityActivationPolicy activationPolicy = ability->GetActivationPolicy();
|
||||
return ((activationPolicy == EOLSAbilityActivationPolicy::OnInputTriggered) || (activationPolicy == EOLSAbilityActivationPolicy::WhileInputActive));
|
||||
};
|
||||
|
||||
CancelAbilitiesByFunc(shouldCancelFunc, shouldReplicateCancelAbility);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::AbilityInputTagPressed(const FGameplayTag& inputTag)
|
||||
{
|
||||
if (inputTag.IsValid())
|
||||
{
|
||||
for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items)
|
||||
{
|
||||
if (abilitySpec.Ability && (abilitySpec.GetDynamicSpecSourceTags().HasTagExact(inputTag)))
|
||||
{
|
||||
InputPressedSpecHandles.AddUnique(abilitySpec.Handle);
|
||||
InputHeldSpecHandles.AddUnique(abilitySpec.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::AbilityInputTagReleased(const FGameplayTag& inputTag)
|
||||
{
|
||||
if (inputTag.IsValid())
|
||||
{
|
||||
for (const FGameplayAbilitySpec& abilitySpec : ActivatableAbilities.Items)
|
||||
{
|
||||
if (abilitySpec.Ability && (abilitySpec.GetDynamicSpecSourceTags().HasTagExact(inputTag)))
|
||||
{
|
||||
InputReleasedSpecHandles.AddUnique(abilitySpec.Handle);
|
||||
InputHeldSpecHandles.Remove(abilitySpec.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::ProcessAbilityInput(float deltaTime, bool shouldGamePaused)
|
||||
{
|
||||
if (HasMatchingGameplayTag(TAG_Gameplay_AbilityInputBlocked))
|
||||
{
|
||||
ClearAbilityInput();
|
||||
return;
|
||||
}
|
||||
|
||||
static TArray<FGameplayAbilitySpecHandle> abilitiesToActivate;
|
||||
abilitiesToActivate.Reset();
|
||||
|
||||
//@TODO: See if we can use FScopedServerAbilityRPCBatcher ScopedRPCBatcher in some of these loops
|
||||
|
||||
//
|
||||
// Process all abilities that activate when the input is held.
|
||||
//
|
||||
for (const FGameplayAbilitySpecHandle& specHandle : InputHeldSpecHandles)
|
||||
{
|
||||
if (const FGameplayAbilitySpec* abilitySpec = FindAbilitySpecFromHandle(specHandle))
|
||||
{
|
||||
if (abilitySpec->Ability && !abilitySpec->IsActive())
|
||||
{
|
||||
const UOLSGameplayAbility* abilityCDO = Cast<UOLSGameplayAbility>(abilitySpec->Ability);
|
||||
|
||||
if (abilityCDO && abilityCDO->GetActivationPolicy() == EOLSAbilityActivationPolicy::WhileInputActive)
|
||||
{
|
||||
abilitiesToActivate.AddUnique(abilitySpec->Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Process all abilities that had their input pressed this frame.
|
||||
//
|
||||
for (const FGameplayAbilitySpecHandle& specHandle : InputPressedSpecHandles)
|
||||
{
|
||||
if (FGameplayAbilitySpec* abilitySpec = FindAbilitySpecFromHandle(specHandle))
|
||||
{
|
||||
if (abilitySpec->Ability)
|
||||
{
|
||||
abilitySpec->InputPressed = true;
|
||||
|
||||
if (abilitySpec->IsActive())
|
||||
{
|
||||
// Ability is active so pass along the input event.
|
||||
AbilitySpecInputPressed(*abilitySpec);
|
||||
}
|
||||
else
|
||||
{
|
||||
const UOLSGameplayAbility* abilityCDO = Cast<UOLSGameplayAbility>(abilitySpec->Ability);
|
||||
|
||||
if (abilityCDO && abilityCDO->GetActivationPolicy() == EOLSAbilityActivationPolicy::OnInputTriggered)
|
||||
{
|
||||
abilitiesToActivate.AddUnique(abilitySpec->Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Try to activate all the abilities that are from presses and holds.
|
||||
// We do it all at once so that held inputs don't activate the ability
|
||||
// and then also send a input event to the ability because of the press.
|
||||
//
|
||||
for (const FGameplayAbilitySpecHandle& abilitySpecHandle : abilitiesToActivate)
|
||||
{
|
||||
TryActivateAbility(abilitySpecHandle);
|
||||
}
|
||||
|
||||
//
|
||||
// Process all abilities that had their input released this frame.
|
||||
//
|
||||
for (const FGameplayAbilitySpecHandle& specHandle : InputReleasedSpecHandles)
|
||||
{
|
||||
if (FGameplayAbilitySpec* abilitySpec = FindAbilitySpecFromHandle(specHandle))
|
||||
{
|
||||
if (abilitySpec->Ability)
|
||||
{
|
||||
abilitySpec->InputPressed = false;
|
||||
|
||||
if (abilitySpec->IsActive())
|
||||
{
|
||||
// Ability is active so pass along the input event.
|
||||
AbilitySpecInputReleased(*abilitySpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Clear the cached ability handles.
|
||||
//
|
||||
InputPressedSpecHandles.Reset();
|
||||
InputReleasedSpecHandles.Reset();
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::ClearAbilityInput()
|
||||
{
|
||||
InputPressedSpecHandles.Reset();
|
||||
InputReleasedSpecHandles.Reset();
|
||||
InputHeldSpecHandles.Reset();
|
||||
}
|
||||
|
||||
bool UOLSAbilitySystemComponent::IsActivationGroupBlocked(EOLSAbilityActivationGroup Group) const
|
||||
{
|
||||
bool isBlocked = false;
|
||||
|
||||
switch (Group)
|
||||
{
|
||||
case EOLSAbilityActivationGroup::Independent:
|
||||
// Independent abilities are never blocked.
|
||||
isBlocked = false;
|
||||
break;
|
||||
|
||||
case EOLSAbilityActivationGroup::Exclusive_Replaceable:
|
||||
case EOLSAbilityActivationGroup::Exclusive_Blocking:
|
||||
// Exclusive abilities can activate if nothing is blocking.
|
||||
isBlocked = (ActivationGroupCounts[(uint8)EOLSAbilityActivationGroup::Exclusive_Blocking] > 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
checkf(false, TEXT("IsActivationGroupBlocked: Invalid ActivationGroup [%d]\n"), (uint8)Group);
|
||||
break;
|
||||
}
|
||||
|
||||
return isBlocked;
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::AddAbilityToActivationGroup(EOLSAbilityActivationGroup group,
|
||||
UOLSGameplayAbility* ability)
|
||||
{
|
||||
check(ability);
|
||||
check(ActivationGroupCounts[(uint8)group] < INT32_MAX);
|
||||
|
||||
ActivationGroupCounts[(uint8)group]++;
|
||||
|
||||
constexpr bool shouldReplicateCancelAbility = false;
|
||||
|
||||
switch (group)
|
||||
{
|
||||
case EOLSAbilityActivationGroup::Independent:
|
||||
// Independent abilities do not cancel any other abilities.
|
||||
break;
|
||||
|
||||
case EOLSAbilityActivationGroup::Exclusive_Replaceable:
|
||||
case EOLSAbilityActivationGroup::Exclusive_Blocking:
|
||||
CancelActivationGroupAbilities(EOLSAbilityActivationGroup::Exclusive_Replaceable, ability, shouldReplicateCancelAbility);
|
||||
break;
|
||||
|
||||
default:
|
||||
checkf(false, TEXT("AddAbilityToActivationGroup: Invalid ActivationGroup [%d]\n"), (uint8)group);
|
||||
break;
|
||||
}
|
||||
|
||||
const int32 exclusiveCount = ActivationGroupCounts[static_cast<uint8>(
|
||||
EOLSAbilityActivationGroup::Exclusive_Replaceable)] + ActivationGroupCounts[static_cast<uint8>(
|
||||
EOLSAbilityActivationGroup::Exclusive_Blocking)];
|
||||
if (!ensure(exclusiveCount <= 1))
|
||||
{
|
||||
OLS_LOG(LogOLSAbilitySystemComponent, Error,
|
||||
TEXT("Multiple exclusive abilities are running."));
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::RemoveAbilityFromActivationGroup(EOLSAbilityActivationGroup group,
|
||||
UOLSGameplayAbility* ability)
|
||||
{
|
||||
check(ability);
|
||||
check(ActivationGroupCounts[(uint8)group] > 0);
|
||||
|
||||
ActivationGroupCounts[(uint8)group]--;
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::CancelActivationGroupAbilities(EOLSAbilityActivationGroup group,
|
||||
UOLSGameplayAbility* ignoreAbility,
|
||||
bool shouldReplicateCancelAbility)
|
||||
{
|
||||
auto shouldCancelFunc = [this, group, ignoreAbility](const UOLSGameplayAbility* ability,
|
||||
FGameplayAbilitySpecHandle handle)
|
||||
{
|
||||
return ((ability->GetActivationGroup() == group) && (ability != ignoreAbility));
|
||||
};
|
||||
|
||||
CancelAbilitiesByFunc(shouldCancelFunc, shouldReplicateCancelAbility);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::AddDynamicTagGameplayEffect(const FGameplayTag& tag)
|
||||
{
|
||||
const TSubclassOf<UGameplayEffect> dynamicTagGE = UOLSAssetManager::GetSubclass(UOLSGameDataAsset::Get().DynamicTagGameplayEffect);
|
||||
if (!dynamicTagGE)
|
||||
{
|
||||
OLS_LOG(LogOLSAbilitySystemComponent, Warning,
|
||||
TEXT("AddDynamicTagGameplayEffect: Unable to find DynamicTagGameplayEffect [%s]."),
|
||||
*UOLSGameDataAsset::Get().DynamicTagGameplayEffect.GetAssetName());
|
||||
return;
|
||||
}
|
||||
|
||||
const FGameplayEffectSpecHandle specHandle = MakeOutgoingSpec(dynamicTagGE, 1.0f, MakeEffectContext());
|
||||
FGameplayEffectSpec* spec = specHandle.Data.Get();
|
||||
|
||||
if (!spec)
|
||||
{
|
||||
OLS_LOG(LogOLSAbilitySystemComponent, Warning,
|
||||
TEXT("AddDynamicTagGameplayEffect: Unable to make outgoing spec for [%s]."),
|
||||
GET_UOBJECT_NAME(dynamicTagGE));
|
||||
return;
|
||||
}
|
||||
|
||||
spec->DynamicGrantedTags.AddTag(tag);
|
||||
|
||||
ApplyGameplayEffectSpecToSelf(*spec);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::RemoveDynamicTagGameplayEffect(const FGameplayTag& Tag)
|
||||
{
|
||||
const TSubclassOf<UGameplayEffect> dynamicTagGE = UOLSAssetManager::GetSubclass(UOLSGameDataAsset::Get().DynamicTagGameplayEffect);
|
||||
if (!dynamicTagGE)
|
||||
{
|
||||
OLS_LOG(LogOLSAbilitySystemComponent, Warning,
|
||||
TEXT("RemoveDynamicTagGameplayEffect: Unable to find gameplay effect [%s]."),
|
||||
*UOLSGameDataAsset::Get().DynamicTagGameplayEffect.GetAssetName());
|
||||
return;
|
||||
}
|
||||
|
||||
FGameplayEffectQuery query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(FGameplayTagContainer(Tag));
|
||||
query.EffectDefinition = dynamicTagGE;
|
||||
|
||||
RemoveActiveEffects(query);
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::GetAbilityTargetData(const FGameplayAbilitySpecHandle abilityHandle,
|
||||
FGameplayAbilityActivationInfo activationInfo,
|
||||
FGameplayAbilityTargetDataHandle& outTargetDataHandle)
|
||||
{
|
||||
TSharedPtr<FAbilityReplicatedDataCache> replicatedData = AbilityTargetDataMap.Find(
|
||||
FGameplayAbilitySpecHandleAndPredictionKey(abilityHandle, activationInfo.GetActivationPredictionKey()));
|
||||
if (replicatedData.IsValid())
|
||||
{
|
||||
outTargetDataHandle = replicatedData->TargetData;
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::SetTagRelationshipMapping(UOLSAbilityTagRelationshipMappingDataAsset* newMapping)
|
||||
{
|
||||
TagRelationshipMapping = newMapping;
|
||||
}
|
||||
|
||||
void UOLSAbilitySystemComponent::GetAdditionalActivationTagRequirements(const FGameplayTagContainer& abilityTags,
|
||||
FGameplayTagContainer& outActivationRequired,
|
||||
FGameplayTagContainer& outActivationBlocked)
|
||||
const
|
||||
{
|
||||
if (TagRelationshipMapping)
|
||||
{
|
||||
TagRelationshipMapping->GetRequiredAndBlockedActivationTags(abilityTags, &outActivationRequired,
|
||||
&outActivationBlocked);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/OLSBatchGameplayAbilityInterface.h"
|
||||
|
||||
|
||||
// Add default functionality here for any IOLSBatchGameplayAbilityInterface functions that are not pure virtual.
|
@ -0,0 +1,66 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/OLSGameplayEffectContext.h"
|
||||
|
||||
#if UE_WITH_IRIS
|
||||
#include "Iris/ReplicationState/PropertyNetSerializerInfoRegistry.h"
|
||||
#include "Serialization/GameplayEffectContextNetSerializer.h"
|
||||
#endif
|
||||
|
||||
#include "AbilitySystem/Interfaces/OLSAbilitySourceInterface.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameplayEffectContext)
|
||||
|
||||
FOLSGameplayEffectContext* FOLSGameplayEffectContext::ExtractEffectContext(FGameplayEffectContextHandle handle)
|
||||
{
|
||||
FGameplayEffectContext* baseEffectContext = handle.Get();
|
||||
if ((baseEffectContext != nullptr) && baseEffectContext->GetScriptStruct()->IsChildOf(StaticStruct()))
|
||||
{
|
||||
return static_cast<FOLSGameplayEffectContext*>(baseEffectContext);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FOLSGameplayEffectContext::SetAbilitySource(const IOLSAbilitySourceInterface* object, float sourceLevel)
|
||||
{
|
||||
AbilitySourceObject = MakeWeakObjectPtr(Cast<const UObject>(object));
|
||||
//SourceLevel = sourceLevel;
|
||||
}
|
||||
|
||||
const IOLSAbilitySourceInterface* FOLSGameplayEffectContext::GetAbilitySource() const
|
||||
{
|
||||
return Cast<IOLSAbilitySourceInterface>(AbilitySourceObject.Get());
|
||||
}
|
||||
|
||||
FGameplayEffectContext* FOLSGameplayEffectContext::Duplicate() const
|
||||
{
|
||||
FOLSGameplayEffectContext* newContext = new FOLSGameplayEffectContext();
|
||||
*newContext = *this;
|
||||
if (GetHitResult())
|
||||
{
|
||||
// Does a deep copy of the hit result
|
||||
newContext->AddHitResult(*GetHitResult(), true);
|
||||
}
|
||||
return newContext;
|
||||
}
|
||||
|
||||
UScriptStruct* FOLSGameplayEffectContext::GetScriptStruct() const
|
||||
{
|
||||
return FOLSGameplayEffectContext::StaticStruct();
|
||||
}
|
||||
|
||||
bool FOLSGameplayEffectContext::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess)
|
||||
{
|
||||
return FGameplayEffectContext::NetSerialize(Ar, Map, bOutSuccess);
|
||||
}
|
||||
|
||||
const UPhysicalMaterial* FOLSGameplayEffectContext::GetPhysicalMaterial() const
|
||||
{
|
||||
if (const FHitResult* hitResultPtr = GetHitResult())
|
||||
{
|
||||
return hitResultPtr->PhysMaterial.Get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
110
Source/ols/Private/AbilitySystem/OLSGlobaAbilitySubsystem.cpp
Normal file
110
Source/ols/Private/AbilitySystem/OLSGlobaAbilitySubsystem.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "AbilitySystem/OLSGlobaAbilitySubsystem.h"
|
||||
|
||||
#include "Abilities/GameplayAbility.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
|
||||
void FOLSGlobalAppliedAbilityList::AddToASC(TSubclassOf<UGameplayAbility> ability, UOLSAbilitySystemComponent* asc)
|
||||
{
|
||||
if (FGameplayAbilitySpecHandle* specHandle = Handles.Find(asc))
|
||||
{
|
||||
RemoveFromASC(asc);
|
||||
}
|
||||
|
||||
UGameplayAbility* abilityCDO = ability->GetDefaultObject<UGameplayAbility>();
|
||||
FGameplayAbilitySpec abilitySpec(abilityCDO);
|
||||
const FGameplayAbilitySpecHandle abilitySpecHandle = asc->GiveAbility(abilitySpec);
|
||||
Handles.Add(asc, abilitySpecHandle);
|
||||
}
|
||||
|
||||
void FOLSGlobalAppliedAbilityList::RemoveFromASC(UOLSAbilitySystemComponent* asc)
|
||||
{
|
||||
if (FGameplayAbilitySpecHandle* specHandle = Handles.Find(asc))
|
||||
{
|
||||
asc->ClearAbility(*specHandle);
|
||||
Handles.Remove(asc);
|
||||
}
|
||||
}
|
||||
|
||||
void FOLSGlobalAppliedAbilityList::RemoveFromAll()
|
||||
{
|
||||
|
||||
for (TTuple<TObjectPtr<UOLSAbilitySystemComponent>, FGameplayAbilitySpecHandle>& handle : Handles)
|
||||
{
|
||||
if (handle.Key != nullptr)
|
||||
{
|
||||
handle.Key->ClearAbility(handle.Value);
|
||||
}
|
||||
}
|
||||
|
||||
Handles.Empty();
|
||||
}
|
||||
|
||||
void FOLSGlobalAppliedEffectList::AddToASC(const TSubclassOf<UGameplayEffect>& effect, UOLSAbilitySystemComponent* asc)
|
||||
{
|
||||
if (FActiveGameplayEffectHandle* effectHandle = Handles.Find(asc))
|
||||
{
|
||||
RemoveFromASC(asc);
|
||||
}
|
||||
|
||||
const UGameplayEffect* gameplayEffectCDO = effect->GetDefaultObject<UGameplayEffect>();
|
||||
const FActiveGameplayEffectHandle gameplayEffectHandle = asc->ApplyGameplayEffectToSelf(gameplayEffectCDO, /*Level=*/ 1, asc->MakeEffectContext());
|
||||
Handles.Add(asc, gameplayEffectHandle);
|
||||
}
|
||||
|
||||
void FOLSGlobalAppliedEffectList::RemoveFromASC(UOLSAbilitySystemComponent* asc)
|
||||
{
|
||||
if (FActiveGameplayEffectHandle* effectHandle = Handles.Find(asc))
|
||||
{
|
||||
asc->RemoveActiveGameplayEffect(*effectHandle);
|
||||
Handles.Remove(asc);
|
||||
}
|
||||
}
|
||||
|
||||
void FOLSGlobalAppliedEffectList::RemoveFromAll()
|
||||
{
|
||||
for (TTuple<TObjectPtr<UOLSAbilitySystemComponent>, FActiveGameplayEffectHandle>& handle : Handles)
|
||||
{
|
||||
if (handle.Key != nullptr)
|
||||
{
|
||||
handle.Key->RemoveActiveGameplayEffect(handle.Value);
|
||||
}
|
||||
}
|
||||
Handles.Empty();
|
||||
}
|
||||
|
||||
void UOLSGlobaAbilitySubsystem::RegisterASC(UOLSAbilitySystemComponent* asc)
|
||||
{
|
||||
check(asc);
|
||||
|
||||
for (TTuple<TSubclassOf<UGameplayAbility>, FOLSGlobalAppliedAbilityList>& appliedAbility : AppliedAbilities)
|
||||
{
|
||||
appliedAbility.Value.AddToASC(appliedAbility.Key, asc);
|
||||
}
|
||||
|
||||
for (TTuple<TSubclassOf<UGameplayEffect>, FOLSGlobalAppliedEffectList>& appliedEffect : AppliedEffects)
|
||||
{
|
||||
appliedEffect.Value.AddToASC(appliedEffect.Key, asc);
|
||||
}
|
||||
|
||||
RegisteredASCs.AddUnique(asc);
|
||||
}
|
||||
|
||||
void UOLSGlobaAbilitySubsystem::UnregisterASC(UOLSAbilitySystemComponent* asc)
|
||||
{
|
||||
check(asc);
|
||||
|
||||
for (auto& appliedAbility : AppliedAbilities)
|
||||
{
|
||||
appliedAbility.Value.RemoveFromASC(asc);
|
||||
}
|
||||
|
||||
for (auto& appliedEffect : AppliedEffects)
|
||||
{
|
||||
appliedEffect.Value.RemoveFromASC(asc);
|
||||
}
|
||||
|
||||
RegisteredASCs.Remove(asc);
|
||||
}
|
134
Source/ols/Private/Camera/Components/OLSCameraComponent.cpp
Normal file
134
Source/ols/Private/Camera/Components/OLSCameraComponent.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Camera/Components/OLSCameraComponent.h"
|
||||
|
||||
#include "Camera/OLSCameraMode.h"
|
||||
#include "Engine/Canvas.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSCameraComponent)
|
||||
|
||||
UOLSCameraComponent::UOLSCameraComponent(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
CameraModeStack = nullptr;
|
||||
FieldOfViewOffset = 0.0f;
|
||||
}
|
||||
|
||||
UOLSCameraComponent* UOLSCameraComponent::FindCameraComponent(const AActor* Actor)
|
||||
{
|
||||
return (Actor ? Actor->FindComponentByClass<UOLSCameraComponent>() : nullptr);
|
||||
}
|
||||
|
||||
AActor* UOLSCameraComponent::GetTargetActor() const
|
||||
{
|
||||
return GetOwner();
|
||||
}
|
||||
|
||||
void UOLSCameraComponent::AddFieldOfViewOffset(float FovOffset)
|
||||
{
|
||||
FieldOfViewOffset += FovOffset;
|
||||
}
|
||||
|
||||
void UOLSCameraComponent::DrawDebug(UCanvas* canvas) const
|
||||
{
|
||||
check(canvas);
|
||||
|
||||
FDisplayDebugManager& DisplayDebugManager = canvas->DisplayDebugManager;
|
||||
|
||||
DisplayDebugManager.SetFont(GEngine->GetSmallFont());
|
||||
DisplayDebugManager.SetDrawColor(FColor::Yellow);
|
||||
DisplayDebugManager.DrawString(FString::Printf(TEXT("LyraCameraComponent: %s"), *GetNameSafe(GetTargetActor())));
|
||||
|
||||
DisplayDebugManager.SetDrawColor(FColor::White);
|
||||
DisplayDebugManager.DrawString(FString::Printf(TEXT(" Location: %s"), *GetComponentLocation().ToCompactString()));
|
||||
DisplayDebugManager.DrawString(FString::Printf(TEXT(" Rotation: %s"), *GetComponentRotation().ToCompactString()));
|
||||
DisplayDebugManager.DrawString(FString::Printf(TEXT(" FOV: %f"), FieldOfView));
|
||||
|
||||
check(CameraModeStack);
|
||||
CameraModeStack->DrawDebug(canvas);
|
||||
}
|
||||
|
||||
void UOLSCameraComponent::GetBlendInfo(float& outWeightOfTopLayer, FGameplayTag& outTagOfTopLayer) const
|
||||
{
|
||||
check(CameraModeStack);
|
||||
CameraModeStack->GetBlendInfo(/*out*/ outWeightOfTopLayer, /*out*/ outTagOfTopLayer);
|
||||
}
|
||||
|
||||
void UOLSCameraComponent::OnRegister()
|
||||
{
|
||||
Super::OnRegister();
|
||||
|
||||
if (!CameraModeStack)
|
||||
{
|
||||
CameraModeStack = NewObject<UOLSCameraModeStack>(this);
|
||||
check(CameraModeStack);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSCameraComponent::GetCameraView(float deltaTime, FMinimalViewInfo& desiredView)
|
||||
{
|
||||
check(CameraModeStack);
|
||||
|
||||
UpdateCameraModes();
|
||||
|
||||
FOLSCameraModeView cameraModeView;
|
||||
CameraModeStack->EvaluateStack(deltaTime, cameraModeView);
|
||||
|
||||
// Keep player controller in sync with the latest view.
|
||||
if (APawn* targetPawn = Cast<APawn>(GetTargetActor()))
|
||||
{
|
||||
if (APlayerController* playerController = targetPawn->GetController<APlayerController>())
|
||||
{
|
||||
playerController->SetControlRotation(cameraModeView.ControlRotation);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any offset that was added to the field of view.
|
||||
cameraModeView.FieldOfView += FieldOfViewOffset;
|
||||
FieldOfViewOffset = 0.0f;
|
||||
|
||||
// Keep camera component in sync with the latest view.
|
||||
SetWorldLocationAndRotation(cameraModeView.Location, cameraModeView.Rotation);
|
||||
FieldOfView = cameraModeView.FieldOfView;
|
||||
|
||||
// Fill in desired view.
|
||||
desiredView.Location = cameraModeView.Location;
|
||||
desiredView.Rotation = cameraModeView.Rotation;
|
||||
desiredView.FOV = cameraModeView.FieldOfView;
|
||||
desiredView.OrthoWidth = OrthoWidth;
|
||||
desiredView.OrthoNearClipPlane = OrthoNearClipPlane;
|
||||
desiredView.OrthoFarClipPlane = OrthoFarClipPlane;
|
||||
desiredView.AspectRatio = AspectRatio;
|
||||
desiredView.bConstrainAspectRatio = bConstrainAspectRatio;
|
||||
desiredView.bUseFieldOfViewForLOD = bUseFieldOfViewForLOD;
|
||||
desiredView.ProjectionMode = ProjectionMode;
|
||||
|
||||
// See if the CameraActor wants to override the PostProcess settings used.
|
||||
desiredView.PostProcessBlendWeight = PostProcessBlendWeight;
|
||||
if (PostProcessBlendWeight > 0.0f)
|
||||
{
|
||||
desiredView.PostProcessSettings = PostProcessSettings;
|
||||
}
|
||||
|
||||
if (IsXRHeadTrackedCamera())
|
||||
{
|
||||
// In XR much of the camera behavior above is irrellevant, but the post process settings are not.
|
||||
Super::GetCameraView(deltaTime, desiredView);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSCameraComponent::UpdateCameraModes()
|
||||
{
|
||||
check(CameraModeStack);
|
||||
|
||||
if (CameraModeStack->IsStackActivate())
|
||||
{
|
||||
if (DetermineCameraModeDelegate.IsBound())
|
||||
{
|
||||
if (const TSubclassOf<UOLSCameraMode> cameraMode = DetermineCameraModeDelegate.Execute())
|
||||
{
|
||||
CameraModeStack->PushCameraMode(cameraMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Camera/Components/OLSUICameraManagerComponent.h"
|
||||
|
||||
#include "Camera/OLSPlayerCameraManager.h"
|
||||
#include "GameFramework/HUD.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSUICameraManagerComponent)
|
||||
|
||||
UOLSUICameraManagerComponent* UOLSUICameraManagerComponent::GetComponent(APlayerController* playerController)
|
||||
{
|
||||
if (playerController)
|
||||
{
|
||||
if (AOLSPlayerCameraManager* playerCamera = Cast<AOLSPlayerCameraManager>(playerController->PlayerCameraManager))
|
||||
{
|
||||
return playerCamera->GetUICameraComponent();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UOLSUICameraManagerComponent::UOLSUICameraManagerComponent()
|
||||
{
|
||||
bWantsInitializeComponent = true;
|
||||
|
||||
if (!HasAnyFlags(RF_ClassDefaultObject))
|
||||
{
|
||||
// Register "showdebug" hook.
|
||||
if (!IsRunningDedicatedServer())
|
||||
{
|
||||
AHUD::OnShowDebugInfo.AddUObject(this, &ThisClass::OnShowDebugInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UOLSUICameraManagerComponent::IsSettingViewTarget() const
|
||||
{
|
||||
return bShouldUpdatingViewTarget;
|
||||
}
|
||||
|
||||
AActor* UOLSUICameraManagerComponent::GetViewTarget() const
|
||||
{
|
||||
return ViewTarget;
|
||||
}
|
||||
|
||||
void UOLSUICameraManagerComponent::SetViewTarget(AActor* viewTarget, FViewTargetTransitionParams transitionParams)
|
||||
{
|
||||
TGuardValue<bool> UpdatingViewTargetGuard(bShouldUpdatingViewTarget, true);
|
||||
|
||||
ViewTarget = viewTarget;
|
||||
CastChecked<AOLSPlayerCameraManager>(GetOwner())->SetViewTarget(ViewTarget, transitionParams);
|
||||
}
|
||||
|
||||
bool UOLSUICameraManagerComponent::NeedsToUpdateViewTarget() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void UOLSUICameraManagerComponent::UpdateViewTarget(FTViewTarget& outVT, float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
void UOLSUICameraManagerComponent::OnShowDebugInfo(AHUD* hud, UCanvas* canvas,
|
||||
const FDebugDisplayInfo& displayInfo,
|
||||
float& yl, float& yPos)
|
||||
{
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Camera/Interfaces/OLSCameraAssistInterface.h"
|
||||
|
||||
|
||||
// Add default functionality here for any IOLSCameraAssistInterface functions that are not pure virtual.
|
||||
void IOLSCameraAssistInterface::GetIgnoredActorsForCameraPenetration(
|
||||
TArray<const AActor*>& outActorsAllowPenetration) const
|
||||
{
|
||||
}
|
||||
|
||||
TOptional<AActor*> IOLSCameraAssistInterface::GetCameraPreventPenetrationTarget() const
|
||||
{
|
||||
return TOptional<AActor*>();
|
||||
}
|
||||
|
||||
void IOLSCameraAssistInterface::OnCameraPenetratingTarget()
|
||||
{
|
||||
}
|
495
Source/ols/Private/Camera/OLSCameraMode.cpp
Normal file
495
Source/ols/Private/Camera/OLSCameraMode.cpp
Normal file
@ -0,0 +1,495 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Camera/OLSCameraMode.h"
|
||||
|
||||
#include "Camera/OLSPlayerCameraManager.h"
|
||||
#include "Camera/Components/OLSCameraComponent.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Engine/Canvas.h"
|
||||
#include "GameFramework/Character.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSCameraMode)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// FLyraCameraModeView
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FOLSCameraModeView::FOLSCameraModeView()
|
||||
: Location(ForceInit)
|
||||
, Rotation(ForceInit)
|
||||
, ControlRotation(ForceInit)
|
||||
, FieldOfView(OLS_CAMERA_DEFAULT_FOV)
|
||||
{
|
||||
}
|
||||
|
||||
void FOLSCameraModeView::Blend(const FOLSCameraModeView& other, float otherWeight)
|
||||
{
|
||||
if (otherWeight <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (otherWeight >= 1.0f)
|
||||
{
|
||||
*this = other;
|
||||
return;
|
||||
}
|
||||
|
||||
Location = FMath::Lerp(Location, other.Location, otherWeight);
|
||||
|
||||
const FRotator deltaRotation = (other.Rotation - Rotation).GetNormalized();
|
||||
Rotation = Rotation + (otherWeight * deltaRotation);
|
||||
|
||||
const FRotator deltaControlRotation = (other.ControlRotation - ControlRotation).GetNormalized();
|
||||
ControlRotation = ControlRotation + (otherWeight * deltaControlRotation);
|
||||
|
||||
FieldOfView = FMath::Lerp(FieldOfView, other.FieldOfView, otherWeight);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// UOLSCameraMode
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UOLSCameraMode::UOLSCameraMode()
|
||||
{
|
||||
FieldOfView = OLS_CAMERA_DEFAULT_FOV;
|
||||
ViewPitchMin = OLS_CAMERA_DEFAULT_PITCH_MIN;
|
||||
ViewPitchMax = OLS_CAMERA_DEFAULT_PITCH_MAX;
|
||||
|
||||
BlendTime = 0.5f;
|
||||
BlendFunction = EOLSCameraModeBlendFunction::EaseOut;
|
||||
BlendExponent = 4.0f;
|
||||
BlendAlpha = 1.0f;
|
||||
BlendWeight = 1.0f;
|
||||
}
|
||||
|
||||
UOLSCameraComponent* UOLSCameraMode::GetOLSCameraComponent() const
|
||||
{
|
||||
return CastChecked<UOLSCameraComponent>(GetOuter());
|
||||
}
|
||||
|
||||
UWorld* UOLSCameraMode::GetWorld() const
|
||||
{
|
||||
return HasAnyFlags(RF_ClassDefaultObject) ? nullptr : GetOuter()->GetWorld();
|
||||
}
|
||||
|
||||
AActor* UOLSCameraMode::GetTargetActor() const
|
||||
{
|
||||
const UOLSCameraComponent* cameraComponent = GetOLSCameraComponent();
|
||||
|
||||
return cameraComponent->GetTargetActor();
|
||||
}
|
||||
|
||||
const FOLSCameraModeView& UOLSCameraMode::GetCameraModeView() const
|
||||
{
|
||||
return View;
|
||||
}
|
||||
|
||||
void UOLSCameraMode::OnActivation()
|
||||
{
|
||||
}
|
||||
|
||||
void UOLSCameraMode::OnDeactivation()
|
||||
{
|
||||
}
|
||||
|
||||
void UOLSCameraMode::UpdateCameraMode(float deltaTime)
|
||||
{
|
||||
UpdateView(deltaTime);
|
||||
UpdateBlending(deltaTime);
|
||||
}
|
||||
|
||||
float UOLSCameraMode::GetBlendTime() const
|
||||
{
|
||||
return BlendTime;
|
||||
}
|
||||
|
||||
float UOLSCameraMode::GetBlendWeight() const
|
||||
{
|
||||
return BlendWeight;
|
||||
}
|
||||
|
||||
void UOLSCameraMode::SetBlendWeight(float weight)
|
||||
{
|
||||
BlendWeight = FMath::Clamp(weight, 0.0f, 1.0f);
|
||||
|
||||
// Since we're setting the blend weight directly, we need to calculate the blend alpha to account for the blend function.
|
||||
const float InvExponent = (BlendExponent > 0.0f) ? (1.0f / BlendExponent) : 1.0f;
|
||||
|
||||
switch (BlendFunction)
|
||||
{
|
||||
case EOLSCameraModeBlendFunction::Linear:
|
||||
BlendAlpha = BlendWeight;
|
||||
break;
|
||||
|
||||
case EOLSCameraModeBlendFunction::EaseIn:
|
||||
BlendAlpha = FMath::InterpEaseIn(0.0f, 1.0f, BlendWeight, InvExponent);
|
||||
break;
|
||||
|
||||
case EOLSCameraModeBlendFunction::EaseOut:
|
||||
BlendAlpha = FMath::InterpEaseOut(0.0f, 1.0f, BlendWeight, InvExponent);
|
||||
break;
|
||||
|
||||
case EOLSCameraModeBlendFunction::EaseInOut:
|
||||
BlendAlpha = FMath::InterpEaseInOut(0.0f, 1.0f, BlendWeight, InvExponent);
|
||||
break;
|
||||
|
||||
default:
|
||||
checkf(false, TEXT("SetBlendWeight: Invalid BlendFunction [%d]\n"), (uint8)BlendFunction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FGameplayTag UOLSCameraMode::GetCameraTypeTag() const
|
||||
{
|
||||
return CameraTypeTag;
|
||||
}
|
||||
|
||||
void UOLSCameraMode::DrawDebug(UCanvas* canvas) const
|
||||
{
|
||||
check(canvas);
|
||||
|
||||
FDisplayDebugManager& displayDebugManager = canvas->DisplayDebugManager;
|
||||
|
||||
displayDebugManager.SetDrawColor(FColor::White);
|
||||
displayDebugManager.DrawString(FString::Printf(TEXT(" OLSCameraMode: %s (%f)"), *GetName(), BlendWeight));
|
||||
}
|
||||
|
||||
FVector UOLSCameraMode::GetPivotLocation() const
|
||||
{
|
||||
const AActor* targetActor = GetTargetActor();
|
||||
check(targetActor);
|
||||
|
||||
if (const APawn* targetPawn = Cast<APawn>(targetActor))
|
||||
{
|
||||
// Height adjustments for characters to account for crouching.
|
||||
if (const ACharacter* targetCharacter = Cast<ACharacter>(targetPawn))
|
||||
{
|
||||
const ACharacter* targetCharacterCDO = targetCharacter->GetClass()->GetDefaultObject<ACharacter>();
|
||||
check(targetCharacterCDO);
|
||||
|
||||
const UCapsuleComponent* capsuleComp = targetCharacter->GetCapsuleComponent();
|
||||
check(capsuleComp);
|
||||
|
||||
const UCapsuleComponent* capsuleCompCDO = targetCharacterCDO->GetCapsuleComponent();
|
||||
check(capsuleCompCDO);
|
||||
|
||||
const float defaultHalfHeight = capsuleCompCDO->GetUnscaledCapsuleHalfHeight();
|
||||
const float actualHalfHeight = capsuleComp->GetUnscaledCapsuleHalfHeight();
|
||||
const float heightAdjustment = (defaultHalfHeight - actualHalfHeight) + targetCharacterCDO->BaseEyeHeight;
|
||||
|
||||
return targetCharacter->GetActorLocation() + (FVector::UpVector * heightAdjustment);
|
||||
}
|
||||
|
||||
return targetPawn->GetPawnViewLocation();
|
||||
}
|
||||
|
||||
return targetActor->GetActorLocation();
|
||||
}
|
||||
|
||||
FRotator UOLSCameraMode::GetPivotRotation() const
|
||||
{
|
||||
const AActor* targetActor = GetTargetActor();
|
||||
check(targetActor);
|
||||
|
||||
if (const APawn* targetPawn = Cast<APawn>(targetActor))
|
||||
{
|
||||
return targetPawn->GetViewRotation();
|
||||
}
|
||||
|
||||
return targetActor->GetActorRotation();
|
||||
}
|
||||
|
||||
void UOLSCameraMode::UpdateView(float deltaTime)
|
||||
{
|
||||
FVector pivotLocation = GetPivotLocation();
|
||||
FRotator pivotRotation = GetPivotRotation();
|
||||
|
||||
pivotRotation.Pitch = FMath::ClampAngle(pivotRotation.Pitch, ViewPitchMin, ViewPitchMax);
|
||||
|
||||
View.Location = pivotLocation;
|
||||
View.Rotation = pivotRotation;
|
||||
View.ControlRotation = View.Rotation;
|
||||
View.FieldOfView = FieldOfView;
|
||||
}
|
||||
|
||||
void UOLSCameraMode::UpdateBlending(float deltaTime)
|
||||
{
|
||||
if (BlendTime > 0.0f)
|
||||
{
|
||||
BlendAlpha += (deltaTime / BlendTime);
|
||||
BlendAlpha = FMath::Min(BlendAlpha, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
BlendAlpha = 1.0f;
|
||||
}
|
||||
|
||||
const float exponent = (BlendExponent > 0.0f) ? BlendExponent : 1.0f;
|
||||
|
||||
switch (BlendFunction)
|
||||
{
|
||||
case EOLSCameraModeBlendFunction::Linear:
|
||||
BlendWeight = BlendAlpha;
|
||||
break;
|
||||
|
||||
case EOLSCameraModeBlendFunction::EaseIn:
|
||||
BlendWeight = FMath::InterpEaseIn(0.0f, 1.0f, BlendAlpha, exponent);
|
||||
break;
|
||||
|
||||
case EOLSCameraModeBlendFunction::EaseOut:
|
||||
BlendWeight = FMath::InterpEaseOut(0.0f, 1.0f, BlendAlpha, exponent);
|
||||
break;
|
||||
|
||||
case EOLSCameraModeBlendFunction::EaseInOut:
|
||||
BlendWeight = FMath::InterpEaseInOut(0.0f, 1.0f, BlendAlpha, exponent);
|
||||
break;
|
||||
|
||||
default:
|
||||
checkf(false, TEXT("UpdateBlending: Invalid BlendFunction [%d]\n"), (uint8)BlendFunction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// UOLSCameraModeStack
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UOLSCameraModeStack::UOLSCameraModeStack()
|
||||
{
|
||||
bIsStackActive = true;
|
||||
}
|
||||
|
||||
void UOLSCameraModeStack::ActivateStack()
|
||||
{
|
||||
if (!IsStackActivate())
|
||||
{
|
||||
bIsStackActive = true;
|
||||
|
||||
// Notify camera modes that they are being activated.
|
||||
for (UOLSCameraMode* cameraMode : CameraModeStack)
|
||||
{
|
||||
check(cameraMode);
|
||||
cameraMode->OnActivation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSCameraModeStack::DeactivateStack()
|
||||
{
|
||||
if (IsStackActivate())
|
||||
{
|
||||
bIsStackActive = false;
|
||||
|
||||
// Notify camera modes that they are being deactivated.
|
||||
for (UOLSCameraMode* cameraMode : CameraModeStack)
|
||||
{
|
||||
check(cameraMode);
|
||||
cameraMode->OnDeactivation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UOLSCameraModeStack::IsStackActivate() const
|
||||
{
|
||||
return bIsStackActive;
|
||||
}
|
||||
|
||||
void UOLSCameraModeStack::PushCameraMode(TSubclassOf<UOLSCameraMode> cameraModeClass)
|
||||
{
|
||||
if (!cameraModeClass)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UOLSCameraMode* cameraMode = GetCameraModeInstance(cameraModeClass);
|
||||
check(cameraMode);
|
||||
|
||||
int32 stackSize = CameraModeStack.Num();
|
||||
|
||||
if ((stackSize > 0) && (CameraModeStack[0] == cameraMode))
|
||||
{
|
||||
// Already top of stack.
|
||||
return;
|
||||
}
|
||||
|
||||
// See if it's already in the stack and remove it.
|
||||
// Figure out how much it was contributing to the stack.
|
||||
int32 existingStackIndex = INDEX_NONE;
|
||||
float existingStackContribution = 1.0f;
|
||||
|
||||
for (int32 stackIndex = 0; stackIndex < stackSize; ++stackIndex)
|
||||
{
|
||||
if (CameraModeStack[stackIndex] == cameraMode)
|
||||
{
|
||||
existingStackIndex = stackIndex;
|
||||
existingStackContribution *= cameraMode->GetBlendWeight();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
existingStackContribution *= (1.0f - CameraModeStack[stackIndex]->GetBlendWeight());
|
||||
}
|
||||
}
|
||||
|
||||
if (existingStackIndex != INDEX_NONE)
|
||||
{
|
||||
CameraModeStack.RemoveAt(existingStackIndex);
|
||||
stackSize--;
|
||||
}
|
||||
else
|
||||
{
|
||||
existingStackContribution = 0.0f;
|
||||
}
|
||||
|
||||
// Decide what initial weight to start with.
|
||||
const bool shouldBlend = ((cameraMode->GetBlendTime() > 0.0f) && (stackSize > 0));
|
||||
const float blendWeight = (shouldBlend ? existingStackContribution : 1.0f);
|
||||
|
||||
cameraMode->SetBlendWeight(blendWeight);
|
||||
|
||||
// Add new entry to top of stack.
|
||||
CameraModeStack.Insert(cameraMode, 0);
|
||||
|
||||
// Make sure stack bottom is always weighted 100%.
|
||||
CameraModeStack.Last()->SetBlendWeight(1.0f);
|
||||
|
||||
// Let the camera mode know if it's being added to the stack.
|
||||
if (existingStackIndex == INDEX_NONE)
|
||||
{
|
||||
cameraMode->OnActivation();
|
||||
}
|
||||
}
|
||||
|
||||
bool UOLSCameraModeStack::EvaluateStack(float deltaTime, FOLSCameraModeView& outCameraModeView)
|
||||
{
|
||||
if (!IsStackActivate())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateStack(deltaTime);
|
||||
BlendStack(outCameraModeView);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UOLSCameraModeStack::DrawDebug(UCanvas* canvas) const
|
||||
{
|
||||
check(canvas);
|
||||
|
||||
FDisplayDebugManager& displayDebugManager = canvas->DisplayDebugManager;
|
||||
|
||||
displayDebugManager.SetDrawColor(FColor::Green);
|
||||
displayDebugManager.DrawString(FString(TEXT(" --- Camera Modes (Begin) ---")));
|
||||
|
||||
for (const UOLSCameraMode* cameraMode : CameraModeStack)
|
||||
{
|
||||
check(cameraMode);
|
||||
cameraMode->DrawDebug(canvas);
|
||||
}
|
||||
|
||||
displayDebugManager.SetDrawColor(FColor::Green);
|
||||
displayDebugManager.DrawString(FString::Printf(TEXT(" --- Camera Modes (End) ---")));
|
||||
}
|
||||
|
||||
void UOLSCameraModeStack::GetBlendInfo(float& outWeightOfTopLayer, FGameplayTag& outTagOfTopLayer) const
|
||||
{
|
||||
if (CameraModeStack.Num() == 0)
|
||||
{
|
||||
outWeightOfTopLayer = 1.0f;
|
||||
outTagOfTopLayer = FGameplayTag();
|
||||
return;
|
||||
}
|
||||
|
||||
UOLSCameraMode* topEntry = CameraModeStack.Last();
|
||||
check(topEntry);
|
||||
outWeightOfTopLayer = topEntry->GetBlendWeight();
|
||||
outTagOfTopLayer = topEntry->GetCameraTypeTag();
|
||||
}
|
||||
|
||||
UOLSCameraMode* UOLSCameraModeStack::GetCameraModeInstance(TSubclassOf<UOLSCameraMode> cameraModeClass)
|
||||
{
|
||||
check(cameraModeClass);
|
||||
|
||||
// First see if we already created one.
|
||||
for (UOLSCameraMode* cameraMode : CameraModeInstances)
|
||||
{
|
||||
if ((cameraMode != nullptr) && (cameraMode->GetClass() == cameraModeClass))
|
||||
{
|
||||
return cameraMode;
|
||||
}
|
||||
}
|
||||
|
||||
// Not found, so we need to create it.
|
||||
UOLSCameraMode* newCameraMode = NewObject<UOLSCameraMode>(GetOuter(), cameraModeClass, NAME_None, RF_NoFlags);
|
||||
check(newCameraMode);
|
||||
|
||||
CameraModeInstances.Add(newCameraMode);
|
||||
|
||||
return newCameraMode;
|
||||
}
|
||||
|
||||
void UOLSCameraModeStack::UpdateStack(float deltaTime)
|
||||
{
|
||||
const int32 stackSize = CameraModeStack.Num();
|
||||
if (stackSize <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int32 removeCount = 0;
|
||||
int32 removeIndex = INDEX_NONE;
|
||||
|
||||
for (int32 stackIndex = 0; stackIndex < stackSize; ++stackIndex)
|
||||
{
|
||||
UOLSCameraMode* cameraMode = CameraModeStack[stackIndex];
|
||||
check(cameraMode);
|
||||
|
||||
cameraMode->UpdateCameraMode(deltaTime);
|
||||
|
||||
if (cameraMode->GetBlendWeight() >= 1.0f)
|
||||
{
|
||||
// Everything below this mode is now irrelevant and can be removed.
|
||||
removeIndex = (stackIndex + 1);
|
||||
removeCount = (stackSize - removeIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removeCount > 0)
|
||||
{
|
||||
// Let the camera modes know they being removed from the stack.
|
||||
for (int32 stackIndex = removeIndex; stackIndex < stackSize; ++stackIndex)
|
||||
{
|
||||
UOLSCameraMode* cameraMode = CameraModeStack[stackIndex];
|
||||
check(cameraMode);
|
||||
|
||||
cameraMode->OnDeactivation();
|
||||
}
|
||||
|
||||
CameraModeStack.RemoveAt(removeIndex, removeCount);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSCameraModeStack::BlendStack(FOLSCameraModeView& outCameraModeView) const
|
||||
{
|
||||
const int32 stackSize = CameraModeStack.Num();
|
||||
if (stackSize <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Start at the bottom and blend up the stack
|
||||
const UOLSCameraMode* cameraMode = CameraModeStack[stackSize - 1];
|
||||
check(cameraMode);
|
||||
|
||||
outCameraModeView = cameraMode->GetCameraModeView();
|
||||
|
||||
for (int32 stackIndex = (stackSize - 2); stackIndex >= 0; --stackIndex)
|
||||
{
|
||||
cameraMode = CameraModeStack[stackIndex];
|
||||
check(cameraMode);
|
||||
|
||||
outCameraModeView.Blend(cameraMode->GetCameraModeView(), cameraMode->GetBlendWeight());
|
||||
}
|
||||
}
|
61
Source/ols/Private/Camera/OLSPlayerCameraManager.cpp
Normal file
61
Source/ols/Private/Camera/OLSPlayerCameraManager.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Camera/OLSPlayerCameraManager.h"
|
||||
|
||||
#include "Camera/Components/OLSCameraComponent.h"
|
||||
#include "Camera/Components/OLSUICameraManagerComponent.h"
|
||||
#include "Engine/Canvas.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSPlayerCameraManager)
|
||||
|
||||
static FName UICameraComponentName(TEXT("UICameraComponent"));
|
||||
|
||||
AOLSPlayerCameraManager::AOLSPlayerCameraManager(const FObjectInitializer& objectInitializer)
|
||||
: Super(objectInitializer)
|
||||
{
|
||||
DefaultFOV = OLS_CAMERA_DEFAULT_FOV;
|
||||
ViewPitchMin = OLS_CAMERA_DEFAULT_PITCH_MIN;
|
||||
ViewPitchMax = OLS_CAMERA_DEFAULT_PITCH_MAX;
|
||||
|
||||
UICameraComponent = CreateDefaultSubobject<UOLSUICameraManagerComponent>(UICameraComponentName);
|
||||
}
|
||||
|
||||
UOLSUICameraManagerComponent* AOLSPlayerCameraManager::GetUICameraComponent() const
|
||||
{
|
||||
return UICameraComponent;
|
||||
}
|
||||
|
||||
void AOLSPlayerCameraManager::UpdateViewTarget(FTViewTarget& outVT, float deltaTime)
|
||||
{
|
||||
// If the UI Camera is looking at something, let it have priority.
|
||||
if (UICameraComponent->NeedsToUpdateViewTarget())
|
||||
{
|
||||
Super::UpdateViewTarget(outVT, deltaTime);
|
||||
UICameraComponent->UpdateViewTarget(outVT, deltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
Super::UpdateViewTarget(outVT, deltaTime);
|
||||
}
|
||||
|
||||
void AOLSPlayerCameraManager::DisplayDebug(UCanvas* canvas, const FDebugDisplayInfo& debugDisplay,
|
||||
float& yl, float& yPos)
|
||||
{
|
||||
check(canvas);
|
||||
|
||||
FDisplayDebugManager& displayDebugManager = canvas->DisplayDebugManager;
|
||||
|
||||
displayDebugManager.SetFont(GEngine->GetSmallFont());
|
||||
displayDebugManager.SetDrawColor(FColor::Yellow);
|
||||
displayDebugManager.DrawString(FString::Printf(TEXT("LyraPlayerCameraManager: %s"), *GetNameSafe(this)));
|
||||
|
||||
Super::DisplayDebug(canvas, debugDisplay, yl, yPos);
|
||||
|
||||
const APawn* pawn = (PCOwner ? PCOwner->GetPawn() : nullptr);
|
||||
|
||||
if (const UOLSCameraComponent* cameraComponent = UOLSCameraComponent::FindCameraComponent(pawn))
|
||||
{
|
||||
cameraComponent->DrawDebug(canvas);
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ AOLSCharacter::AOLSCharacter(const FObjectInitializer& objectInitializer) : Supe
|
||||
PrimaryActorTick.bCanEverTick = false;
|
||||
PrimaryActorTick.bStartWithTickEnabled = false;
|
||||
|
||||
NetCullDistanceSquared = 900000000.0f;
|
||||
SetNetCullDistanceSquared(900000000.0f);
|
||||
|
||||
LocomotionComponent = CreateDefaultSubobject<UOLSLocomotionComponent>(TEXT("Locomotion Component"));
|
||||
}
|
||||
|
410
Source/ols/Private/Components/OLSHealthComponent.cpp
Normal file
410
Source/ols/Private/Components/OLSHealthComponent.cpp
Normal file
@ -0,0 +1,410 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Components/OLSHealthComponent.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "AbilitySystem/Attributes/OLSHealthAttributeSet.h"
|
||||
#include "DataAssets/OLSGameDataAsset.h"
|
||||
#include "GameFramework/GameplayMessageSubsystem.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "Messages/OLSVerbMessage.h"
|
||||
#include "Messages/OLSVerbMessageHelpers.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Systems/OLSAssetManager.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSHealthComponent);
|
||||
|
||||
UOLSHealthComponent::UOLSHealthComponent(const FObjectInitializer& objectInitializer)
|
||||
: Super(objectInitializer)
|
||||
{
|
||||
PrimaryComponentTick.bStartWithTickEnabled = false;
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
|
||||
SetIsReplicatedByDefault(true);
|
||||
|
||||
AbilitySystemComponent = nullptr;
|
||||
HealthSet = nullptr;
|
||||
DeathState = EOLSDeathState::NotDead;
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME(ThisClass, DeathState);
|
||||
}
|
||||
|
||||
UOLSHealthComponent* UOLSHealthComponent::FindHealthComponent(const AActor* actor)
|
||||
{
|
||||
return (actor ? actor->FindComponentByClass<UOLSHealthComponent>() : nullptr);
|
||||
}
|
||||
|
||||
|
||||
void UOLSHealthComponent::InitializeWithAbilitySystem(UOLSAbilitySystemComponent* asc)
|
||||
{
|
||||
AActor* owner = GetOwner();
|
||||
check(owner);
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
OLS_LOG(LogOLSHealthComponent, Error,
|
||||
TEXT(
|
||||
"LyraHealthComponent: Health component for owner [%s] has already been initialized with an ability system."
|
||||
), GET_UOBJECT_NAME(owner));
|
||||
return;
|
||||
}
|
||||
|
||||
AbilitySystemComponent = asc;
|
||||
if (!AbilitySystemComponent)
|
||||
{
|
||||
OLS_LOG(LogOLSHealthComponent, Error,
|
||||
TEXT("Cannot initialize health component for owner [%s] with NULL ability system."),
|
||||
GET_UOBJECT_NAME(owner));
|
||||
return;
|
||||
}
|
||||
|
||||
HealthSet = AbilitySystemComponent->GetSet<UOLSHealthAttributeSet>();
|
||||
if (!HealthSet)
|
||||
{
|
||||
OLS_LOG(LogOLSHealthComponent, Error,
|
||||
TEXT(
|
||||
"LyraHealthComponent: Cannot initialize health component for owner [%s] with NULL health set on the ability system."
|
||||
), GET_UOBJECT_NAME(owner));
|
||||
return;
|
||||
}
|
||||
|
||||
// Register to listen for attribute changes.
|
||||
HealthSet->OnHealthChanged.AddUObject(this, &ThisClass::HandleHealthChanged);
|
||||
HealthSet->OnMaxHealthChanged.AddUObject(this, &ThisClass::HandleMaxHealthChanged);
|
||||
HealthSet->OnOutOfHealth.AddUObject(this, &ThisClass::HandleOutOfHealth);
|
||||
|
||||
// TEMP: Reset attributes to default values. Eventually this will be driven by a spreadsheet.
|
||||
AbilitySystemComponent->SetNumericAttributeBase(UOLSHealthAttributeSet::GetHealthAttribute(), HealthSet->GetMaxHealth());
|
||||
|
||||
ClearGameplayTags();
|
||||
|
||||
Broadcast_OnHealthChanged(this, HealthSet->GetHealth(), HealthSet->GetHealth(), nullptr);
|
||||
Broadcast_OnMaxHealthChanged(this, HealthSet->GetHealth(), HealthSet->GetMaxHealth(), nullptr);
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::UninitializeFromAbilitySystem()
|
||||
{
|
||||
ClearGameplayTags();
|
||||
|
||||
if (HealthSet)
|
||||
{
|
||||
HealthSet->OnHealthChanged.RemoveAll(this);
|
||||
HealthSet->OnMaxHealthChanged.RemoveAll(this);
|
||||
HealthSet->OnOutOfHealth.RemoveAll(this);
|
||||
}
|
||||
|
||||
HealthSet = nullptr;
|
||||
AbilitySystemComponent = nullptr;
|
||||
}
|
||||
|
||||
float UOLSHealthComponent::GetHealth() const
|
||||
{
|
||||
return (HealthSet ? HealthSet->GetHealth() : 0.0f);
|
||||
}
|
||||
|
||||
float UOLSHealthComponent::GetMaxHealth() const
|
||||
{
|
||||
return (HealthSet ? HealthSet->GetMaxHealth() : 0.0f);
|
||||
}
|
||||
|
||||
float UOLSHealthComponent::GetHealthNormalized() const
|
||||
{
|
||||
if (HealthSet)
|
||||
{
|
||||
const float health = HealthSet->GetHealth();
|
||||
const float maxHealth = HealthSet->GetMaxHealth();
|
||||
|
||||
return ((maxHealth > 0.0f) ? (health / maxHealth) : 0.0f);
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
EOLSDeathState UOLSHealthComponent::GetDeathState() const
|
||||
{
|
||||
return DeathState;
|
||||
}
|
||||
|
||||
bool UOLSHealthComponent::IsDeadOrDying() const
|
||||
{
|
||||
return (DeathState > EOLSDeathState::NotDead);
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::StartDeath()
|
||||
{
|
||||
if (DeathState != EOLSDeathState::NotDead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeathState = EOLSDeathState::DeathStarted;
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
// @TODO: Add LyraGameplayTags::Status_Death_Dying.
|
||||
// AbilitySystemComponent->SetLooseGameplayTagCount(LyraGameplayTags::Status_Death_Dying, 1);
|
||||
}
|
||||
|
||||
AActor* owner = GetOwner();
|
||||
check(owner);
|
||||
|
||||
Broadcast_OnDeathStarted(owner);
|
||||
|
||||
owner->ForceNetUpdate();
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::FinishDeath()
|
||||
{
|
||||
if (DeathState != EOLSDeathState::DeathStarted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeathState = EOLSDeathState::DeathFinished;
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
// @TODO: Add LyraGameplayTags::Status_Death_Dead.
|
||||
// AbilitySystemComponent->SetLooseGameplayTagCount(LyraGameplayTags::Status_Death_Dead, 1);
|
||||
}
|
||||
|
||||
AActor* owner = GetOwner();
|
||||
check(owner);
|
||||
|
||||
Broadcast_OnDeathFinished(owner);
|
||||
|
||||
owner->ForceNetUpdate();
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::DamageSelfDestruct(bool isFellOutOfWorld)
|
||||
{
|
||||
if ((DeathState == EOLSDeathState::NotDead) && AbilitySystemComponent)
|
||||
{
|
||||
const TSubclassOf<UGameplayEffect> damageGE = UOLSAssetManager::GetSubclass(UOLSGameDataAsset::Get().DamageGameplayEffect_SetByCaller);
|
||||
if (!damageGE)
|
||||
{
|
||||
OLS_LOG(LogOLSHealthComponent, Error,
|
||||
TEXT("DamageSelfDestruct failed for owner [%s]. Unable to find gameplay effect [%s]."),
|
||||
GET_UOBJECT_NAME(GetOwner()),
|
||||
*UOLSGameDataAsset::Get().DamageGameplayEffect_SetByCaller.GetAssetName());
|
||||
return;
|
||||
}
|
||||
|
||||
FGameplayEffectSpecHandle specHandle = AbilitySystemComponent->MakeOutgoingSpec(damageGE, 1.0f, AbilitySystemComponent->MakeEffectContext());
|
||||
FGameplayEffectSpec* spec = specHandle.Data.Get();
|
||||
|
||||
if (!spec)
|
||||
{
|
||||
OLS_LOG(LogOLSHealthComponent, Error,
|
||||
TEXT("DamageSelfDestruct failed for owner [%s]. Unable to make outgoing spec for [%s]."),
|
||||
GET_UOBJECT_NAME(GetOwner()), GET_UOBJECT_NAME(damageGE));
|
||||
return;
|
||||
}
|
||||
|
||||
spec->AddDynamicAssetTag(TAG_Gameplay_DamageSelfDestruct);
|
||||
|
||||
if (isFellOutOfWorld)
|
||||
{
|
||||
spec->AddDynamicAssetTag(TAG_Gameplay_FellOutOfWorld);
|
||||
}
|
||||
|
||||
const float damageAmount = GetMaxHealth();
|
||||
|
||||
|
||||
// @TODO: Add LyraGameplayTags::SetByCaller_Damage.
|
||||
// Spec->SetSetByCallerMagnitude(LyraGameplayTags::SetByCaller_Damage, damageAmount);
|
||||
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*spec);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::Broadcast_OnHealthChanged(UOLSHealthComponent* healthComponent, float oldValue,
|
||||
float newValue, AActor* instigator) const
|
||||
{
|
||||
if (OnHealthChangedDynamicDelegate.IsBound())
|
||||
{
|
||||
OnHealthChangedDynamicDelegate.Broadcast(healthComponent, oldValue, newValue, instigator);
|
||||
}
|
||||
|
||||
if (OnHealthChangedNativeDelegate.IsBound())
|
||||
{
|
||||
OnHealthChangedNativeDelegate.Broadcast(healthComponent, oldValue, newValue, instigator);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::Broadcast_OnMaxHealthChanged(UOLSHealthComponent* healthComponent, float oldValue,
|
||||
float newValue, AActor* instigator) const
|
||||
{
|
||||
if (OnMaxHealthChangedDynamicDelegate.IsBound())
|
||||
{
|
||||
OnMaxHealthChangedDynamicDelegate.Broadcast(healthComponent, oldValue, newValue, instigator);
|
||||
}
|
||||
|
||||
if (OnMaxHealthChangedNativeDelegate.IsBound())
|
||||
{
|
||||
OnMaxHealthChangedNativeDelegate.Broadcast(healthComponent, oldValue, newValue, instigator);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::Broadcast_OnDeathStarted(AActor* owningActor) const
|
||||
{
|
||||
if (OnDeathStartedDynamicDelegate.IsBound())
|
||||
{
|
||||
OnDeathStartedDynamicDelegate.Broadcast(owningActor);
|
||||
}
|
||||
|
||||
if (OnDeathStartNativeDelegate.IsBound())
|
||||
{
|
||||
OnDeathStartNativeDelegate.Broadcast(owningActor);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::Broadcast_OnDeathFinished(AActor* owningActor) const
|
||||
{
|
||||
if (OnDeathFinishedDynamicDelegate.IsBound())
|
||||
{
|
||||
OnDeathFinishedDynamicDelegate.Broadcast(owningActor);
|
||||
}
|
||||
|
||||
if (OnDeathFinishedNativeDelegate.IsBound())
|
||||
{
|
||||
OnDeathFinishedNativeDelegate.Broadcast(owningActor);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::OnUnregister()
|
||||
{
|
||||
UninitializeFromAbilitySystem();
|
||||
|
||||
Super::OnUnregister();
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::ClearGameplayTags()
|
||||
{
|
||||
if (HealthSet)
|
||||
{
|
||||
HealthSet->OnHealthChanged.RemoveAll(this);
|
||||
HealthSet->OnMaxHealthChanged.RemoveAll(this);
|
||||
HealthSet->OnOutOfHealth.RemoveAll(this);
|
||||
}
|
||||
|
||||
HealthSet = nullptr;
|
||||
AbilitySystemComponent = nullptr;
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::HandleHealthChanged(AActor* damageInstigator, AActor* damageCauser,
|
||||
const FGameplayEffectSpec* damageEffectSpec, float damageMagnitude,
|
||||
float oldValue, float newValue)
|
||||
{
|
||||
Broadcast_OnHealthChanged(this, oldValue, newValue, damageInstigator);
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::HandleMaxHealthChanged(AActor* damageInstigator, AActor* damageCauser,
|
||||
const FGameplayEffectSpec* damageEffectSpec, float damageMagnitude,
|
||||
float oldValue, float newValue)
|
||||
{
|
||||
Broadcast_OnMaxHealthChanged(this, oldValue, newValue, damageInstigator);
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::HandleOutOfHealth(AActor* damageInstigator, AActor* damageCauser,
|
||||
const FGameplayEffectSpec* damageEffectSpec, float damageMagnitude,
|
||||
float oldValue, float newValue)
|
||||
{
|
||||
#if WITH_SERVER_CODE
|
||||
if (AbilitySystemComponent && damageEffectSpec)
|
||||
{
|
||||
// Send the "GameplayEvent.Death" gameplay event through the owner's ability system. This can be used to trigger a death gameplay ability.
|
||||
{
|
||||
FGameplayEventData payload;
|
||||
// @TODO: Add LyraGameplayTags::GameplayEvent_Death.
|
||||
// payload.EventTag = LyraGameplayTags::GameplayEvent_Death;
|
||||
payload.Instigator = damageInstigator;
|
||||
payload.Target = AbilitySystemComponent->GetAvatarActor();
|
||||
payload.OptionalObject = damageEffectSpec->Def;
|
||||
payload.ContextHandle = damageEffectSpec->GetEffectContext();
|
||||
payload.InstigatorTags = *damageEffectSpec->CapturedSourceTags.GetAggregatedTags();
|
||||
payload.TargetTags = *damageEffectSpec->CapturedTargetTags.GetAggregatedTags();
|
||||
payload.EventMagnitude = damageMagnitude;
|
||||
|
||||
FScopedPredictionWindow newScopedWindow(AbilitySystemComponent, true);
|
||||
AbilitySystemComponent->HandleGameplayEvent(payload.EventTag, &payload);
|
||||
}
|
||||
|
||||
// Send a standardized verb message that other systems can observe
|
||||
{
|
||||
FOLSVerbMessage message;
|
||||
// @TODO: Add LyraGameplayTags::TAG_Lyra_Elimination_Message.
|
||||
// message.Verb = TAG_Lyra_Elimination_Message;
|
||||
message.Instigator = damageInstigator;
|
||||
message.InstigatorTags = *damageEffectSpec->CapturedSourceTags.GetAggregatedTags();
|
||||
message.Target = UOLSVerbMessageHelpers::GetPlayerStateFromObject(AbilitySystemComponent->GetAvatarActor());
|
||||
message.TargetTags = *damageEffectSpec->CapturedTargetTags.GetAggregatedTags();
|
||||
//@TODO: Fill out context tags, and any non-ability-system source/instigator tags
|
||||
//@TODO: Determine if it's an opposing team kill, self-own, team kill, etc...
|
||||
|
||||
UGameplayMessageSubsystem& MessageSystem = UGameplayMessageSubsystem::Get(GetWorld());
|
||||
MessageSystem.BroadcastMessage(message.Verb, message);
|
||||
}
|
||||
|
||||
//@TODO: assist messages (could compute from damage dealt elsewhere)?
|
||||
}
|
||||
|
||||
#endif // #if WITH_SERVER_CODE
|
||||
}
|
||||
|
||||
void UOLSHealthComponent::OnRep_DeathState(EOLSDeathState oldDeathState)
|
||||
{
|
||||
const EOLSDeathState newDeathState = DeathState;
|
||||
|
||||
// Revert the death state for now since we rely on StartDeath and FinishDeath to change it.
|
||||
DeathState = oldDeathState;
|
||||
|
||||
if (oldDeathState > newDeathState)
|
||||
{
|
||||
OLS_LOG(LogOLSHealthComponent, Warning,
|
||||
TEXT("Predicted past server death state [%d] -> [%d] for owner [%s]."),
|
||||
(uint8)oldDeathState, (uint8)newDeathState, GET_UOBJECT_NAME(GetOwner()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldDeathState == EOLSDeathState::NotDead)
|
||||
{
|
||||
if (newDeathState == EOLSDeathState::DeathStarted)
|
||||
{
|
||||
StartDeath();
|
||||
}
|
||||
else if (newDeathState == EOLSDeathState::DeathFinished)
|
||||
{
|
||||
StartDeath();
|
||||
FinishDeath();
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogOLSHealthComponent, Error, TEXT("Invalid death transition [%d] -> [%d] for owner [%s]."),
|
||||
(uint8)oldDeathState, (uint8)newDeathState, GET_UOBJECT_NAME(GetOwner()));
|
||||
}
|
||||
}
|
||||
else if (oldDeathState == EOLSDeathState::DeathStarted)
|
||||
{
|
||||
if (newDeathState == EOLSDeathState::DeathFinished)
|
||||
{
|
||||
FinishDeath();
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogOLSHealthComponent, Error, TEXT("Invalid death transition [%d] -> [%d] for owner [%s]."),
|
||||
(uint8)oldDeathState, (uint8)newDeathState, GET_UOBJECT_NAME(GetOwner()));
|
||||
}
|
||||
}
|
||||
|
||||
ensureMsgf((DeathState == newDeathState),
|
||||
TEXT("OLSHealthComponent: Death transition failed [%d] -> [%d] for owner [%s]."),
|
||||
static_cast<uint8>(oldDeathState), static_cast<uint8>(newDeathState), *GetNameSafe(GetOwner()));
|
||||
}
|
551
Source/ols/Private/Components/OLSHeroComponent.cpp
Normal file
551
Source/ols/Private/Components/OLSHeroComponent.cpp
Normal file
@ -0,0 +1,551 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Components/OLSHeroComponent.h"
|
||||
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "Camera/OLSCameraMode.h"
|
||||
#include "Characters/OLSCharacter.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
#include "Components/OLSPawnExtensionComponent.h"
|
||||
#include "DataAssets/OLSPawnDataAsset.h"
|
||||
#include "GameFeatures/OLSGameFeatureAction_AddInputContextMapping.h"
|
||||
#include "Player/OLSPlayerController.h"
|
||||
#include "Player/OLSPlayerState.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Misc/UObjectToken.h"
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSHeroComponent);
|
||||
|
||||
namespace OLSHero
|
||||
{
|
||||
static const float LookYawRate = 300.0f;
|
||||
static const float LookPitchRate = 165.0f;
|
||||
};
|
||||
|
||||
const FName UOLSHeroComponent::NAME_BindInputsNow("BindInputsNow");
|
||||
const FName UOLSHeroComponent::NAME_ActorFeatureName("Hero");
|
||||
|
||||
UOLSHeroComponent::UOLSHeroComponent(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
AbilityCameraMode = nullptr;
|
||||
bIsReadyToBindInputs = false;
|
||||
}
|
||||
|
||||
FName UOLSHeroComponent::GetFeatureName() const
|
||||
{
|
||||
// Don't call Super since it does not fit in this.
|
||||
|
||||
return NAME_ActorFeatureName;
|
||||
}
|
||||
|
||||
bool UOLSHeroComponent::CanChangeInitState(UGameFrameworkComponentManager* manager,
|
||||
FGameplayTag currentState,
|
||||
FGameplayTag desiredState) const
|
||||
{
|
||||
// Don't call Super since it does not fit into this.
|
||||
|
||||
check(manager);
|
||||
|
||||
APawn* pawn = GetPawn<APawn>();
|
||||
|
||||
//@TODO: implement LyraGameplayTags::InitState_Spawned.
|
||||
//@TODO: implement LyraGameplayTags::InitState_DataAvailable.
|
||||
//@TODO: implement LyraGameplayTags::InitState_DataInitialized.
|
||||
//@TODO: implement LyraGameplayTags::InitState_GameplayReady.
|
||||
// if (!currentState.IsValid() && desiredState == LyraGameplayTags::InitState_Spawned)
|
||||
// {
|
||||
// // As long as we have a real pawn, let us transition
|
||||
// if (pawn)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// else if (currentState == LyraGameplayTags::InitState_Spawned && desiredState == LyraGameplayTags::InitState_DataAvailable)
|
||||
// {
|
||||
// // The player state is required.
|
||||
// if (!GetPlayerState<AOLSPlayerState>())
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // If we're authority or autonomous, we need to wait for a controller with registered ownership of the player state.
|
||||
// if (pawn->GetLocalRole() != ROLE_SimulatedProxy)
|
||||
// {
|
||||
// AController* controller = GetController<AController>();
|
||||
//
|
||||
// const bool hasControllerPairedWithPS = (controller != nullptr) && \
|
||||
// (controller->PlayerState != nullptr) && \
|
||||
// (controller->PlayerState->GetOwner() == controller);
|
||||
//
|
||||
// if (!hasControllerPairedWithPS)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// const bool isLocallyControlled = pawn->IsLocallyControlled();
|
||||
// const bool isBot = pawn->IsBotControlled();
|
||||
//
|
||||
// if (isLocallyControlled && !isBot)
|
||||
// {
|
||||
// AOLSPlayerController* playerController = GetController<AOLSPlayerController>();
|
||||
//
|
||||
// // The input component and local player is required when locally controlled.
|
||||
// if (!pawn->InputComponent || !playerController || !playerController->GetLocalPlayer())
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
// else if (currentState == LyraGameplayTags::InitState_DataAvailable && desiredState == LyraGameplayTags::InitState_DataInitialized)
|
||||
// {
|
||||
// // Wait for player state and extension component
|
||||
// AOLSPlayerState* playerState = GetPlayerState<AOLSPlayerState>();
|
||||
//
|
||||
// return playerState && manager->HasFeatureReachedInitState(pawn, UOLSPawnExtensionComponent::NAME_ActorFeatureName, LyraGameplayTags::InitState_DataInitialized);
|
||||
// }
|
||||
// else if (currentState == LyraGameplayTags::InitState_DataInitialized && desiredState == LyraGameplayTags::InitState_GameplayReady)
|
||||
// {
|
||||
// // TODO add ability initialization checks?
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::HandleChangeInitState(UGameFrameworkComponentManager* manager,
|
||||
FGameplayTag currentState,
|
||||
FGameplayTag desiredState)
|
||||
{
|
||||
//@TODO: implement LyraGameplayTags::InitState_DataAvailable.
|
||||
//@TODO: implement LyraGameplayTags::InitState_DataInitialized.
|
||||
// if (currentState == LyraGameplayTags::InitState_DataAvailable && desiredState == LyraGameplayTags::InitState_DataInitialized)
|
||||
// {
|
||||
// APawn* pawn = GetPawn<APawn>();
|
||||
// AOLSPlayerState* playerState = GetPlayerState<AOLSPlayerState>();
|
||||
// if (!ensure(pawn && playerState))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const UOLSPawnDataAsset* pawnData = nullptr;
|
||||
//
|
||||
// if (UOLSPawnExtensionComponent* PawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn))
|
||||
// {
|
||||
// pawnData = PawnExtComp->GetPawnData<UOLSPawnDataAsset>();
|
||||
//
|
||||
// // The player state holds the persistent data for this player (state that persists across deaths and multiple pawns).
|
||||
// // The ability system component and attribute sets live on the player state.
|
||||
// PawnExtComp->InitializeAbilitySystem(playerState->GetOLSAbilitySystemComponent(), playerState);
|
||||
// }
|
||||
//
|
||||
// if (AOLSPlayerController* playerController = GetController<AOLSPlayerController>())
|
||||
// {
|
||||
// if (pawn->InputComponent)
|
||||
// {
|
||||
// InitializePlayerInput(pawn->InputComponent);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // Hook up the delegate for all pawns, in case we spectate later
|
||||
// // if (pawnData)
|
||||
// // {
|
||||
// // if (UOLSCameraComponent* CameraComponent = UOLSCameraComponent::FindCameraComponent(pawn))
|
||||
// // {
|
||||
// // CameraComponent->DetermineCameraModeDelegate.BindUObject(this, &ThisClass::DetermineCameraMode);
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& params)
|
||||
{
|
||||
// if (params.FeatureName == UOLSPawnExtensionComponent::NAME_ActorFeatureName)
|
||||
// {
|
||||
// //@TODO: implement LyraGameplayTags::InitState_DataAvailable.
|
||||
// if (params.FeatureState == LyraGameplayTags::InitState_DataInitialized)
|
||||
// {
|
||||
// // If the extension component says all other components are initialized, try to progress to next state
|
||||
// CheckDefaultInitialization();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::CheckDefaultInitialization()
|
||||
{
|
||||
//@TODO: implement LyraGameplayTags::InitState_Spawned.
|
||||
//@TODO: implement LyraGameplayTags::InitState_DataAvailable.
|
||||
//@TODO: implement LyraGameplayTags::InitState_DataInitialized.
|
||||
//@TODO: implement LyraGameplayTags::InitState_GameplayReady.
|
||||
// static const TArray<FGameplayTag> stateChain = {
|
||||
// LyraGameplayTags::InitState_Spawned, LyraGameplayTags::InitState_DataAvailable,
|
||||
// LyraGameplayTags::InitState_DataInitialized, LyraGameplayTags::InitState_GameplayReady
|
||||
// };
|
||||
//
|
||||
// // This will try to progress from spawned (which is only set in BeginPlay) through the data initialization stages until it gets to gameplay ready
|
||||
// ContinueInitStateChain(stateChain);
|
||||
}
|
||||
|
||||
UOLSHeroComponent* UOLSHeroComponent::FindHeroComponent(const AActor* actor)
|
||||
{
|
||||
return (actor ? actor->FindComponentByClass<UOLSHeroComponent>() : nullptr);
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::SetAbilityCameraMode(TSubclassOf<UOLSCameraMode> CameraMode,
|
||||
const FGameplayAbilitySpecHandle& OwningSpecHandle)
|
||||
{
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::ClearAbilityCameraMode(const FGameplayAbilitySpecHandle& owningSpecHandle)
|
||||
{
|
||||
if (AbilityCameraModeOwningSpecHandle == owningSpecHandle)
|
||||
{
|
||||
//@TODO: implement UOLSCameraMode.
|
||||
// AbilityCameraMode = nullptr;
|
||||
AbilityCameraModeOwningSpecHandle = FGameplayAbilitySpecHandle();
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::AddAdditionalInputConfig(const UOLSInputConfigDataAsset* inputConfig)
|
||||
{
|
||||
TArray<uint32> bindHandles;
|
||||
|
||||
const APawn* pawn = GetPawn<APawn>();
|
||||
if (!pawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const APlayerController* playerController = GetController<APlayerController>();
|
||||
check(playerController);
|
||||
|
||||
const ULocalPlayer* localPlayer = playerController->GetLocalPlayer();
|
||||
check(localPlayer);
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* subsystem = localPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
||||
check(subsystem);
|
||||
|
||||
if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn))
|
||||
{
|
||||
//@TODO: Implement UOLSInputComponent.
|
||||
// UOLSInputComponent* inputComponent = pawn->FindComponentByClass<UOLSInputComponent>();
|
||||
// if (ensureMsgf(inputComponent,
|
||||
// TEXT(
|
||||
// "Unexpected Input Component class! The Gameplay Abilities will not be bound to their inputs. Change the input component to ULyraInputComponent or a subclass of it."
|
||||
// )))
|
||||
// {
|
||||
// inputComponent->BindAbilityActions(inputConfig, this, &ThisClass::Input_AbilityInputTagPressed,
|
||||
// &ThisClass::Input_AbilityInputTagReleased, /*out*/ bindHandles);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::RemoveAdditionalInputConfig(const UOLSInputConfigDataAsset* inputConfig)
|
||||
{
|
||||
//@TODO: Implement me!
|
||||
}
|
||||
|
||||
bool UOLSHeroComponent::IsReadyToBindInputs() const
|
||||
{
|
||||
return bIsReadyToBindInputs;
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::OnRegister()
|
||||
{
|
||||
Super::OnRegister();
|
||||
|
||||
if (!GetPawn<APawn>())
|
||||
{
|
||||
OLS_LOG(LogOLSHeroComponent, Error,
|
||||
TEXT(
|
||||
"This component has been added to a blueprint whose base class is not a Pawn. To use this component, it MUST be placed on a Pawn Blueprint."
|
||||
));
|
||||
|
||||
#if WITH_EDITOR
|
||||
if (GIsEditor)
|
||||
{
|
||||
static const FText message = NSLOCTEXT("OLSHeroComponent", "NotOnPawnError",
|
||||
"has been added to a blueprint whose base class is not a Pawn. To use this component, it MUST be placed on a Pawn Blueprint. This will cause a crash if you PIE!");
|
||||
static const FName heroMessageLogName = TEXT("OLSHeroComponent");
|
||||
|
||||
FMessageLog(heroMessageLogName).Error()
|
||||
->AddToken(FUObjectToken::Create(this, FText::FromString(GetNameSafe(this))))
|
||||
->AddToken(FTextToken::Create(message));
|
||||
|
||||
FMessageLog(heroMessageLogName).Open();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// Register with the init state system early, this will only work if this is a game world
|
||||
RegisterInitStateFeature();
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Listen for when the pawn extension component changes init state
|
||||
BindOnActorInitStateChanged(UOLSPawnExtensionComponent::NAME_ActorFeatureName, FGameplayTag(), false);
|
||||
|
||||
// Notifies that we are done spawning, then try the rest of initialization
|
||||
//@TODO: implement LyraGameplayTags::InitState_Spawned.
|
||||
// ensure(TryToChangeInitState(LyraGameplayTags::InitState_Spawned));
|
||||
CheckDefaultInitialization();
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UnregisterInitStateFeature();
|
||||
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::InitializePlayerInput(UInputComponent* playerInputComponent)
|
||||
{
|
||||
check(playerInputComponent);
|
||||
|
||||
const APawn* Pawn = GetPawn<APawn>();
|
||||
if (!Pawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const APlayerController* playerController = GetController<APlayerController>();
|
||||
check(playerController);
|
||||
|
||||
//@TODO implement UOLSLocalPlayer.
|
||||
// const UOLSLocalPlayer* localPlayer = Cast<UOLSLocalPlayer>(playerController->GetLocalPlayer());
|
||||
// check(localPlayer);
|
||||
|
||||
// UEnhancedInputLocalPlayerSubsystem* subsystem = localPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
||||
// check(subsystem);
|
||||
//
|
||||
// subsystem->ClearAllMappings();
|
||||
|
||||
// if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
|
||||
// {
|
||||
// if (const UOLSPawnDataAsset* pawnData = pawnExtComp->GetPawnData<UOLSPawnDataAsset>())
|
||||
// {
|
||||
// //@TODO: Implement UOLSInputConfig
|
||||
// if (const UOLSInputConfig* inputConfig = pawnData->InputConfig)
|
||||
// {
|
||||
// for (const FOLSInputMappingContextAndPriority& mapping : DefaultInputMappings)
|
||||
// {
|
||||
// if (UInputMappingContext* imc = mapping.InputMapping.Get())
|
||||
// {
|
||||
// if (mapping.bShouldRegisterWithSettings)
|
||||
// {
|
||||
// if (UEnhancedInputUserSettings* settings = subsystem->GetUserSettings())
|
||||
// {
|
||||
// settings->RegisterInputMappingContext(imc);
|
||||
// }
|
||||
//
|
||||
// FModifyContextOptions options = {};
|
||||
// options.bIgnoreAllPressedKeysUntilRelease = false;
|
||||
// // Actually add the config to the local player
|
||||
// subsystem->AddMappingContext(imc, mapping.Priority, options);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // The Lyra Input Component has some additional functions to map Gameplay Tags to an Input Action.
|
||||
// // If you want this functionality but still want to change your input component class, make it a subclass
|
||||
// // of the ULyraInputComponent or modify this component accordingly.
|
||||
// //@TODO: Implement UOLSInputComponent.
|
||||
// // UOLSInputComponent* inputComponent = Cast<UOLSInputComponent>(playerInputComponent);
|
||||
// if (ensureMsgf(inputComponent,
|
||||
// TEXT(
|
||||
// "Unexpected Input Component class! The Gameplay Abilities will not be bound to their inputs. Change the input component to ULyraInputComponent or a subclass of it."
|
||||
// )))
|
||||
// {
|
||||
// // Add the key mappings that may have been set by the player
|
||||
// inputComponent->AddInputMappings(inputConfig, subsystem);
|
||||
//
|
||||
// // This is where we actually bind and input action to a gameplay tag, which means that Gameplay Ability Blueprints will
|
||||
// // be triggered directly by these input actions Triggered events.
|
||||
// TArray<uint32> BindHandles;
|
||||
// inputComponent->BindAbilityActions(inputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles);
|
||||
//
|
||||
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move, /*bLogIfNotFound=*/ false);
|
||||
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Look_Mouse, ETriggerEvent::Triggered, this, &ThisClass::Input_LookMouse, /*bLogIfNotFound=*/ false);
|
||||
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Look_Stick, ETriggerEvent::Triggered, this, &ThisClass::Input_LookStick, /*bLogIfNotFound=*/ false);
|
||||
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_Crouch, ETriggerEvent::Triggered, this, &ThisClass::Input_Crouch, /*bLogIfNotFound=*/ false);
|
||||
// inputComponent->BindNativeAction(inputConfig, LyraGameplayTags::InputTag_AutoRun, ETriggerEvent::Triggered, this, &ThisClass::Input_AutoRun, /*bLogIfNotFound=*/ false);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (ensure(!bIsReadyToBindInputs))
|
||||
{
|
||||
bIsReadyToBindInputs = true;
|
||||
}
|
||||
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APlayerController*>(playerController), NAME_BindInputsNow);
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APawn*>(Pawn), NAME_BindInputsNow);
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::Input_AbilityInputTagPressed(FGameplayTag inputTag)
|
||||
{
|
||||
if (const APawn* pawn = GetPawn<APawn>())
|
||||
{
|
||||
if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn))
|
||||
{
|
||||
if (UOLSAbilitySystemComponent* asc = pawnExtComp->GetOLSAbilitySystemComponent())
|
||||
{
|
||||
asc->AbilityInputTagPressed(inputTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::Input_AbilityInputTagReleased(FGameplayTag inputTag)
|
||||
{
|
||||
const APawn* pawn = GetPawn<APawn>();
|
||||
if (!pawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (const UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(pawn))
|
||||
{
|
||||
if (UOLSAbilitySystemComponent* asc = pawnExtComp->GetOLSAbilitySystemComponent())
|
||||
{
|
||||
asc->AbilityInputTagReleased(inputTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::Input_Move(const FInputActionValue& inputActionValue)
|
||||
{
|
||||
APawn* pawn = GetPawn<APawn>();
|
||||
AController* controller = pawn ? pawn->GetController() : nullptr;
|
||||
|
||||
// If the player has attempted to move again then cancel auto running
|
||||
if (AOLSPlayerController* playerController = Cast<AOLSPlayerController>(controller))
|
||||
{
|
||||
//@TODO: Should we have this?
|
||||
// playerController->SetIsAutoRunning(false);
|
||||
}
|
||||
|
||||
if (controller)
|
||||
{
|
||||
const FVector2D value = inputActionValue.Get<FVector2D>();
|
||||
const FRotator movementRotation(0.0f, controller->GetControlRotation().Yaw, 0.0f);
|
||||
|
||||
if (value.X != 0.0f)
|
||||
{
|
||||
const FVector movementDirection = movementRotation.RotateVector(FVector::RightVector);
|
||||
pawn->AddMovementInput(movementDirection, value.X);
|
||||
}
|
||||
|
||||
if (value.Y != 0.0f)
|
||||
{
|
||||
const FVector movementDirection = movementRotation.RotateVector(FVector::ForwardVector);
|
||||
pawn->AddMovementInput(movementDirection, value.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::Input_LookMouse(const FInputActionValue& inputActionValue)
|
||||
{
|
||||
APawn* pawn = GetPawn<APawn>();
|
||||
|
||||
if (!pawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector2D value = inputActionValue.Get<FVector2D>();
|
||||
|
||||
if (value.X != 0.0f)
|
||||
{
|
||||
pawn->AddControllerYawInput(value.X);
|
||||
}
|
||||
|
||||
if (value.Y != 0.0f)
|
||||
{
|
||||
pawn->AddControllerPitchInput(value.Y);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::Input_LookStick(const FInputActionValue& inputActionValue)
|
||||
{
|
||||
APawn* pawn = GetPawn<APawn>();
|
||||
|
||||
if (!pawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector2D value = inputActionValue.Get<FVector2D>();
|
||||
|
||||
const UWorld* world = GetWorld();
|
||||
check(world);
|
||||
|
||||
if (value.X != 0.0f)
|
||||
{
|
||||
pawn->AddControllerYawInput(value.X * OLSHero::LookYawRate * world->GetDeltaSeconds());
|
||||
}
|
||||
|
||||
if (value.Y != 0.0f)
|
||||
{
|
||||
pawn->AddControllerPitchInput(value.Y * OLSHero::LookPitchRate * world->GetDeltaSeconds());
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::Input_Crouch(const FInputActionValue& inputActionValue)
|
||||
{
|
||||
if (AOLSCharacter* character = GetPawn<AOLSCharacter>())
|
||||
{
|
||||
character->ToggleCrouch();
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSHeroComponent::Input_AutoRun(const FInputActionValue& inputActionValue)
|
||||
{
|
||||
if (APawn* Pawp = GetPawn<APawn>())
|
||||
{
|
||||
if (AOLSPlayerController* controller = Cast<AOLSPlayerController>(Pawp->GetController()))
|
||||
{
|
||||
//@TODO: Should we have this?
|
||||
// Toggle auto running
|
||||
// controller->SetIsAutoRunning(!controller->GetIsAutoRunning());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TSubclassOf<UOLSCameraMode> UOLSHeroComponent::DetermineCameraMode() const
|
||||
{
|
||||
if (AbilityCameraMode)
|
||||
{
|
||||
return AbilityCameraMode;
|
||||
}
|
||||
|
||||
const APawn* Pawn = GetPawn<APawn>();
|
||||
if (!Pawn)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
|
||||
{
|
||||
if (const UOLSPawnDataAsset* pawnData = pawnExtComp->GetPawnData<UOLSPawnDataAsset>())
|
||||
{
|
||||
return pawnData->DefaultCameraMode;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
314
Source/ols/Private/Components/OLSPawnExtensionComponent.cpp
Normal file
314
Source/ols/Private/Components/OLSPawnExtensionComponent.cpp
Normal file
@ -0,0 +1,314 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Components/OLSPawnExtensionComponent.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
#include "DataAssets/OLSPawnDataAsset.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSPawnExtensionComponent);
|
||||
|
||||
const FName UOLSPawnExtensionComponent::NAME_ActorFeatureName("PawnExtension");
|
||||
|
||||
UOLSPawnExtensionComponent::UOLSPawnExtensionComponent(const FObjectInitializer& objectInitializer)
|
||||
: Super(objectInitializer)
|
||||
{
|
||||
PrimaryComponentTick.bStartWithTickEnabled = false;
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
|
||||
SetIsReplicatedByDefault(true);
|
||||
|
||||
PawnData = nullptr;
|
||||
AbilitySystemComponent = nullptr;
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME(UOLSPawnExtensionComponent, PawnData);
|
||||
}
|
||||
|
||||
FName UOLSPawnExtensionComponent::GetFeatureName() const
|
||||
{
|
||||
return NAME_ActorFeatureName;
|
||||
}
|
||||
|
||||
bool UOLSPawnExtensionComponent::CanChangeInitState(
|
||||
UGameFrameworkComponentManager* manager,
|
||||
FGameplayTag currentState,
|
||||
FGameplayTag desiredState) const
|
||||
{
|
||||
check(manager);
|
||||
|
||||
APawn* pawn = GetPawn<APawn>();
|
||||
|
||||
// @TODO: Implement LyraGameplayTags::InitState_Spawned.
|
||||
if (!currentState.IsValid() /* && desiredState == LyraGameplayTags::InitState_Spawned */)
|
||||
{
|
||||
// As long as we are on a valid pawn, we count as spawned
|
||||
if (pawn)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: Implement LyraGameplayTags::InitState_Spawned,
|
||||
// @TODO: Implement LyraGameplayTags::InitState_DataAvailable,
|
||||
// @TODO: Implement LyraGameplayTags::InitState_DataInitialized,
|
||||
// @TODO: Implement LyraGameplayTags::InitState_GameplayReady
|
||||
// if (currentState == LyraGameplayTags::InitState_Spawned && desiredState == LyraGameplayTags::InitState_DataAvailable)
|
||||
// {
|
||||
// // Pawn data is required.
|
||||
// if (!PawnData)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// const bool bHasAuthority = pawn->HasAuthority();
|
||||
// const bool bIsLocallyControlled = pawn->IsLocallyControlled();
|
||||
//
|
||||
// if (bHasAuthority || bIsLocallyControlled)
|
||||
// {
|
||||
// // Check for being possessed by a controller.
|
||||
// if (!GetController<AController>())
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
// else if (currentState == LyraGameplayTags::InitState_DataAvailable && desiredState == LyraGameplayTags::InitState_DataInitialized)
|
||||
// {
|
||||
// // Transition to initialize if all features have their data available
|
||||
// return manager->HaveAllFeaturesReachedInitState(pawn, LyraGameplayTags::InitState_DataAvailable);
|
||||
// }
|
||||
// else if (currentState == LyraGameplayTags::InitState_DataInitialized && desiredState == LyraGameplayTags::InitState_GameplayReady)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::HandleChangeInitState(
|
||||
UGameFrameworkComponentManager* manager,
|
||||
FGameplayTag currentState,
|
||||
FGameplayTag desiredState)
|
||||
{
|
||||
// @TODO: Implement LyraGameplayTags::InitState_Spawned.
|
||||
// if (desiredState == LyraGameplayTags::InitState_DataInitialized)
|
||||
// {
|
||||
// // This is currently all handled by other components listening to this state change
|
||||
// }
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& Params)
|
||||
{
|
||||
// If another feature is now in DataAvailable, see if we should transition to DataInitialized
|
||||
if (Params.FeatureName != NAME_ActorFeatureName)
|
||||
{
|
||||
// @TODO: Implement LyraGameplayTags::InitState_DataAvailable.
|
||||
// if (Params.FeatureState == LyraGameplayTags::InitState_DataAvailable)
|
||||
// {
|
||||
// CheckDefaultInitialization();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::CheckDefaultInitialization()
|
||||
{
|
||||
// Before checking our progress, try progressing any other features we might depend on
|
||||
CheckDefaultInitializationForImplementers();
|
||||
|
||||
// @TODO: Implement LyraGameplayTags::InitState_Spawned,
|
||||
// @TODO: Implement LyraGameplayTags::InitState_DataAvailable,
|
||||
// @TODO: Implement LyraGameplayTags::InitState_DataInitialized,
|
||||
// @TODO: Implement LyraGameplayTags::InitState_GameplayReady
|
||||
// static const TArray<FGameplayTag> StateChain = {
|
||||
// LyraGameplayTags::InitState_Spawned, LyraGameplayTags::InitState_DataAvailable,
|
||||
// LyraGameplayTags::InitState_DataInitialized, LyraGameplayTags::InitState_GameplayReady
|
||||
// };
|
||||
|
||||
// This will try to progress from spawned (which is only set in BeginPlay) through the data initialization stages until it gets to gameplay ready
|
||||
// ContinueInitStateChain(StateChain);
|
||||
}
|
||||
|
||||
UOLSPawnExtensionComponent* UOLSPawnExtensionComponent::FindPawnExtensionComponent(const AActor* actor)
|
||||
{
|
||||
return (actor ? actor->FindComponentByClass<UOLSPawnExtensionComponent>() : nullptr);
|
||||
}
|
||||
|
||||
UOLSAbilitySystemComponent* UOLSPawnExtensionComponent::GetOLSAbilitySystemComponent() const
|
||||
{
|
||||
return AbilitySystemComponent;
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::SetPawnData(const UOLSPawnDataAsset* pawnData)
|
||||
{
|
||||
check(pawnData);
|
||||
|
||||
APawn* pawn = GetPawnChecked<APawn>();
|
||||
|
||||
if (pawn->GetLocalRole() != ROLE_Authority)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (PawnData)
|
||||
{
|
||||
OLS_LOG(LogOLSPawnExtensionComponent, Error,
|
||||
TEXT("Trying to set PawnData [%s] on pawn [%s] that already has valid PawnData [%s]."),
|
||||
GET_UOBJECT_NAME(pawnData), GET_UOBJECT_NAME(pawn), GET_UOBJECT_NAME(PawnData));
|
||||
return;
|
||||
}
|
||||
|
||||
PawnData = pawnData;
|
||||
|
||||
pawn->ForceNetUpdate();
|
||||
|
||||
CheckDefaultInitialization();
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::InitializeAbilitySystem(UOLSAbilitySystemComponent* asc, AActor* ownerActor)
|
||||
{
|
||||
check(asc);
|
||||
check(ownerActor);
|
||||
|
||||
if (AbilitySystemComponent == asc)
|
||||
{
|
||||
// The ability system component hasn't changed.
|
||||
return;
|
||||
}
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
// Clean up the old ability system component.
|
||||
UninitializeAbilitySystem();
|
||||
}
|
||||
|
||||
APawn* pawn = GetPawnChecked<APawn>();
|
||||
AActor* existingAvatar = asc->GetAvatarActor();
|
||||
|
||||
OLS_LOG(LogOLSPawnExtensionComponent, Verbose, TEXT("Setting up ASC [%s] on pawn [%s] owner [%s], existing [%s] "),
|
||||
GET_UOBJECT_NAME(asc), GET_UOBJECT_NAME(pawn), GET_UOBJECT_NAME(ownerActor),
|
||||
GET_UOBJECT_NAME(existingAvatar));
|
||||
|
||||
if ((existingAvatar != nullptr) && (existingAvatar != pawn))
|
||||
{
|
||||
OLS_LOG(LogOLSPawnExtensionComponent, Log, TEXT("Existing avatar (authority=%d)"), existingAvatar->HasAuthority() ? 1 : 0);
|
||||
|
||||
// There is already a pawn acting as the ASC's avatar, so we need to kick it out
|
||||
// This can happen on clients if they're lagged: their new pawn is spawned + possessed before the dead one is removed
|
||||
ensure(!existingAvatar->HasAuthority());
|
||||
|
||||
if (UOLSPawnExtensionComponent* OtherExtensionComponent = FindPawnExtensionComponent(existingAvatar))
|
||||
{
|
||||
OtherExtensionComponent->UninitializeAbilitySystem();
|
||||
}
|
||||
}
|
||||
|
||||
AbilitySystemComponent = asc;
|
||||
AbilitySystemComponent->InitAbilityActorInfo(ownerActor, pawn);
|
||||
|
||||
if (ensure(PawnData))
|
||||
{
|
||||
asc->SetTagRelationshipMapping(PawnData->TagRelationshipMapping);
|
||||
}
|
||||
|
||||
OnAbilitySystemInitialized.Broadcast();
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::UninitializeAbilitySystem()
|
||||
{
|
||||
if (!AbilitySystemComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Uninitialize the ASC if we're still the avatar actor (otherwise another pawn already did it when they became the avatar actor)
|
||||
if (AbilitySystemComponent->GetAvatarActor() == GetOwner())
|
||||
{
|
||||
FGameplayTagContainer abilityTypesToIgnore;
|
||||
|
||||
// @TOD:; Implement LyraGameplayTags::Ability_Behavior_SurvivesDeath;
|
||||
// abilityTypesToIgnore.AddTag(LyraGameplayTags::Ability_Behavior_SurvivesDeath);
|
||||
|
||||
AbilitySystemComponent->CancelAbilities(nullptr, &abilityTypesToIgnore);
|
||||
AbilitySystemComponent->ClearAbilityInput();
|
||||
AbilitySystemComponent->RemoveAllGameplayCues();
|
||||
|
||||
if (AbilitySystemComponent->GetOwnerActor() != nullptr)
|
||||
{
|
||||
AbilitySystemComponent->SetAvatarActor(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ASC doesn't have a valid owner, we need to clear *all* actor info, not just the avatar pairing
|
||||
AbilitySystemComponent->ClearActorInfo();
|
||||
}
|
||||
|
||||
OnAbilitySystemUninitialized.Broadcast();
|
||||
}
|
||||
|
||||
AbilitySystemComponent = nullptr;
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::HandleControllerChanged()
|
||||
{
|
||||
if (AbilitySystemComponent && (AbilitySystemComponent->GetAvatarActor() == GetPawnChecked<APawn>()))
|
||||
{
|
||||
ensure(AbilitySystemComponent->AbilityActorInfo->OwnerActor == AbilitySystemComponent->GetOwnerActor());
|
||||
if (AbilitySystemComponent->GetOwnerActor() == nullptr)
|
||||
{
|
||||
UninitializeAbilitySystem();
|
||||
}
|
||||
else
|
||||
{
|
||||
AbilitySystemComponent->RefreshAbilityActorInfo();
|
||||
}
|
||||
}
|
||||
|
||||
CheckDefaultInitialization();
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::HandlePlayerStateReplicated()
|
||||
{
|
||||
CheckDefaultInitialization();
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::SetupPlayerInputComponent()
|
||||
{
|
||||
CheckDefaultInitialization();
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::OnAbilitySystemInitialized_RegisterAndCall(
|
||||
FSimpleMulticastDelegate::FDelegate delegate)
|
||||
{
|
||||
if (!OnAbilitySystemInitialized.IsBoundToObject(delegate.GetUObject()))
|
||||
{
|
||||
OnAbilitySystemInitialized.Add(delegate);
|
||||
}
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
delegate.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate delegate)
|
||||
{
|
||||
if (!OnAbilitySystemUninitialized.IsBoundToObject(delegate.GetUObject()))
|
||||
{
|
||||
OnAbilitySystemUninitialized.Add(delegate);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSPawnExtensionComponent::OnRep_PawnData()
|
||||
{
|
||||
CheckDefaultInitialization();
|
||||
}
|
156
Source/ols/Private/DataAssets/OLSAbilitySetDataAsset.cpp
Normal file
156
Source/ols/Private/DataAssets/OLSAbilitySetDataAsset.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "DataAssets/OLSAbilitySetDataAsset.h"
|
||||
|
||||
#include "ActiveGameplayEffectHandle.h"
|
||||
#include "GameplayAbilitySpecHandle.h"
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "AbilitySystem/Abilities/OLSGameplayAbility.h"
|
||||
#include "AbilitySystem/Attributes/OLSAttributeSetBase.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSAbilitySetDataAsset);
|
||||
|
||||
void FOLSAbilitySet_GrantedHandles::AddAbilitySpecHandle(const FGameplayAbilitySpecHandle& handle)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
{
|
||||
AbilitySpecHandles.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void FOLSAbilitySet_GrantedHandles::AddGameplayEffectHandle(const FActiveGameplayEffectHandle& handle)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
{
|
||||
GameplayEffectHandles.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void FOLSAbilitySet_GrantedHandles::AddAttributeSet(UAttributeSet* set)
|
||||
{
|
||||
GrantedAttributeSets.Add(set);
|
||||
}
|
||||
|
||||
void FOLSAbilitySet_GrantedHandles::TakeFromAbilitySystem(UOLSAbilitySystemComponent* olsASC)
|
||||
{
|
||||
check(olsASC);
|
||||
|
||||
if (!olsASC->IsOwnerActorAuthoritative())
|
||||
{
|
||||
// Must be authoritative to give or take ability sets.
|
||||
return;
|
||||
}
|
||||
|
||||
for (const FGameplayAbilitySpecHandle& handle : AbilitySpecHandles)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
{
|
||||
olsASC->ClearAbility(handle);
|
||||
}
|
||||
}
|
||||
|
||||
for (const FActiveGameplayEffectHandle& handle : GameplayEffectHandles)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
{
|
||||
olsASC->RemoveActiveGameplayEffect(handle);
|
||||
}
|
||||
}
|
||||
|
||||
for (UAttributeSet* set : GrantedAttributeSets)
|
||||
{
|
||||
olsASC->RemoveSpawnedAttribute(set);
|
||||
}
|
||||
|
||||
AbilitySpecHandles.Reset();
|
||||
GameplayEffectHandles.Reset();
|
||||
GrantedAttributeSets.Reset();
|
||||
}
|
||||
|
||||
UOLSAbilitySetDataAsset::UOLSAbilitySetDataAsset(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
void UOLSAbilitySetDataAsset::GiveToAbilitySystem(
|
||||
UOLSAbilitySystemComponent* olsASC,
|
||||
FOLSAbilitySet_GrantedHandles* outGrantedHandlePtrs,
|
||||
UObject* sourceObject) const
|
||||
{
|
||||
check(olsASC);
|
||||
|
||||
if (!olsASC->IsOwnerActorAuthoritative())
|
||||
{
|
||||
// Must be authoritative to give or take ability sets.
|
||||
return;
|
||||
}
|
||||
|
||||
// Grant the attribute sets.
|
||||
for (int32 setIndex = 0; setIndex < GrantedAttributes.Num(); ++setIndex)
|
||||
{
|
||||
const FOLSAbilitySet_AttributeSet& setToGrant = GrantedAttributes[setIndex];
|
||||
|
||||
if (!IsValid(setToGrant.AttributeSet))
|
||||
{
|
||||
OLS_LOG(LogOLSAbilitySetDataAsset, Error, TEXT("GrantedAttributes[%d] on ability set [%s] is not valid"),
|
||||
setIndex, GET_UOBJECT_NAME(this));
|
||||
continue;
|
||||
}
|
||||
|
||||
UAttributeSet* newSet = NewObject<UAttributeSet>(olsASC->GetOwner(), setToGrant.AttributeSet);
|
||||
olsASC->AddAttributeSetSubobject(newSet);
|
||||
|
||||
if (outGrantedHandlePtrs)
|
||||
{
|
||||
outGrantedHandlePtrs->AddAttributeSet(newSet);
|
||||
}
|
||||
}
|
||||
|
||||
// Grant the gameplay abilities.
|
||||
for (int32 abilityIndex = 0; abilityIndex < GrantedGameplayAbilities.Num(); ++abilityIndex)
|
||||
{
|
||||
const FOLSAbilitySet_GameplayAbility& AbilityToGrant = GrantedGameplayAbilities[abilityIndex];
|
||||
|
||||
if (!IsValid(AbilityToGrant.Ability))
|
||||
{
|
||||
OLS_LOG(LogOLSAbilitySetDataAsset, Error, TEXT("GrantedGameplayAbilities[%d] on ability set [%s] is not valid"),
|
||||
abilityIndex, GET_UOBJECT_NAME(this));
|
||||
continue;
|
||||
}
|
||||
|
||||
UOLSGameplayAbility* abilityCDO = AbilityToGrant.Ability->GetDefaultObject<UOLSGameplayAbility>();
|
||||
|
||||
FGameplayAbilitySpec abilitySpec(abilityCDO, AbilityToGrant.AbilityLevel);
|
||||
abilitySpec.SourceObject = sourceObject;
|
||||
abilitySpec.GetDynamicSpecSourceTags().AddTag(AbilityToGrant.InputTag);
|
||||
|
||||
const FGameplayAbilitySpecHandle abilitySpecHandle = olsASC->GiveAbility(abilitySpec);
|
||||
|
||||
if (outGrantedHandlePtrs)
|
||||
{
|
||||
outGrantedHandlePtrs->AddAbilitySpecHandle(abilitySpecHandle);
|
||||
}
|
||||
}
|
||||
|
||||
// Grant the gameplay effects.
|
||||
for (int32 effectIndex = 0; effectIndex < GrantedGameplayEffects.Num(); ++effectIndex)
|
||||
{
|
||||
const FOLSAbilitySet_GameplayEffect& EffectToGrant = GrantedGameplayEffects[effectIndex];
|
||||
|
||||
if (!IsValid(EffectToGrant.GameplayEffect))
|
||||
{
|
||||
OLS_LOG(LogOLSAbilitySetDataAsset, Error, TEXT("GrantedGameplayEffects[%d] on ability set [%s] is not valid"),
|
||||
effectIndex, GET_UOBJECT_NAME(this));
|
||||
continue;
|
||||
}
|
||||
|
||||
const UGameplayEffect* gameplayEffect = EffectToGrant.GameplayEffect->GetDefaultObject<UGameplayEffect>();
|
||||
const FActiveGameplayEffectHandle gameplayEffectHandle = olsASC->ApplyGameplayEffectToSelf(gameplayEffect, EffectToGrant.EffectLevel, olsASC->MakeEffectContext());
|
||||
|
||||
if (outGrantedHandlePtrs)
|
||||
{
|
||||
outGrantedHandlePtrs->AddGameplayEffectHandle(gameplayEffectHandle);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "DataAssets/OLSAbilityTagRelationshipMappingDataAsset.h"
|
||||
|
||||
void UOLSAbilityTagRelationshipMappingDataAsset::GetAbilityTagsToBlockAndCancel(
|
||||
const FGameplayTagContainer& abilityTags,
|
||||
FGameplayTagContainer* outTagsToBlockPtr,
|
||||
FGameplayTagContainer* outTagsToCancelPtr) const
|
||||
{
|
||||
for (int32 i = 0; i < AbilityTagRelationships.Num(); i++)
|
||||
{
|
||||
const FOLSAbilityTagRelationship& tags = AbilityTagRelationships[i];
|
||||
if (abilityTags.HasTag(tags.AbilityTag))
|
||||
{
|
||||
if (outTagsToBlockPtr)
|
||||
{
|
||||
outTagsToBlockPtr->AppendTags(tags.AbilityTagsToBlock);
|
||||
}
|
||||
if (outTagsToCancelPtr)
|
||||
{
|
||||
outTagsToCancelPtr->AppendTags(tags.AbilityTagsToCancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAbilityTagRelationshipMappingDataAsset::GetRequiredAndBlockedActivationTags(
|
||||
const FGameplayTagContainer& abilityTags,
|
||||
FGameplayTagContainer* outActivationRequiredPtr,
|
||||
FGameplayTagContainer* outActivationBlockedPtr) const
|
||||
{
|
||||
// Simple iteration for now
|
||||
for (int32 i = 0; i < AbilityTagRelationships.Num(); i++)
|
||||
{
|
||||
const FOLSAbilityTagRelationship& tags = AbilityTagRelationships[i];
|
||||
if (abilityTags.HasTag(tags.AbilityTag))
|
||||
{
|
||||
if (outActivationRequiredPtr)
|
||||
{
|
||||
outActivationRequiredPtr->AppendTags(tags.ActivationRequiredTags);
|
||||
}
|
||||
if (outActivationBlockedPtr)
|
||||
{
|
||||
outActivationBlockedPtr->AppendTags(tags.ActivationBlockedTags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UOLSAbilityTagRelationshipMappingDataAsset::IsAbilityCancelledByTag(
|
||||
const FGameplayTagContainer& abilityTags,
|
||||
const FGameplayTag& actionTag) const
|
||||
{
|
||||
// Simple iteration for now
|
||||
for (int32 i = 0; i < AbilityTagRelationships.Num(); i++)
|
||||
{
|
||||
const FOLSAbilityTagRelationship& tags = AbilityTagRelationships[i];
|
||||
|
||||
if (tags.AbilityTag == actionTag && tags.AbilityTagsToCancel.HasAny(abilityTags))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "DataAssets/OLSExperienceActionSetDataAsset.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Misc/DataValidation.h"
|
||||
#endif
|
||||
|
||||
#include "GameFeatureAction.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSExperienceActionSetDataAsset)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "OLSSystem"
|
||||
|
||||
#if WITH_EDITOR
|
||||
EDataValidationResult UOLSExperienceActionSetDataAsset::IsDataValid(FDataValidationContext& context) const
|
||||
{
|
||||
EDataValidationResult result = CombineDataValidationResults(Super::IsDataValid(context), EDataValidationResult::Valid);
|
||||
|
||||
int32 entryIndex = 0;
|
||||
for (const UGameFeatureAction* action : Actions)
|
||||
{
|
||||
if (action)
|
||||
{
|
||||
EDataValidationResult ChildResult = action->IsDataValid(context);
|
||||
result = CombineDataValidationResults(result, ChildResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = EDataValidationResult::Invalid;
|
||||
context.AddError(FText::Format(LOCTEXT("ActionEntryIsNull", "Null entry at index {0} in Actions"), FText::AsNumber(entryIndex)));
|
||||
}
|
||||
|
||||
++entryIndex;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
void UOLSExperienceActionSetDataAsset::UpdateAssetBundleData()
|
||||
{
|
||||
Super::UpdateAssetBundleData();
|
||||
|
||||
for (UGameFeatureAction* action : Actions)
|
||||
{
|
||||
if (action)
|
||||
{
|
||||
action->AddAdditionalAssetBundleData(AssetBundleData);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,85 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "DataAssets/OLSExperienceDefinitionDataAsset.h"
|
||||
|
||||
#include "GameFeatureAction.h"
|
||||
#if WITH_EDITOR
|
||||
#include "Misc/DataValidation.h"
|
||||
#endif
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSExperienceDefinitionDataAsset)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "OLSSystem"
|
||||
|
||||
UOLSExperienceDefinitionDataAsset::UOLSExperienceDefinitionDataAsset()
|
||||
{
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
EDataValidationResult UOLSExperienceDefinitionDataAsset::IsDataValid(FDataValidationContext& context) const
|
||||
{
|
||||
EDataValidationResult result = CombineDataValidationResults(Super::IsDataValid(context), EDataValidationResult::Valid);
|
||||
|
||||
int32 entryIndex = 0;
|
||||
for (const UGameFeatureAction* action : Actions)
|
||||
{
|
||||
if (action)
|
||||
{
|
||||
EDataValidationResult childResult = action->IsDataValid(context);
|
||||
result = CombineDataValidationResults(result, childResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = EDataValidationResult::Invalid;
|
||||
context.AddError(FText::Format(LOCTEXT("ActionEntryIsNull", "Null entry at index {0} in Actions"), FText::AsNumber(entryIndex)));
|
||||
}
|
||||
|
||||
++entryIndex;
|
||||
}
|
||||
|
||||
// Make sure users didn't subclass from a BP of this (it's fine and expected to subclass once in BP, just not twice)
|
||||
if (!GetClass()->IsNative())
|
||||
{
|
||||
const UClass* parentClass = GetClass()->GetSuperClass();
|
||||
|
||||
// Find the native parent
|
||||
const UClass* firstNativeParent = parentClass;
|
||||
while ((firstNativeParent != nullptr) && !firstNativeParent->IsNative())
|
||||
{
|
||||
firstNativeParent = firstNativeParent->GetSuperClass();
|
||||
}
|
||||
|
||||
if (firstNativeParent != parentClass)
|
||||
{
|
||||
context.AddError(FText::Format(LOCTEXT("ExperienceInheritenceIsUnsupported", "Blueprint subclasses of Blueprint experiences is not currently supported (use composition via ActionSets instead). Parent class was {0} but should be {1}."),
|
||||
FText::AsCultureInvariant(GetPathNameSafe(parentClass)),
|
||||
FText::AsCultureInvariant(GetPathNameSafe(firstNativeParent))
|
||||
));
|
||||
result = EDataValidationResult::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
FString UOLSExperienceDefinitionDataAsset::GetIdentifierString() const
|
||||
{
|
||||
return GetPrimaryAssetId().ToString();
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UOLSExperienceDefinitionDataAsset::UpdateAssetBundleData()
|
||||
{
|
||||
Super::UpdateAssetBundleData();
|
||||
|
||||
for (UGameFeatureAction* action : Actions)
|
||||
{
|
||||
if (action)
|
||||
{
|
||||
action->AddAdditionalAssetBundleData(AssetBundleData);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
15
Source/ols/Private/DataAssets/OLSGameDataAsset.cpp
Normal file
15
Source/ols/Private/DataAssets/OLSGameDataAsset.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "DataAssets/OLSGameDataAsset.h"
|
||||
|
||||
#include "Systems/OLSAssetManager.h"
|
||||
|
||||
UOLSGameDataAsset::UOLSGameDataAsset()
|
||||
{
|
||||
}
|
||||
|
||||
const UOLSGameDataAsset& UOLSGameDataAsset::Get()
|
||||
{
|
||||
return UOLSAssetManager::Get().GetGameData();
|
||||
}
|
56
Source/ols/Private/DataAssets/OLSInputConfigDataAsset.cpp
Normal file
56
Source/ols/Private/DataAssets/OLSInputConfigDataAsset.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "DataAssets/OLSInputConfigDataAsset.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSInputConfigDataAsset);
|
||||
|
||||
UOLSInputConfigDataAsset::UOLSInputConfigDataAsset(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
const UInputAction* UOLSInputConfigDataAsset::FindNativeInputActionForTag(
|
||||
const FGameplayTag& inputTag,
|
||||
bool shouldLogNotFound) const
|
||||
{
|
||||
for (const FOLSInputAction& action : NativeInputActions)
|
||||
{
|
||||
if (action.InputAction && (action.InputTag == inputTag))
|
||||
{
|
||||
return action.InputAction;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldLogNotFound)
|
||||
{
|
||||
OLS_LOG(LogOLSInputConfigDataAsset, Error,
|
||||
TEXT("Can't find NativeInputAction for InputTag [%s] on InputConfig [%s]."), GET_TAG_NAME(inputTag),
|
||||
GET_UOBJECT_NAME(this));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const UInputAction* UOLSInputConfigDataAsset::FindAbilityInputActionForTag(
|
||||
const FGameplayTag& inputTag,
|
||||
bool shouldLogNotFound) const
|
||||
{
|
||||
for (const FOLSInputAction& action : AbilityInputActions)
|
||||
{
|
||||
if (action.InputAction && (action.InputTag == inputTag))
|
||||
{
|
||||
return action.InputAction;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldLogNotFound)
|
||||
{
|
||||
OLS_LOG(LogOLSInputConfigDataAsset, Error,
|
||||
TEXT("Can't find AbilityInputAction for InputTag [%s] on InputConfig [%s]."), GET_TAG_NAME(inputTag),
|
||||
GET_UOBJECT_NAME(this));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
20
Source/ols/Private/DataAssets/OLSPawnDataAsset.cpp
Normal file
20
Source/ols/Private/DataAssets/OLSPawnDataAsset.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "DataAssets/OLSPawnDataAsset.h"
|
||||
|
||||
UOLSPawnDataAsset::UOLSPawnDataAsset(const FObjectInitializer& objectInitializer)
|
||||
: Super(objectInitializer)
|
||||
{
|
||||
PawnClass = nullptr;
|
||||
TagRelationshipMapping = nullptr;
|
||||
InputConfig = nullptr;
|
||||
|
||||
// @Todo: implement DefaultCamera mode here.
|
||||
// DefaultCameraMode = nullptr;
|
||||
}
|
||||
|
||||
FString UOLSPawnDataAsset::GetIdentifierString() const
|
||||
{
|
||||
return GetPrimaryAssetId().ToString();
|
||||
}
|
9
Source/ols/Private/DataAssets/OLSPrimaryDataAsset.cpp
Normal file
9
Source/ols/Private/DataAssets/OLSPrimaryDataAsset.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "DataAssets/OLSPrimaryDataAsset.h"
|
||||
|
||||
FString UOLSPrimaryDataAsset::GetIdentifierString() const
|
||||
{
|
||||
return GetPrimaryAssetId().ToString();
|
||||
}
|
@ -0,0 +1,308 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "GameFeatures/OLSGameFeatureAction_AddInputContextMapping.h"
|
||||
|
||||
#include "UserSettings/EnhancedInputUserSettings.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "Systems/OLSAssetManager.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Misc/DataValidation.h"
|
||||
#endif
|
||||
|
||||
#include "InputMappingContext.h"
|
||||
#include "OLSLog.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
#include "Components/OLSHeroComponent.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSGameFA_AddInputContextMapping);
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameFeatureAction_AddInputContextMapping)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GameFeatures"
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::OnGameFeatureRegistering()
|
||||
{
|
||||
Super::OnGameFeatureRegistering();
|
||||
|
||||
RegisterInputMappingContexts();
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::OnGameFeatureActivating(FGameFeatureActivatingContext& context)
|
||||
{
|
||||
FPerContextData& activeData = ContextData.FindOrAdd(context);
|
||||
if (!ensure(activeData.ExtensionRequestHandles.IsEmpty()) ||
|
||||
!ensure(activeData.ControllersAddedTo.IsEmpty()))
|
||||
{
|
||||
Reset(activeData);
|
||||
}
|
||||
|
||||
Super::OnGameFeatureActivating(context);
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& context)
|
||||
{
|
||||
Super::OnGameFeatureDeactivating(context);
|
||||
|
||||
FPerContextData* activeData = ContextData.Find(context);
|
||||
if (ensure(activeData))
|
||||
{
|
||||
Reset(*activeData);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::OnGameFeatureUnregistering()
|
||||
{
|
||||
Super::OnGameFeatureUnregistering();
|
||||
|
||||
UnregisterInputMappingContexts();
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
EDataValidationResult UOLSGameFeatureAction_AddInputContextMapping::IsDataValid(FDataValidationContext& context) const
|
||||
{
|
||||
// Don't call Super since it does not fit in this.
|
||||
|
||||
EDataValidationResult result = CombineDataValidationResults(Super::IsDataValid(context), EDataValidationResult::Valid);
|
||||
|
||||
int32 index = 0;
|
||||
|
||||
for (const FOLSInputMappingContextAndPriority& entry : InputMappings)
|
||||
{
|
||||
if (entry.InputMapping.IsNull())
|
||||
{
|
||||
result = EDataValidationResult::Invalid;
|
||||
context.AddError(FText::Format(LOCTEXT("NullInputMapping", "Null InputMapping at index {0}."), index));
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::RegisterInputMappingContexts()
|
||||
{
|
||||
RegisterInputContextMappingsForGameInstanceHandle = FWorldDelegates::OnStartGameInstance.AddUObject(
|
||||
this, &UOLSGameFeatureAction_AddInputContextMapping::RegisterInputContextMappingsForGameInstance);
|
||||
|
||||
const TIndirectArray<FWorldContext>& worldContexts = GEngine->GetWorldContexts();
|
||||
for (TIndirectArray<FWorldContext>::TConstIterator worldContextIterator = worldContexts.CreateConstIterator();
|
||||
worldContextIterator; ++worldContextIterator)
|
||||
{
|
||||
RegisterInputContextMappingsForGameInstance(worldContextIterator->OwningGameInstance);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::RegisterInputContextMappingsForGameInstance(
|
||||
UGameInstance* gameInstance)
|
||||
{
|
||||
if (gameInstance != nullptr && !gameInstance->OnLocalPlayerAddedEvent.IsBoundToObject(this))
|
||||
{
|
||||
gameInstance->OnLocalPlayerAddedEvent.AddUObject(
|
||||
this, &UOLSGameFeatureAction_AddInputContextMapping::RegisterInputMappingContextsForLocalPlayer);
|
||||
gameInstance->OnLocalPlayerRemovedEvent.AddUObject(
|
||||
this, &UOLSGameFeatureAction_AddInputContextMapping::UnregisterInputMappingContextsForLocalPlayer);
|
||||
|
||||
for (TArray<ULocalPlayer*>::TConstIterator localPlayerIterator = gameInstance->GetLocalPlayerIterator();
|
||||
localPlayerIterator; ++localPlayerIterator)
|
||||
{
|
||||
RegisterInputMappingContextsForLocalPlayer(*localPlayerIterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::RegisterInputMappingContextsForLocalPlayer(ULocalPlayer* localPlayer)
|
||||
{
|
||||
if (ensure(localPlayer))
|
||||
{
|
||||
UOLSAssetManager& assetManager = UOLSAssetManager::Get();
|
||||
|
||||
if (UEnhancedInputLocalPlayerSubsystem* eiSubsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(localPlayer))
|
||||
{
|
||||
if (UEnhancedInputUserSettings* settings = eiSubsystem->GetUserSettings())
|
||||
{
|
||||
for (const FOLSInputMappingContextAndPriority& entry : InputMappings)
|
||||
{
|
||||
// Skip entries that don't want to be registered
|
||||
if (!entry.bShouldRegisterWithSettings)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Register this IMC with the settings!
|
||||
if (UInputMappingContext* imc = assetManager.GetAsset(entry.InputMapping))
|
||||
{
|
||||
settings->RegisterInputMappingContext(imc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::UnregisterInputMappingContexts()
|
||||
{
|
||||
FWorldDelegates::OnStartGameInstance.Remove(RegisterInputContextMappingsForGameInstanceHandle);
|
||||
RegisterInputContextMappingsForGameInstanceHandle.Reset();
|
||||
|
||||
const TIndirectArray<FWorldContext>& worldContexts = GEngine->GetWorldContexts();
|
||||
for (TIndirectArray<FWorldContext>::TConstIterator worldContextIterator = worldContexts.CreateConstIterator();
|
||||
worldContextIterator; ++worldContextIterator)
|
||||
{
|
||||
UnregisterInputContextMappingsForGameInstance(worldContextIterator->OwningGameInstance);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::UnregisterInputContextMappingsForGameInstance(
|
||||
UGameInstance* gameInstance)
|
||||
{
|
||||
if (gameInstance)
|
||||
{
|
||||
gameInstance->OnLocalPlayerAddedEvent.RemoveAll(this);
|
||||
gameInstance->OnLocalPlayerRemovedEvent.RemoveAll(this);
|
||||
|
||||
for (TArray<ULocalPlayer*>::TConstIterator localPlayerIterator = gameInstance->GetLocalPlayerIterator();
|
||||
localPlayerIterator; ++localPlayerIterator)
|
||||
{
|
||||
UnregisterInputMappingContextsForLocalPlayer(*localPlayerIterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::UnregisterInputMappingContextsForLocalPlayer(
|
||||
ULocalPlayer* localPlayer)
|
||||
{
|
||||
if (ensure(localPlayer))
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* eiSubsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(localPlayer))
|
||||
{
|
||||
if (UEnhancedInputUserSettings* settings = eiSubsystem->GetUserSettings())
|
||||
{
|
||||
for (const FOLSInputMappingContextAndPriority& entry : InputMappings)
|
||||
{
|
||||
// Skip entries that don't want to be registered
|
||||
if (!entry.bShouldRegisterWithSettings)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Register this IMC with the settings!
|
||||
if (UInputMappingContext* imc = entry.InputMapping.Get())
|
||||
{
|
||||
settings->UnregisterInputMappingContext(imc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::AddToWorld(const FWorldContext& worldContext,
|
||||
const FGameFeatureStateChangeContext& changeContext)
|
||||
{
|
||||
UWorld* world = worldContext.World();
|
||||
UGameInstance* gameInstance = worldContext.OwningGameInstance;
|
||||
FPerContextData& activeData = ContextData.FindOrAdd(changeContext);
|
||||
|
||||
if (gameInstance && world && world->IsGameWorld())
|
||||
{
|
||||
if (UGameFrameworkComponentManager* componentManager = UGameInstance::GetSubsystem<
|
||||
UGameFrameworkComponentManager>(gameInstance))
|
||||
{
|
||||
UGameFrameworkComponentManager::FExtensionHandlerDelegate addAbilitiesDelegate =
|
||||
UGameFrameworkComponentManager::FExtensionHandlerDelegate::CreateUObject(
|
||||
this, &ThisClass::HandleControllerExtension, changeContext);
|
||||
TSharedPtr<FComponentRequestHandle> extensionRequestHandle =
|
||||
componentManager->AddExtensionHandler(APlayerController::StaticClass(), addAbilitiesDelegate);
|
||||
|
||||
activeData.ExtensionRequestHandles.Add(extensionRequestHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::Reset(FPerContextData& activeData)
|
||||
{
|
||||
activeData.ExtensionRequestHandles.Empty();
|
||||
|
||||
while (!activeData.ControllersAddedTo.IsEmpty())
|
||||
{
|
||||
TWeakObjectPtr<APlayerController> controllerPtr = activeData.ControllersAddedTo.Top();
|
||||
if (controllerPtr.IsValid())
|
||||
{
|
||||
RemoveInputMapping(controllerPtr.Get(), activeData);
|
||||
}
|
||||
else
|
||||
{
|
||||
activeData.ControllersAddedTo.Pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::HandleControllerExtension(AActor* actor,
|
||||
FName eventName,
|
||||
FGameFeatureStateChangeContext changeContext)
|
||||
{
|
||||
APlayerController* playerController = CastChecked<APlayerController>(actor);
|
||||
FPerContextData& activeData = ContextData.FindOrAdd(changeContext);
|
||||
|
||||
// TODO Why does this code mix and match controllers and local players? ControllersAddedTo is never modified
|
||||
if ((eventName == UGameFrameworkComponentManager::NAME_ExtensionRemoved) || (eventName == UGameFrameworkComponentManager::NAME_ReceiverRemoved))
|
||||
{
|
||||
RemoveInputMapping(playerController, activeData);
|
||||
}
|
||||
else if ((eventName == UGameFrameworkComponentManager::NAME_ExtensionAdded) || (eventName == UOLSHeroComponent::NAME_BindInputsNow))
|
||||
{
|
||||
AddInputMappingForPlayer(playerController->GetLocalPlayer(), activeData);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::AddInputMappingForPlayer(UPlayer* player,
|
||||
FPerContextData& activeData)
|
||||
{
|
||||
if (ULocalPlayer* localPlayer = Cast<ULocalPlayer>(player))
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* inputSystem = localPlayer->GetSubsystem<
|
||||
UEnhancedInputLocalPlayerSubsystem>())
|
||||
{
|
||||
for (const FOLSInputMappingContextAndPriority& entry : InputMappings)
|
||||
{
|
||||
if (const UInputMappingContext* imc = entry.InputMapping.Get())
|
||||
{
|
||||
inputSystem->AddMappingContext(imc, entry.Priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogOLSGameFA_AddInputContextMapping, Error,
|
||||
TEXT(
|
||||
"Failed to find `UEnhancedInputLocalPlayerSubsystem` for local player. Input mappings will not be added. Make sure you're set to use the EnhancedInput system via config file."
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_AddInputContextMapping::RemoveInputMapping(APlayerController* playerController,
|
||||
FPerContextData& activeData)
|
||||
{
|
||||
if (ULocalPlayer* localPlayer = playerController->GetLocalPlayer())
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* inputSystem = localPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
|
||||
{
|
||||
for (const FOLSInputMappingContextAndPriority& entry : InputMappings)
|
||||
{
|
||||
if (const UInputMappingContext* imc = entry.InputMapping.Get())
|
||||
{
|
||||
inputSystem->RemoveMappingContext(imc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeData.ControllersAddedTo.Remove(playerController);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,46 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "GameFeatures/OLSGameFeatureAction_WorldActionBase.h"
|
||||
|
||||
#include "GameFeaturesSubsystem.h"
|
||||
|
||||
void UOLSGameFeatureAction_WorldActionBase::OnGameFeatureActivating(FGameFeatureActivatingContext& context)
|
||||
{
|
||||
// Don't call Super since we don't need it.
|
||||
|
||||
GameInstanceStartHandles.FindOrAdd(context) = FWorldDelegates::OnStartGameInstance.AddUObject(this,
|
||||
&UOLSGameFeatureAction_WorldActionBase::HandleGameInstanceStart, FGameFeatureStateChangeContext(context));
|
||||
|
||||
// Add to any worlds with associated game instances that have already been initialized
|
||||
for (const FWorldContext& worldContext : GEngine->GetWorldContexts())
|
||||
{
|
||||
if (context.ShouldApplyToWorldContext(worldContext))
|
||||
{
|
||||
AddToWorld(worldContext, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_WorldActionBase::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& context)
|
||||
{
|
||||
// Don't call Super since we don't need it.
|
||||
|
||||
FDelegateHandle* foundHandle = GameInstanceStartHandles.Find(context);
|
||||
if (ensure(foundHandle))
|
||||
{
|
||||
FWorldDelegates::OnStartGameInstance.Remove(*foundHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSGameFeatureAction_WorldActionBase::HandleGameInstanceStart(UGameInstance* gameInstance,
|
||||
FGameFeatureStateChangeContext changeContext)
|
||||
{
|
||||
if (FWorldContext* worldContext = gameInstance->GetWorldContext())
|
||||
{
|
||||
if (changeContext.ShouldApplyToWorldContext(*worldContext))
|
||||
{
|
||||
AddToWorld(*worldContext, changeContext);
|
||||
}
|
||||
}
|
||||
}
|
48
Source/ols/Private/GameModes/OLSExperienceManager.cpp
Normal file
48
Source/ols/Private/GameModes/OLSExperienceManager.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "GameModes/OLSExperienceManager.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UOLSExperienceManager::OnPlayInEditorBegun()
|
||||
{
|
||||
ensure(GameFeaturePluginRequestCountMap.IsEmpty());
|
||||
GameFeaturePluginRequestCountMap.Empty();
|
||||
}
|
||||
|
||||
void UOLSExperienceManager::NotifyOfPluginActivation(const FString pluginURL)
|
||||
{
|
||||
if (GIsEditor)
|
||||
{
|
||||
UOLSExperienceManager* experienceManagerSubsystem = GEngine->GetEngineSubsystem<UOLSExperienceManager>();
|
||||
check(experienceManagerSubsystem);
|
||||
|
||||
// Track the number of requesters who activate this plugin. Multiple load/activation requests are always allowed because concurrent requests are handled.
|
||||
int32& count = experienceManagerSubsystem->GameFeaturePluginRequestCountMap.FindOrAdd(pluginURL);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
bool UOLSExperienceManager::RequestToDeactivatePlugin(const FString pluginURL)
|
||||
{
|
||||
if (GIsEditor)
|
||||
{
|
||||
UOLSExperienceManager* experienceManagerSubsystem = GEngine->GetEngineSubsystem<UOLSExperienceManager>();
|
||||
check(experienceManagerSubsystem);
|
||||
|
||||
// Only let the last requester to get this far deactivate the plugin
|
||||
int32& count = experienceManagerSubsystem->GameFeaturePluginRequestCountMap.FindChecked(pluginURL);
|
||||
--count;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
experienceManagerSubsystem->GameFeaturePluginRequestCountMap.Remove(pluginURL);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
472
Source/ols/Private/GameModes/OLSExperienceManagerComponent.cpp
Normal file
472
Source/ols/Private/GameModes/OLSExperienceManagerComponent.cpp
Normal file
@ -0,0 +1,472 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "GameModes/OLSExperienceManagerComponent.h"
|
||||
|
||||
#include "GameFeatureAction.h"
|
||||
#include "GameModes/OLSExperienceManager.h"
|
||||
#include "GameFeaturesSubsystem.h"
|
||||
#include "GameFeatureAction.h"
|
||||
#include "GameFeaturesSubsystemSettings.h"
|
||||
#include "OLSLog.h"
|
||||
#include "TimerManager.h"
|
||||
#include "DataAssets/OLSExperienceActionSetDataAsset.h"
|
||||
#include "DataAssets/OLSExperienceDefinitionDataAsset.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Systems/OLSAssetManager.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSExperienceManagerComponent);
|
||||
|
||||
//@TODO: Async load the experience definition itself
|
||||
//@TODO: Handle failures explicitly (go into a 'completed but failed' state rather than check()-ing)
|
||||
//@TODO: Do the action phases at the appropriate times instead of all at once
|
||||
//@TODO: Support deactivating an experience and do the unloading actions
|
||||
//@TODO: Think about what deactivation/cleanup means for preloaded assets
|
||||
//@TODO: Handle deactivating game features, right now we 'leak' them enabled
|
||||
// (for a client moving from experience to experience we actually want to diff the requirements and only unload some, not unload everything for them to just be immediately reloaded)
|
||||
//@TODO: Handle both built-in and URL-based plugins (search for colon?)
|
||||
|
||||
namespace OLSConsoleVariables
|
||||
{
|
||||
static float ExperienceLoadRandomDelayMin = 0.0f;
|
||||
static FAutoConsoleVariableRef CVarExperienceLoadRandomDelayMin(
|
||||
TEXT("lyra.chaos.ExperienceDelayLoad.MinSecs"),
|
||||
ExperienceLoadRandomDelayMin,
|
||||
TEXT("This value (in seconds) will be added as a delay of load completion of the experience (along with the random value lyra.chaos.ExperienceDelayLoad.RandomSecs)"),
|
||||
ECVF_Default);
|
||||
|
||||
static float ExperienceLoadRandomDelayRange = 0.0f;
|
||||
static FAutoConsoleVariableRef CVarExperienceLoadRandomDelayRange(
|
||||
TEXT("lyra.chaos.ExperienceDelayLoad.RandomSecs"),
|
||||
ExperienceLoadRandomDelayRange,
|
||||
TEXT("A random amount of time between 0 and this value (in seconds) will be added as a delay of load completion of the experience (along with the fixed value lyra.chaos.ExperienceDelayLoad.MinSecs)"),
|
||||
ECVF_Default);
|
||||
|
||||
float GetExperienceLoadDelayDuration()
|
||||
{
|
||||
return FMath::Max(0.0f, ExperienceLoadRandomDelayMin + FMath::FRand() * ExperienceLoadRandomDelayRange);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UOLSExperienceManagerComponent::UOLSExperienceManagerComponent(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
SetIsReplicatedByDefault(true);
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
Super::EndPlay(endPlayReason);
|
||||
|
||||
// deactivate any features this experience loaded
|
||||
//@TODO: This should be handled FILO as well
|
||||
for (const FString& PluginURL : GameFeaturePluginURLs)
|
||||
{
|
||||
if (UOLSExperienceManager::RequestToDeactivatePlugin(PluginURL))
|
||||
{
|
||||
UGameFeaturesSubsystem::Get().DeactivateGameFeaturePlugin(PluginURL);
|
||||
}
|
||||
}
|
||||
|
||||
//@TODO: Ensure proper handling of a partially-loaded state too
|
||||
if (LoadState == EOLSExperienceLoadState::Loaded)
|
||||
{
|
||||
LoadState = EOLSExperienceLoadState::Deactivating;
|
||||
|
||||
// Make sure we won't complete the transition prematurely if someone registers as a pauser but fires immediately
|
||||
NumExpectedPausers = INDEX_NONE;
|
||||
NumObservedPausers = 0;
|
||||
|
||||
// Deactivate and unload the actions
|
||||
FGameFeatureDeactivatingContext context(TEXT(""), [this](FStringView) { this->OnActionDeactivationCompleted(); });
|
||||
|
||||
const FWorldContext* existingWorldContext = GEngine->GetWorldContextFromWorld(GetWorld());
|
||||
if (existingWorldContext)
|
||||
{
|
||||
context.SetRequiredWorldContextHandle(existingWorldContext->ContextHandle);
|
||||
}
|
||||
|
||||
auto DeactivateListOfActions = [&context](const TArray<UGameFeatureAction*>& ActionList)
|
||||
{
|
||||
for (UGameFeatureAction* Action : ActionList)
|
||||
{
|
||||
if (Action)
|
||||
{
|
||||
Action->OnGameFeatureDeactivating(context);
|
||||
Action->OnGameFeatureUnregistering();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DeactivateListOfActions(CurrentExperience->Actions);
|
||||
for (const TObjectPtr<UOLSExperienceActionSetDataAsset>& actionSet : CurrentExperience->ActionSets)
|
||||
{
|
||||
if (actionSet != nullptr)
|
||||
{
|
||||
DeactivateListOfActions(actionSet->Actions);
|
||||
}
|
||||
}
|
||||
|
||||
NumExpectedPausers = context.GetNumPausers();
|
||||
|
||||
if (NumExpectedPausers > 0)
|
||||
{
|
||||
OLS_LOG(LogOLSExperienceManagerComponent, Error,
|
||||
TEXT("Actions that have asynchronous deactivation aren't fully supported yet in Lyra experiences"));
|
||||
}
|
||||
|
||||
if (NumExpectedPausers == NumObservedPausers)
|
||||
{
|
||||
OnAllActionsDeactivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME(ThisClass, CurrentExperience);
|
||||
}
|
||||
|
||||
bool UOLSExperienceManagerComponent::ShouldShowLoadingScreen(FString& outReason) const
|
||||
{
|
||||
if (LoadState != EOLSExperienceLoadState::Loaded)
|
||||
{
|
||||
outReason = TEXT("Experience still loading");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const UOLSExperienceDefinitionDataAsset* UOLSExperienceManagerComponent::GetCurrentExperienceChecked() const
|
||||
{
|
||||
check(LoadState == EOLSExperienceLoadState::Loaded);
|
||||
check(CurrentExperience != nullptr);
|
||||
return CurrentExperience;
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::SetCurrentExperience(FPrimaryAssetId experienceId)
|
||||
{
|
||||
UOLSAssetManager& assetManager = UOLSAssetManager::Get();
|
||||
FSoftObjectPath assetPath = assetManager.GetPrimaryAssetPath(experienceId);
|
||||
TSubclassOf<UOLSExperienceDefinitionDataAsset> assetClass = Cast<UClass>(assetPath.TryLoad());
|
||||
check(assetClass);
|
||||
const UOLSExperienceDefinitionDataAsset* experience = GetDefault<UOLSExperienceDefinitionDataAsset>(assetClass);
|
||||
|
||||
check(experience != nullptr);
|
||||
check(CurrentExperience == nullptr);
|
||||
CurrentExperience = experience;
|
||||
StartExperienceLoad();
|
||||
}
|
||||
|
||||
bool UOLSExperienceManagerComponent::IsExperienceLoaded() const
|
||||
{
|
||||
return (LoadState == EOLSExperienceLoadState::Loaded) && (CurrentExperience != nullptr);
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_HighPriority(
|
||||
FOLSExperienceLoadedNativeDelegate::FDelegate&& delegate)
|
||||
{
|
||||
if (IsExperienceLoaded())
|
||||
{
|
||||
delegate.Execute(CurrentExperience);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnExperienceLoaded_HighPriority.Add(MoveTemp(delegate));
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded(FOLSExperienceLoadedNativeDelegate::FDelegate&& delegate)
|
||||
{
|
||||
if (IsExperienceLoaded())
|
||||
{
|
||||
delegate.Execute(CurrentExperience);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnExperienceLoaded.Add(MoveTemp(delegate));
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_LowPriority(
|
||||
FOLSExperienceLoadedNativeDelegate::FDelegate&& delegate)
|
||||
{
|
||||
if (IsExperienceLoaded())
|
||||
{
|
||||
delegate.Execute(CurrentExperience);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnExperienceLoaded_LowPriority.Add(MoveTemp(delegate));
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::OnRep_CurrentExperience()
|
||||
{
|
||||
StartExperienceLoad();
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::StartExperienceLoad()
|
||||
{
|
||||
check(CurrentExperience != nullptr);
|
||||
check(LoadState == EOLSExperienceLoadState::Unloaded);
|
||||
|
||||
OLS_LOG(LogOLSExperienceManagerComponent, Log, TEXT("EXPERIENCE: StartExperienceLoad(CurrentExperience = %s, %s)"),
|
||||
*CurrentExperience->GetPrimaryAssetId().ToString(), *GetClientServerContextString(this));
|
||||
|
||||
LoadState = EOLSExperienceLoadState::Loading;
|
||||
|
||||
UOLSAssetManager& assetManager = UOLSAssetManager::Get();
|
||||
|
||||
TSet<FPrimaryAssetId> bundleAssetList;
|
||||
TSet<FSoftObjectPath> rawAssetList;
|
||||
|
||||
bundleAssetList.Add(CurrentExperience->GetPrimaryAssetId());
|
||||
|
||||
for (const TObjectPtr<UOLSExperienceActionSetDataAsset>& actionSet : CurrentExperience->ActionSets)
|
||||
{
|
||||
if (actionSet != nullptr)
|
||||
{
|
||||
bundleAssetList.Add(actionSet->GetPrimaryAssetId());
|
||||
}
|
||||
}
|
||||
|
||||
// Load assets associated with the experience
|
||||
|
||||
TArray<FName> bundlesToLoad;
|
||||
bundlesToLoad.Add(FOLSBundles::Equipped);
|
||||
|
||||
//@TODO: Centralize this client/server stuff into the OLSAssetManager
|
||||
const ENetMode ownerNetMode = GetOwner()->GetNetMode();
|
||||
const bool shouldLoadClient = GIsEditor || (ownerNetMode != NM_DedicatedServer);
|
||||
const bool shouldLoadServer = GIsEditor || (ownerNetMode != NM_Client);
|
||||
if (shouldLoadClient)
|
||||
{
|
||||
bundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateClient);
|
||||
}
|
||||
if (shouldLoadServer)
|
||||
{
|
||||
bundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateServer);
|
||||
}
|
||||
|
||||
TSharedPtr<FStreamableHandle> bundleLoadHandle = nullptr;
|
||||
if (bundleAssetList.Num() > 0)
|
||||
{
|
||||
bundleLoadHandle = assetManager.ChangeBundleStateForPrimaryAssets(bundleAssetList.Array(), bundlesToLoad, {}, false, FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority);
|
||||
}
|
||||
|
||||
TSharedPtr<FStreamableHandle> rawLoadHandle = nullptr;
|
||||
if (rawAssetList.Num() > 0)
|
||||
{
|
||||
rawLoadHandle = assetManager.LoadAssetList(rawAssetList.Array(), FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority, TEXT("StartExperienceLoad()"));
|
||||
}
|
||||
|
||||
// If both async loads are running, combine them
|
||||
TSharedPtr<FStreamableHandle> handle = nullptr;
|
||||
if (bundleLoadHandle.IsValid() && rawLoadHandle.IsValid())
|
||||
{
|
||||
handle = assetManager.GetStreamableManager().CreateCombinedHandle({ bundleLoadHandle, rawLoadHandle });
|
||||
}
|
||||
else
|
||||
{
|
||||
handle = bundleLoadHandle.IsValid() ? bundleLoadHandle : rawLoadHandle;
|
||||
}
|
||||
|
||||
FStreamableDelegate onAssetsLoadedDelegate = FStreamableDelegate::CreateUObject(this, &ThisClass::OnExperienceLoadComplete);
|
||||
if (!handle.IsValid() || handle->HasLoadCompleted())
|
||||
{
|
||||
// Assets were already loaded, call the delegate now
|
||||
FStreamableHandle::ExecuteDelegate(onAssetsLoadedDelegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle->BindCompleteDelegate(onAssetsLoadedDelegate);
|
||||
|
||||
handle->BindCancelDelegate(FStreamableDelegate::CreateLambda([onAssetsLoadedDelegate]()
|
||||
{
|
||||
onAssetsLoadedDelegate.ExecuteIfBound();
|
||||
}));
|
||||
}
|
||||
|
||||
// This set of assets gets preloaded, but we don't block the start of the experience based on it
|
||||
TSet<FPrimaryAssetId> preloadAssetList;
|
||||
//@TODO: Determine assets to preload (but not blocking-ly)
|
||||
if (preloadAssetList.Num() > 0)
|
||||
{
|
||||
assetManager.ChangeBundleStateForPrimaryAssets(preloadAssetList.Array(), bundlesToLoad, {});
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::OnExperienceLoadComplete()
|
||||
{
|
||||
check(LoadState == EOLSExperienceLoadState::Loading);
|
||||
check(CurrentExperience != nullptr);
|
||||
|
||||
OLS_LOG(LogOLSExperienceManagerComponent, Log, TEXT("EXPERIENCE: OnExperienceLoadComplete(CurrentExperience = %s, %s)"),
|
||||
*CurrentExperience->GetPrimaryAssetId().ToString(),
|
||||
*GetClientServerContextString(this));
|
||||
|
||||
// find the URLs for our GameFeaturePlugins - filtering out dupes and ones that don't have a valid mapping
|
||||
GameFeaturePluginURLs.Reset();
|
||||
|
||||
auto collectGameFeaturePluginURLs = [This=this](const UPrimaryDataAsset* context, const TArray<FString>& featurePluginList)
|
||||
{
|
||||
for (const FString& pluginName : featurePluginList)
|
||||
{
|
||||
FString pluginURL;
|
||||
if (UGameFeaturesSubsystem::Get().GetPluginURLByName(pluginName, /*out*/ pluginURL))
|
||||
{
|
||||
This->GameFeaturePluginURLs.AddUnique(pluginURL);
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureMsgf(false, TEXT("OnExperienceLoadComplete failed to find plugin URL from PluginName %s for experience %s - fix data, ignoring for this run"), *pluginName, *context->GetPrimaryAssetId().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Add in our extra plugin (Lyra commented this out).
|
||||
// if (!CurrentPlaylistData->GameFeaturePluginToActivateUntilDownloadedContentIsPresent.IsEmpty())
|
||||
// {
|
||||
// FString PluginURL;
|
||||
// if (UGameFeaturesSubsystem::Get().GetPluginURLByName(CurrentPlaylistData->GameFeaturePluginToActivateUntilDownloadedContentIsPresent, PluginURL))
|
||||
// {
|
||||
// GameFeaturePluginURLs.AddUnique(PluginURL);
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
collectGameFeaturePluginURLs(CurrentExperience, CurrentExperience->GameFeaturesToEnable);
|
||||
for (const TObjectPtr<UOLSExperienceActionSetDataAsset>& actionSet : CurrentExperience->ActionSets)
|
||||
{
|
||||
if (actionSet)
|
||||
{
|
||||
collectGameFeaturePluginURLs(actionSet, actionSet->GameFeaturesToEnable);
|
||||
}
|
||||
}
|
||||
|
||||
// Load and activate the features
|
||||
NumGameFeaturePluginsLoading = GameFeaturePluginURLs.Num();
|
||||
if (NumGameFeaturePluginsLoading > 0)
|
||||
{
|
||||
LoadState = EOLSExperienceLoadState::LoadingGameFeatures;
|
||||
for (const FString& pluginURL : GameFeaturePluginURLs)
|
||||
{
|
||||
UOLSExperienceManager::NotifyOfPluginActivation(pluginURL);
|
||||
UGameFeaturesSubsystem::Get().LoadAndActivateGameFeaturePlugin(
|
||||
pluginURL,
|
||||
FGameFeaturePluginLoadComplete::CreateUObject(this, &ThisClass::OnGameFeaturePluginLoadComplete));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OnExperienceFullLoadCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::OnGameFeaturePluginLoadComplete(const UE::GameFeatures::FResult& result)
|
||||
{
|
||||
// decrement the number of plugins that are loading
|
||||
NumGameFeaturePluginsLoading--;
|
||||
|
||||
if (NumGameFeaturePluginsLoading == 0)
|
||||
{
|
||||
OnExperienceFullLoadCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::OnExperienceFullLoadCompleted()
|
||||
{
|
||||
check(LoadState != EOLSExperienceLoadState::Loaded);
|
||||
|
||||
// Insert a random delay for testing (if configured)
|
||||
if (LoadState != EOLSExperienceLoadState::LoadingChaosTestingDelay)
|
||||
{
|
||||
const float delaySecs = OLSConsoleVariables::GetExperienceLoadDelayDuration();
|
||||
if (delaySecs > 0.0f)
|
||||
{
|
||||
FTimerHandle dummyHandle;
|
||||
|
||||
LoadState = EOLSExperienceLoadState::LoadingChaosTestingDelay;
|
||||
GetWorld()->GetTimerManager().SetTimer(dummyHandle, this, &ThisClass::OnExperienceFullLoadCompleted, delaySecs, /*bLooping=*/ false);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LoadState = EOLSExperienceLoadState::ExecutingActions;
|
||||
|
||||
// Execute the actions
|
||||
FGameFeatureActivatingContext context;
|
||||
|
||||
// Only apply to our specific world context if set
|
||||
const FWorldContext* existingWorldContext = GEngine->GetWorldContextFromWorld(GetWorld());
|
||||
if (existingWorldContext)
|
||||
{
|
||||
context.SetRequiredWorldContextHandle(existingWorldContext->ContextHandle);
|
||||
}
|
||||
|
||||
auto activateListOfActions = [&context](const TArray<UGameFeatureAction*>& actionList)
|
||||
{
|
||||
for (UGameFeatureAction* action : actionList)
|
||||
{
|
||||
if (action != nullptr)
|
||||
{
|
||||
//@TODO: The fact that these don't take a world are potentially problematic in client-server PIE
|
||||
// The current behavior matches systems like gameplay tags where loading and registering apply to the entire process,
|
||||
// but actually applying the results to actors is restricted to a specific world
|
||||
action->OnGameFeatureRegistering();
|
||||
action->OnGameFeatureLoading();
|
||||
action->OnGameFeatureActivating(context);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
activateListOfActions(CurrentExperience->Actions);
|
||||
|
||||
for (const TObjectPtr<UOLSExperienceActionSetDataAsset>& actionSet : CurrentExperience->ActionSets)
|
||||
{
|
||||
if (actionSet)
|
||||
{
|
||||
activateListOfActions(actionSet->Actions);
|
||||
}
|
||||
}
|
||||
|
||||
LoadState = EOLSExperienceLoadState::Loaded;
|
||||
|
||||
OnExperienceLoaded_HighPriority.Broadcast(CurrentExperience);
|
||||
OnExperienceLoaded_HighPriority.Clear();
|
||||
|
||||
OnExperienceLoaded.Broadcast(CurrentExperience);
|
||||
OnExperienceLoaded.Clear();
|
||||
|
||||
OnExperienceLoaded_LowPriority.Broadcast(CurrentExperience);
|
||||
OnExperienceLoaded_LowPriority.Clear();
|
||||
|
||||
// Apply any necessary scalability settings
|
||||
#if !UE_SERVER
|
||||
// @TODO implement UOLSSettingsLocal
|
||||
// UOLSSettingsLocal::Get()->OnExperienceLoaded();
|
||||
#endif
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::OnActionDeactivationCompleted()
|
||||
{
|
||||
check(IsInGameThread());
|
||||
++NumObservedPausers;
|
||||
|
||||
if (NumObservedPausers == NumExpectedPausers)
|
||||
{
|
||||
OnAllActionsDeactivated();
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSExperienceManagerComponent::OnAllActionsDeactivated()
|
||||
{
|
||||
//@TODO: We actually only deactivated and didn't fully unload...
|
||||
LoadState = EOLSExperienceLoadState::Unloaded;
|
||||
CurrentExperience = nullptr;
|
||||
//@TODO: GEngine->ForceGarbageCollection(true);
|
||||
}
|
535
Source/ols/Private/GameModes/OLSGameMode.cpp
Normal file
535
Source/ols/Private/GameModes/OLSGameMode.cpp
Normal file
@ -0,0 +1,535 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "GameModes/OLSGameMode.h"
|
||||
|
||||
#include "CommonUserSubsystem.h"
|
||||
#include "GameMapsSettings.h"
|
||||
#include "OLSLog.h"
|
||||
#include "Characters/OLSCharacter.h"
|
||||
#include "Components/OLSPawnExtensionComponent.h"
|
||||
#include "DataAssets/OLSExperienceDefinitionDataAsset.h"
|
||||
#include "DataAssets/OLSPawnDataAsset.h"
|
||||
#include "GameModes/OLSExperienceManagerComponent.h"
|
||||
#include "GameModes/OLSGameState.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Player/OLSPlayerController.h"
|
||||
#include "Player/OLSPlayerState.h"
|
||||
#include "Systems/OLSAssetManager.h"
|
||||
#include "Systems/OLSGameSession.h"
|
||||
#include "CommonUserSubsystem.h"
|
||||
#include "CommonSessionSubsystem.h"
|
||||
#include "UI/OLSHUD.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameMode)
|
||||
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSGameMode);
|
||||
|
||||
AOLSGameMode::AOLSGameMode(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
GameStateClass = AOLSGameState::StaticClass();
|
||||
GameSessionClass = AOLSGameSession::StaticClass();
|
||||
PlayerControllerClass = AOLSPlayerController::StaticClass();
|
||||
// ReplaySpectatorPlayerControllerClass = ALyraReplayPlayerController::StaticClass();
|
||||
PlayerStateClass = AOLSPlayerState::StaticClass();
|
||||
DefaultPawnClass = AOLSCharacter::StaticClass();
|
||||
HUDClass = AOLSHUD::StaticClass();
|
||||
}
|
||||
|
||||
const UOLSPawnDataAsset* AOLSGameMode::GetPawnDataForController(const AController* controller) const
|
||||
{
|
||||
// See if pawn data is already set on the player state
|
||||
if (controller)
|
||||
{
|
||||
if (const AOLSPlayerState* playerState = controller->GetPlayerState<AOLSPlayerState>())
|
||||
{
|
||||
if (const UOLSPawnDataAsset* pawnData = playerState->GetPawnData<UOLSPawnDataAsset>())
|
||||
{
|
||||
return pawnData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not, fall back to the the default for the current experience
|
||||
check(GameState);
|
||||
UOLSExperienceManagerComponent* experienceComponent = GameState->FindComponentByClass<UOLSExperienceManagerComponent>();
|
||||
check(experienceComponent);
|
||||
|
||||
if (experienceComponent->IsExperienceLoaded())
|
||||
{
|
||||
const UOLSExperienceDefinitionDataAsset* experienceDataAsset = experienceComponent->GetCurrentExperienceChecked();
|
||||
if (experienceDataAsset->DefaultPawnData)
|
||||
{
|
||||
return experienceDataAsset->DefaultPawnData;
|
||||
}
|
||||
|
||||
// Experience is loaded and there's still no pawn data, fall back to the default for now
|
||||
return UOLSAssetManager::Get().GetDefaultPawnData();
|
||||
}
|
||||
|
||||
// Experience not loaded yet, so there is no pawn data to be had
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AOLSGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
|
||||
{
|
||||
Super::InitGame(MapName, Options, ErrorMessage);
|
||||
|
||||
// Wait for the next frame to give time to initialize startup settings
|
||||
GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::HandleMatchAssignmentIfNotExpectingOne);
|
||||
}
|
||||
|
||||
UClass* AOLSGameMode::GetDefaultPawnClassForController_Implementation(AController* controller)
|
||||
{
|
||||
if (const UOLSPawnDataAsset* pawnData = GetPawnDataForController(controller))
|
||||
{
|
||||
if (pawnData->PawnClass)
|
||||
{
|
||||
return pawnData->PawnClass;
|
||||
}
|
||||
}
|
||||
|
||||
return Super::GetDefaultPawnClassForController_Implementation(controller);
|
||||
}
|
||||
|
||||
APawn* AOLSGameMode::SpawnDefaultPawnAtTransform_Implementation(
|
||||
AController* newPlayer,
|
||||
const FTransform& spawnTransform)
|
||||
{
|
||||
FActorSpawnParameters spawnInfo;
|
||||
spawnInfo.Instigator = GetInstigator();
|
||||
spawnInfo.ObjectFlags |= RF_Transient; // Never save the default player pawns into a map.
|
||||
spawnInfo.bDeferConstruction = true;
|
||||
|
||||
if (UClass* pawnClass = GetDefaultPawnClassForController(newPlayer))
|
||||
{
|
||||
if (APawn* spawnedPawn = GetWorld()->SpawnActor<APawn>(pawnClass, spawnTransform, spawnInfo))
|
||||
{
|
||||
if (UOLSPawnExtensionComponent* pawnExtComp = UOLSPawnExtensionComponent::FindPawnExtensionComponent(spawnedPawn))
|
||||
{
|
||||
if (const UOLSPawnDataAsset* pawnData = GetPawnDataForController(newPlayer))
|
||||
{
|
||||
pawnExtComp->SetPawnData(pawnData);
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogOLSGameMode, Error,
|
||||
TEXT("Game mode was unable to set PawnData on the spawned pawn [%s]."),
|
||||
*GetNameSafe(spawnedPawn));
|
||||
}
|
||||
}
|
||||
|
||||
spawnedPawn->FinishSpawning(spawnTransform);
|
||||
|
||||
return spawnedPawn;
|
||||
}
|
||||
OLS_LOG(LogOLSGameMode, Error, TEXT("Game mode was unable to spawn Pawn of class [%s] at [%s]."), *GetNameSafe(pawnClass), *spawnTransform.ToHumanReadableString());
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogOLSGameMode, Error, TEXT("Game mode was unable to spawn Pawn due to NULL pawn class."));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AOLSGameMode::ShouldSpawnAtStartSpot(AController* Player)
|
||||
{
|
||||
// We never want to use the start spot, always use the spawn management component.
|
||||
return false;
|
||||
}
|
||||
|
||||
void AOLSGameMode::HandleStartingNewPlayer_Implementation(APlayerController* newPlayer)
|
||||
{
|
||||
// Delay starting new players until the experience has been loaded
|
||||
// (players who log in prior to that will be started by OnExperienceLoaded)
|
||||
if (IsExperienceLoaded())
|
||||
{
|
||||
Super::HandleStartingNewPlayer_Implementation(newPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
AActor* AOLSGameMode::ChoosePlayerStart_Implementation(AController* player)
|
||||
{
|
||||
// @TODO: Implement this as well as implement UOLSPlayerSpawningManagerComponent
|
||||
// if (UOLSPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass<ULyraPlayerSpawningManagerComponent>())
|
||||
// {
|
||||
// return PlayerSpawningComponent->ChoosePlayerStart(player);
|
||||
// }
|
||||
|
||||
return Super::ChoosePlayerStart_Implementation(player);
|
||||
}
|
||||
|
||||
void AOLSGameMode::FinishRestartPlayer(AController* newPlayer, const FRotator& startRotation)
|
||||
{
|
||||
// @TODO: Implement this as well as implement UOLSPlayerSpawningManagerComponent
|
||||
// if (UOLSPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass<UOLSPlayerSpawningManagerComponent>())
|
||||
// {
|
||||
// PlayerSpawningComponent->FinishRestartPlayer(NewPlayer, StartRotation);
|
||||
// }
|
||||
|
||||
Super::FinishRestartPlayer(newPlayer, startRotation);
|
||||
}
|
||||
|
||||
bool AOLSGameMode::PlayerCanRestart_Implementation(APlayerController* player)
|
||||
{
|
||||
return ControllerCanRestart(player);
|
||||
}
|
||||
|
||||
void AOLSGameMode::InitGameState()
|
||||
{
|
||||
Super::InitGameState();
|
||||
|
||||
// Listen for the experience load to complete
|
||||
UOLSExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<UOLSExperienceManagerComponent>();
|
||||
check(ExperienceComponent);
|
||||
ExperienceComponent->CallOrRegister_OnExperienceLoaded(FOLSExperienceLoadedNativeDelegate::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded));
|
||||
|
||||
}
|
||||
|
||||
bool AOLSGameMode::UpdatePlayerStartSpot(AController* player, const FString& portal, FString& outErrorMessage)
|
||||
{
|
||||
// Do nothing, we'll wait until PostLogin when we try to spawn the player for real.
|
||||
// Doing anything right now is no good, systems like team assignment haven't even occurred yet.
|
||||
return true;
|
||||
}
|
||||
|
||||
void AOLSGameMode::GenericPlayerInitialization(AController* newPlayer)
|
||||
{
|
||||
Super::GenericPlayerInitialization(newPlayer);
|
||||
|
||||
OnGameModePlayerInitialized.Broadcast(this, newPlayer);
|
||||
}
|
||||
|
||||
void AOLSGameMode::FailedToRestartPlayer(AController* newPlayer)
|
||||
{
|
||||
Super::FailedToRestartPlayer(newPlayer);
|
||||
|
||||
// If we tried to spawn a pawn and it failed, lets try again *note* check if there's actually a pawn class
|
||||
// before we try this forever.
|
||||
if (UClass* PawnClass = GetDefaultPawnClassForController(newPlayer))
|
||||
{
|
||||
if (APlayerController* newPC = Cast<APlayerController>(newPlayer))
|
||||
{
|
||||
// If it's a player don't loop forever, maybe something changed and they can no longer restart if so stop trying.
|
||||
if (PlayerCanRestart(newPC))
|
||||
{
|
||||
RequestPlayerRestartNextFrame(newPlayer, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// @TODO: Replace this with our custom log.
|
||||
// UE_LOG(LogLyra, Verbose, TEXT("FailedToRestartPlayer(%s) and PlayerCanRestart returned false, so we're not going to try again."), *GetPathNameSafe(newPlayer));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestPlayerRestartNextFrame(newPlayer, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// @TODO: Replace this with our custom log.
|
||||
// UE_LOG(LogLyra, Verbose, TEXT("FailedToRestartPlayer(%s) but there's no pawn class so giving up."), *GetPathNameSafe(newPlayer));
|
||||
}
|
||||
}
|
||||
|
||||
void AOLSGameMode::RequestPlayerRestartNextFrame(AController* controller, bool shouldForceReset)
|
||||
{
|
||||
if (shouldForceReset && controller)
|
||||
{
|
||||
controller->Reset();
|
||||
}
|
||||
|
||||
if (APlayerController* playerController = Cast<APlayerController>(controller))
|
||||
{
|
||||
GetWorldTimerManager().SetTimerForNextTick(playerController, &APlayerController::ServerRestartPlayer_Implementation);
|
||||
}
|
||||
// else if (AOLSPlayerBotController* BotController = Cast<ALyraPlayerBotController>(controller))
|
||||
// {
|
||||
// GetWorldTimerManager().SetTimerForNextTick(BotController, &ALyraPlayerBotController::ServerRestartController);
|
||||
// }
|
||||
}
|
||||
|
||||
bool AOLSGameMode::ControllerCanRestart(AController* controller)
|
||||
{
|
||||
if (APlayerController* playerController = Cast<APlayerController>(controller))
|
||||
{
|
||||
if (!Super::PlayerCanRestart_Implementation(playerController))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bot version of Super::PlayerCanRestart_Implementation
|
||||
if ((controller == nullptr) || controller->IsPendingKillPending())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: Implement this after having PlayerSpawningManagerComponent
|
||||
// if (ULyraPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass<ULyraPlayerSpawningManagerComponent>())
|
||||
// {
|
||||
// return PlayerSpawningComponent->ControllerCanRestart(controller);
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AOLSGameMode::OnExperienceLoaded(const UOLSExperienceDefinitionDataAsset* currentExperience)
|
||||
{
|
||||
// Spawn any players that are already attached
|
||||
//@TODO: Here we're handling only *player* controllers, but in GetDefaultPawnClassForController_Implementation we skipped all controllers
|
||||
// GetDefaultPawnClassForController_Implementation might only be getting called for players anyways
|
||||
for (FConstPlayerControllerIterator iterator = GetWorld()->GetPlayerControllerIterator(); iterator; ++iterator)
|
||||
{
|
||||
APlayerController* playerController = Cast<APlayerController>(*iterator);
|
||||
if (playerController && playerController->GetPawn())
|
||||
{
|
||||
if (PlayerCanRestart(playerController))
|
||||
{
|
||||
RestartPlayer(playerController);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AOLSGameMode::IsExperienceLoaded() const
|
||||
{
|
||||
check(GameState);
|
||||
UOLSExperienceManagerComponent* experienceComponent = GameState->FindComponentByClass<
|
||||
UOLSExperienceManagerComponent>();
|
||||
check(experienceComponent);
|
||||
|
||||
return experienceComponent->IsExperienceLoaded();
|
||||
}
|
||||
|
||||
void AOLSGameMode::OnMatchAssignmentGiven(FPrimaryAssetId experienceId, const FString& experienceIdSource)
|
||||
{
|
||||
if (experienceId.IsValid())
|
||||
{
|
||||
OLS_LOG(LogOLSGameMode, Log, TEXT("Identified experience %s (Source: %s)"), *experienceId.ToString(), *experienceIdSource);
|
||||
|
||||
UOLSExperienceManagerComponent* experienceComponent = GameState->FindComponentByClass<UOLSExperienceManagerComponent>();
|
||||
check(experienceComponent);
|
||||
experienceComponent->SetCurrentExperience(experienceId);
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogOLSGameMode, Error, TEXT("Failed to identify experience, loading screen will stay up forever"));
|
||||
}
|
||||
}
|
||||
|
||||
void AOLSGameMode::HandleMatchAssignmentIfNotExpectingOne()
|
||||
{
|
||||
FPrimaryAssetId experienceId;
|
||||
FString experienceIdSource;
|
||||
|
||||
// Precedence order (highest wins)
|
||||
// - Matchmaking assignment (if present)
|
||||
// - URL Options override
|
||||
// - Developer Settings (PIE only)
|
||||
// - Command Line override
|
||||
// - World Settings
|
||||
// - Dedicated server
|
||||
// - Default experience
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
|
||||
if (!experienceId.IsValid() && UGameplayStatics::HasOption(OptionsString, TEXT("Experience")))
|
||||
{
|
||||
const FString ExperienceFromOptions = UGameplayStatics::ParseOption(OptionsString, TEXT("Experience"));
|
||||
experienceId = FPrimaryAssetId(FPrimaryAssetType(UOLSExperienceDefinitionDataAsset::StaticClass()->GetFName()), FName(*ExperienceFromOptions));
|
||||
experienceIdSource = TEXT("OptionsString");
|
||||
}
|
||||
|
||||
if (!experienceId.IsValid() && World->IsPlayInEditor())
|
||||
{
|
||||
// @TODO: Implement this when UOLSDeveloperSettings is implemented.
|
||||
// experienceId = GetDefault<UOLSDeveloperSettings>()->ExperienceOverride;
|
||||
experienceIdSource = TEXT("DeveloperSettings");
|
||||
}
|
||||
|
||||
// see if the command line wants to set the experience
|
||||
if (!experienceId.IsValid())
|
||||
{
|
||||
FString ExperienceFromCommandLine;
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("Experience="), ExperienceFromCommandLine))
|
||||
{
|
||||
experienceId = FPrimaryAssetId::ParseTypeAndName(ExperienceFromCommandLine);
|
||||
if (!experienceId.PrimaryAssetType.IsValid())
|
||||
{
|
||||
experienceId = FPrimaryAssetId(FPrimaryAssetType(UOLSExperienceDefinitionDataAsset::StaticClass()->GetFName()), FName(*ExperienceFromCommandLine));
|
||||
}
|
||||
experienceIdSource = TEXT("CommandLine");
|
||||
}
|
||||
}
|
||||
|
||||
// see if the world settings has a default experience
|
||||
if (!experienceId.IsValid())
|
||||
{
|
||||
// @TODO: Implement this when AOLSWorldSettings is implemented.
|
||||
// if (AOLSWorldSettings* TypedWorldSettings = Cast<ALyraWorldSettings>(GetWorldSettings()))
|
||||
// {
|
||||
// experienceId = TypedWorldSettings->GetDefaultGameplayExperience();
|
||||
// experienceIdSource = TEXT("WorldSettings");
|
||||
// }
|
||||
}
|
||||
|
||||
UOLSAssetManager& assetManager = UOLSAssetManager::Get();
|
||||
FAssetData dummy;
|
||||
if (experienceId.IsValid() && !assetManager.GetPrimaryAssetData(experienceId, /*out*/ dummy))
|
||||
{
|
||||
OLS_LOG(LogOLSGameMode, Error, TEXT("EXPERIENCE: Wanted to use %s but couldn't find it, falling back to the default)"), *experienceId.ToString());
|
||||
experienceId = FPrimaryAssetId();
|
||||
}
|
||||
|
||||
// Final fallback to the default experience
|
||||
if (!experienceId.IsValid())
|
||||
{
|
||||
if (TryDedicatedServerLogin())
|
||||
{
|
||||
// This will start to host as a dedicated server
|
||||
return;
|
||||
}
|
||||
|
||||
//@TODO: Pull this from a config setting or something
|
||||
experienceId = FPrimaryAssetId(FPrimaryAssetType("OLSExperienceDefinition"), FName("B_OLSDefaultExperience"));
|
||||
experienceIdSource = TEXT("Default");
|
||||
}
|
||||
|
||||
OnMatchAssignmentGiven(experienceId, experienceIdSource);
|
||||
}
|
||||
|
||||
bool AOLSGameMode::TryDedicatedServerLogin()
|
||||
{
|
||||
// Some basic code to register as an active dedicated server, this would be heavily modified by the game
|
||||
// FString defaultMap = UGameMapsSettings::GetGameDefaultMap();
|
||||
// UWorld* world = GetWorld();
|
||||
// UGameInstance* gameInstance = GetGameInstance();
|
||||
// if (gameInstance && world && world->GetNetMode() == NM_DedicatedServer && world->URL.Map == defaultMap)
|
||||
// {
|
||||
// // Only register if this is the default map on a dedicated server
|
||||
// UCommonUserSubsystem* userSubsystem = gameInstance->GetSubsystem<UCommonUserSubsystem>();
|
||||
//
|
||||
// // Dedicated servers may need to do an online login
|
||||
// userSubsystem->OnUserInitializeComplete.AddDynamic(this, &ThisClass::OnUserInitializedForDedicatedServer);
|
||||
//
|
||||
// // There are no local users on dedicated server, but index 0 means the default platform user which is handled by the online login code
|
||||
// if (!userSubsystem->TryToLoginForOnlinePlay(0))
|
||||
// {
|
||||
// OnUserInitializedForDedicatedServer(nullptr, false, FText(), ECommonUserPrivilege::CanPlayOnline, ECommonUserOnlineContext::Default);
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AOLSGameMode::HostDedicatedServerMatch(ECommonSessionOnlineMode onlineMode)
|
||||
{
|
||||
// FPrimaryAssetType UserExperienceType = ULyraUserFacingExperienceDefinition::StaticClass()->GetFName();
|
||||
//
|
||||
// // Figure out what UserFacingExperience to load
|
||||
// FPrimaryAssetId UserExperienceId;
|
||||
// FString UserExperienceFromCommandLine;
|
||||
// if (FParse::Value(FCommandLine::Get(), TEXT("UserExperience="), UserExperienceFromCommandLine) ||
|
||||
// FParse::Value(FCommandLine::Get(), TEXT("Playlist="), UserExperienceFromCommandLine))
|
||||
// {
|
||||
// UserExperienceId = FPrimaryAssetId::ParseTypeAndName(UserExperienceFromCommandLine);
|
||||
// if (!UserExperienceId.PrimaryAssetType.IsValid())
|
||||
// {
|
||||
// UserExperienceId = FPrimaryAssetId(FPrimaryAssetType(UserExperienceType), FName(*UserExperienceFromCommandLine));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Search for the matching experience, it's fine to force load them because we're in dedicated server startup
|
||||
// ULyraAssetManager& AssetManager = ULyraAssetManager::Get();
|
||||
// TSharedPtr<FStreamableHandle> Handle = AssetManager.LoadPrimaryAssetsWithType(UserExperienceType);
|
||||
// if (ensure(Handle.IsValid()))
|
||||
// {
|
||||
// Handle->WaitUntilComplete();
|
||||
// }
|
||||
//
|
||||
// TArray<UObject*> UserExperiences;
|
||||
// AssetManager.GetPrimaryAssetObjectList(UserExperienceType, UserExperiences);
|
||||
// ULyraUserFacingExperienceDefinition* FoundExperience = nullptr;
|
||||
// ULyraUserFacingExperienceDefinition* DefaultExperience = nullptr;
|
||||
//
|
||||
// for (UObject* Object : UserExperiences)
|
||||
// {
|
||||
// ULyraUserFacingExperienceDefinition* UserExperience = Cast<ULyraUserFacingExperienceDefinition>(Object);
|
||||
// if (ensure(UserExperience))
|
||||
// {
|
||||
// if (UserExperience->GetPrimaryAssetId() == UserExperienceId)
|
||||
// {
|
||||
// FoundExperience = UserExperience;
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if (UserExperience->bIsDefaultExperience && DefaultExperience == nullptr)
|
||||
// {
|
||||
// DefaultExperience = UserExperience;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (FoundExperience == nullptr)
|
||||
// {
|
||||
// FoundExperience = DefaultExperience;
|
||||
// }
|
||||
//
|
||||
// UGameInstance* GameInstance = GetGameInstance();
|
||||
// if (ensure(FoundExperience && GameInstance))
|
||||
// {
|
||||
// // Actually host the game
|
||||
// UCommonSession_HostSessionRequest* HostRequest = FoundExperience->CreateHostingRequest(this);
|
||||
// if (ensure(HostRequest))
|
||||
// {
|
||||
// HostRequest->OnlineMode = OnlineMode;
|
||||
//
|
||||
// // TODO override other parameters?
|
||||
//
|
||||
// UCommonSessionSubsystem* SessionSubsystem = GameInstance->GetSubsystem<UCommonSessionSubsystem>();
|
||||
// SessionSubsystem->HostSession(nullptr, HostRequest);
|
||||
//
|
||||
// // This will handle the map travel
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void AOLSGameMode::OnUserInitializedForDedicatedServer(
|
||||
const UCommonUserInfo* userInfo,
|
||||
bool isSuccess,
|
||||
FText error,
|
||||
ECommonUserPrivilege requestedPrivilege,
|
||||
ECommonUserOnlineContext onlineContext)
|
||||
{
|
||||
UGameInstance* gameInstance = GetGameInstance();
|
||||
if (gameInstance)
|
||||
{
|
||||
// Unbind
|
||||
UCommonUserSubsystem* userSubsystem = gameInstance->GetSubsystem<UCommonUserSubsystem>();
|
||||
userSubsystem->OnUserInitializeComplete.RemoveDynamic(this, &ThisClass::OnUserInitializedForDedicatedServer);
|
||||
|
||||
// Dedicated servers do not require user login, but some online subsystems may expect it
|
||||
if (isSuccess && ensure(userInfo))
|
||||
{
|
||||
// @TODO: Fix compiler error.
|
||||
// OLS_LOG(LogOLSGameMode, Log,
|
||||
// TEXT("Dedicated server user login succeeded for id %s, starting online server"),
|
||||
// *userInfo->GetNetId().ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogOLSGameMode, Log,
|
||||
TEXT("Dedicated server user login unsuccessful, starting online server as login is not required"));
|
||||
}
|
||||
|
||||
HostDedicatedServerMatch(ECommonSessionOnlineMode::Online);
|
||||
}
|
||||
}
|
4
Source/ols/Private/GameModes/OLSGameState.cpp
Normal file
4
Source/ols/Private/GameModes/OLSGameState.cpp
Normal file
@ -0,0 +1,4 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "GameModes/OLSGameState.h"
|
@ -0,0 +1,7 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Interfaces/OLSPrimaryDataAssetInterface.h"
|
||||
|
||||
|
||||
// Add default functionality here for any IOLSPrimaryDataAssetInterface functions that are not pure virtual.
|
7
Source/ols/Private/Messages/OLSNotificationMessage.cpp
Normal file
7
Source/ols/Private/Messages/OLSNotificationMessage.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Messages/OLSNotificationMessage.h"
|
||||
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_OLS_AddNotification_Message, "Lyra.AddNotification.Message");
|
11
Source/ols/Private/Messages/OLSVerbMessage.cpp
Normal file
11
Source/ols/Private/Messages/OLSVerbMessage.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Messages/OLSVerbMessage.h"
|
||||
|
||||
FString FOLSVerbMessage::ToString() const
|
||||
{
|
||||
FString HumanReadableMessage;
|
||||
FOLSVerbMessage::StaticStruct()->ExportText(/*out*/ HumanReadableMessage, this, /*Defaults=*/ nullptr, /*OwnerObject=*/ nullptr, PPF_None, /*ExportRootScope=*/ nullptr);
|
||||
return HumanReadableMessage;
|
||||
}
|
83
Source/ols/Private/Messages/OLSVerbMessageHelpers.cpp
Normal file
83
Source/ols/Private/Messages/OLSVerbMessageHelpers.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Messages/OLSVerbMessageHelpers.h"
|
||||
|
||||
#include "GameplayEffectTypes.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "Messages/OLSVerbMessage.h"
|
||||
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSVerbMessageHelpers)
|
||||
|
||||
APlayerState* UOLSVerbMessageHelpers::GetPlayerStateFromObject(UObject* object)
|
||||
{
|
||||
if (APlayerController* playerController = Cast<APlayerController>(object))
|
||||
{
|
||||
return playerController->PlayerState;
|
||||
}
|
||||
|
||||
if (APlayerState* targetPlayerState = Cast<APlayerState>(object))
|
||||
{
|
||||
return targetPlayerState;
|
||||
}
|
||||
|
||||
if (APawn* targetPawn = Cast<APawn>(object))
|
||||
{
|
||||
if (APlayerState* targetPlayerState = targetPawn->GetPlayerState())
|
||||
{
|
||||
return targetPlayerState;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
APlayerController* UOLSVerbMessageHelpers::GetPlayerControllerFromObject(UObject* object)
|
||||
{
|
||||
if (APlayerController* playerController = Cast<APlayerController>(object))
|
||||
{
|
||||
return playerController;
|
||||
}
|
||||
|
||||
if (APlayerState* targetPlayerState = Cast<APlayerState>(object))
|
||||
{
|
||||
return targetPlayerState->GetPlayerController();
|
||||
}
|
||||
|
||||
if (APawn* targetPawn = Cast<APawn>(object))
|
||||
{
|
||||
return Cast<APlayerController>(targetPawn->GetController());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FGameplayCueParameters UOLSVerbMessageHelpers::VerbMessageToCueParameters(const FOLSVerbMessage& message)
|
||||
{
|
||||
FGameplayCueParameters Result;
|
||||
|
||||
Result.OriginalTag = message.Verb;
|
||||
Result.Instigator = Cast<AActor>(message.Instigator);
|
||||
Result.EffectCauser = Cast<AActor>(message.Target);
|
||||
Result.AggregatedSourceTags = message.InstigatorTags;
|
||||
Result.AggregatedTargetTags = message.TargetTags;
|
||||
//@TODO: = Message.ContextTags;
|
||||
Result.RawMagnitude = message.Magnitude;
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
FOLSVerbMessage UOLSVerbMessageHelpers::CueParametersToVerbMessage(const FGameplayCueParameters& params)
|
||||
{
|
||||
FOLSVerbMessage result;
|
||||
|
||||
result.Verb = params.OriginalTag;
|
||||
result.Instigator = params.Instigator.Get();
|
||||
result.Target = params.EffectCauser.Get();
|
||||
result.InstigatorTags = params.AggregatedSourceTags;
|
||||
result.TargetTags = params.AggregatedTargetTags;
|
||||
//@TODO: Result.ContextTags = ???;
|
||||
result.Magnitude = params.RawMagnitude;
|
||||
|
||||
return result;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularAIController.h"
|
||||
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
|
||||
void AOLSModularAIController::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularAIController::BeginPlay()
|
||||
{
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AOLSModularAIController::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
103
Source/ols/Private/ModularGameplayActors/OLSModularActor.cpp
Normal file
103
Source/ols/Private/ModularGameplayActors/OLSModularActor.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularActor.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSModularActor);
|
||||
|
||||
AOLSModularActor::AOLSModularActor(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
// Set this actor to replicate like a Pawn would
|
||||
bReplicates = true;
|
||||
|
||||
// Set Ability System Companion with GAS Companion subclass
|
||||
AbilitySystemComponent = CreateDefaultSubobject<UOLSAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
|
||||
AbilitySystemComponent->SetIsReplicated(true);
|
||||
|
||||
ReplicationMode = EGameplayEffectReplicationMode::Mixed;
|
||||
}
|
||||
|
||||
void AOLSModularActor::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularActor::BeginPlay()
|
||||
{
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AOLSModularActor::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
||||
|
||||
void AOLSModularActor::PostInitProperties()
|
||||
{
|
||||
Super::PostInitProperties();
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
OLS_LOG(LogOLSModularActor, Verbose, TEXT("PostInitProperties for %s - Setting up ASC Replication Mode to: %d"),
|
||||
GET_UOBJECT_NAME(this), ReplicationMode);
|
||||
AbilitySystemComponent->SetReplicationMode(ReplicationMode);
|
||||
}
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* AOLSModularActor::GetAbilitySystemComponent() const
|
||||
{
|
||||
return AbilitySystemComponent;
|
||||
}
|
||||
|
||||
void AOLSModularActor::GetOwnedGameplayTags(FGameplayTagContainer& outTagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
FGameplayTagContainer ownedTags;
|
||||
AbilitySystemComponent->GetOwnedGameplayTags(ownedTags);
|
||||
outTagContainer = MoveTemp(ownedTags);
|
||||
}
|
||||
}
|
||||
|
||||
bool AOLSModularActor::HasMatchingGameplayTag(FGameplayTag tagToCheck) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasMatchingGameplayTag(tagToCheck);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularActor::HasAllMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasAllMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularActor::HasAnyMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasAnyMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
102
Source/ols/Private/ModularGameplayActors/OLSModularCharacter.cpp
Normal file
102
Source/ols/Private/ModularGameplayActors/OLSModularCharacter.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularCharacter.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSModularCharacter);
|
||||
|
||||
AOLSModularCharacter::AOLSModularCharacter(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
// Set this actor to replicate like a Pawn would
|
||||
bReplicates = true;
|
||||
|
||||
// Set Ability System Companion with GAS Companion subclass
|
||||
AbilitySystemComponent = CreateDefaultSubobject<UOLSAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
|
||||
AbilitySystemComponent->SetIsReplicated(true);
|
||||
|
||||
ReplicationMode = EGameplayEffectReplicationMode::Mixed;
|
||||
}
|
||||
|
||||
void AOLSModularCharacter::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularCharacter::BeginPlay()
|
||||
{
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(
|
||||
this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AOLSModularCharacter::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
||||
|
||||
void AOLSModularCharacter::PostInitProperties()
|
||||
{
|
||||
Super::PostInitProperties();
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
OLS_LOG(LogOLSModularCharacter, Verbose,
|
||||
TEXT("PostInitProperties for %s - Setting up ASC Replication Mode to: %d"), GET_UOBJECT_NAME(this),
|
||||
ReplicationMode);
|
||||
AbilitySystemComponent->SetReplicationMode(ReplicationMode);
|
||||
}
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* AOLSModularCharacter::GetAbilitySystemComponent() const
|
||||
{
|
||||
return AbilitySystemComponent;
|
||||
}
|
||||
|
||||
void AOLSModularCharacter::GetOwnedGameplayTags(FGameplayTagContainer& outTagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
FGameplayTagContainer ownedTags;
|
||||
AbilitySystemComponent->GetOwnedGameplayTags(ownedTags);
|
||||
outTagContainer = MoveTemp(ownedTags);
|
||||
}
|
||||
}
|
||||
|
||||
bool AOLSModularCharacter::HasMatchingGameplayTag(FGameplayTag tagToCheck) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasMatchingGameplayTag(tagToCheck);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularCharacter::HasAllMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasAllMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularCharacter::HasAnyMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasAnyMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularDefaultPawn.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSModularDefaultPawn);
|
||||
|
||||
// Sets default values
|
||||
AOLSModularDefaultPawn::AOLSModularDefaultPawn(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
// Set this actor to replicate like a Pawn would
|
||||
bReplicates = true;
|
||||
|
||||
// Set Ability System Companion with GAS Companion subclass
|
||||
AbilitySystemComponent = CreateDefaultSubobject<UOLSAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
|
||||
AbilitySystemComponent->SetIsReplicated(true);
|
||||
|
||||
ReplicationMode = EGameplayEffectReplicationMode::Mixed;
|
||||
}
|
||||
|
||||
void AOLSModularDefaultPawn::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularDefaultPawn::BeginPlay()
|
||||
{
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(
|
||||
this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AOLSModularDefaultPawn::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
||||
|
||||
void AOLSModularDefaultPawn::PostInitProperties()
|
||||
{
|
||||
Super::PostInitProperties();
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
OLS_LOG(LogOLSModularDefaultPawn, Verbose,
|
||||
TEXT("PostInitProperties for %s - Setting up ASC Replication Mode to: %d"), GET_UOBJECT_NAME(this),
|
||||
ReplicationMode);
|
||||
AbilitySystemComponent->SetReplicationMode(ReplicationMode);
|
||||
}
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* AOLSModularDefaultPawn::GetAbilitySystemComponent() const
|
||||
{
|
||||
return AbilitySystemComponent;
|
||||
}
|
||||
|
||||
void AOLSModularDefaultPawn::GetOwnedGameplayTags(FGameplayTagContainer& outTagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
FGameplayTagContainer ownedTags;
|
||||
AbilitySystemComponent->GetOwnedGameplayTags(ownedTags);
|
||||
outTagContainer = MoveTemp(ownedTags);
|
||||
}
|
||||
}
|
||||
|
||||
bool AOLSModularDefaultPawn::HasMatchingGameplayTag(FGameplayTag tagToCheck) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasMatchingGameplayTag(tagToCheck);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularDefaultPawn::HasAllMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasAllMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularDefaultPawn::HasAnyMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasAnyMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularGameMode.h"
|
||||
|
||||
#include "ModularGameplayActors/OLSModularCharacter.h"
|
||||
#include "ModularGameplayActors/OLSModularGameState.h"
|
||||
#include "ModularGameplayActors/OLSModularPlayerController.h"
|
||||
#include "ModularGameplayActors/OLSModularPlayerState.h"
|
||||
|
||||
AOLSModularGameModeBase::AOLSModularGameModeBase(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
GameStateClass = AOLSModularGameStateBase::StaticClass();
|
||||
PlayerControllerClass = AOLSModularPlayerController::StaticClass();
|
||||
PlayerStateClass = AOLSModularPlayerState::StaticClass();
|
||||
DefaultPawnClass = AOLSModularCharacter::StaticClass();
|
||||
}
|
||||
|
||||
AOLSModularGameMode::AOLSModularGameMode(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
GameStateClass = AOLSModularGameStateBase::StaticClass();
|
||||
PlayerControllerClass = AOLSModularPlayerController::StaticClass();
|
||||
PlayerStateClass = AOLSModularPlayerState::StaticClass();
|
||||
DefaultPawnClass = AOLSModularCharacter::StaticClass();
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularGameState.h"
|
||||
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
#include "Components/GameStateComponent.h"
|
||||
|
||||
void AOLSModularGameStateBase::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularGameStateBase::BeginPlay()
|
||||
{
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AOLSModularGameStateBase::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
||||
|
||||
void AOLSModularGameState::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularGameState::BeginPlay()
|
||||
{
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AOLSModularGameState::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
||||
|
||||
void AOLSModularGameState::HandleMatchHasStarted()
|
||||
{
|
||||
Super::HandleMatchHasStarted();
|
||||
|
||||
TArray<UGameStateComponent*> modularComponents;
|
||||
GetComponents(modularComponents);
|
||||
for (UGameStateComponent* component : modularComponents)
|
||||
{
|
||||
component->HandleMatchHasStarted();
|
||||
}
|
||||
}
|
94
Source/ols/Private/ModularGameplayActors/OLSModularPawn.cpp
Normal file
94
Source/ols/Private/ModularGameplayActors/OLSModularPawn.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularPawn.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSModularPawn);
|
||||
|
||||
AOLSModularPawn::AOLSModularPawn(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
AbilitySystemComponent = CreateDefaultSubobject<UOLSAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
|
||||
AbilitySystemComponent->SetIsReplicated(true);
|
||||
|
||||
ReplicationMode = EGameplayEffectReplicationMode::Mixed;
|
||||
}
|
||||
|
||||
void AOLSModularPawn::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularPawn::BeginPlay()
|
||||
{
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AOLSModularPawn::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
||||
|
||||
void AOLSModularPawn::PostInitProperties()
|
||||
{
|
||||
Super::PostInitProperties();
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
OLS_LOG(LogOLSModularPawn, Verbose,
|
||||
TEXT("PostInitProperties for %s - Setting up ASC Replication Mode to: %d"), GET_UOBJECT_NAME(this),
|
||||
ReplicationMode);
|
||||
AbilitySystemComponent->SetReplicationMode(ReplicationMode);
|
||||
}
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* AOLSModularPawn::GetAbilitySystemComponent() const
|
||||
{
|
||||
return AbilitySystemComponent;
|
||||
}
|
||||
|
||||
void AOLSModularPawn::GetOwnedGameplayTags(FGameplayTagContainer& outTagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
FGameplayTagContainer ownedTags;
|
||||
AbilitySystemComponent->GetOwnedGameplayTags(ownedTags);
|
||||
outTagContainer = MoveTemp(ownedTags);
|
||||
}
|
||||
}
|
||||
|
||||
bool AOLSModularPawn::HasMatchingGameplayTag(FGameplayTag tagToCheck) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasMatchingGameplayTag(tagToCheck);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularPawn::HasAllMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasAllMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularPawn::HasAnyMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
return AbilitySystemComponent->HasAnyMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularPlayerController.h"
|
||||
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
#include "Components/ControllerComponent.h"
|
||||
|
||||
void AOLSModularPlayerController::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularPlayerController::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
||||
|
||||
void AOLSModularPlayerController::ReceivedPlayer()
|
||||
{
|
||||
// Player controllers always get assigned a player and can't do much until then
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(
|
||||
this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
Super::ReceivedPlayer();
|
||||
|
||||
TArray<UControllerComponent*> modularComponents;
|
||||
GetComponents(modularComponents);
|
||||
for (UControllerComponent* component : modularComponents)
|
||||
{
|
||||
component->ReceivedPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
void AOLSModularPlayerController::PlayerTick(float deltaTime)
|
||||
{
|
||||
Super::PlayerTick(deltaTime);
|
||||
|
||||
TArray<UControllerComponent*> modularComponents;
|
||||
GetComponents(modularComponents);
|
||||
for (UControllerComponent* component : modularComponents)
|
||||
{
|
||||
component->PlayerTick(deltaTime);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularPlayerState.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
#include "Components/PlayerStateComponent.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSModularPlayerState);
|
||||
|
||||
AOLSModularPlayerState::AOLSModularPlayerState(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
// Create ability system component, and set it to be explicitly replicated
|
||||
AbilitySystemComponent = CreateDefaultSubobject<UOLSAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
|
||||
AbilitySystemComponent->SetIsReplicated(true);
|
||||
|
||||
SetNetUpdateFrequency(100.f);
|
||||
}
|
||||
|
||||
void AOLSModularPlayerState::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularPlayerState::BeginPlay()
|
||||
{
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AOLSModularPlayerState::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void AOLSModularPlayerState::PostInitProperties()
|
||||
{
|
||||
Super::PostInitProperties();
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
OLS_LOG(LogOLSModularPlayerState, Verbose,
|
||||
TEXT("PostInitProperties for %s - Setting up ASC Replication Mode to: %d"), GET_UOBJECT_NAME(this),
|
||||
ReplicationMode);
|
||||
AbilitySystemComponent->SetReplicationMode(ReplicationMode);
|
||||
}
|
||||
}
|
||||
|
||||
void AOLSModularPlayerState::Reset()
|
||||
{
|
||||
Super::Reset();
|
||||
|
||||
TArray<UPlayerStateComponent*> modularComponents;
|
||||
GetComponents(modularComponents);
|
||||
for (UPlayerStateComponent* component : modularComponents)
|
||||
{
|
||||
component->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* AOLSModularPlayerState::GetAbilitySystemComponent() const
|
||||
{
|
||||
return AbilitySystemComponent;
|
||||
}
|
||||
|
||||
void AOLSModularPlayerState::CopyProperties(APlayerState* playerState)
|
||||
{
|
||||
Super::CopyProperties(playerState);
|
||||
|
||||
TInlineComponentArray<UPlayerStateComponent*> playerStateComponents;
|
||||
GetComponents(playerStateComponents);
|
||||
for (UPlayerStateComponent* sourceComponent : playerStateComponents)
|
||||
{
|
||||
if (UPlayerStateComponent* targetComp = Cast<UPlayerStateComponent>(static_cast<UObject*>(FindObjectWithOuter(playerState, sourceComponent->GetClass(), sourceComponent->GetFName()))))
|
||||
{
|
||||
sourceComponent->CopyProperties(targetComp);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "ModularGameplayActors/OLSModularPlayerStateCharacter.h"
|
||||
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "AbilitySystemGlobals.h"
|
||||
#include "OLSLog.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSModularPlayerStateCharacter);
|
||||
|
||||
AOLSModularPlayerStateCharacter::AOLSModularPlayerStateCharacter(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
SetMinNetUpdateFrequency(33.f);
|
||||
SetNetUpdateFrequency(66.f);
|
||||
|
||||
ReplicationMode = EGameplayEffectReplicationMode::Mixed;
|
||||
}
|
||||
|
||||
void AOLSModularPlayerStateCharacter::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
|
||||
}
|
||||
|
||||
void AOLSModularPlayerStateCharacter::BeginPlay()
|
||||
{
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AOLSModularPlayerStateCharacter::EndPlay(const EEndPlayReason::Type endPlayReason)
|
||||
{
|
||||
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
|
||||
Super::EndPlay(endPlayReason);
|
||||
}
|
||||
|
||||
void AOLSModularPlayerStateCharacter::PostInitProperties()
|
||||
{
|
||||
Super::PostInitProperties();
|
||||
|
||||
if (AbilitySystemComponent.IsValid())
|
||||
{
|
||||
OLS_LOG(LogOLSModularPlayerStateCharacter, Verbose,
|
||||
TEXT("PostInitProperties for %s - Setting up ASC Replication Mode to: %d"), GET_UOBJECT_NAME(this),
|
||||
ReplicationMode);
|
||||
AbilitySystemComponent->SetReplicationMode(ReplicationMode);
|
||||
}
|
||||
}
|
||||
|
||||
void AOLSModularPlayerStateCharacter::PossessedBy(AController* newController)
|
||||
{
|
||||
Super::PossessedBy(newController);
|
||||
|
||||
// For Player State ASC Pawns, initialize ASC on server in PossessedBy
|
||||
if (TObjectPtr<APlayerState> playerState = GetPlayerState())
|
||||
{
|
||||
AbilitySystemComponent = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(playerState);
|
||||
if (AbilitySystemComponent.IsValid())
|
||||
{
|
||||
AbilitySystemComponent->InitAbilityActorInfo(playerState, this);
|
||||
// TODO: Might consider sending GFC extension event in InitAbilityActorInfo instead
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
|
||||
// Required for ability input binding to update itself when ability are granted again in case of a respawn
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(playerState, UGameFrameworkComponentManager::NAME_GameActorReady);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AOLSModularPlayerStateCharacter::OnRep_PlayerState()
|
||||
{
|
||||
Super::OnRep_PlayerState();
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* AOLSModularPlayerStateCharacter::GetAbilitySystemComponent() const
|
||||
{
|
||||
if (AbilitySystemComponent.IsValid())
|
||||
{
|
||||
return AbilitySystemComponent.Get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AOLSModularPlayerStateCharacter::GetOwnedGameplayTags(FGameplayTagContainer& outTagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent.IsValid())
|
||||
{
|
||||
FGameplayTagContainer ownedTags;
|
||||
AbilitySystemComponent->GetOwnedGameplayTags(ownedTags);
|
||||
outTagContainer = MoveTemp(ownedTags);
|
||||
}
|
||||
}
|
||||
|
||||
bool AOLSModularPlayerStateCharacter::HasMatchingGameplayTag(FGameplayTag tagToCheck) const
|
||||
{
|
||||
if (AbilitySystemComponent.IsValid())
|
||||
{
|
||||
return AbilitySystemComponent->HasMatchingGameplayTag(tagToCheck);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularPlayerStateCharacter::HasAllMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent.IsValid())
|
||||
{
|
||||
return AbilitySystemComponent->HasAllMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AOLSModularPlayerStateCharacter::HasAnyMatchingGameplayTags(const FGameplayTagContainer& tagContainer) const
|
||||
{
|
||||
if (AbilitySystemComponent.IsValid())
|
||||
{
|
||||
return AbilitySystemComponent->HasAnyMatchingGameplayTags(tagContainer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
35
Source/ols/Private/OLSLog.cpp
Normal file
35
Source/ols/Private/OLSLog.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "OLSLog.h"
|
||||
|
||||
FString GetClientServerContextString(UObject* contextObject)
|
||||
{
|
||||
ENetRole role = ROLE_None;
|
||||
|
||||
if (AActor* actor = Cast<AActor>(contextObject))
|
||||
{
|
||||
role = actor->GetLocalRole();
|
||||
}
|
||||
else if (UActorComponent* component = Cast<UActorComponent>(contextObject))
|
||||
{
|
||||
role = component->GetOwnerRole();
|
||||
}
|
||||
|
||||
if (role != ROLE_None)
|
||||
{
|
||||
return (role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
|
||||
}
|
||||
else
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
if (GIsEditor)
|
||||
{
|
||||
extern ENGINE_API FString GPlayInEditorContextString;
|
||||
return GPlayInEditorContextString;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return TEXT("[]");
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Physics/OLSPhysicalMaterialWithTags.h"
|
||||
|
||||
UOLSPhysicalMaterialWithTags::UOLSPhysicalMaterialWithTags(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
}
|
4
Source/ols/Private/Player/OLSPlayerController.cpp
Normal file
4
Source/ols/Private/Player/OLSPlayerController.cpp
Normal file
@ -0,0 +1,4 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Player/OLSPlayerController.h"
|
146
Source/ols/Private/Player/OLSPlayerState.cpp
Normal file
146
Source/ols/Private/Player/OLSPlayerState.cpp
Normal file
@ -0,0 +1,146 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Player/OLSPlayerState.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "AbilitySystem/OLSAbilitySystemComponent.h"
|
||||
#include "AbilitySystem/Attributes/OLSCombatAttributeSet.h"
|
||||
#include "AbilitySystem/Attributes/OLSHealthAttributeSet.h"
|
||||
#include "Components/GameFrameworkComponentManager.h"
|
||||
#include "DataAssets/OLSAbilitySetDataAsset.h"
|
||||
#include "GameModes/OLSExperienceManagerComponent.h"
|
||||
#include "GameModes/OLSGameMode.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Player/OLSPlayerController.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSPlayerState)
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSPlayerState);
|
||||
|
||||
const FName AOLSPlayerState::NAME_OLSAbilityReady("OLSAbilitiesReady");
|
||||
|
||||
AOLSPlayerState::AOLSPlayerState(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
|
||||
{
|
||||
// Create attribute sets here.
|
||||
|
||||
AbilitySystemComponent = objectInitializer.CreateDefaultSubobject<UOLSAbilitySystemComponent>(this, TEXT("AbilitySystemComponent"));
|
||||
AbilitySystemComponent->SetIsReplicated(true);
|
||||
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
|
||||
|
||||
// These attribute sets will be detected by AbilitySystemComponent::InitializeComponent. Keeping a reference so that the sets don't get garbage collected before that.
|
||||
HealthSet = CreateDefaultSubobject<UOLSHealthAttributeSet>(TEXT("HealthSet"));
|
||||
CombatSet = CreateDefaultSubobject<UOLSCombatAttributeSet>(TEXT("CombatSet"));
|
||||
|
||||
// AbilitySystemComponent needs to be updated at a high frequency.
|
||||
SetNetUpdateFrequency(100.0f);
|
||||
}
|
||||
|
||||
void AOLSPlayerState::SetPawnData(const UOLSPawnDataAsset* pawnData)
|
||||
{
|
||||
check(pawnData);
|
||||
|
||||
if (!HasAuthority())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (PawnData)
|
||||
{
|
||||
OLS_LOG(LogOLSPlayerState, Error,
|
||||
TEXT("Trying to set PawnData [%s] on player state [%s] that already has valid PawnData [%s]."),
|
||||
GET_UOBJECT_NAME(pawnData), GET_UOBJECT_NAME(this), GET_UOBJECT_NAME(PawnData));
|
||||
return;
|
||||
}
|
||||
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, PawnData, this);
|
||||
|
||||
for (const UOLSAbilitySetDataAsset* abilityDataAsset : PawnData->AbilitySets)
|
||||
{
|
||||
if (abilityDataAsset)
|
||||
{
|
||||
abilityDataAsset->GiveToAbilitySystem(AbilitySystemComponent, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, NAME_OLSAbilityReady);
|
||||
|
||||
ForceNetUpdate();
|
||||
}
|
||||
|
||||
void AOLSPlayerState::AddStatTagStack(FGameplayTag tag, int32 stackCount)
|
||||
{
|
||||
StatTags.AddStack(tag, stackCount);
|
||||
}
|
||||
|
||||
void AOLSPlayerState::RemoveStatTagStack(FGameplayTag tag, int32 stackCount)
|
||||
{
|
||||
StatTags.RemoveStack(tag, stackCount);
|
||||
}
|
||||
|
||||
int32 AOLSPlayerState::GetStatTagStackCount(FGameplayTag tag) const
|
||||
{
|
||||
return StatTags.GetStackCount(tag);
|
||||
}
|
||||
|
||||
bool AOLSPlayerState::HasStatTag(FGameplayTag tag) const
|
||||
{
|
||||
return StatTags.ContainsTag(tag);
|
||||
}
|
||||
|
||||
void AOLSPlayerState::OnExperienceLoaded(const UOLSExperienceDefinitionDataAsset* currentExperience)
|
||||
{
|
||||
if (AOLSGameMode* gameMode = GetWorld()->GetAuthGameMode<AOLSGameMode>())
|
||||
{
|
||||
if (const UOLSPawnDataAsset* newPawnData = gameMode->GetPawnDataForController(GetOwningController()))
|
||||
{
|
||||
SetPawnData(newPawnData);
|
||||
}
|
||||
else
|
||||
{
|
||||
OLS_LOG(LogOLSPlayerState, Error, TEXT("Unable to find PawnData to initialize player state [%s]!"), GET_UOBJECT_NAME(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AOLSPlayerState::OnRep_PawnData()
|
||||
{
|
||||
}
|
||||
|
||||
void AOLSPlayerState::PostInitializeComponents()
|
||||
{
|
||||
Super::PostInitializeComponents();
|
||||
|
||||
check(AbilitySystemComponent);
|
||||
AbilitySystemComponent->InitAbilityActorInfo(this, GetPawn());
|
||||
|
||||
const TObjectPtr<UWorld> world = GetWorld();
|
||||
if (world && world->IsGameWorld() && world->GetNetMode() != NM_Client)
|
||||
{
|
||||
const TObjectPtr<AGameStateBase> gameState = GetWorld()->GetGameState();
|
||||
check(gameState);
|
||||
UOLSExperienceManagerComponent* ExperienceComponent = gameState->FindComponentByClass<UOLSExperienceManagerComponent>();
|
||||
check(ExperienceComponent);
|
||||
ExperienceComponent->CallOrRegister_OnExperienceLoaded(FOLSExperienceLoadedNativeDelegate::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded));
|
||||
}
|
||||
}
|
||||
|
||||
void AOLSPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
FDoRepLifetimeParams SharedParams;
|
||||
SharedParams.bIsPushBased = true;
|
||||
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, PawnData, SharedParams);
|
||||
}
|
||||
|
||||
AOLSPlayerController* AOLSPlayerState::GetOLSPlayerController() const
|
||||
{
|
||||
return Cast<AOLSPlayerController>(GetOwner());
|
||||
}
|
||||
|
||||
UOLSAbilitySystemComponent* AOLSPlayerState::GetOLSAbilitySystemComponent() const
|
||||
{
|
||||
return AbilitySystemComponent;
|
||||
}
|
297
Source/ols/Private/Systems/OLSAssetManager.cpp
Normal file
297
Source/ols/Private/Systems/OLSAssetManager.cpp
Normal file
@ -0,0 +1,297 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Systems/OLSAssetManager.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
#include "DataAssets/OLSGameDataAsset.h"
|
||||
#include "Misc/App.h"
|
||||
#include "Stats/StatsMisc.h"
|
||||
#include "Misc/ScopedSlowTask.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Systems/OLSAssetManagerStartupJob.h"
|
||||
#include "DataAssets/OLSPawnDataAsset.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSAssetManager)
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSAssetManager);
|
||||
|
||||
const FName FOLSBundles::Equipped("Equipped");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
static FAutoConsoleCommand CVarDumpLoadedAssets(
|
||||
TEXT("OLS.DumpLoadedAssets"),
|
||||
TEXT("Shows all assets that were loaded via the asset manager and are currently in memory."),
|
||||
FConsoleCommandDelegate::CreateStatic(UOLSAssetManager::DumpLoadedAssets)
|
||||
);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define STARTUP_JOB_WEIGHTED(jobFunc, jobWeight) StartupJobs.Add(FOLSAssetManagerStartupJob(#jobFunc, [this](const FOLSAssetManagerStartupJob& startupJob, TSharedPtr<FStreamableHandle>& loadHandle){jobFunc;}, jobWeight))
|
||||
#define STARTUP_JOB(jobFunc) STARTUP_JOB_WEIGHTED(jobFunc, 1.f)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
UOLSAssetManager::UOLSAssetManager()
|
||||
{
|
||||
DefaultPawnData = nullptr;
|
||||
}
|
||||
|
||||
UOLSAssetManager& UOLSAssetManager::Get()
|
||||
{
|
||||
check(GEngine);
|
||||
|
||||
if (UOLSAssetManager* Singleton = Cast<UOLSAssetManager>(GEngine->AssetManager))
|
||||
{
|
||||
return *Singleton;
|
||||
}
|
||||
|
||||
OLS_LOG_NO_WORLD(LogOLSAssetManager, Fatal, TEXT("Invalid AssetManagerClassName in DefaultEngine.ini. It must be set to LyraAssetManager!"));
|
||||
|
||||
// Fatal error above prevents this from being called.
|
||||
return *NewObject<UOLSAssetManager>();
|
||||
}
|
||||
|
||||
void UOLSAssetManager::DumpLoadedAssets()
|
||||
{
|
||||
OLS_LOG_NO_WORLD(LogOLSAssetManager, Log, TEXT("========== Start Dumping Loaded Assets =========="));
|
||||
|
||||
for (const UObject* loadedAsset : Get().LoadedAssets)
|
||||
{
|
||||
OLS_LOG_NO_WORLD(LogOLSAssetManager, Log, TEXT(" %s"), GET_UOBJECT_NAME(loadedAsset));
|
||||
}
|
||||
|
||||
OLS_LOG_NO_WORLD(LogOLSAssetManager, Log, TEXT("... %d assets in loaded pool"), Get().LoadedAssets.Num());
|
||||
OLS_LOG_NO_WORLD(LogOLSAssetManager, Log, TEXT("========== Finish Dumping Loaded Assets =========="));
|
||||
}
|
||||
|
||||
const UOLSGameDataAsset& UOLSAssetManager::GetGameData()
|
||||
{
|
||||
return GetOrLoadTypedGameData<UOLSGameDataAsset>(OLSGameDataPath);
|
||||
}
|
||||
|
||||
const UOLSPawnDataAsset* UOLSAssetManager::GetDefaultPawnData() const
|
||||
{
|
||||
return GetAsset(DefaultPawnData);
|
||||
}
|
||||
|
||||
template <typename GameDataClass>
|
||||
const GameDataClass& UOLSAssetManager::GetOrLoadTypedGameData(const TSoftObjectPtr<GameDataClass>& dataPath)
|
||||
{
|
||||
if (const TObjectPtr<UPrimaryDataAsset>* pResult = GameDataMap.Find(GameDataClass::StaticClass()))
|
||||
{
|
||||
return *CastChecked<GameDataClass>(*pResult);
|
||||
}
|
||||
|
||||
// Does a blocking load if needed
|
||||
return *CastChecked<const GameDataClass>(
|
||||
LoadGameDataOfClass(GameDataClass::StaticClass(), dataPath, GameDataClass::StaticClass()->GetFName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
UObject* UOLSAssetManager::SynchronousLoadAsset(const FSoftObjectPath& assetPath)
|
||||
{
|
||||
if (assetPath.IsValid())
|
||||
{
|
||||
TUniquePtr<FScopeLogTime> logTimePtr;
|
||||
|
||||
if (ShouldLogAssetLoads())
|
||||
{
|
||||
logTimePtr = MakeUnique<FScopeLogTime>(*FString::Printf(TEXT("Synchronously loaded asset [%s]"), *assetPath.ToString()), nullptr, FScopeLogTime::ScopeLog_Seconds);
|
||||
}
|
||||
|
||||
if (UAssetManager::IsInitialized())
|
||||
{
|
||||
return UAssetManager::GetStreamableManager().LoadSynchronous(assetPath, false);
|
||||
}
|
||||
|
||||
// Use LoadObject if asset manager isn't ready yet.
|
||||
return assetPath.TryLoad();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UOLSAssetManager::ShouldLogAssetLoads()
|
||||
{
|
||||
static bool shouldLogAssetLoads = FParse::Param(FCommandLine::Get(), TEXT("LogAssetLoads"));
|
||||
return shouldLogAssetLoads;
|
||||
}
|
||||
|
||||
void UOLSAssetManager::AddLoadedAsset(const UObject* Asset)
|
||||
{
|
||||
if (ensureAlways(Asset))
|
||||
{
|
||||
FScopeLock LoadedAssetsLock(&LoadedAssetsCritical);
|
||||
LoadedAssets.Add(Asset);
|
||||
}
|
||||
}
|
||||
|
||||
void UOLSAssetManager::StartInitialLoading()
|
||||
{
|
||||
SCOPED_BOOT_TIMING("UOLSAssetManager::StartInitialLoading");
|
||||
|
||||
// This does all of the scanning, need to do this now even if loads are deferred
|
||||
Super::StartInitialLoading();
|
||||
|
||||
STARTUP_JOB(InitializeGameplayCueManager());
|
||||
|
||||
{
|
||||
// Load base game data asset
|
||||
STARTUP_JOB_WEIGHTED(GetGameData(), 25.f);
|
||||
}
|
||||
|
||||
// Run all the queued up startup jobs
|
||||
DoAllStartupJobs();
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UOLSAssetManager::PreBeginPIE(bool shouldStartSimulate)
|
||||
{
|
||||
Super::PreBeginPIE(shouldStartSimulate);
|
||||
|
||||
{
|
||||
FScopedSlowTask slowTask(0, NSLOCTEXT("OLSEditor", "BeginLoadingPIEData", "Loading PIE Data"));
|
||||
const bool shouldShowCancelButton = false;
|
||||
const bool shouldAllowInPIE = true;
|
||||
slowTask.MakeDialog(shouldShowCancelButton, shouldAllowInPIE);
|
||||
|
||||
const UOLSGameDataAsset& LocalGameDataCommon = GetGameData();
|
||||
|
||||
// Intentionally after GetGameData to avoid counting GameData time in this timer
|
||||
SCOPE_LOG_TIME_IN_SECONDS(TEXT("PreBeginPIE asset preloading complete"), nullptr);
|
||||
|
||||
// You could add preloading of anything else needed for the experience we'll be using here
|
||||
// (e.g., by grabbing the default experience from the world settings + the experience override in developer settings)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
UPrimaryDataAsset* UOLSAssetManager::LoadGameDataOfClass(
|
||||
TSubclassOf<UPrimaryDataAsset> dataClass,
|
||||
const TSoftObjectPtr<UPrimaryDataAsset>& dataClassPath,
|
||||
FPrimaryAssetType primaryAssetType)
|
||||
{
|
||||
UPrimaryDataAsset* asset = nullptr;
|
||||
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading GameData Object"), STAT_GameData, STATGROUP_LoadTime);
|
||||
if (!dataClassPath.IsNull())
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
FScopedSlowTask slowTask(0, FText::Format(NSLOCTEXT("OLSEditor", "BeginLoadingGameDataTask", "Loading GameData {0}"), FText::FromName(dataClass->GetFName())));
|
||||
const bool shouldShowCancelButton = false;
|
||||
const bool shouldAllowInPIE = true;
|
||||
slowTask.MakeDialog(shouldShowCancelButton, shouldAllowInPIE);
|
||||
#endif
|
||||
|
||||
OLS_LOG(LogOLSAssetManager, Log, TEXT("Loading GameData: %%s ..."), *dataClassPath.ToString());
|
||||
SCOPE_LOG_TIME_IN_SECONDS(TEXT(" ... GameData loaded!"), nullptr);
|
||||
|
||||
// This can be called recursively in the editor because it is called on demand from PostLoad so force a sync load for primary asset and async load the rest in that case
|
||||
if (GIsEditor)
|
||||
{
|
||||
asset = dataClassPath.LoadSynchronous();
|
||||
LoadPrimaryAssetsWithType(primaryAssetType);
|
||||
}
|
||||
else
|
||||
{
|
||||
TSharedPtr<FStreamableHandle> handle = LoadPrimaryAssetsWithType(primaryAssetType);
|
||||
if (handle.IsValid())
|
||||
{
|
||||
handle->WaitUntilComplete(0.0f, false);
|
||||
|
||||
// This should always work
|
||||
asset = Cast<UPrimaryDataAsset>(handle->GetLoadedAsset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (asset)
|
||||
{
|
||||
GameDataMap.Add(dataClass, asset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// It is not acceptable to fail to load any GameData asset. It will result in soft failures that are hard to diagnose.
|
||||
OLS_LOG(LogOLSAssetManager, Fatal,
|
||||
TEXT(
|
||||
"Failed to load GameData asset at %s. Type %s. This is not recoverable and likely means you do not have the correct data to run %s."
|
||||
), *dataClassPath.ToString(), *primaryAssetType.ToString(), FApp::GetProjectName());
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
void UOLSAssetManager::DoAllStartupJobs()
|
||||
{
|
||||
SCOPED_BOOT_TIMING("UOLSAssetManager::DoAllStartupJobs");
|
||||
const double allStartupJobsStartTime = FPlatformTime::Seconds();
|
||||
|
||||
if (IsRunningDedicatedServer())
|
||||
{
|
||||
// No need for periodic progress updates, just run the jobs
|
||||
for (const FOLSAssetManagerStartupJob& startupJob : StartupJobs)
|
||||
{
|
||||
startupJob.DoJob();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (StartupJobs.Num() > 0)
|
||||
{
|
||||
float totalJobValue = 0.0f;
|
||||
for (const FOLSAssetManagerStartupJob& startupJob : StartupJobs)
|
||||
{
|
||||
totalJobValue += startupJob.JobWeight;
|
||||
}
|
||||
|
||||
float accumulatedJobValue = 0.0f;
|
||||
for (FOLSAssetManagerStartupJob& startupJob : StartupJobs)
|
||||
{
|
||||
const float jobValue = startupJob.JobWeight;
|
||||
startupJob.SubstepProgressDelegate.BindLambda([This = this, accumulatedJobValue, jobValue, totalJobValue](float NewProgress)
|
||||
{
|
||||
const float SubstepAdjustment = FMath::Clamp(NewProgress, 0.0f, 1.0f) * jobValue;
|
||||
const float OverallPercentWithSubstep = (accumulatedJobValue + SubstepAdjustment) / totalJobValue;
|
||||
|
||||
This->UpdateInitialGameContentLoadPercent(OverallPercentWithSubstep);
|
||||
});
|
||||
|
||||
startupJob.DoJob();
|
||||
|
||||
startupJob.SubstepProgressDelegate.Unbind();
|
||||
|
||||
accumulatedJobValue += jobValue;
|
||||
|
||||
UpdateInitialGameContentLoadPercent(accumulatedJobValue / totalJobValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateInitialGameContentLoadPercent(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
StartupJobs.Empty();
|
||||
|
||||
OLS_LOG(LogOLSAssetManager, Display, TEXT("All startup jobs took %.2f seconds to complete"),
|
||||
FPlatformTime::Seconds() - allStartupJobsStartTime);
|
||||
}
|
||||
|
||||
void UOLSAssetManager::InitializeGameplayCueManager()
|
||||
{
|
||||
SCOPED_BOOT_TIMING("UOLSAssetManager::InitializeGameplayCueManager");
|
||||
|
||||
// @TODO: Implement this after implementing ULyraGameplayCueManager.
|
||||
// ULyraGameplayCueManager* GCM = ULyraGameplayCueManager::Get();
|
||||
// check(GCM);
|
||||
// GCM->LoadAlwaysLoadedCues();
|
||||
}
|
||||
|
||||
void UOLSAssetManager::UpdateInitialGameContentLoadPercent(float gameContentPercent)
|
||||
{
|
||||
// Lyra left this comment.
|
||||
// Could route this to the early startup loading screen.
|
||||
}
|
49
Source/ols/Private/Systems/OLSAssetManagerStartupJob.cpp
Normal file
49
Source/ols/Private/Systems/OLSAssetManagerStartupJob.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Systems/OLSAssetManagerStartupJob.h"
|
||||
|
||||
#include "OLSLog.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOLSAssetManagerStartupJob);
|
||||
|
||||
TSharedPtr<FStreamableHandle> FOLSAssetManagerStartupJob::DoJob() const
|
||||
{
|
||||
const double jobStartTime = FPlatformTime::Seconds();
|
||||
|
||||
TSharedPtr<FStreamableHandle> handle;
|
||||
OLS_LOG_NO_WORLD(LogOLSAssetManagerStartupJob, Display, TEXT("Startup job \"%s\" starting"), *JobName);
|
||||
JobFunc(*this, handle);
|
||||
|
||||
if (handle.IsValid())
|
||||
{
|
||||
handle->BindUpdateDelegate(FStreamableUpdateDelegate::CreateRaw(this, &FOLSAssetManagerStartupJob::UpdateSubstepProgressFromStreamable));
|
||||
handle->WaitUntilComplete(0.0f, false);
|
||||
handle->BindUpdateDelegate(FStreamableUpdateDelegate());
|
||||
}
|
||||
|
||||
OLS_LOG_NO_WORLD(LogOLSAssetManagerStartupJob, Display, TEXT("Startup job \"%s\" took %.2f seconds to complete"),
|
||||
*JobName, FPlatformTime::Seconds() - jobStartTime);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void FOLSAssetManagerStartupJob::UpdateSubstepProgress(float newProgress) const
|
||||
{
|
||||
SubstepProgressDelegate.ExecuteIfBound(newProgress);
|
||||
}
|
||||
|
||||
void FOLSAssetManagerStartupJob::UpdateSubstepProgressFromStreamable(
|
||||
TSharedRef<FStreamableHandle> streamableHandle) const
|
||||
{
|
||||
if (SubstepProgressDelegate.IsBound())
|
||||
{
|
||||
// StreamableHandle::GetProgress traverses() a large graph and is quite expensive
|
||||
double now = FPlatformTime::Seconds();
|
||||
if (LastUpdate - now > 1.0 / 60)
|
||||
{
|
||||
SubstepProgressDelegate.Execute(streamableHandle->GetProgress());
|
||||
LastUpdate = now;
|
||||
}
|
||||
}
|
||||
}
|
26
Source/ols/Private/Systems/OLSGameSession.cpp
Normal file
26
Source/ols/Private/Systems/OLSGameSession.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Systems/OLSGameSession.h"
|
||||
|
||||
|
||||
// Sets default values
|
||||
AOLSGameSession::AOLSGameSession()
|
||||
{
|
||||
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
}
|
||||
|
||||
// Called when the game starts or when spawned
|
||||
void AOLSGameSession::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void AOLSGameSession::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
}
|
||||
|
104
Source/ols/Private/Systems/OLSGameplayTagStack.cpp
Normal file
104
Source/ols/Private/Systems/OLSGameplayTagStack.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "Systems/OLSGameplayTagStack.h"
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// FGameplayTagStack
|
||||
|
||||
FString FOLSGameplayTagStack::GetDebugString() const
|
||||
{
|
||||
return FString::Printf(TEXT("%sx%d"), *Tag.ToString(), StackCount);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// FGameplayTagStackContainer
|
||||
|
||||
void FOLSGameplayTagStackContainer::AddStack(FGameplayTag tag, int32 stackCount)
|
||||
{
|
||||
if (!tag.IsValid())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to AddStack"), ELogVerbosity::Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stackCount > 0)
|
||||
{
|
||||
for (FOLSGameplayTagStack& stack : Stacks)
|
||||
{
|
||||
if (stack.Tag == tag)
|
||||
{
|
||||
const int32 newCount = stack.StackCount + stackCount;
|
||||
stack.StackCount = newCount;
|
||||
TagToCountMap[tag] = newCount;
|
||||
MarkItemDirty(stack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FOLSGameplayTagStack& newStack = Stacks.Emplace_GetRef(tag, stackCount);
|
||||
MarkItemDirty(newStack);
|
||||
TagToCountMap.Add(tag, stackCount);
|
||||
}
|
||||
}
|
||||
|
||||
void FOLSGameplayTagStackContainer::RemoveStack(FGameplayTag tag, int32 stackCount)
|
||||
{
|
||||
if (!tag.IsValid())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to RemoveStack"), ELogVerbosity::Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
//@TODO: Should we error if you try to remove a stack that doesn't exist or has a smaller count?
|
||||
if (stackCount > 0)
|
||||
{
|
||||
for (auto it = Stacks.CreateIterator(); it; ++it)
|
||||
{
|
||||
FOLSGameplayTagStack& stack = *it;
|
||||
if (stack.Tag == tag)
|
||||
{
|
||||
if (stack.StackCount <= stackCount)
|
||||
{
|
||||
it.RemoveCurrent();
|
||||
TagToCountMap.Remove(tag);
|
||||
MarkArrayDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32 newCount = stack.StackCount - stackCount;
|
||||
stack.StackCount = newCount;
|
||||
TagToCountMap[tag] = newCount;
|
||||
MarkItemDirty(stack);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FOLSGameplayTagStackContainer::PreReplicatedRemove(const TArrayView<int32> removedIndices, int32 finalSize)
|
||||
{
|
||||
for (int32 index : removedIndices)
|
||||
{
|
||||
const FGameplayTag tag = Stacks[index].Tag;
|
||||
TagToCountMap.Remove(tag);
|
||||
}
|
||||
}
|
||||
|
||||
void FOLSGameplayTagStackContainer::PostReplicatedAdd(const TArrayView<int32> addedIndices, int32 finalSize)
|
||||
{
|
||||
for (int32 index : addedIndices)
|
||||
{
|
||||
const FOLSGameplayTagStack& stack = Stacks[index];
|
||||
TagToCountMap.Add(stack.Tag, stack.StackCount);
|
||||
}
|
||||
}
|
||||
|
||||
void FOLSGameplayTagStackContainer::PostReplicatedChange(const TArrayView<int32> changedIndices, int32 finalSize)
|
||||
{
|
||||
for (int32 index : changedIndices)
|
||||
{
|
||||
const FOLSGameplayTagStack& stack = Stacks[index];
|
||||
TagToCountMap[stack.Tag] = stack.StackCount;
|
||||
}
|
||||
}
|
4
Source/ols/Private/UI/OLSHUD.cpp
Normal file
4
Source/ols/Private/UI/OLSHUD.cpp
Normal file
@ -0,0 +1,4 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
|
||||
#include "UI/OLSHUD.h"
|
61
Source/ols/Public/AbilitySystem/Abilities/OLSAbilityCost.h
Normal file
61
Source/ols/Public/AbilitySystem/Abilities/OLSAbilityCost.h
Normal file
@ -0,0 +1,61 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trad`emark is strictly prohibited and may result in legal action.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayAbilitySpec.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Abilities/GameplayAbilityTypes.h"
|
||||
#include "OLSAbilityCost.generated.h"
|
||||
|
||||
|
||||
/**
|
||||
* UOLSAbilityCost
|
||||
*
|
||||
* Base class for costs that a LyraGameplayAbility has (e.g., ammo or charges)
|
||||
*/
|
||||
UCLASS(DefaultToInstanced, EditInlineNew, Abstract)
|
||||
class OLS_API UOLSAbilityCost : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UOLSAbilityCost();
|
||||
|
||||
/**
|
||||
* Checks if we can afford this cost.
|
||||
*
|
||||
* A failure reason tag can be added to OptionalRelevantTags (if non-null), which can be queried
|
||||
* elsewhere to determine how to provide user feedback (e.g., a clicking noise if a weapon is out of ammo)
|
||||
*
|
||||
* Ability and ActorInfo are guaranteed to be non-null on entry, but OptionalRelevantTags can be nullptr.
|
||||
*
|
||||
* @return true if we can pay for the ability, false otherwise.
|
||||
*/
|
||||
virtual bool CheckCost(const class UOLSGameplayAbility* ability,
|
||||
const FGameplayAbilitySpecHandle handle,
|
||||
const FGameplayAbilityActorInfo* actorInfo,
|
||||
FGameplayTagContainer* optionalRelevantTags) const;
|
||||
|
||||
/**
|
||||
* Applies the ability's cost to the target
|
||||
*
|
||||
* Notes:
|
||||
* - Your implementation don't need to check ShouldOnlyApplyCostOnHit(), the caller does that for you.
|
||||
* - Ability and ActorInfo are guaranteed to be non-null on entry.
|
||||
*/
|
||||
virtual void ApplyCost(const class UOLSGameplayAbility* ability,
|
||||
const FGameplayAbilitySpecHandle handle,
|
||||
const FGameplayAbilityActorInfo* actorInfo,
|
||||
const FGameplayAbilityActivationInfo activationInfo);
|
||||
|
||||
/** If true, this cost should only be applied if this ability hits successfully */
|
||||
bool ShouldOnlyApplyCostOnHit() const;
|
||||
|
||||
protected:
|
||||
|
||||
/** If true, this cost should only be applied if this ability hits successfully */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OLS|Costs")
|
||||
uint8 bShouldOnlyApplyCostOnHit : 1 = false;
|
||||
};
|
192
Source/ols/Public/AbilitySystem/Abilities/OLSGameplayAbility.h
Normal file
192
Source/ols/Public/AbilitySystem/Abilities/OLSGameplayAbility.h
Normal file
@ -0,0 +1,192 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Abilities/GameplayAbility.h"
|
||||
#include "OLSGameplayAbility.generated.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogOLSGameplayAbility, Verbose, All);
|
||||
|
||||
/**
|
||||
* ELyraAbilityActivationPolicy
|
||||
*
|
||||
* Defines how an ability is meant to activate.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EOLSAbilityActivationPolicy : uint8
|
||||
{
|
||||
// Try to activate the ability when the input is triggered.
|
||||
OnInputTriggered,
|
||||
|
||||
// Continually try to activate the ability while the input is active.
|
||||
WhileInputActive,
|
||||
|
||||
// Try to activate the ability when an avatar is assigned.
|
||||
OnSpawn
|
||||
};
|
||||
|
||||
/**
|
||||
* ELyraAbilityActivationGroup
|
||||
*
|
||||
* Defines how an ability activates in relation to other abilities.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EOLSAbilityActivationGroup : uint8
|
||||
{
|
||||
// Ability runs independently of all other abilities.
|
||||
Independent,
|
||||
|
||||
// Ability is canceled and replaced by other exclusive abilities.
|
||||
Exclusive_Replaceable,
|
||||
|
||||
// Ability blocks all other exclusive abilities from activating.
|
||||
Exclusive_Blocking,
|
||||
|
||||
MAX UMETA(Hidden)
|
||||
};
|
||||
|
||||
/** Failure reason that can be used to play an animation montage when a failure occurs */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FOLSAbilityMontageFailureMessage
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Player controller that failed to activate the ability, if the AbilitySystemComponent was player owned
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
TObjectPtr<APlayerController> PlayerController = nullptr;
|
||||
|
||||
// Avatar actor that failed to activate the ability
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
TObjectPtr<AActor> AvatarActor = nullptr;
|
||||
|
||||
// All the reasons why this ability has failed
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
FGameplayTagContainer FailureTags;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
TObjectPtr<UAnimMontage> FailureMontage = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* UOLSGameplayAbility
|
||||
*
|
||||
* The base gameplay ability class used by this project.
|
||||
*/
|
||||
UCLASS(Abstract, HideCategories = Input, Meta = (ShortTooltip = "The base gameplay ability class used by this project."))
|
||||
class OLS_API UOLSGameplayAbility : public UGameplayAbility
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UOLSGameplayAbility(const FObjectInitializer& objectInitializer);
|
||||
|
||||
protected:
|
||||
|
||||
//~UGameplayAbility interface
|
||||
virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle handle, const FGameplayAbilityActorInfo* actorInfo, const FGameplayTagContainer* sourceTags, const FGameplayTagContainer* targetTags, FGameplayTagContainer* optionalRelevantTags) const override;
|
||||
virtual void SetCanBeCanceled(bool canBeCanceled) override;
|
||||
virtual void OnGiveAbility(const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilitySpec& spec) override;
|
||||
virtual void OnRemoveAbility(const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilitySpec& spec) override;
|
||||
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
|
||||
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
|
||||
virtual bool CheckCost(const FGameplayAbilitySpecHandle handle, const FGameplayAbilityActorInfo* actorInfo, OUT FGameplayTagContainer* optionalRelevantTags = nullptr) const override;
|
||||
virtual void ApplyCost(const FGameplayAbilitySpecHandle handle, const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilityActivationInfo activationInfo) const override;
|
||||
virtual FGameplayEffectContextHandle MakeEffectContext(const FGameplayAbilitySpecHandle handle, const FGameplayAbilityActorInfo* actorInfo) const override;
|
||||
virtual void ApplyAbilityTagsToGameplayEffectSpec(FGameplayEffectSpec& spec, FGameplayAbilitySpec* abilitySpec) const override;
|
||||
virtual bool DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& abilitySystemComponent, const FGameplayTagContainer* sourceTags = nullptr, const FGameplayTagContainer* targetTags = nullptr, OUT FGameplayTagContainer* optionalRelevantTags = nullptr) const override;
|
||||
//~End of UGameplayAbility interface
|
||||
|
||||
public:
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OLS|Ability")
|
||||
class UOLSAbilitySystemComponent* GetOLSAbilitySystemComponentFromActorInfo() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "OLS|Ability")
|
||||
AController* GetControllerFromActorInfo() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Lyra|Ability")
|
||||
class UOLSHeroComponent* GetHeroComponentFromActorInfo() const;
|
||||
|
||||
// Returns true if the requested activation group is a valid transition.
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = "OLS|Ability", Meta = (ExpandBoolAsExecs = "ReturnValue"))
|
||||
bool CanChangeActivationGroup(EOLSAbilityActivationGroup newGroup) const;
|
||||
|
||||
// Tries to change the activation group. Returns true if it successfully changed.
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = "OLS|Ability", Meta = (ExpandBoolAsExecs = "ReturnValue"))
|
||||
bool ChangeActivationGroup(EOLSAbilityActivationGroup newGroup);
|
||||
|
||||
// Sets the ability's camera mode.
|
||||
UFUNCTION(BlueprintCallable, Category = "Lyra|Ability")
|
||||
void SetCameraMode(TSubclassOf<class UOLSCameraMode> cameraMode);
|
||||
|
||||
// Clears the ability's camera mode. Automatically called if needed when the ability ends.
|
||||
UFUNCTION(BlueprintCallable, Category = "Lyra|Ability")
|
||||
void ClearCameraMode();
|
||||
|
||||
public:
|
||||
|
||||
EOLSAbilityActivationPolicy GetActivationPolicy() const;
|
||||
EOLSAbilityActivationGroup GetActivationGroup() const;
|
||||
|
||||
void TryActivateAbilityOnSpawn(const FGameplayAbilityActorInfo* actorInfo, const FGameplayAbilitySpec& spec) const;
|
||||
|
||||
void OnAbilityFailedToActivate(const FGameplayTagContainer& failedReason) const;
|
||||
|
||||
protected:
|
||||
virtual void OnPawnAvatarSet();
|
||||
|
||||
virtual void GetAbilitySource(FGameplayAbilitySpecHandle handle, const FGameplayAbilityActorInfo* actorInfo,
|
||||
float& outSourceLevel, const class IOLSAbilitySourceInterface*& outAbilitySource,
|
||||
AActor*& outEffectCauser) const;
|
||||
|
||||
// Called when the ability fails to activate
|
||||
virtual void NativeOnAbilityFailedToActivate(const FGameplayTagContainer& failedReason) const;
|
||||
|
||||
// Called when the ability fails to activate
|
||||
UFUNCTION(BlueprintImplementableEvent, DisplayName = "OnAbilityFailedToActivate")
|
||||
void K2_OnAbilityFailedToActivate(const FGameplayTagContainer& FailedReason) const;
|
||||
|
||||
/** Called when this ability is granted to the ability system component. */
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = Ability, DisplayName = "OnAbilityAdded")
|
||||
void K2_OnAbilityAdded();
|
||||
|
||||
/** Called when this ability is removed from the ability system component. */
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = Ability, DisplayName = "OnAbilityRemoved")
|
||||
void K2_OnAbilityRemoved();
|
||||
|
||||
/** Called when the ability system is initialized with a pawn avatar. */
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = Ability, DisplayName = "OnPawnAvatarSet")
|
||||
void K2_OnPawnAvatarSet();
|
||||
|
||||
protected:
|
||||
|
||||
// Defines how this ability is meant to activate.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "OLS|Ability Activation")
|
||||
EOLSAbilityActivationPolicy ActivationPolicy;
|
||||
|
||||
// Defines the relationship between this ability activating and other abilities activating.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "OLS|Ability Activation")
|
||||
EOLSAbilityActivationGroup ActivationGroup;
|
||||
|
||||
// Additional costs that must be paid to activate this ability
|
||||
UPROPERTY(EditDefaultsOnly, Instanced, Category = Costs)
|
||||
TArray<TObjectPtr<class UOLSAbilityCost>> AdditionalCosts;
|
||||
|
||||
// Map of failure tags to simple error messages
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Advanced")
|
||||
TMap<FGameplayTag, FText> FailureTagToUserFacingMessages;
|
||||
|
||||
// Map of failure tags to anim montages that should be played with them
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Advanced")
|
||||
TMap<FGameplayTag, TObjectPtr<UAnimMontage>> FailureTagToAnimMontage;
|
||||
|
||||
// If true, extra information should be logged when this ability is canceled. This is temporary, used for tracking a bug.
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Advanced")
|
||||
uint8 bShouldLogCancellation : 1 = false;
|
||||
|
||||
// Current camera mode set by the ability.
|
||||
TSubclassOf<class UOLSCameraMode> ActiveCameraMode;
|
||||
};
|
@ -0,0 +1,65 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AttributeSet.h"
|
||||
#include "OLSAttributeSetBase.generated.h"
|
||||
|
||||
/**
|
||||
* This macro defines a set of helper functions for accessing and initializing attributes.
|
||||
*
|
||||
* The following example of the macro:
|
||||
* ATTRIBUTE_ACCESSORS(ULyraHealthSet, Health)
|
||||
* will create the following functions:
|
||||
* static FGameplayAttribute GetHealthAttribute();
|
||||
* float GetHealth() const;
|
||||
* void SetHealth(float NewVal);
|
||||
* void InitHealth(float NewVal);
|
||||
*/
|
||||
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
|
||||
|
||||
/**
|
||||
* Delegate used to broadcast attribute events, some of these parameters may be null on clients:
|
||||
* @param EffectInstigator The original instigating actor for this event
|
||||
* @param EffectCauser The physical actor that caused the change
|
||||
* @param EffectSpec The full effect spec for this change
|
||||
* @param EffectMagnitude The raw magnitude, this is before clamping
|
||||
* @param OldValue The value of the attribute before it was changed
|
||||
* @param NewValue The value after it was changed
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_SixParams(
|
||||
FOLSAttributeEventNativeDelegate,
|
||||
class AActor* /*effectInstigator*/,
|
||||
class AActor* /*effectCauser*/,
|
||||
const struct FGameplayEffectSpec* /*effectSpec*/,
|
||||
float /*effectMagnitude*/,
|
||||
float /*oldValue*/,
|
||||
float /*newValue*/);
|
||||
|
||||
/**
|
||||
* UOLSAttributeSet
|
||||
*
|
||||
* Base attribute set class for the project.
|
||||
*/
|
||||
UCLASS()
|
||||
class OLS_API UOLSAttributeSetBase : public UAttributeSet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UOLSAttributeSetBase();
|
||||
|
||||
//~ Begin UObject interface
|
||||
class UWorld* GetWorld() const override;
|
||||
//~ End UObject interface
|
||||
|
||||
public:
|
||||
|
||||
class UOLSAbilitySystemComponent* GetOLSAbilitySystemComponent() const;
|
||||
};
|
@ -0,0 +1,49 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "OLSAttributeSetBase.h"
|
||||
#include "OLSCombatAttributeSet.generated.h"
|
||||
|
||||
/**
|
||||
* UOLSCombatAttributeSet
|
||||
*
|
||||
* Class that defines attributes that are necessary for applying damage or healing.
|
||||
* Attribute examples include: damage, healing, attack power, and shield penetrations.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class OLS_API UOLSCombatAttributeSet : public UOLSAttributeSetBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UOLSCombatAttributeSet();
|
||||
|
||||
//~ Begin UObject interface.
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
//~ End UObject interface.
|
||||
|
||||
ATTRIBUTE_ACCESSORS(ThisClass, BaseDamage);
|
||||
ATTRIBUTE_ACCESSORS(ThisClass, BaseHeal);
|
||||
|
||||
protected:
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_BaseDamage(const FGameplayAttributeData& oldValue);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_BaseHeal(const FGameplayAttributeData& oldValue);
|
||||
|
||||
private:
|
||||
|
||||
// The base amount of damage to apply in the damage execution.
|
||||
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_BaseDamage, Category = "OLS|Combat", Meta = (AllowPrivateAccess = true))
|
||||
FGameplayAttributeData BaseDamage;
|
||||
|
||||
// The base amount of healing to apply in the heal execution.
|
||||
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_BaseHeal, Category = "OLS|Combat", Meta = (AllowPrivateAccess = true))
|
||||
FGameplayAttributeData BaseHeal;
|
||||
};
|
@ -0,0 +1,96 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "NativeGameplayTags.h"
|
||||
#include "OLSAttributeSetBase.h"
|
||||
#include "OLSHealthAttributeSet.generated.h"
|
||||
|
||||
OLS_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Gameplay_Damage);
|
||||
OLS_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Gameplay_DamageImmunity);
|
||||
OLS_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Gameplay_DamageSelfDestruct);
|
||||
OLS_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Gameplay_FellOutOfWorld);
|
||||
OLS_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_OLS_Damage_Message);
|
||||
|
||||
/**
|
||||
* UOLSHealthAttributeSet
|
||||
*
|
||||
* Class that defines attributes that are necessary for taking damage.
|
||||
* Attribute examples include: health, shields, and resistances.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class OLS_API UOLSHealthAttributeSet : public UOLSAttributeSetBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UOLSHealthAttributeSet();
|
||||
|
||||
//~ Begin UObject interface.
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
//~ End UObject interface.
|
||||
|
||||
ATTRIBUTE_ACCESSORS(ThisClass, Health);
|
||||
ATTRIBUTE_ACCESSORS(ThisClass, MaxHealth);
|
||||
ATTRIBUTE_ACCESSORS(ThisClass, Healing);
|
||||
ATTRIBUTE_ACCESSORS(ThisClass, Damage);
|
||||
|
||||
// Delegate when health changes due to damage/healing, some information may be missing on the client
|
||||
mutable FOLSAttributeEventNativeDelegate OnHealthChanged;
|
||||
|
||||
// Delegate when max health changes
|
||||
mutable FOLSAttributeEventNativeDelegate OnMaxHealthChanged;
|
||||
|
||||
// Delegate to broadcast when the health attribute reaches zero
|
||||
mutable FOLSAttributeEventNativeDelegate OnOutOfHealth;
|
||||
|
||||
protected:
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_Health(const FGameplayAttributeData& oldValue);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_MaxHealth(const FGameplayAttributeData& OldValue);
|
||||
|
||||
virtual bool PreGameplayEffectExecute(FGameplayEffectModCallbackData& data) override;
|
||||
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
|
||||
|
||||
virtual void PreAttributeBaseChange(const FGameplayAttribute& attribute, float& newValue) const override;
|
||||
virtual void PreAttributeChange(const FGameplayAttribute& attribute, float& newValue) override;
|
||||
virtual void PostAttributeChange(const FGameplayAttribute& attribute, float oldValue, float newValue) override;
|
||||
|
||||
void ClampAttribute(const FGameplayAttribute& attribute, float& outNewValue) const;
|
||||
|
||||
private:
|
||||
|
||||
|
||||
// The current health attribute. The health will be capped by the max health attribute. Health is hidden from modifiers so only executions can modify it.
|
||||
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health, Category = "OLS|Health", Meta = (HideFromModifiers, AllowPrivateAccess = true))
|
||||
FGameplayAttributeData Health;
|
||||
|
||||
// The current max health attribute. Max health is an attribute since gameplay effects can modify it.
|
||||
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MaxHealth, Category = "OLS|Health", Meta = (AllowPrivateAccess = true))
|
||||
FGameplayAttributeData MaxHealth;
|
||||
|
||||
// Used to track when the health reaches 0.
|
||||
uint8 bIsOutOfHealth : 1 = false;
|
||||
|
||||
// Store the health before any changes
|
||||
float MaxHealthBeforeAttributeChange = 0.f;
|
||||
float HealthBeforeAttributeChange = 0.f
|
||||
;
|
||||
// -------------------------------------------------------------------
|
||||
// Meta Attribute (please keep attributes that aren't 'stateful' below
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Incoming healing. This is mapped directly to +Health
|
||||
UPROPERTY(BlueprintReadOnly, Category="Lyra|Health", Meta=(AllowPrivateAccess=true))
|
||||
FGameplayAttributeData Healing;
|
||||
|
||||
// Incoming damage. This is mapped directly to -Health
|
||||
UPROPERTY(BlueprintReadOnly, Category="Lyra|Health", Meta=(HideFromModifiers, AllowPrivateAccess=true))
|
||||
FGameplayAttributeData Damage;
|
||||
};
|
49
Source/ols/Public/AbilitySystem/Effects/OLSGameplayEffect.h
Normal file
49
Source/ols/Public/AbilitySystem/Effects/OLSGameplayEffect.h
Normal file
@ -0,0 +1,49 @@
|
||||
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffect.h"
|
||||
#include "OLSGameplayEffect.generated.h"
|
||||
|
||||
USTRUCT()
|
||||
struct OLS_API FOLSDynamicGameplayEffectData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
// The magnitude or efficacy of the gameplay effect.
|
||||
FGameplayEffectModifierMagnitude EffectMagnitude = FScalableFloat(1.f);
|
||||
|
||||
// The attribute to which the gameplay effect will be applied.
|
||||
FGameplayAttribute AffectedAttribute = {};
|
||||
|
||||
// The gameplay effect's name.
|
||||
FName EffectName = FName(TEXT("EffectName"));
|
||||
|
||||
// A pointer to the ability system component to which the gameplay effect will be applied.
|
||||
TWeakObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
|
||||
|
||||
// The manner in which the gameplay effect will be applied (additive, multiplicative, etc.).
|
||||
TEnumAsByte<EGameplayModOp::Type> EffectModifierOpType = EGameplayModOp::Additive;
|
||||
|
||||
// The level of the gameplay effect, which may in part determine its efficacy.
|
||||
float EffectLevel = 1.f;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS()
|
||||
class OLS_API UOLSGameplayEffect : public UGameplayEffect
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Applies a dynamic gameplay effect at runtime, and in accordance with the passed FTowersDynamicGameplayEffectData.
|
||||
* @param data - struct containing all of the relevant data by which the dynamic gameplay effect may be applied.
|
||||
*/
|
||||
static void ApplyDynamicGameplayEffect(const FOLSDynamicGameplayEffectData& data);
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user