Compare commits

...

26 Commits

Author SHA1 Message Date
57b53b9c0c Implemented OLSCameraMode.
Addressed @TODOs related to custom logs and OLSCameraMode
2025-01-20 14:08:07 -07:00
c0b033fb22 Implemented OLSGameplayAbility.
Implemented OLSCollisionChannels.
Implemented OLSGameplayEffectContext.
Implemented OLSPhysicalMaterialWithTags.
2025-01-20 11:59:57 -07:00
2c4a71b343 Added GameFeatureActions, HeroComponent, and AbilityCost. 2025-01-17 15:14:51 -07:00
51306c57a9 Implemented OLSPawnExtensionComponent.
Added some functionalities for OLSAbilitySystemComponent
2025-01-16 16:04:14 -07:00
e0545d6323 Cleared all TODOs related to custom logs 2025-01-16 14:24:05 -07:00
6c053954c8 Cleared some todos 2025-01-16 13:54:02 -07:00
8917782a44 Cleared TODOs in OLSAssetManagerStartupJob 2025-01-16 12:19:53 -07:00
7a9b1b82b2 Renamed OLSAbilitySetPrimaryDataAsset to OLSAbilitySetDataAsset.
Cleared some TODOs related to custom log.
2025-01-16 12:05:19 -07:00
0332ef5789 Added OLSLog 2025-01-16 11:09:31 -07:00
fe68d8be31 Renamed local variable 2025-01-15 15:53:15 -07:00
57be5728b7 Implemented OLSHealthComponent and OLSGameDataAsset. 2025-01-15 15:30:52 -07:00
1c148494ff Added OLSNotificationMessage 2025-01-15 14:31:46 -07:00
bac7ff7498 Added VerMessageHelper 2025-01-15 12:23:11 -07:00
fe8ee2e867 Implemented CombatAttribute 2025-01-15 11:00:34 -07:00
a43fc18456 Implemented HealthAttributeSet and VerbMessage 2025-01-15 10:00:06 -07:00
b9b7e5344b Implemented some classes to unlock OLSGameMode's constructor 2025-01-14 13:35:23 -07:00
7415b74e8f Added CommonUser Plugins.
Implemented GameplayStackTags
2025-01-13 15:36:08 -07:00
313cf68f61 Merge remote-tracking branch 'origin/ability-system' into ability-system 2025-01-09 16:06:12 -07:00
c1fd9b2c95 Renamed files 2025-01-09 16:05:57 -07:00
01eda48d38 Merge remote-tracking branch 'origin/ability-system' into ability-system 2025-01-09 11:27:38 -07:00
2a6162c728 Added UOLSExperienceActionSet 2025-01-09 11:27:12 -07:00
49843cdecf Fixed comment error 2025-01-07 22:44:28 -07:00
38ae567d20 Added OLSAssetManager, OLSExperienceManager, and OLSAssetManagerStartupJob 2025-01-07 22:30:09 -07:00
0eb6d52ca8 Added Initialization Data Assets 2025-01-06 14:29:37 -07:00
6814732c4b Added CommonLoadingScreen 2025-01-06 14:27:00 -07:00
799cfa4303 Initial commit of ability system 2025-01-04 09:41:49 -07:00
155 changed files with 18708 additions and 7 deletions

View 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"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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 ...
}
);
}
}

View File

@ -0,0 +1,5 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Modules/ModuleManager.h"
IMPLEMENT_MODULE(FDefaultModuleImpl, CommonLoadingScreen)

View File

@ -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");
}

View File

@ -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;
};

View File

@ -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();
}
}

View File

@ -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;
}
};

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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 ...
}
);
}
}

View File

@ -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

View File

@ -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;
};

View File

@ -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)

View File

@ -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

View File

@ -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:
};

View 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
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View 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 ...
}
);
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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);
};

View File

@ -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;
};

View File

@ -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;
};

View 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);
};

View File

@ -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" });
}
}

View File

@ -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();

View File

@ -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)

View File

@ -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" });
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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());
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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());
}

View 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 "AbilitySystem/Interfaces/OLSAbilitySourceInterface.h"
// Add default functionality here for any IOLSAbilitySourceInterface functions that are not pure virtual.

View 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 "AbilitySystem/OLSAbilitySimpleFailureMessage.h"

View 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);
}
}

View 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 "AbilitySystem/OLSBatchGameplayAbilityInterface.h"
// Add default functionality here for any IOLSBatchGameplayAbilityInterface functions that are not pure virtual.

View File

@ -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;
}

View 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);
}

View 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);
}
}
}
}

View File

@ -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)
{
}

View 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 "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()
{
}

View 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());
}
}

View 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);
}
}

View File

@ -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"));
}

View 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()));
}

View 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;
}

View 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();
}

View 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);
}
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View 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();
}

View 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;
}

View 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();
}

View 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();
}

View File

@ -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

View File

@ -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);
}
}
}

View 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

View 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);
}

View 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);
}
}

View 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"

View 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 "Interfaces/OLSPrimaryDataAssetInterface.h"
// Add default functionality here for any IOLSPrimaryDataAssetInterface functions that are not pure virtual.

View 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");

View 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;
}

View 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;
}

View File

@ -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);
}

View 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;
}

View 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;
}

View 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/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;
}

View File

@ -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();
}

View File

@ -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();
}
}

View 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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View 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("[]");
}

View File

@ -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)
{
}

View 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"

View 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;
}

View 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.
}

View 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;
}
}
}

View 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);
}

View 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;
}
}

View 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"

View 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;
};

View 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;
};

View File

@ -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;
};

View 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 "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;
};

View File

@ -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;
};

View 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