diff --git a/ALSV4_CPP.uplugin b/ALSV4_CPP.uplugin index f5b1bb8b..cfe17347 100644 --- a/ALSV4_CPP.uplugin +++ b/ALSV4_CPP.uplugin @@ -9,7 +9,7 @@ "CreatedByURL": "https://github.com/dyanikoglu", "DocsURL": "https://github.com/dyanikoglu/ALS-Community", "SupportURL": "https://github.com/dyanikoglu/ALS-Community/issues", - "EngineVersion": "5.1", + "EngineAssociation": "5.1", "EnabledByDefault": true, "CanContainContent": true, "IsBetaVersion": false, @@ -24,6 +24,7 @@ "Engine", "AIModule", "GameplayTasks", + "GameplayAbilities", "PhysicsCore" ] } @@ -36,6 +37,42 @@ { "Name": "EnhancedInput", "Enabled": true + }, + { + "Name": "GameplayMessageRouter", + "Enabled": true + }, + { + "Name": "GameFeatures", + "Enabled": true + }, + { + "Name": "CommonLoadingScreen", + "Enabled": true + }, + { + "Name": "CommonUI", + "Enabled": true + }, + { + "Name": "CommonUser", + "Enabled": true + }, + { + "Name": "CommonGame", + "Enabled": true + }, + { + "Name": "GameplayAbilities", + "Enabled": true + }, + { + "Name": "ModularGameplay", + "Enabled": true + }, + { + "Name": "ModularGameplayActors", + "Enabled": true } ] } diff --git a/Content/AdvancedLocomotionV4/Blueprints/CharacterLogic/ALS_CharacterBP.uasset b/Content/AdvancedLocomotionV4/Blueprints/CharacterLogic/ALS_CharacterBP.uasset index 05fb5d0a..3a97be51 100644 Binary files a/Content/AdvancedLocomotionV4/Blueprints/CharacterLogic/ALS_CharacterBP.uasset and b/Content/AdvancedLocomotionV4/Blueprints/CharacterLogic/ALS_CharacterBP.uasset differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/CharacterLogic/ALS_Player_Controller.uasset b/Content/AdvancedLocomotionV4/Blueprints/CharacterLogic/ALS_Player_Controller.uasset index e8f4f0da..7ce93c15 100644 Binary files a/Content/AdvancedLocomotionV4/Blueprints/CharacterLogic/ALS_Player_Controller.uasset and b/Content/AdvancedLocomotionV4/Blueprints/CharacterLogic/ALS_Player_Controller.uasset differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/GameModes/ALS_GameMode_SP.uasset b/Content/AdvancedLocomotionV4/Blueprints/GameModes/ALS_GameMode_SP.uasset index c2ce701c..d81d5a2b 100644 Binary files a/Content/AdvancedLocomotionV4/Blueprints/GameModes/ALS_GameMode_SP.uasset and b/Content/AdvancedLocomotionV4/Blueprints/GameModes/ALS_GameMode_SP.uasset differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugFocusedCharacterCycleAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugFocusedCharacterCycleAction.uasset deleted file mode 100644 index 6839deb2..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugFocusedCharacterCycleAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugOpenOverlayMenuAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugOpenOverlayMenuAction.uasset deleted file mode 100644 index de0cf709..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugOpenOverlayMenuAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugOverlayMenuCycleAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugOverlayMenuCycleAction.uasset deleted file mode 100644 index 12b333da..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugOverlayMenuCycleAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleCharacterInfoAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleCharacterInfoAction.uasset deleted file mode 100644 index 6d3de21f..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleCharacterInfoAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleDebugMeshAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleDebugMeshAction.uasset deleted file mode 100644 index 3093b6ab..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleDebugMeshAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleDebugViewAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleDebugViewAction.uasset deleted file mode 100644 index d68cd208..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleDebugViewAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleHudAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleHudAction.uasset deleted file mode 100644 index 6c98c580..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleHudAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleLayerColorsAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleLayerColorsAction.uasset deleted file mode 100644 index fa93946d..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleLayerColorsAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleShapesAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleShapesAction.uasset deleted file mode 100644 index 4f691789..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleShapesAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleSlomoAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleSlomoAction.uasset deleted file mode 100644 index 3c60407a..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleSlomoAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleTracesAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleTracesAction.uasset deleted file mode 100644 index ffae55a5..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Debug/DebugToggleTracesAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/AimAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/AimAction.uasset deleted file mode 100644 index 8af43bb1..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/AimAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraHeldAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraHeldAction.uasset deleted file mode 100644 index 496aeb2f..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraHeldAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraRightAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraRightAction.uasset deleted file mode 100644 index 2d5dc4d3..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraRightAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraTapAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraTapAction.uasset deleted file mode 100644 index 45173657..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraTapAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraUpAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraUpAction.uasset deleted file mode 100644 index 78edb9d4..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/CameraUpAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/ForwardMovementAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/ForwardMovementAction.uasset deleted file mode 100644 index d06f1f42..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/ForwardMovementAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/JumpAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/JumpAction.uasset deleted file mode 100644 index 4d82c407..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/JumpAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/LookingDirectionAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/LookingDirectionAction.uasset deleted file mode 100644 index e0e83408..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/LookingDirectionAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/RagdollAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/RagdollAction.uasset deleted file mode 100644 index 44ac9860..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/RagdollAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/RightMovementAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/RightMovementAction.uasset deleted file mode 100644 index e6b5d130..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/RightMovementAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/SprintAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/SprintAction.uasset deleted file mode 100644 index 69a3e5d1..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/SprintAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/StanceAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/StanceAction.uasset deleted file mode 100644 index e4dba433..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/StanceAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/VelocityDirectionAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/VelocityDirectionAction.uasset deleted file mode 100644 index f38133fa..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/VelocityDirectionAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/WalkAction.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/Default/WalkAction.uasset deleted file mode 100644 index b9505168..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/Default/WalkAction.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/IMC_Debug.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/IMC_Debug.uasset deleted file mode 100644 index bf447f34..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/IMC_Debug.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/Input/IMC_Default.uasset b/Content/AdvancedLocomotionV4/Blueprints/Input/IMC_Default.uasset deleted file mode 100644 index 862be802..00000000 Binary files a/Content/AdvancedLocomotionV4/Blueprints/Input/IMC_Default.uasset and /dev/null differ diff --git a/Content/AdvancedLocomotionV4/Blueprints/UI/ALS_HUD.uasset b/Content/AdvancedLocomotionV4/Blueprints/UI/ALS_HUD.uasset index 02d9f201..b8261e44 100644 Binary files a/Content/AdvancedLocomotionV4/Blueprints/UI/ALS_HUD.uasset and b/Content/AdvancedLocomotionV4/Blueprints/UI/ALS_HUD.uasset differ diff --git a/Content/AdvancedLocomotionV4/Levels/ALS_DemoLevel.umap b/Content/AdvancedLocomotionV4/Levels/ALS_DemoLevel.umap index be1870dc..321fc803 100644 Binary files a/Content/AdvancedLocomotionV4/Levels/ALS_DemoLevel.umap and b/Content/AdvancedLocomotionV4/Levels/ALS_DemoLevel.umap differ diff --git a/Source/ALSV4_CPP/ALSV4_CPP.Build.cs b/Source/ALSV4_CPP/ALSV4_CPP.Build.cs index 90a5d820..8d0e3730 100644 --- a/Source/ALSV4_CPP/ALSV4_CPP.Build.cs +++ b/Source/ALSV4_CPP/ALSV4_CPP.Build.cs @@ -8,11 +8,45 @@ public class ALSV4_CPP : ModuleRules public ALSV4_CPP(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - + PublicDependencyModuleNames.AddRange(new[] - {"Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "AIModule", "GameplayTasks","PhysicsCore", "Niagara", "EnhancedInput" - }); + { + "GameplayAbilities", + "GameplayTags", + "GameplayTasks", + "Core", + "CoreUObject", + "Engine", + "InputCore", + "NavigationSystem", + "AIModule", + "ModularGameplay", + "ModularGameplayActors", + "GameplayTasks", + "GameFeatures", + "PhysicsCore", + "Niagara", + "CommonLoadingScreen", + "EnhancedInput" + } + ); - PrivateDependencyModuleNames.AddRange(new[] {"Slate", "SlateCore"}); + PrivateDependencyModuleNames.AddRange( + new string[] + { + "InputCore", + "AudioMixer", + "AudioModulation", + "EnhancedInput", + "NetCore", + "CommonInput", + "CommonGame", + "CommonUser", + "CommonUI", + "GameplayMessageRuntime", + "Slate", + "SlateCore" + } + ); } } \ No newline at end of file diff --git a/Source/ALSV4_CPP/Private/ALSGameplayTags.cpp b/Source/ALSV4_CPP/Private/ALSGameplayTags.cpp new file mode 100644 index 00000000..71de6d73 --- /dev/null +++ b/Source/ALSV4_CPP/Private/ALSGameplayTags.cpp @@ -0,0 +1,100 @@ +#include "ALSGameplayTags.h" +#include "ALSLogChannels.h" +#include "GameplayTagsManager.h" +#include "Engine/EngineTypes.h" + +FALSGameplayTags FALSGameplayTags::GameplayTags; + +void FALSGameplayTags::InitializeNativeTags() +{ + UE_LOG(LogTemp, Warning, TEXT("FALSGameplayTags::InitializeNativeTags")) + UGameplayTagsManager& Manager = UGameplayTagsManager::Get(); + + GameplayTags.AddAllTags(Manager); + + // Notify manager that we are done adding native tags. + Manager.DoneAddingNativeTags(); +} + +void FALSGameplayTags::AddAllTags(UGameplayTagsManager& Manager) +{ + AddTag(Ability_ActivateFail_IsDead, "Ability.ActivateFail.IsDead", "Ability failed to activate because its owner is dead."); + AddTag(Ability_ActivateFail_Cooldown, "Ability.ActivateFail.Cooldown", "Ability failed to activate because it is on cool down."); + AddTag(Ability_ActivateFail_Cost, "Ability.ActivateFail.Cost", "Ability failed to activate because it did not pass the cost checks."); + AddTag(Ability_ActivateFail_TagsBlocked, "Ability.ActivateFail.TagsBlocked", "Ability failed to activate because tags are blocking it."); + AddTag(Ability_ActivateFail_TagsMissing, "Ability.ActivateFail.TagsMissing", "Ability failed to activate because tags are missing."); + AddTag(Ability_ActivateFail_Networking, "Ability.ActivateFail.Networking", "Ability failed to activate because it did not pass the network checks."); + AddTag(Ability_ActivateFail_ActivationGroup, "Ability.ActivateFail.ActivationGroup", "Ability failed to activate because of its activation group."); + + AddTag(Ability_Behavior_SurvivesDeath, "Ability.Behavior.SurvivesDeath", "An ability with this type tag should not be canceled due to death."); + + AddTag(InputTag_Move, "InputTag.Move", "Move input."); + AddTag(InputTag_Look_Mouse, "InputTag.Look.Mouse", "Look (mouse) input."); + AddTag(InputTag_Look_Stick, "InputTag.Look.Stick", "Look (stick) input."); + AddTag(InputTag_Crouch, "InputTag.Crouch", "Crouch input."); + AddTag(InputTag_AutoRun, "InputTag.AutoRun", "Auto-run input."); + + AddTag(GameplayEvent_Death, "GameplayEvent.Death", "Event that fires on death. This event only fires on the server."); + AddTag(GameplayEvent_Reset, "GameplayEvent.Reset", "Event that fires once a player reset is executed."); + AddTag(GameplayEvent_RequestReset, "GameplayEvent.RequestReset", "Event to request a player's pawn to be instantly replaced with a new one at a valid spawn location."); + + AddTag(SetByCaller_Damage, "SetByCaller.Damage", "SetByCaller tag used by damage gameplay effects."); + AddTag(SetByCaller_Heal, "SetByCaller.Heal", "SetByCaller tag used by healing gameplay effects."); + + AddTag(Cheat_GodMode, "Cheat.GodMode", "GodMode cheat is active on the owner."); + AddTag(Cheat_UnlimitedHealth, "Cheat.UnlimitedHealth", "UnlimitedHealth cheat is active on the owner."); + + AddTag(Status_Crouching, "Status.Crouching", "Target is crouching."); + AddTag(Status_AutoRunning, "Status.AutoRunning", "Target is auto-running."); + AddTag(Status_Death, "Status.Death", "Target has the death status."); + AddTag(Status_Death_Dying, "Status.Death.Dying", "Target has begun the death process."); + AddTag(Status_Death_Dead, "Status.Death.Dead", "Target has finished the death process."); + + AddMovementModeTag(Movement_Mode_Walking, "Movement.Mode.Walking", MOVE_Walking); + AddMovementModeTag(Movement_Mode_NavWalking, "Movement.Mode.NavWalking", MOVE_NavWalking); + AddMovementModeTag(Movement_Mode_Falling, "Movement.Mode.Falling", MOVE_Falling); + AddMovementModeTag(Movement_Mode_Swimming, "Movement.Mode.Swimming", MOVE_Swimming); + AddMovementModeTag(Movement_Mode_Flying, "Movement.Mode.Flying", MOVE_Flying); + AddMovementModeTag(Movement_Mode_Custom, "Movement.Mode.Custom", MOVE_Custom); +} + +void FALSGameplayTags::AddTag(FGameplayTag& OutTag, const ANSICHAR* TagName, const ANSICHAR* TagComment) +{ + OutTag = UGameplayTagsManager::Get().AddNativeGameplayTag(FName(TagName), FString(TEXT("(Native) ")) + FString(TagComment)); +} + +void FALSGameplayTags::AddMovementModeTag(FGameplayTag& OutTag, const ANSICHAR* TagName, uint8 MovementMode) +{ + AddTag(OutTag, TagName, "Character movement mode tag."); + GameplayTags.MovementModeTagMap.Add(MovementMode, OutTag); +} + +void FALSGameplayTags::AddCustomMovementModeTag(FGameplayTag& OutTag, const ANSICHAR* TagName, uint8 CustomMovementMode) +{ + AddTag(OutTag, TagName, "Character custom movement mode tag."); + GameplayTags.CustomMovementModeTagMap.Add(CustomMovementMode, OutTag); +} + +FGameplayTag FALSGameplayTags::FindTagByString(FString TagString, bool bMatchPartialString) +{ + const UGameplayTagsManager& Manager = UGameplayTagsManager::Get(); + FGameplayTag Tag = Manager.RequestGameplayTag(FName(*TagString), false); + + if (!Tag.IsValid() && bMatchPartialString) + { + FGameplayTagContainer AllTags; + Manager.RequestAllGameplayTags(AllTags, true); + + for (const FGameplayTag TestTag : AllTags) + { + if (TestTag.ToString().Contains(TagString)) + { + UE_LOG(LogALS, Display, TEXT("Could not find exact match for tag [%s] but found partial match on tag [%s]."), *TagString, *TestTag.ToString()); + Tag = TestTag; + break; + } + } + } + + return Tag; +} diff --git a/Source/ALSV4_CPP/Private/ALSLogChannels.cpp b/Source/ALSV4_CPP/Private/ALSLogChannels.cpp new file mode 100644 index 00000000..020029c3 --- /dev/null +++ b/Source/ALSV4_CPP/Private/ALSLogChannels.cpp @@ -0,0 +1,37 @@ +#include "ALSLogChannels.h" +#include "GameFramework/Actor.h" + +DEFINE_LOG_CATEGORY(LogALS); +DEFINE_LOG_CATEGORY(LogALSAbilitySystem); +DEFINE_LOG_CATEGORY(LogALSExperience); + +FString GetClientServerContextString(UObject* ContextObject) +{ + ENetRole Role = ROLE_None; + + if (AActor* Actor = Cast(ContextObject)) + { + Role = Actor->GetLocalRole(); + } + else if (UActorComponent* Component = Cast(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("[]"); +} diff --git a/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilitySet.cpp b/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilitySet.cpp new file mode 100644 index 00000000..7f469644 --- /dev/null +++ b/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilitySet.cpp @@ -0,0 +1,145 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "AbilitySystem/ALSAbilitySet.h" +#include "ALSLogChannels.h" +#include "AbilitySystem/ALSAbilitySystemComponent.h" +#include "AbilitySystem/Abilities/ALSGameplayAbility.h" + +void FALSAbilitySet_GrantedHandles::AddAbilitySpecHandle(const FGameplayAbilitySpecHandle& Handle) +{ + if (Handle.IsValid()) + { + AbilitySpecHandles.Add(Handle); + } +} + +void FALSAbilitySet_GrantedHandles::AddGameplayEffectHandle(const FActiveGameplayEffectHandle& Handle) +{ + if (Handle.IsValid()) + { + GameplayEffectHandles.Add(Handle); + } +} + +void FALSAbilitySet_GrantedHandles::AddAttributeSet(UAttributeSet* Set) +{ + GrantedAttributeSets.Add(Set); +} + +void FALSAbilitySet_GrantedHandles::TakeFromAbilitySystem(UALSAbilitySystemComponent* ALSASC) +{ + check(ALSASC); + + if (!ALSASC->IsOwnerActorAuthoritative()) + { + // Must be authoritative to give or take ability sets. + return; + } + + for (const FGameplayAbilitySpecHandle& Handle : AbilitySpecHandles) + { + if (Handle.IsValid()) + { + ALSASC->ClearAbility(Handle); + } + } + + for (const FActiveGameplayEffectHandle& Handle : GameplayEffectHandles) + { + if (Handle.IsValid()) + { + ALSASC->RemoveActiveGameplayEffect(Handle); + } + } + + for (UAttributeSet* Set : GrantedAttributeSets) + { + ALSASC->GetSpawnedAttributes_Mutable().Remove(Set); + } + + AbilitySpecHandles.Reset(); + GameplayEffectHandles.Reset(); + GrantedAttributeSets.Reset(); +} + +UALSAbilitySet::UALSAbilitySet(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UALSAbilitySet::GiveToAbilitySystem(UALSAbilitySystemComponent* ALSASC, FALSAbilitySet_GrantedHandles* OutGrantedHandles, UObject* SourceObject) const +{ + check(ALSASC); + + if (!ALSASC->IsOwnerActorAuthoritative()) + { + // Must be authoritative to give or take ability sets. + return; + } + + // Grant the gameplay abilities. + for (int32 AbilityIndex = 0; AbilityIndex < GrantedGameplayAbilities.Num(); ++AbilityIndex) + { + const FALSAbilitySet_GameplayAbility& AbilityToGrant = GrantedGameplayAbilities[AbilityIndex]; + + if (!IsValid(AbilityToGrant.Ability)) + { + UE_LOG(LogALSAbilitySystem, Error, TEXT("GrantedGameplayAbilities[%d] on ability set [%s] is not valid."), AbilityIndex, *GetNameSafe(this)); + continue; + } + + UALSGameplayAbility* AbilityCDO = AbilityToGrant.Ability->GetDefaultObject(); + + FGameplayAbilitySpec AbilitySpec(AbilityCDO, AbilityToGrant.AbilityLevel); + AbilitySpec.SourceObject = SourceObject; + AbilitySpec.DynamicAbilityTags.AddTag(AbilityToGrant.InputTag); + + const FGameplayAbilitySpecHandle AbilitySpecHandle = ALSASC->GiveAbility(AbilitySpec); + + if (OutGrantedHandles) + { + OutGrantedHandles->AddAbilitySpecHandle(AbilitySpecHandle); + } + } + + // Grant the gameplay effects. + for (int32 EffectIndex = 0; EffectIndex < GrantedGameplayEffects.Num(); ++EffectIndex) + { + const FALSAbilitySet_GameplayEffect& EffectToGrant = GrantedGameplayEffects[EffectIndex]; + + if (!IsValid(EffectToGrant.GameplayEffect)) + { + UE_LOG(LogALSAbilitySystem, Error, TEXT("GrantedGameplayEffects[%d] on ability set [%s] is not valid"), EffectIndex, *GetNameSafe(this)); + continue; + } + + const UGameplayEffect* GameplayEffect = EffectToGrant.GameplayEffect->GetDefaultObject(); + const FActiveGameplayEffectHandle GameplayEffectHandle = ALSASC->ApplyGameplayEffectToSelf(GameplayEffect, EffectToGrant.EffectLevel, ALSASC->MakeEffectContext()); + + if (OutGrantedHandles) + { + OutGrantedHandles->AddGameplayEffectHandle(GameplayEffectHandle); + } + } + + // Grant the attribute sets. + for (int32 SetIndex = 0; SetIndex < GrantedAttributes.Num(); ++SetIndex) + { + const FALSAbilitySet_AttributeSet& SetToGrant = GrantedAttributes[SetIndex]; + + if (!IsValid(SetToGrant.AttributeSet)) + { + UE_LOG(LogALSAbilitySystem, Error, TEXT("GrantedAttributes[%d] on ability set [%s] is not valid"), SetIndex, *GetNameSafe(this)); + continue; + } + + UAttributeSet* NewSet = NewObject(ALSASC->GetOwner(), SetToGrant.AttributeSet); + ALSASC->AddAttributeSetSubobject(NewSet); + + if (OutGrantedHandles) + { + OutGrantedHandles->AddAttributeSet(NewSet); + } + } +} \ No newline at end of file diff --git a/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilitySourceInterface.cpp b/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilitySourceInterface.cpp new file mode 100644 index 00000000..f3fffb95 --- /dev/null +++ b/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilitySourceInterface.cpp @@ -0,0 +1,8 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "AbilitySystem/ALSAbilitySourceInterface.h" + +UALSAbilitySourceInterface::UALSAbilitySourceInterface(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{} diff --git a/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilitySystemComponent.cpp b/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilitySystemComponent.cpp new file mode 100644 index 00000000..c5fc41ff --- /dev/null +++ b/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilitySystemComponent.cpp @@ -0,0 +1,515 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "AbilitySystem/ALSAbilitySystemComponent.h" +#include "ALSLogChannels.h" +#include "System/ALSGameData.h" +#include "System/ALSAssetManager.h" +#include "AbilitySystem/ALSGlobalAbilitySystem.h" +#include "GameplayTagContainer.h" +#include "GameplayAbilitySpec.h" +#include "AbilitySystem/Abilities/ALSGameplayAbility.h" +#include "Character/Animation/ALSCharacterAnimInstance.h" +#include "AbilitySystem/ALSAbilityTagRelationshipMapping.h" + +UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_AbilityInputBlocked, "Gameplay.AbilityInputBlocked"); + +UALSAbilitySystemComponent::UALSAbilitySystemComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + InputPressedSpecHandles.Reset(); + InputReleasedSpecHandles.Reset(); + InputHeldSpecHandles.Reset(); + + FMemory::Memset(ActivationGroupCounts, 0, sizeof(ActivationGroupCounts)); +} + +void UALSAbilitySystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (UALSGlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem(GetWorld())) + { + GlobalAbilitySystem->UnregisterASC(this); + } +} + +void UALSAbilitySystemComponent::InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor) +{ + FGameplayAbilityActorInfo* ActorInfo = AbilityActorInfo.Get(); + check(ActorInfo); + check(InOwnerActor); + + const bool bHasNewPawnAvatar = Cast(InAvatarActor) && (InAvatarActor != ActorInfo->AvatarActor); + + Super::InitAbilityActorInfo(InOwnerActor, InAvatarActor); + + if (bHasNewPawnAvatar) + { + // Notify all abilities that a new pawn avatar has been set + for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) + { + UALSGameplayAbility* ALSAbilityCDO = CastChecked(AbilitySpec.Ability); + + if (ALSAbilityCDO->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced) + { + TArray Instances = AbilitySpec.GetAbilityInstances(); + for (UGameplayAbility* AbilityInstance : Instances) + { + UALSGameplayAbility* ALSAbilityInstance = CastChecked(AbilityInstance); + ALSAbilityInstance->OnPawnAvatarSet(); + } + } + else + { + ALSAbilityCDO->OnPawnAvatarSet(); + } + } + + // Register with the global system once we actually have a pawn avatar. We wait until this time since some globally-applied effects may require an avatar. + if (UALSGlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem(GetWorld())) + { + GlobalAbilitySystem->RegisterASC(this); + } + + if (UALSCharacterAnimInstance* ALSAnimInst = Cast(ActorInfo->GetAnimInstance())) + { + ALSAnimInst->InitializeWithAbilitySystem(this); + } + + TryActivateAbilitiesOnSpawn(); + } +} + +void UALSAbilitySystemComponent::TryActivateAbilitiesOnSpawn() +{ + ABILITYLIST_SCOPE_LOCK(); + for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) + { + const UALSGameplayAbility* ALSAbilityCDO = CastChecked(AbilitySpec.Ability); + ALSAbilityCDO->TryActivateAbilityOnSpawn(AbilityActorInfo.Get(), AbilitySpec); + } +} + +void UALSAbilitySystemComponent::CancelAbilitiesByFunc(TShouldCancelAbilityFunc ShouldCancelFunc, bool bReplicateCancelAbility) +{ + ABILITYLIST_SCOPE_LOCK(); + for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) + { + if (!AbilitySpec.IsActive()) + { + continue; + } + + UALSGameplayAbility* ALSAbilityCDO = CastChecked(AbilitySpec.Ability); + + if (ALSAbilityCDO->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced) + { + // Cancel all the spawned instances, not the CDO. + TArray Instances = AbilitySpec.GetAbilityInstances(); + for (UGameplayAbility* AbilityInstance : Instances) + { + UALSGameplayAbility* ALSAbilityInstance = CastChecked(AbilityInstance); + + if (ShouldCancelFunc(ALSAbilityInstance, AbilitySpec.Handle)) + { + if (ALSAbilityInstance->CanBeCanceled()) + { + ALSAbilityInstance->CancelAbility(AbilitySpec.Handle, AbilityActorInfo.Get(), ALSAbilityInstance->GetCurrentActivationInfo(), bReplicateCancelAbility); + } + else + { + UE_LOG(LogALSAbilitySystem, Error, TEXT("CancelAbilitiesByFunc: Can't cancel ability [%s] because CanBeCanceled is false."), *ALSAbilityInstance->GetName()); + } + } + } + } + else + { + // Cancel the non-instanced ability CDO. + if (ShouldCancelFunc(ALSAbilityCDO, AbilitySpec.Handle)) + { + // Non-instanced abilities can always be canceled. + check(ALSAbilityCDO->CanBeCanceled()); + ALSAbilityCDO->CancelAbility(AbilitySpec.Handle, AbilityActorInfo.Get(), FGameplayAbilityActivationInfo(), bReplicateCancelAbility); + } + } + } +} + +void UALSAbilitySystemComponent::CancelInputActivatedAbilities(bool bReplicateCancelAbility) +{ + TShouldCancelAbilityFunc ShouldCancelFunc = [this](const UALSGameplayAbility* ALSAbility, FGameplayAbilitySpecHandle Handle) + { + const EALSAbilityActivationPolicy ActivationPolicy = ALSAbility->GetActivationPolicy(); + return ((ActivationPolicy == EALSAbilityActivationPolicy::OnInputTriggered) || (ActivationPolicy == EALSAbilityActivationPolicy::WhileInputActive)); + }; + + CancelAbilitiesByFunc(ShouldCancelFunc, bReplicateCancelAbility); +} + +void UALSAbilitySystemComponent::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()) + { + // 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, Spec.ActivationInfo.GetActivationPredictionKey()); + } +} + +void UALSAbilitySystemComponent::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()) + { + // 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, Spec.ActivationInfo.GetActivationPredictionKey()); + } +} + +void UALSAbilitySystemComponent::AbilityInputTagPressed(const FGameplayTag& InputTag) +{ + if (InputTag.IsValid()) + { + for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) + { + if (AbilitySpec.Ability && (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))) + { + InputPressedSpecHandles.AddUnique(AbilitySpec.Handle); + InputHeldSpecHandles.AddUnique(AbilitySpec.Handle); + } + } + } +} + +void UALSAbilitySystemComponent::AbilityInputTagReleased(const FGameplayTag& InputTag) +{ + if (InputTag.IsValid()) + { + for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) + { + if (AbilitySpec.Ability && (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))) + { + InputReleasedSpecHandles.AddUnique(AbilitySpec.Handle); + InputHeldSpecHandles.Remove(AbilitySpec.Handle); + } + } + } +} + +void UALSAbilitySystemComponent::ProcessAbilityInput(float DeltaTime, bool bGamePaused) +{ + if (HasMatchingGameplayTag(TAG_Gameplay_AbilityInputBlocked)) + { + ClearAbilityInput(); + return; + } + + static TArray 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 UALSGameplayAbility* ALSAbilityCDO = CastChecked(AbilitySpec->Ability); + + if (ALSAbilityCDO->GetActivationPolicy() == EALSAbilityActivationPolicy::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 UALSGameplayAbility* ALSAbilityCDO = CastChecked(AbilitySpec->Ability); + + if (ALSAbilityCDO->GetActivationPolicy() == EALSAbilityActivationPolicy::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 UALSAbilitySystemComponent::ClearAbilityInput() +{ + InputPressedSpecHandles.Reset(); + InputReleasedSpecHandles.Reset(); + InputHeldSpecHandles.Reset(); +} + +void UALSAbilitySystemComponent::NotifyAbilityActivated(const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability) +{ + Super::NotifyAbilityActivated(Handle, Ability); + + UALSGameplayAbility* ALSAbility = CastChecked(Ability); + + AddAbilityToActivationGroup(ALSAbility->GetActivationGroup(), ALSAbility); +} + +void UALSAbilitySystemComponent::NotifyAbilityFailed(const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason) +{ + Super::NotifyAbilityFailed(Handle, Ability, FailureReason); + + if (APawn* Avatar = Cast(GetAvatarActor())) + { + if (!Avatar->IsLocallyControlled() && Ability->IsSupportedForNetworking()) + { + ClientNotifyAbilityFailed(Ability, FailureReason); + return; + } + } + + HandleAbilityFailed(Ability, FailureReason); +} + +void UALSAbilitySystemComponent::NotifyAbilityEnded(FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability, bool bWasCancelled) +{ + Super::NotifyAbilityEnded(Handle, Ability, bWasCancelled); + + UALSGameplayAbility* ALSAbility = CastChecked(Ability); + + RemoveAbilityFromActivationGroup(ALSAbility->GetActivationGroup(), ALSAbility); +} + +void UALSAbilitySystemComponent::ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags, const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags, 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, bEnableBlockTags, ModifiedBlockTags, bExecuteCancelTags, ModifiedCancelTags); + + //@TODO: Apply any special logic like blocking input or movement +} + +void UALSAbilitySystemComponent::HandleChangeAbilityCanBeCanceled(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bCanBeCanceled) +{ + Super::HandleChangeAbilityCanBeCanceled(AbilityTags, RequestingAbility, bCanBeCanceled); + + //@TODO: Apply any special logic like blocking input or movement +} + +void UALSAbilitySystemComponent::GetAdditionalActivationTagRequirements(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer& OutActivationRequired, FGameplayTagContainer& OutActivationBlocked) const +{ + if (TagRelationshipMapping) + { + TagRelationshipMapping->GetRequiredAndBlockedActivationTags(AbilityTags, &OutActivationRequired, &OutActivationBlocked); + } +} + +void UALSAbilitySystemComponent::SetTagRelationshipMapping(UALSAbilityTagRelationshipMapping* NewMapping) +{ + TagRelationshipMapping = NewMapping; +} + +void UALSAbilitySystemComponent::ClientNotifyAbilityFailed_Implementation(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason) +{ + HandleAbilityFailed(Ability, FailureReason); +} + +void UALSAbilitySystemComponent::HandleAbilityFailed(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason) +{ + //UE_LOG(LogALSAbilitySystem, Warning, TEXT("Ability %s failed to activate (tags: %s)"), *GetPathNameSafe(Ability), *FailureReason.ToString()); + + if (const UALSGameplayAbility* ALSAbility = Cast(Ability)) + { + ALSAbility->OnAbilityFailedToActivate(FailureReason); + } +} + +bool UALSAbilitySystemComponent::IsActivationGroupBlocked(EALSAbilityActivationGroup Group) const +{ + bool bBlocked = false; + + switch (Group) + { + case EALSAbilityActivationGroup::Independent: + // Independent abilities are never blocked. + bBlocked = false; + break; + + case EALSAbilityActivationGroup::Exclusive_Replaceable: + case EALSAbilityActivationGroup::Exclusive_Blocking: + // Exclusive abilities can activate if nothing is blocking. + bBlocked = (ActivationGroupCounts[(uint8)EALSAbilityActivationGroup::Exclusive_Blocking] > 0); + break; + + default: + checkf(false, TEXT("IsActivationGroupBlocked: Invalid ActivationGroup [%d]\n"), (uint8)Group); + break; + } + + return bBlocked; +} + +void UALSAbilitySystemComponent::AddAbilityToActivationGroup(EALSAbilityActivationGroup Group, UALSGameplayAbility* ALSAbility) +{ + check(ALSAbility); + check(ActivationGroupCounts[(uint8)Group] < INT32_MAX); + + ActivationGroupCounts[(uint8)Group]++; + + const bool bReplicateCancelAbility = false; + + switch (Group) + { + case EALSAbilityActivationGroup::Independent: + // Independent abilities do not cancel any other abilities. + break; + + case EALSAbilityActivationGroup::Exclusive_Replaceable: + case EALSAbilityActivationGroup::Exclusive_Blocking: + CancelActivationGroupAbilities(EALSAbilityActivationGroup::Exclusive_Replaceable, ALSAbility, bReplicateCancelAbility); + break; + + default: + checkf(false, TEXT("AddAbilityToActivationGroup: Invalid ActivationGroup [%d]\n"), (uint8)Group); + break; + } + + const int32 ExclusiveCount = ActivationGroupCounts[(uint8)EALSAbilityActivationGroup::Exclusive_Replaceable] + ActivationGroupCounts[(uint8)EALSAbilityActivationGroup::Exclusive_Blocking]; + if (!ensure(ExclusiveCount <= 1)) + { + UE_LOG(LogALSAbilitySystem, Error, TEXT("AddAbilityToActivationGroup: Multiple exclusive abilities are running.")); + } +} + +void UALSAbilitySystemComponent::RemoveAbilityFromActivationGroup(EALSAbilityActivationGroup Group, UALSGameplayAbility* ALSAbility) +{ + check(ALSAbility); + check(ActivationGroupCounts[(uint8)Group] > 0); + + ActivationGroupCounts[(uint8)Group]--; +} + +void UALSAbilitySystemComponent::CancelActivationGroupAbilities(EALSAbilityActivationGroup Group, UALSGameplayAbility* IgnoreALSAbility, bool bReplicateCancelAbility) +{ + TShouldCancelAbilityFunc ShouldCancelFunc = [this, Group, IgnoreALSAbility](const UALSGameplayAbility* ALSAbility, FGameplayAbilitySpecHandle Handle) + { + return ((ALSAbility->GetActivationGroup() == Group) && (ALSAbility != IgnoreALSAbility)); + }; + + CancelAbilitiesByFunc(ShouldCancelFunc, bReplicateCancelAbility); +} + +void UALSAbilitySystemComponent::AddDynamicTagGameplayEffect(const FGameplayTag& Tag) +{ + const TSubclassOf DynamicTagGE = UALSAssetManager::GetSubclass(UALSGameData::Get().DynamicTagGameplayEffect); + if (!DynamicTagGE) + { + UE_LOG(LogALSAbilitySystem, Warning, TEXT("AddDynamicTagGameplayEffect: Unable to find DynamicTagGameplayEffect [%s]."), *UALSGameData::Get().DynamicTagGameplayEffect.GetAssetName()); + return; + } + + const FGameplayEffectSpecHandle SpecHandle = MakeOutgoingSpec(DynamicTagGE, 1.0f, MakeEffectContext()); + FGameplayEffectSpec* Spec = SpecHandle.Data.Get(); + + if (!Spec) + { + UE_LOG(LogALSAbilitySystem, Warning, TEXT("AddDynamicTagGameplayEffect: Unable to make outgoing spec for [%s]."), *GetNameSafe(DynamicTagGE)); + return; + } + + Spec->DynamicGrantedTags.AddTag(Tag); + + ApplyGameplayEffectSpecToSelf(*Spec); +} + +void UALSAbilitySystemComponent::RemoveDynamicTagGameplayEffect(const FGameplayTag& Tag) +{ + const TSubclassOf DynamicTagGE = UALSAssetManager::GetSubclass(UALSGameData::Get().DynamicTagGameplayEffect); + if (!DynamicTagGE) + { + UE_LOG(LogALSAbilitySystem, Warning, TEXT("RemoveDynamicTagGameplayEffect: Unable to find gameplay effect [%s]."), *UALSGameData::Get().DynamicTagGameplayEffect.GetAssetName()); + return; + } + + FGameplayEffectQuery Query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(FGameplayTagContainer(Tag)); + Query.EffectDefinition = DynamicTagGE; + + RemoveActiveEffects(Query); +} + +void UALSAbilitySystemComponent::GetAbilityTargetData(const FGameplayAbilitySpecHandle AbilityHandle, FGameplayAbilityActivationInfo ActivationInfo, FGameplayAbilityTargetDataHandle& OutTargetDataHandle) +{ + TSharedPtr ReplicatedData = AbilityTargetDataMap.Find(FGameplayAbilitySpecHandleAndPredictionKey(AbilityHandle, ActivationInfo.GetActivationPredictionKey())); + if (ReplicatedData.IsValid()) + { + OutTargetDataHandle = ReplicatedData->TargetData; + } +} diff --git a/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilityTagRelationshipMapping.cpp b/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilityTagRelationshipMapping.cpp new file mode 100644 index 00000000..76db9632 --- /dev/null +++ b/Source/ALSV4_CPP/Private/AbilitySystem/ALSAbilityTagRelationshipMapping.cpp @@ -0,0 +1,60 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "AbilitySystem/ALSAbilityTagRelationshipMapping.h" + +void UALSAbilityTagRelationshipMapping::GetAbilityTagsToBlockAndCancel(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutTagsToBlock, FGameplayTagContainer* OutTagsToCancel) const +{ + // Simple iteration for now + for (int32 i = 0; i < AbilityTagRelationships.Num(); i++) + { + const FALSAbilityTagRelationship& Tags = AbilityTagRelationships[i]; + if (AbilityTags.HasTag(Tags.AbilityTag)) + { + if (OutTagsToBlock) + { + OutTagsToBlock->AppendTags(Tags.AbilityTagsToBlock); + } + if (OutTagsToCancel) + { + OutTagsToCancel->AppendTags(Tags.AbilityTagsToCancel); + } + } + } +} + +void UALSAbilityTagRelationshipMapping::GetRequiredAndBlockedActivationTags(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutActivationRequired, FGameplayTagContainer* OutActivationBlocked) const +{ + // Simple iteration for now + for (int32 i = 0; i < AbilityTagRelationships.Num(); i++) + { + const FALSAbilityTagRelationship& Tags = AbilityTagRelationships[i]; + if (AbilityTags.HasTag(Tags.AbilityTag)) + { + if (OutActivationRequired) + { + OutActivationRequired->AppendTags(Tags.ActivationRequiredTags); + } + if (OutActivationBlocked) + { + OutActivationBlocked->AppendTags(Tags.ActivationBlockedTags); + } + } + } +} + +bool UALSAbilityTagRelationshipMapping::IsAbilityCancelledByTag(const FGameplayTagContainer& AbilityTags, const FGameplayTag& ActionTag) const +{ + // Simple iteration for now + for (int32 i = 0; i < AbilityTagRelationships.Num(); i++) + { + const FALSAbilityTagRelationship& Tags = AbilityTagRelationships[i]; + + if (Tags.AbilityTag == ActionTag && Tags.AbilityTagsToCancel.HasAny(AbilityTags)) + { + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/Source/ALSV4_CPP/Private/AbilitySystem/ALSGameplayEffectContext.cpp b/Source/ALSV4_CPP/Private/AbilitySystem/ALSGameplayEffectContext.cpp new file mode 100644 index 00000000..b39ed823 --- /dev/null +++ b/Source/ALSV4_CPP/Private/AbilitySystem/ALSGameplayEffectContext.cpp @@ -0,0 +1,44 @@ +#include "AbilitySystem/ALSGameplayEffectContext.h" +#include "Components/PrimitiveComponent.h" +#include "AbilitySystem/ALSAbilitySourceInterface.h" + +FALSGameplayEffectContext* FALSGameplayEffectContext::ExtractEffectContext(struct FGameplayEffectContextHandle Handle) +{ + FGameplayEffectContext* BaseEffectContext = Handle.Get(); + if ((BaseEffectContext != nullptr) && BaseEffectContext->GetScriptStruct()->IsChildOf(FALSGameplayEffectContext::StaticStruct())) + { + return (FALSGameplayEffectContext*)BaseEffectContext; + } + + return nullptr; +} + +bool FALSGameplayEffectContext::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + FGameplayEffectContext::NetSerialize(Ar, Map, bOutSuccess); + + // Not serialized for post-activation use: + // CartridgeID + + return true; +} + +void FALSGameplayEffectContext::SetAbilitySource(const IALSAbilitySourceInterface* InObject, float InSourceLevel) +{ + AbilitySourceObject = MakeWeakObjectPtr(Cast(InObject)); + //SourceLevel = InSourceLevel; +} + +const IALSAbilitySourceInterface* FALSGameplayEffectContext::GetAbilitySource() const +{ + return Cast(AbilitySourceObject.Get()); +} + +const UPhysicalMaterial* FALSGameplayEffectContext::GetPhysicalMaterial() const +{ + if (const FHitResult* HitResultPtr = GetHitResult()) + { + return HitResultPtr->PhysMaterial.Get(); + } + return nullptr; +} \ No newline at end of file diff --git a/Source/ALSV4_CPP/Private/AbilitySystem/ALSGlobalAbilitySystem.cpp b/Source/ALSV4_CPP/Private/AbilitySystem/ALSGlobalAbilitySystem.cpp new file mode 100644 index 00000000..97a4e0d9 --- /dev/null +++ b/Source/ALSV4_CPP/Private/AbilitySystem/ALSGlobalAbilitySystem.cpp @@ -0,0 +1,151 @@ +#include "AbilitySystem/ALSGlobalAbilitySystem.h" +#include "Net/UnrealNetwork.h" +#include "AbilitySystem/ALSAbilitySystemComponent.h" + +void FGlobalAppliedAbilityList::AddToASC(TSubclassOf Ability, UALSAbilitySystemComponent* ASC) +{ + if (FGameplayAbilitySpecHandle* SpecHandle = Handles.Find(ASC)) + { + RemoveFromASC(ASC); + } + + UGameplayAbility* AbilityCDO = Ability->GetDefaultObject(); + FGameplayAbilitySpec AbilitySpec(AbilityCDO); + const FGameplayAbilitySpecHandle AbilitySpecHandle = ASC->GiveAbility(AbilitySpec); + Handles.Add(ASC, AbilitySpecHandle); +} + +void FGlobalAppliedAbilityList::RemoveFromASC(UALSAbilitySystemComponent* ASC) +{ + if (FGameplayAbilitySpecHandle* SpecHandle = Handles.Find(ASC)) + { + ASC->ClearAbility(*SpecHandle); + Handles.Remove(ASC); + } +} + +void FGlobalAppliedAbilityList::RemoveFromAll() +{ + for (auto& KVP : Handles) + { + if (KVP.Key != nullptr) + { + KVP.Key->ClearAbility(KVP.Value); + } + } + Handles.Empty(); +} + + + +void FGlobalAppliedEffectList::AddToASC(TSubclassOf Effect, UALSAbilitySystemComponent* ASC) +{ + if (FActiveGameplayEffectHandle* EffectHandle = Handles.Find(ASC)) + { + RemoveFromASC(ASC); + } + + const UGameplayEffect* GameplayEffectCDO = Effect->GetDefaultObject(); + const FActiveGameplayEffectHandle GameplayEffectHandle = ASC->ApplyGameplayEffectToSelf(GameplayEffectCDO, /*Level=*/ 1, ASC->MakeEffectContext()); + Handles.Add(ASC, GameplayEffectHandle); +} + +void FGlobalAppliedEffectList::RemoveFromASC(UALSAbilitySystemComponent* ASC) +{ + if (FActiveGameplayEffectHandle* EffectHandle = Handles.Find(ASC)) + { + ASC->RemoveActiveGameplayEffect(*EffectHandle); + Handles.Remove(ASC); + } +} + +void FGlobalAppliedEffectList::RemoveFromAll() +{ + for (auto& KVP : Handles) + { + if (KVP.Key != nullptr) + { + KVP.Key->RemoveActiveGameplayEffect(KVP.Value); + } + } + Handles.Empty(); +} + +UALSGlobalAbilitySystem::UALSGlobalAbilitySystem() +{ +} + +void UALSGlobalAbilitySystem::ApplyAbilityToAll(TSubclassOf Ability) +{ + if ((Ability.Get() != nullptr) && (!AppliedAbilities.Contains(Ability))) + { + FGlobalAppliedAbilityList& Entry = AppliedAbilities.Add(Ability); + for (UALSAbilitySystemComponent* ASC : RegisteredASCs) + { + Entry.AddToASC(Ability, ASC); + } + } +} + +void UALSGlobalAbilitySystem::ApplyEffectToAll(TSubclassOf Effect) +{ + if ((Effect.Get() != nullptr) && (!AppliedEffects.Contains(Effect))) + { + FGlobalAppliedEffectList& Entry = AppliedEffects.Add(Effect); + for (UALSAbilitySystemComponent* ASC : RegisteredASCs) + { + Entry.AddToASC(Effect, ASC); + } + } +} + +void UALSGlobalAbilitySystem::RemoveAbilityFromAll(TSubclassOf Ability) +{ + if ((Ability.Get() != nullptr) && AppliedAbilities.Contains(Ability)) + { + FGlobalAppliedAbilityList& Entry = AppliedAbilities[Ability]; + Entry.RemoveFromAll(); + AppliedAbilities.Remove(Ability); + } +} + +void UALSGlobalAbilitySystem::RemoveEffectFromAll(TSubclassOf Effect) +{ + if ((Effect.Get() != nullptr) && AppliedEffects.Contains(Effect)) + { + FGlobalAppliedEffectList& Entry = AppliedEffects[Effect]; + Entry.RemoveFromAll(); + AppliedEffects.Remove(Effect); + } +} + +void UALSGlobalAbilitySystem::RegisterASC(UALSAbilitySystemComponent* ASC) +{ + check(ASC); + + for (auto& Entry : AppliedAbilities) + { + Entry.Value.AddToASC(Entry.Key, ASC); + } + for (auto& Entry : AppliedEffects) + { + Entry.Value.AddToASC(Entry.Key, ASC); + } + + RegisteredASCs.AddUnique(ASC); +} + +void UALSGlobalAbilitySystem::UnregisterASC(UALSAbilitySystemComponent* ASC) +{ + check(ASC); + for (auto& Entry : AppliedAbilities) + { + Entry.Value.RemoveFromASC(ASC); + } + for (auto& Entry : AppliedEffects) + { + Entry.Value.RemoveFromASC(ASC); + } + + RegisteredASCs.Remove(ASC); +} diff --git a/Source/ALSV4_CPP/Private/AbilitySystem/Abilities/ALSGameplayAbility.cpp b/Source/ALSV4_CPP/Private/AbilitySystem/Abilities/ALSGameplayAbility.cpp new file mode 100644 index 00000000..f992d490 --- /dev/null +++ b/Source/ALSV4_CPP/Private/AbilitySystem/Abilities/ALSGameplayAbility.cpp @@ -0,0 +1,546 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "AbilitySystem/Abilities/ALSGameplayAbility.h" +#include "ALSLogChannels.h" +#include "AbilitySystem/ALSAbilitySystemComponent.h" +#include "Camera/ALSCameraMode.h" +#include "Player/ALSPlayerController2.h" +#include "Character/ALSCharacter.h" +#include "ALSGameplayTags.h" +#include "AbilitySystem/Abilities/ALSAbilityCost.h" +#include "Character/ALSHeroComponent.h" +#include "AbilitySystemBlueprintLibrary.h" +#include "AbilitySystemGlobals.h" +#include "AbilitySystemLog.h" +#include "AbilitySystem/Abilities/ALSAbilitySimpleFailureMessage.h" +#include "GameFramework/GameplayMessageSubsystem.h" +#include "AbilitySystem/ALSAbilitySourceInterface.h" +#include "AbilitySystem/ALSGameplayEffectContext.h" +#include "Physics/PhysicalMaterialWithTags.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSGameplayAbility) + +#define ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(FunctionName, ReturnValue) \ +{ \ +if (!ensure(IsInstantiated())) \ +{ \ +ABILITY_LOG(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"); + +UALSGameplayAbility::UALSGameplayAbility(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo; + InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor; + NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted; + NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ClientOrServer; + + ActivationPolicy = EALSAbilityActivationPolicy::OnInputTriggered; + ActivationGroup = EALSAbilityActivationGroup::Independent; + + ActiveCameraMode = nullptr; +} + +UALSAbilitySystemComponent* UALSGameplayAbility::GetALSAbilitySystemComponentFromActorInfo() const +{ + return (CurrentActorInfo ? Cast(CurrentActorInfo->AbilitySystemComponent.Get()) : nullptr); +} + +AALSPlayerController2* UALSGameplayAbility::GetALSPlayerControllerFromActorInfo() const +{ + return (CurrentActorInfo ? Cast(CurrentActorInfo->PlayerController.Get()) : nullptr); +} + +AController* UALSGameplayAbility::GetControllerFromActorInfo() const +{ + if (CurrentActorInfo) + { + if (AController* PC = CurrentActorInfo->PlayerController.Get()) + { + return PC; + } + + // Look for a player controller or pawn in the owner chain. + AActor* TestActor = CurrentActorInfo->OwnerActor.Get(); + while (TestActor) + { + if (AController* C = Cast(TestActor)) + { + return C; + } + + if (APawn* Pawn = Cast(TestActor)) + { + return Pawn->GetController(); + } + + TestActor = TestActor->GetOwner(); + } + } + + return nullptr; +} + +AALSCharacter* UALSGameplayAbility::GetALSCharacterFromActorInfo() const +{ + return (CurrentActorInfo ? Cast(CurrentActorInfo->AvatarActor.Get()) : nullptr); +} + +UALSHeroComponent* UALSGameplayAbility::GetHeroComponentFromActorInfo() const +{ + return (CurrentActorInfo ? UALSHeroComponent::FindHeroComponent(CurrentActorInfo->AvatarActor.Get()) : nullptr); +} + +void UALSGameplayAbility::NativeOnAbilityFailedToActivate(const FGameplayTagContainer& FailedReason) const +{ + bool bSimpleFailureFound = false; + for (FGameplayTag Reason : FailedReason) + { + if (!bSimpleFailureFound) + { + if (const FText* pUserFacingMessage = FailureTagToUserFacingMessages.Find(Reason)) + { + FALSAbilitySimpleFailureMessage 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); + bSimpleFailureFound = true; + } + } + + if (UAnimMontage* pMontage = FailureTagToAnimMontage.FindRef(Reason)) + { + FALSAbilityMontageFailureMessage Message; + Message.PlayerController = GetActorInfo().PlayerController.Get(); + Message.FailureTags = FailedReason; + Message.FailureMontage = pMontage; + + UGameplayMessageSubsystem& MessageSystem = UGameplayMessageSubsystem::Get(GetWorld()); + MessageSystem.BroadcastMessage(TAG_ABILITY_PLAY_MONTAGE_FAILURE_MESSAGE, Message); + } + } +} + +bool UALSGameplayAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const +{ + if (!ActorInfo || !ActorInfo->AbilitySystemComponent.IsValid()) + { + return false; + } + + UALSAbilitySystemComponent* ALSASC = CastChecked(ActorInfo->AbilitySystemComponent.Get()); + const FALSGameplayTags& GameplayTags = FALSGameplayTags::Get(); + + if (!Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags)) + { + return false; + } + + //@TODO Possibly remove after setting up tag relationships + if (ALSASC->IsActivationGroupBlocked(ActivationGroup)) + { + if (OptionalRelevantTags) + { + OptionalRelevantTags->AddTag(GameplayTags.Ability_ActivateFail_ActivationGroup); + } + return false; + } + + return true; +} + +void UALSGameplayAbility::SetCanBeCanceled(bool bCanBeCanceled) +{ + // The ability can not block canceling if it's replaceable. + if (!bCanBeCanceled && (ActivationGroup == EALSAbilityActivationGroup::Exclusive_Replaceable)) + { + UE_LOG(LogALSAbilitySystem, Error, TEXT("SetCanBeCanceled: Ability [%s] can not block canceling because its activation group is replaceable."), *GetName()); + return; + } + + Super::SetCanBeCanceled(bCanBeCanceled); +} + +void UALSGameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) +{ + Super::OnGiveAbility(ActorInfo, Spec); + + K2_OnAbilityAdded(); + + TryActivateAbilityOnSpawn(ActorInfo, Spec); +} + +void UALSGameplayAbility::OnRemoveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) +{ + K2_OnAbilityRemoved(); + + Super::OnRemoveAbility(ActorInfo, Spec); +} + +void UALSGameplayAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) +{ + Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); +} + +void UALSGameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) +{ + ClearCameraMode(); + + Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); +} + +bool UALSGameplayAbility::CheckCost(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, OUT FGameplayTagContainer* OptionalRelevantTags) const +{ + if (!Super::CheckCost(Handle, ActorInfo, OptionalRelevantTags) || !ActorInfo) + { + return false; + } + + // Verify we can afford any additional costs + for (TObjectPtr AdditionalCost : AdditionalCosts) + { + if (AdditionalCost != nullptr) + { + if (!AdditionalCost->CheckCost(this, Handle, ActorInfo, /*inout*/ OptionalRelevantTags)) + { + return false; + } + } + } + + return true; +} + +void UALSGameplayAbility::ApplyCost(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const +{ + Super::ApplyCost(Handle, ActorInfo, ActivationInfo); + + 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 (UALSAbilitySystemComponent* ASC = Cast(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 bAbilityHitTarget = false; + bool bHasDeterminedIfAbilityHitTarget = false; + for (TObjectPtr AdditionalCost : AdditionalCosts) + { + if (AdditionalCost != nullptr) + { + if (AdditionalCost->ShouldOnlyApplyCostOnHit()) + { + if (!bHasDeterminedIfAbilityHitTarget) + { + bAbilityHitTarget = DetermineIfAbilityHitTarget(); + bHasDeterminedIfAbilityHitTarget = true; + } + + if (!bAbilityHitTarget) + { + continue; + } + } + + AdditionalCost->ApplyCost(this, Handle, ActorInfo, ActivationInfo); + } + } +} + +FGameplayEffectContextHandle UALSGameplayAbility::MakeEffectContext(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo) const +{ + FGameplayEffectContextHandle ContextHandle = Super::MakeEffectContext(Handle, ActorInfo); + + FALSGameplayEffectContext* EffectContext = FALSGameplayEffectContext::ExtractEffectContext(ContextHandle); + check(EffectContext); + + check(ActorInfo); + + AActor* EffectCauser = nullptr; + const IALSAbilitySourceInterface* 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 UALSGameplayAbility::ApplyAbilityTagsToGameplayEffectSpec(FGameplayEffectSpec& Spec, FGameplayAbilitySpec* AbilitySpec) const +{ + Super::ApplyAbilityTagsToGameplayEffectSpec(Spec, AbilitySpec); + + if (const FHitResult* HitResult = Spec.GetContext().GetHitResult()) + { + if (const UPhysicalMaterialWithTags* PhysMatWithTags = Cast(HitResult->PhysMaterial.Get())) + { + Spec.CapturedTargetTags.GetSpecTags().AppendTags(PhysMatWithTags->Tags); + } + } +} + +bool UALSGameplayAbility::DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const +{ + // Specialized version to handle death exclusion and AbilityTags expansion via ASC + + bool bBlocked = false; + bool bMissing = 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(AbilityTags)) + { + bBlocked = true; + } + + const UALSAbilitySystemComponent* ALSASC = Cast(&AbilitySystemComponent); + static FGameplayTagContainer AllRequiredTags; + static FGameplayTagContainer AllBlockedTags; + + AllRequiredTags = ActivationRequiredTags; + AllBlockedTags = ActivationBlockedTags; + + // Expand our ability tags to add additional required/blocked tags + if (ALSASC) + { + ALSASC->GetAdditionalActivationTagRequirements(AbilityTags, 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)) + { + const FALSGameplayTags& GameplayTags = FALSGameplayTags::Get(); + if (OptionalRelevantTags && AbilitySystemComponentTags.HasTag(GameplayTags.Status_Death)) + { + // If player is dead and was rejected due to blocking tags, give that feedback + OptionalRelevantTags->AddTag(GameplayTags.Ability_ActivateFail_IsDead); + } + + bBlocked = true; + } + + if (!AbilitySystemComponentTags.HasAll(AllRequiredTags)) + { + bMissing = true; + } + } + + if (SourceTags != nullptr) + { + if (SourceBlockedTags.Num() || SourceRequiredTags.Num()) + { + if (SourceTags->HasAny(SourceBlockedTags)) + { + bBlocked = true; + } + + if (!SourceTags->HasAll(SourceRequiredTags)) + { + bMissing = true; + } + } + } + + if (TargetTags != nullptr) + { + if (TargetBlockedTags.Num() || TargetRequiredTags.Num()) + { + if (TargetTags->HasAny(TargetBlockedTags)) + { + bBlocked = true; + } + + if (!TargetTags->HasAll(TargetRequiredTags)) + { + bMissing = true; + } + } + } + + if (bBlocked) + { + if (OptionalRelevantTags && BlockedTag.IsValid()) + { + OptionalRelevantTags->AddTag(BlockedTag); + } + return false; + } + if (bMissing) + { + if (OptionalRelevantTags && MissingTag.IsValid()) + { + OptionalRelevantTags->AddTag(MissingTag); + } + return false; + } + + return true; +} + +void UALSGameplayAbility::OnPawnAvatarSet() +{ + K2_OnPawnAvatarSet(); +} + +void UALSGameplayAbility::GetAbilitySource(FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, float& OutSourceLevel, const IALSAbilitySourceInterface*& 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(SourceObject); +} + +void UALSGameplayAbility::TryActivateAbilityOnSpawn(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) const +{ + const bool bIsPredicting = (Spec.ActivationInfo.ActivationMode == EGameplayAbilityActivationMode::Predicting); + + // Try to activate if activation policy is on spawn. + if (ActorInfo && !Spec.IsActive() && !bIsPredicting && (ActivationPolicy == EALSAbilityActivationPolicy::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 bIsLocalExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalPredicted) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalOnly); + const bool bIsServerExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerOnly) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerInitiated); + + const bool bClientShouldActivate = ActorInfo->IsLocallyControlled() && bIsLocalExecution; + const bool bServerShouldActivate = ActorInfo->IsNetAuthority() && bIsServerExecution; + + if (bClientShouldActivate || bServerShouldActivate) + { + ASC->TryActivateAbility(Spec.Handle); + } + } + } +} + +bool UALSGameplayAbility::CanChangeActivationGroup(EALSAbilityActivationGroup NewGroup) const +{ + if (!IsInstantiated() || !IsActive()) + { + return false; + } + + if (ActivationGroup == NewGroup) + { + return true; + } + + UALSAbilitySystemComponent* ALSASC = GetALSAbilitySystemComponentFromActorInfo(); + check(ALSASC); + + if ((ActivationGroup != EALSAbilityActivationGroup::Exclusive_Blocking) && ALSASC->IsActivationGroupBlocked(NewGroup)) + { + // This ability can't change groups if it's blocked (unless it is the one doing the blocking). + return false; + } + + if ((NewGroup == EALSAbilityActivationGroup::Exclusive_Replaceable) && !CanBeCanceled()) + { + // This ability can't become replaceable if it can't be canceled. + return false; + } + + return true; +} + +bool UALSGameplayAbility::ChangeActivationGroup(EALSAbilityActivationGroup NewGroup) +{ + ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(ChangeActivationGroup, false); + + if (!CanChangeActivationGroup(NewGroup)) + { + return false; + } + + if (ActivationGroup != NewGroup) + { + UALSAbilitySystemComponent* ALSASC = GetALSAbilitySystemComponentFromActorInfo(); + check(ALSASC); + + ALSASC->RemoveAbilityFromActivationGroup(ActivationGroup, this); + ALSASC->AddAbilityToActivationGroup(NewGroup, this); + + ActivationGroup = NewGroup; + } + + return true; +} + +void UALSGameplayAbility::SetCameraMode(TSubclassOf CameraMode) +{ + ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(SetCameraMode, ); + + if (UALSHeroComponent* HeroComponent = GetHeroComponentFromActorInfo()) + { + HeroComponent->SetAbilityCameraMode(CameraMode, CurrentSpecHandle); + ActiveCameraMode = CameraMode; + } +} + +void UALSGameplayAbility::ClearCameraMode() +{ + ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(ClearCameraMode, ); + + if (ActiveCameraMode) + { + if (UALSHeroComponent* HeroComponent = GetHeroComponentFromActorInfo()) + { + HeroComponent->ClearAbilityCameraMode(CurrentSpecHandle); + } + + ActiveCameraMode = nullptr; + } +} diff --git a/Source/ALSV4_CPP/Private/Camera/ALSCameraComponent.cpp b/Source/ALSV4_CPP/Private/Camera/ALSCameraComponent.cpp new file mode 100644 index 00000000..bd94bf57 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Camera/ALSCameraComponent.cpp @@ -0,0 +1,8 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Camera/ALSCameraComponent.h" + +UALSCameraComponent::UALSCameraComponent(const FObjectInitializer& ObjectInitializer) +{ +} diff --git a/Source/ALSV4_CPP/Private/Camera/ALSCameraMode.cpp b/Source/ALSV4_CPP/Private/Camera/ALSCameraMode.cpp new file mode 100644 index 00000000..4d0829ba --- /dev/null +++ b/Source/ALSV4_CPP/Private/Camera/ALSCameraMode.cpp @@ -0,0 +1,4 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Camera/ALSCameraMode.h" diff --git a/Source/ALSV4_CPP/Private/Character/ALSBaseCharacter.cpp b/Source/ALSV4_CPP/Private/Character/ALSBaseCharacter.cpp index 899f7fdc..6d943ea8 100644 --- a/Source/ALSV4_CPP/Private/Character/ALSBaseCharacter.cpp +++ b/Source/ALSV4_CPP/Private/Character/ALSBaseCharacter.cpp @@ -587,6 +587,25 @@ FVector AALSBaseCharacter::GetMovementInput() const return ReplicatedCurrentAcceleration; } +void AALSBaseCharacter::RollStart() +{ + // Roll + if (bHasMovementInput) + { + float MovementInputYaw, MovementInputPitch; + const FVector InputForwardVector = UKismetMathLibrary::GetForwardVector(GetControlRotation()) * LastForwardInput; + const FVector InputRightVector = UKismetMathLibrary::GetRightVector(GetControlRotation()) * LastRightInput; + UKismetMathLibrary::GetYawPitchFromVector( + GetCharacterMovement()->GetLastInputVector(), MovementInputYaw,MovementInputPitch + ); + FRotator RollRotator = GetActorRotation(); + RollRotator.Yaw = MovementInputYaw; + SetActorRotation(RollRotator); + } + + Replicated_PlayMontage(GetRollAnimation(), 1.15f); +} + float AALSBaseCharacter::GetAnimCurveValue(FName CurveName) const { if (GetMesh()->GetAnimInstance()) @@ -638,6 +657,35 @@ FTransform AALSBaseCharacter::GetThirdPersonPivotTarget() return GetActorTransform(); } +FTransform AALSBaseCharacter::GetTopDownPivotTarget() +{ + return GetActorTransform(); +} + +FTransform AALSBaseCharacter::GetCurrentPivotTarget() +{ + if (ViewMode == EALSViewMode::ThirdPerson) + { + return GetThirdPersonPivotTarget(); + } + if (ViewMode == EALSViewMode::TopDown) + { + return GetTopDownPivotTarget(); + } + + return GetActorTransform(); +} + +FRotator AALSBaseCharacter::GetCurrentCameraControlRotation() const +{ + if (ViewMode == EALSViewMode::TopDown) + { + return TopDownCameraRotation; + } + + return GetControlRotation(); +} + FVector AALSBaseCharacter::GetFirstPersonCameraTarget() { return GetMesh()->GetSocketLocation(NAME_FP_Camera); @@ -650,6 +698,11 @@ void AALSBaseCharacter::GetCameraParameters(float& TPFOVOut, float& FPFOVOut, bo bRightShoulderOut = bRightShoulder; } +bool AALSBaseCharacter::CanPlayCameraShake() const +{ + return ViewMode != EALSViewMode::TopDown; +} + void AALSBaseCharacter::RagdollUpdate(float DeltaTime) { GetMesh()->bOnlyAllowAutonomousTickPose = false; @@ -854,7 +907,7 @@ void AALSBaseCharacter::OnGaitChanged(const EALSGait PreviousGait) void AALSBaseCharacter::OnViewModeChanged(const EALSViewMode PreviousViewMode) { - if (ViewMode == EALSViewMode::ThirdPerson) + if (ViewMode == EALSViewMode::ThirdPerson || ViewMode == EALSViewMode::TopDown) { if (RotationMode == EALSRotationMode::VelocityDirection || RotationMode == EALSRotationMode::LookingDirection) { @@ -949,6 +1002,7 @@ void AALSBaseCharacter::SetEssentialValues(float DeltaTime) { ReplicatedCurrentAcceleration = GetCharacterMovement()->GetCurrentAcceleration(); ReplicatedControlRotation = GetControlRotation(); + ReplicatedControlRotation.Pitch = 0; EasedMaxAcceleration = GetCharacterMovement()->GetMaxAcceleration(); } else @@ -1089,7 +1143,13 @@ void AALSBaseCharacter::UpdateGroundedRotation(float DeltaTime) // Rolling Rotation (Not allowed on networked games) if (!bEnableNetworkOptimizations && bHasMovementInput) { - SmoothCharacterRotation({0.0f, LastMovementInputRotation.Yaw, 0.0f}, 0.0f, 2.0f, DeltaTime); + /*UE_LOG(LogTemp, Warning, TEXT("Yaw: %f Pitch %f | Forward %s Right %s"), + MovementInputYaw, MovementInputPitch, *InputForwardVector.ToString(), *InputRightVector.ToString());*/ + + SmoothCharacterRotation( + {0.0f, LastMovementInputRotation.Yaw, 0.0f}, + 0.0f, 2.0f, DeltaTime + ); } } @@ -1207,8 +1267,13 @@ void AALSBaseCharacter::ForwardMovementAction_Implementation(float Value) { if (MovementState == EALSMovementState::Grounded || MovementState == EALSMovementState::InAir) { + // Get the Yaw Rotation based on the View Mode + const float YawRotation = ViewMode == EALSViewMode::TopDown ? 1.0f : AimingRotation.Yaw; + // Default camera relative movement behavior - const FRotator DirRotator(0.0f, AimingRotation.Yaw, 0.0f); + LastForwardInput = Value; + UE_LOG(LogTemp, Warning, TEXT("ForwardMovementAction_Implementation: %f"), Value); + const FRotator DirRotator(0.0f, YawRotation, 0.0f); AddMovementInput(UKismetMathLibrary::GetForwardVector(DirRotator), Value); } } @@ -1217,20 +1282,43 @@ void AALSBaseCharacter::RightMovementAction_Implementation(float Value) { if (MovementState == EALSMovementState::Grounded || MovementState == EALSMovementState::InAir) { + const float YawRotation = ViewMode == EALSViewMode::TopDown ? -1.0f : AimingRotation.Yaw; + // Default camera relative movement behavior - const FRotator DirRotator(0.0f, AimingRotation.Yaw, 0.0f); + LastRightInput = Value; + const FRotator DirRotator(0.0f, YawRotation, 0.0f); AddMovementInput(UKismetMathLibrary::GetRightVector(DirRotator), Value); } } void AALSBaseCharacter::CameraUpAction_Implementation(float Value) { - AddControllerPitchInput(LookUpDownRate * Value); + LastCameraUpInput = Value; + + if (GetViewMode() != EALSViewMode::TopDown) + { + AddControllerPitchInput(LookUpDownRate * Value); + } + else + { + FVector CameraVector = FVector(LastCameraUpInput, LastCameraRightInput, 0.0f); + GetController()->SetControlRotation(UKismetMathLibrary::MakeRotFromX(CameraVector)); + } } void AALSBaseCharacter::CameraRightAction_Implementation(float Value) { - AddControllerYawInput(LookLeftRightRate * Value); + LastCameraRightInput = Value; + + if (GetViewMode() != EALSViewMode::TopDown) + { + AddControllerYawInput(LookLeftRightRate * Value); + } + else + { + FVector CameraVector = FVector(LastCameraUpInput, LastCameraRightInput, 0.0f); + GetController()->SetControlRotation(UKismetMathLibrary::MakeRotFromX(CameraVector)); + } } void AALSBaseCharacter::JumpAction_Implementation(bool bValue) @@ -1342,9 +1430,9 @@ void AALSBaseCharacter::StanceAction_Implementation() if (LastStanceInputTime - PrevStanceInputTime <= RollDoubleTapTimeout) { - // Roll - Replicated_PlayMontage(GetRollAnimation(), 1.15f); - + // Double tap + RollStart(); + if (Stance == EALSStance::Standing) { SetDesiredStance(EALSStance::Crouching); diff --git a/Source/ALSV4_CPP/Private/Character/ALSCharacter.cpp b/Source/ALSV4_CPP/Private/Character/ALSCharacter.cpp index 6b962691..2b0e180b 100644 --- a/Source/ALSV4_CPP/Private/Character/ALSCharacter.cpp +++ b/Source/ALSV4_CPP/Private/Character/ALSCharacter.cpp @@ -4,8 +4,11 @@ #include "Character/ALSCharacter.h" +#include "ALSGameplayTags.h" +#include "AbilitySystem/ALSAbilitySystemComponent.h" #include "Engine/StaticMesh.h" #include "AI/ALSAIController.h" +#include "Character/ALSPawnExtensionComponent.h" #include "Kismet/GameplayStatics.h" AALSCharacter::AALSCharacter(const FObjectInitializer& ObjectInitializer) @@ -20,9 +23,23 @@ AALSCharacter::AALSCharacter(const FObjectInitializer& ObjectInitializer) StaticMesh = CreateDefaultSubobject(TEXT("StaticMesh")); StaticMesh->SetupAttachment(HeldObjectRoot); + PawnExtComponent = CreateDefaultSubobject(TEXT("PawnExtensionComponent")); + PawnExtComponent->OnAbilitySystemInitialized_RegisterAndCall(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnAbilitySystemInitialized)); + PawnExtComponent->OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnAbilitySystemUninitialized)); + AIControllerClass = AALSAIController::StaticClass(); } +UALSAbilitySystemComponent* AALSCharacter::GetALSAbilitySystemComponent() const +{ + return Cast(GetAbilitySystemComponent()); +} + +UAbilitySystemComponent* AALSCharacter::GetAbilitySystemComponent() const +{ + return PawnExtComponent->GetALSAbilitySystemComponent(); +} + void AALSCharacter::ClearHeldObject() { StaticMesh->SetStaticMesh(nullptr); @@ -84,25 +101,167 @@ ECollisionChannel AALSCharacter::GetThirdPersonTraceParams(FVector& TraceOrigin, } FTransform AALSCharacter::GetThirdPersonPivotTarget() -{ +{ return FTransform(GetActorRotation(), (GetMesh()->GetSocketLocation(TEXT("Head")) + GetMesh()->GetSocketLocation(TEXT("root"))) / 2.0f, FVector::OneVector); } +FTransform AALSCharacter::GetTopDownPivotTarget() +{ + return FTransform(GetActorRotation(), + (GetMesh()->GetSocketLocation(TEXT("Head")) + TopDownPivotOffset), + FVector::OneVector); +} + FVector AALSCharacter::GetFirstPersonCameraTarget() { return GetMesh()->GetSocketLocation(TEXT("FP_Camera")); } +void AALSCharacter::OnAbilitySystemInitialized() +{ + UALSAbilitySystemComponent* ALSASC = GetALSAbilitySystemComponent(); + check(ALSASC); +} + +void AALSCharacter::OnAbilitySystemUninitialized() +{ + // TODO: Unititialize components +} + +void AALSCharacter::PossessedBy(AController* NewController) +{ + Super::PossessedBy(NewController); + + PawnExtComponent->HandleControllerChanged(); +} + +void AALSCharacter::UnPossessed() +{ + AController* const OldController = Controller; + + Super::UnPossessed(); + + PawnExtComponent->HandleControllerChanged(); +} + +void AALSCharacter::OnRep_Controller() +{ + Super::OnRep_Controller(); + + PawnExtComponent->HandleControllerChanged(); +} + +void AALSCharacter::OnRep_PlayerState() +{ + Super::OnRep_PlayerState(); + + PawnExtComponent->HandlePlayerStateReplicated(); +} + +void AALSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + + PawnExtComponent->SetupPlayerInputComponent(); +} + +void AALSCharacter::InitializeGameplayTags() +{ + // Clear tags that may be lingering on the ability system from the previous pawn. + if (UALSAbilitySystemComponent* ALSASC = GetALSAbilitySystemComponent()) + { + const FALSGameplayTags& GameplayTags = FALSGameplayTags::Get(); + + for (const TPair& TagMapping : GameplayTags.MovementModeTagMap) + { + if (TagMapping.Value.IsValid()) + { + ALSASC->SetLooseGameplayTagCount(TagMapping.Value, 0); + } + } + + for (const TPair& TagMapping : GameplayTags.CustomMovementModeTagMap) + { + if (TagMapping.Value.IsValid()) + { + ALSASC->SetLooseGameplayTagCount(TagMapping.Value, 0); + } + } + + //UALSCharacterMovementComponent* ALSMoveComp = CastChecked(GetCharacterMovement()); + //SetMovementModeTag(ALSMoveComp->MovementMode, ALSMoveComp->CustomMovementMode, true); + } +} + + void AALSCharacter::OnOverlayStateChanged(EALSOverlayState PreviousState) { Super::OnOverlayStateChanged(PreviousState); UpdateHeldObject(); } +void AALSCharacter::OnViewModeChanged(EALSViewMode PreviousViewMode) +{ + Super::OnViewModeChanged(PreviousViewMode); + + if (ViewMode == EALSViewMode::TopDown) + { + EnableCursor(true); + } + else + { + EnableCursor(false); + } +} + +void AALSCharacter::EnableCursor(bool bEnable) +{ + if (!PlayerController) + { + return; + } + UE_LOG(LogTemp, Warning, TEXT("EanbleCursor: %s"), bEnable ? TEXT("true") : TEXT("false")); + + /*PlayerController->bShowMouseCursor = bEnable; + PlayerController->bEnableClickEvents = bEnable; + PlayerController->bEnableMouseOverEvents = bEnable;*/ +} + +void AALSCharacter::UpdateAimMovement(float DeltaTime) +{ + if (!PlayerController + || GetViewMode() != EALSViewMode::TopDown + || GetRotationMode() != EALSRotationMode::LookingDirection) + { + return; + } + + FVector MouseWorldLocation, MouseWorldDirection; + const bool SuccessConvert = PlayerController->DeprojectMousePositionToWorld(MouseWorldLocation, MouseWorldDirection); + + if (SuccessConvert) + { + // Find intersect point with plane originating on actor + FVector ActorLocation = GetActorLocation(); + FVector AimTargetLocation = FMath::LinePlaneIntersection( + MouseWorldLocation, + MouseWorldLocation + (MouseWorldDirection * 10000.f), + ActorLocation, + FVector{ 0.f, 0.f, 1.f }); + + // Change actor's yaw rotation + FRotator ActorRotation = GetActorRotation(); + ActorRotation.Yaw = (AimTargetLocation - ActorLocation).Rotation().Yaw; + PlayerController->SetControlRotation(ActorRotation); + } +} + void AALSCharacter::Tick(float DeltaTime) { + UpdateAimMovement(DeltaTime); + Super::Tick(DeltaTime); UpdateHeldObjectAnimations(); @@ -111,6 +270,13 @@ void AALSCharacter::Tick(float DeltaTime) void AALSCharacter::BeginPlay() { Super::BeginPlay(); + + PlayerController = Cast(GetController()); + + /*if (ViewMode == EALSViewMode::TopDown) + { + EnableCursor(true); + }*/ UpdateHeldObject(); } diff --git a/Source/ALSV4_CPP/Private/Character/ALSHeroComponent.cpp b/Source/ALSV4_CPP/Private/Character/ALSHeroComponent.cpp new file mode 100644 index 00000000..38e86f42 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Character/ALSHeroComponent.cpp @@ -0,0 +1,517 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Character/ALSHeroComponent.h" +#include "ALSLogChannels.h" +#include "GameFramework/Pawn.h" +#include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" +#include "Player/ALSPlayerController2.h" +#include "Player/ALSPlayerState.h" +#include "Character/ALSPawnExtensionComponent.h" +#include "Character/ALSPawnData.h" +#include "Character/ALSCharacter.h" +#include "AbilitySystem/ALSAbilitySystemComponent.h" +#include "Input/ALSInputConfig.h" +#include "Input/ALSInputComponent.h" +// #include "Camera/ALSCameraComponent.h" +#include "ALSGameplayTags.h" +#include "Engine/LocalPlayer.h" +#include "Camera/ALSCameraMode.h" +#include "Components/GameFrameworkComponentManager.h" +#include "Settings/ALSSettingsLocal.h" +#include "System/ALSAssetManager.h" +#include "PlayerMappableInputConfig.h" +#include "Camera/ALSCameraComponent.h" + + +#if WITH_EDITOR +#include "Misc/UObjectToken.h" +#endif // WITH_EDITOR + +namespace ALSHero +{ + static const float LookYawRate = 300.0f; + static const float LookPitchRate = 165.0f; +}; + +const FName UALSHeroComponent::NAME_BindInputsNow("BindInputsNow"); + +UALSHeroComponent::UALSHeroComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + // AbilityCameraMode = nullptr; + bPawnHasInitialized = false; + bReadyToBindInputs = false; +} + +void UALSHeroComponent::OnRegister() +{ + Super::OnRegister(); + + UE_LOG(LogTemp, Warning, TEXT("UALSHeroComponent::OnRegister()")) + + if (const APawn* Pawn = GetPawn()) + { + if (UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) + { + UE_LOG(LogTemp, Warning, TEXT("UALSHeroComponent::FindPawnExtensionComponent()")) + PawnExtComp->OnPawnReadyToInitialize_RegisterAndCall(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnPawnReadyToInitialize)); + } + } + else + { + UE_LOG(LogALS, Error, TEXT("[UALSHeroComponent::OnRegister] 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("ALSHeroComponent", "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("ALSHeroComponent"); + + FMessageLog(HeroMessageLogName).Error() + ->AddToken(FUObjectToken::Create(this, FText::FromString(GetNameSafe(this)))) + ->AddToken(FTextToken::Create(Message)); + + FMessageLog(HeroMessageLogName).Open(); + } +#endif + } +} + +bool UALSHeroComponent::IsPawnComponentReadyToInitialize() const +{ + // The player state is required. + if (!GetPlayerState()) + { + return false; + } + + const APawn* Pawn = GetPawn(); + + // A pawn is required. + if (!Pawn) + { + 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(); + + const bool bHasControllerPairedWithPS = (Controller != nullptr) && \ + (Controller->PlayerState != nullptr) && \ + (Controller->PlayerState->GetOwner() == Controller); + + if (!bHasControllerPairedWithPS) + { + return false; + } + } + + const bool bIsLocallyControlled = Pawn->IsLocallyControlled(); + const bool bIsBot = Pawn->IsBotControlled(); + + if (bIsLocallyControlled && !bIsBot) + { + // The input component is required when locally controlled. + if (!Pawn->InputComponent) + { + return false; + } + } + + return true; +} + +void UALSHeroComponent::OnPawnReadyToInitialize() +{ + UE_LOG(LogTemp, Warning, TEXT("UALSHeroComponent::OnPawnReadyToInitialize")) + + if (!ensure(!bPawnHasInitialized)) + { + // Don't initialize twice + return; + } + + APawn* Pawn = GetPawn(); + if (!Pawn) + { + return; + } + const bool bIsLocallyControlled = Pawn->IsLocallyControlled(); + + AALSPlayerState* ALSPS = GetPlayerState(); + check(ALSPS); + + const UALSPawnData* PawnData = nullptr; + + if (UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) + { + PawnData = PawnExtComp->GetPawnData(); + + // 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(ALSPS->GetALSAbilitySystemComponent(), ALSPS); + } + + if (AALSPlayerController2* ALSPC = GetController()) + { + if (Pawn->InputComponent != nullptr) + { + InitializePlayerInput(Pawn->InputComponent); + } + } + + if (bIsLocallyControlled && PawnData) + { + if (UALSCameraComponent* CameraComponent = UALSCameraComponent::FindCameraComponent(Pawn)) + { + CameraComponent->DetermineCameraModeDelegate.BindUObject(this, &ThisClass::DetermineCameraMode); + } + } + + bPawnHasInitialized = true; +} + +void UALSHeroComponent::BeginPlay() +{ + Super::BeginPlay(); +} + +void UALSHeroComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (const APawn* Pawn = GetPawn()) + { + if (UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) + { + PawnExtComp->UninitializeAbilitySystem(); + } + } + + Super::EndPlay(EndPlayReason); +} + +void UALSHeroComponent::InitializePlayerInput(UInputComponent* PlayerInputComponent) +{ + UE_LOG(LogTemp, Warning, TEXT("UALSHeroComponent::InitializePlayerInput")) + check(PlayerInputComponent); + + const APawn* Pawn = GetPawn(); + if (!Pawn) + { + return; + } + + const APlayerController* PC = GetController(); + check(PC); + + const ULocalPlayer* LP = PC->GetLocalPlayer(); + check(LP); + + UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem(); + check(Subsystem); + + Subsystem->ClearAllMappings(); + + if (const UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) + { + if (const UALSPawnData* PawnData = PawnExtComp->GetPawnData()) + { + if (const UALSInputConfig* InputConfig = PawnData->InputConfig) + { + const FALSGameplayTags& GameplayTags = FALSGameplayTags::Get(); + + // Register any default input configs with the settings so that they will be applied to the player during AddInputMappings + for (const FMappableConfigPair& Pair : DefaultInputConfigs) + { + FMappableConfigPair::ActivatePair(Pair); + } + + UALSInputComponent* ALSIC = CastChecked(PlayerInputComponent); + ALSIC->AddInputMappings(InputConfig, Subsystem); + if (UALSSettingsLocal* LocalSettings = UALSSettingsLocal::Get()) + { + LocalSettings->OnInputConfigActivated.AddUObject(this, &UALSHeroComponent::OnInputConfigActivated); + LocalSettings->OnInputConfigDeactivated.AddUObject(this, &UALSHeroComponent::OnInputConfigDeactivated); + } + + TArray BindHandles; + ALSIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles); + + ALSIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move, /*bLogIfNotFound=*/ true); + ALSIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Look_Mouse, ETriggerEvent::Triggered, this, &ThisClass::Input_LookMouse, /*bLogIfNotFound=*/ true); + ALSIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Look_Stick, ETriggerEvent::Triggered, this, &ThisClass::Input_LookStick, /*bLogIfNotFound=*/ true); + ALSIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Crouch, ETriggerEvent::Triggered, this, &ThisClass::Input_Crouch, /*bLogIfNotFound=*/ true); + ALSIC->BindNativeAction(InputConfig, GameplayTags.InputTag_AutoRun, ETriggerEvent::Triggered, this, &ThisClass::Input_AutoRun, /*bLogIfNotFound=*/ true); + UE_LOG(LogTemp, Warning, TEXT("UALSHeroComponent::InitializePlayerInput - BindNativeAction")) + } + } + } + + if (ensure(!bReadyToBindInputs)) + { + bReadyToBindInputs = true; + } + + UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast(PC), NAME_BindInputsNow); + UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast(Pawn), NAME_BindInputsNow); +} + +void UALSHeroComponent::OnInputConfigActivated(const FLoadedMappableConfigPair& ConfigPair) +{ + UE_LOG(LogTemp, Warning, TEXT("UALSHeroComponent::OnInputConfigActivated")) + if (AALSPlayerController2* ALSPC = GetController()) + { + if (APawn* Pawn = GetPawn()) + { + if (UALSInputComponent* ALSIC = Cast(Pawn->InputComponent)) + { + if (const ULocalPlayer* LP = ALSPC->GetLocalPlayer()) + { + if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem()) + { + ALSIC->AddInputConfig(ConfigPair, Subsystem); + } + } + } + } + } +} + +void UALSHeroComponent::OnInputConfigDeactivated(const FLoadedMappableConfigPair& ConfigPair) +{ + if (AALSPlayerController2* ALSPC = GetController()) + { + if (APawn* Pawn = GetPawn()) + { + if (UALSInputComponent* ALSIC = Cast(Pawn->InputComponent)) + { + if (const ULocalPlayer* LP = ALSPC->GetLocalPlayer()) + { + if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem()) + { + ALSIC->RemoveInputConfig(ConfigPair, Subsystem); + } + } + } + } + } +} + +void UALSHeroComponent::AddAdditionalInputConfig(const UALSInputConfig* InputConfig) +{ + TArray BindHandles; + + const APawn* Pawn = GetPawn(); + if (!Pawn) + { + return; + } + + UALSInputComponent* ALSIC = Pawn->FindComponentByClass(); + check(ALSIC); + + const APlayerController* PC = GetController(); + check(PC); + + const ULocalPlayer* LP = PC->GetLocalPlayer(); + check(LP); + + UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem(); + check(Subsystem); + + if (const UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) + { + ALSIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles); + } +} + +void UALSHeroComponent::RemoveAdditionalInputConfig(const UALSInputConfig* InputConfig) +{ + //@TODO: Implement me! +} + +bool UALSHeroComponent::HasPawnInitialized() const +{ + return bPawnHasInitialized; +} + +bool UALSHeroComponent::IsReadyToBindInputs() const +{ + return bReadyToBindInputs; +} + +void UALSHeroComponent::Input_AbilityInputTagPressed(FGameplayTag InputTag) +{ + if (const APawn* Pawn = GetPawn()) + { + if (const UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) + { + if (UALSAbilitySystemComponent* ALSASC = PawnExtComp->GetALSAbilitySystemComponent()) + { + ALSASC->AbilityInputTagPressed(InputTag); + } + } + } +} + +void UALSHeroComponent::Input_AbilityInputTagReleased(FGameplayTag InputTag) +{ + const APawn* Pawn = GetPawn(); + if (!Pawn) + { + return; + } + + if (const UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) + { + if (UALSAbilitySystemComponent* ALSASC = PawnExtComp->GetALSAbilitySystemComponent()) + { + ALSASC->AbilityInputTagReleased(InputTag); + } + } +} + +void UALSHeroComponent::Input_Move(const FInputActionValue& InputActionValue) +{ + UE_LOG(LogTemp, Warning, TEXT("INPUT_MOOOOVE")) + + APawn* Pawn = GetPawn(); + AController* Controller = Pawn ? Pawn->GetController() : nullptr; + + // If the player has attempted to move again then cancel auto running + if (AALSPlayerController2* ALSController = Cast(Controller)) + { + ALSController->SetIsAutoRunning(false); + } + + if (Controller) + { + const FVector2D Value = InputActionValue.Get(); + 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 UALSHeroComponent::Input_LookMouse(const FInputActionValue& InputActionValue) +{ + APawn* Pawn = GetPawn(); + + if (!Pawn) + { + return; + } + + const FVector2D Value = InputActionValue.Get(); + + if (Value.X != 0.0f) + { + Pawn->AddControllerYawInput(Value.X); + } + + if (Value.Y != 0.0f) + { + Pawn->AddControllerPitchInput(Value.Y); + } +} + +void UALSHeroComponent::Input_LookStick(const FInputActionValue& InputActionValue) +{ + APawn* Pawn = GetPawn(); + + if (!Pawn) + { + return; + } + + const FVector2D Value = InputActionValue.Get(); + + const UWorld* World = GetWorld(); + check(World); + + if (Value.X != 0.0f) + { + Pawn->AddControllerYawInput(Value.X * ALSHero::LookYawRate * World->GetDeltaSeconds()); + } + + if (Value.Y != 0.0f) + { + Pawn->AddControllerPitchInput(Value.Y * ALSHero::LookPitchRate * World->GetDeltaSeconds()); + } +} + +void UALSHeroComponent::Input_Crouch(const FInputActionValue& InputActionValue) +{ + UE_LOG(LogTemp, Warning, TEXT("UALSHeroComponent::Input_Crouch")) + if (AALSCharacter* Character = GetPawn()) + { + Character->Crouch(); + } +} + +void UALSHeroComponent::Input_AutoRun(const FInputActionValue& InputActionValue) +{ + if (APawn* Pawn = GetPawn()) + { + if (AALSPlayerController2* Controller = Cast(Pawn->GetController())) + { + // Toggle auto running + Controller->SetIsAutoRunning(!Controller->GetIsAutoRunning()); + } + } +} + +TSubclassOf UALSHeroComponent::DetermineCameraMode() const +{ + if (AbilityCameraMode) + { + return AbilityCameraMode; + } + + const APawn* Pawn = GetPawn(); + if (!Pawn) + { + return nullptr; + } + + if (UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) + { + if (const UALSPawnData* PawnData = PawnExtComp->GetPawnData()) + { + return PawnData->DefaultCameraMode; + } + } + + return nullptr; +} + +void UALSHeroComponent::SetAbilityCameraMode(TSubclassOf CameraMode, const FGameplayAbilitySpecHandle& OwningSpecHandle) +{ + if (CameraMode) + { + AbilityCameraMode = CameraMode; + AbilityCameraModeOwningSpecHandle = OwningSpecHandle; + } +} + +void UALSHeroComponent::ClearAbilityCameraMode(const FGameplayAbilitySpecHandle& OwningSpecHandle) +{ + if (AbilityCameraModeOwningSpecHandle == OwningSpecHandle) + { + AbilityCameraMode = nullptr; + AbilityCameraModeOwningSpecHandle = FGameplayAbilitySpecHandle(); + } +} diff --git a/Source/ALSV4_CPP/Private/Character/ALSPawn.cpp b/Source/ALSV4_CPP/Private/Character/ALSPawn.cpp new file mode 100644 index 00000000..8e604124 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Character/ALSPawn.cpp @@ -0,0 +1,31 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Character/ALSPawn.h" +#include "GameFramework/Controller.h" + + +AALSPawn::AALSPawn(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void AALSPawn::PreInitializeComponents() +{ + Super::PreInitializeComponents(); +} + +void AALSPawn::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); +} + +void AALSPawn::PossessedBy(AController* NewController) +{ + Super::PossessedBy(NewController); +} + +void AALSPawn::UnPossessed() +{ + Super::UnPossessed(); +} diff --git a/Source/ALSV4_CPP/Private/Character/ALSPawnComponent.cpp b/Source/ALSV4_CPP/Private/Character/ALSPawnComponent.cpp new file mode 100644 index 00000000..5566d5cf --- /dev/null +++ b/Source/ALSV4_CPP/Private/Character/ALSPawnComponent.cpp @@ -0,0 +1,13 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Character/ALSPawnComponent.h" + + +UALSPawnComponent::UALSPawnComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = false; +} + diff --git a/Source/ALSV4_CPP/Private/Character/ALSPawnData.cpp b/Source/ALSV4_CPP/Private/Character/ALSPawnData.cpp new file mode 100644 index 00000000..36bc44f0 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Character/ALSPawnData.cpp @@ -0,0 +1,12 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Character/ALSPawnData.h" + +UALSPawnData::UALSPawnData(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PawnClass = nullptr; + InputConfig = nullptr; + DefaultCameraMode = nullptr; +} diff --git a/Source/ALSV4_CPP/Private/Character/ALSPawnExtensionComponent.cpp b/Source/ALSV4_CPP/Private/Character/ALSPawnExtensionComponent.cpp new file mode 100644 index 00000000..504cae10 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Character/ALSPawnExtensionComponent.cpp @@ -0,0 +1,353 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Character/ALSPawnExtensionComponent.h" + +#include "ALSGameplayTags.h" +#include "ALSLogChannels.h" +#include "Net/UnrealNetwork.h" +#include "GameFramework/Pawn.h" +#include "GameFramework/Controller.h" +#include "Character/ALSPawnData.h" +#include "Components/GameFrameworkComponentManager.h" +#include "Components/GameFrameworkInitStateInterface.h" +#include "AbilitySystem/ALSAbilitySystemComponent.h" + +const FName UALSPawnExtensionComponent::NAME_ActorFeatureName("PawnExtension"); + +UALSPawnExtensionComponent::UALSPawnExtensionComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = false; + + SetIsReplicatedByDefault(true); + + PawnData = nullptr; + AbilitySystemComponent = nullptr; + bPawnReadyToInitialize = false; +} + +void UALSPawnExtensionComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UALSPawnExtensionComponent, PawnData); +} + +void UALSPawnExtensionComponent::OnRegister() +{ + Super::OnRegister(); + UE_LOG(LogTemp, Warning, TEXT("UALSPawnExtensionComponent::OnRegister()")) + + const APawn* Pawn = GetPawn(); + ensureAlwaysMsgf((Pawn != nullptr), TEXT("ALSPawnExtensionComponent on [%s] can only be added to Pawn actors."), *GetNameSafe(GetOwner())); + + TArray PawnExtensionComponents; + Pawn->GetComponents(UALSPawnExtensionComponent::StaticClass(), PawnExtensionComponents); + ensureAlwaysMsgf((PawnExtensionComponents.Num() == 1), TEXT("Only one ALSPawnExtensionComponent should exist on [%s]."), *GetNameSafe(GetOwner())); +} + +void UALSPawnExtensionComponent::SetPawnData(const UALSPawnData* InPawnData) +{ + check(InPawnData); + + bPawnReadyToInitialize = false; + + APawn* Pawn = GetPawnChecked(); + + if (Pawn->GetLocalRole() != ROLE_Authority) + { + return; + } + + if (PawnData) + { + UE_LOG(LogALS, Error, TEXT("Trying to set PawnData [%s] on pawn [%s] that already has valid PawnData [%s]."), *GetNameSafe(InPawnData), *GetNameSafe(Pawn), *GetNameSafe(PawnData)); + return; + } + + PawnData = InPawnData; + + Pawn->ForceNetUpdate(); + + CheckPawnReadyToInitialize(); +} + +void UALSPawnExtensionComponent::OnRep_PawnData() +{ + CheckPawnReadyToInitialize(); +} + +void UALSPawnExtensionComponent::InitializeAbilitySystem(UALSAbilitySystemComponent* InASC, AActor* InOwnerActor) +{ + check(InASC); + check(InOwnerActor); + + if (AbilitySystemComponent == InASC) + { + // The ability system component hasn't changed. + return; + } + + if (AbilitySystemComponent) + { + // Clean up the old ability system component. + UninitializeAbilitySystem(); + } + + APawn* Pawn = GetPawnChecked(); + AActor* ExistingAvatar = InASC->GetAvatarActor(); + + UE_LOG(LogALS, Verbose, TEXT("Setting up ASC [%s] on pawn [%s] owner [%s], existing [%s] "), *GetNameSafe(InASC), *GetNameSafe(Pawn), *GetNameSafe(InOwnerActor), *GetNameSafe(ExistingAvatar)); + + if ((ExistingAvatar != nullptr) && (ExistingAvatar != Pawn)) + { + UE_LOG(LogALS, 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 (UALSPawnExtensionComponent* OtherExtensionComponent = FindPawnExtensionComponent(ExistingAvatar)) + { + OtherExtensionComponent->UninitializeAbilitySystem(); + } + } + + AbilitySystemComponent = InASC; + AbilitySystemComponent->InitAbilityActorInfo(InOwnerActor, Pawn); + + if (ensure(PawnData)) + { + InASC->SetTagRelationshipMapping(PawnData->TagRelationshipMapping); + } + + OnAbilitySystemInitialized.Broadcast(); +} + +void UALSPawnExtensionComponent::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()) + { + AbilitySystemComponent->CancelAbilities(nullptr, nullptr); + 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 UALSPawnExtensionComponent::HandleControllerChanged() +{ + if (AbilitySystemComponent && (AbilitySystemComponent->GetAvatarActor() == GetPawnChecked())) + { + ensure(AbilitySystemComponent->AbilityActorInfo->OwnerActor == AbilitySystemComponent->GetOwnerActor()); + if (AbilitySystemComponent->GetOwnerActor() == nullptr) + { + UninitializeAbilitySystem(); + } + else + { + AbilitySystemComponent->RefreshAbilityActorInfo(); + } + } + + CheckPawnReadyToInitialize(); +} + +void UALSPawnExtensionComponent::HandlePlayerStateReplicated() +{ + CheckPawnReadyToInitialize(); +} + +void UALSPawnExtensionComponent::SetupPlayerInputComponent() +{ + CheckPawnReadyToInitialize(); +} + +bool UALSPawnExtensionComponent::CheckPawnReadyToInitialize() +{ + UE_LOG(LogTemp, Warning, TEXT("CheckPawnReadyToInitialize")) + if (bPawnReadyToInitialize) + { + return true; + } + + // Pawn data is required. + if (!PawnData) + { + return false; + } + UE_LOG(LogTemp, Warning, TEXT("HAS PAWN DATA")) + + APawn* Pawn = GetPawnChecked(); + + const bool bHasAuthority = Pawn->HasAuthority(); + const bool bIsLocallyControlled = Pawn->IsLocallyControlled(); + + if (bHasAuthority || bIsLocallyControlled) + { + // Check for being possessed by a controller. + if (!GetController()) + { + return false; + } + } + + // Allow pawn components to have requirements. + TArray InteractableComponents = Pawn->GetComponentsByInterface(UALSReadyInterface::StaticClass()); + for (UActorComponent* InteractableComponent : InteractableComponents) + { + const IALSReadyInterface* Ready = CastChecked(InteractableComponent); + if (!Ready->IsPawnComponentReadyToInitialize()) + { + return false; + } + } + UE_LOG(LogTemp, Warning, TEXT("IS READY TO INITIALIZE")) + + // Pawn is ready to initialize. + bPawnReadyToInitialize = true; + OnPawnReadyToInitialize.Broadcast(); + BP_OnPawnReadyToInitialize.Broadcast(); + + return true; +} + +void UALSPawnExtensionComponent::CheckDefaultInitialization() +{ + // Before checking our progress, try progressing any other features we might depend on + CheckDefaultInitializationForImplementers(); + + const FALSGameplayTags& InitTags = FALSGameplayTags::Get(); + static const TArray StateChain = { InitTags.InitState_Spawned, InitTags.InitState_DataAvailable, InitTags.InitState_DataInitialized, InitTags.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); +} + +bool UALSPawnExtensionComponent::CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const +{ + check(Manager); + + APawn* Pawn = GetPawn(); + const FALSGameplayTags& InitTags = FALSGameplayTags::Get(); + + if (!CurrentState.IsValid() && DesiredState == InitTags.InitState_Spawned) + { + // As long as we are on a valid pawn, we count as spawned + if (Pawn) + { + return true; + } + } + if (CurrentState == InitTags.InitState_Spawned && DesiredState == InitTags.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()) + { + return false; + } + } + + return true; + } + else if (CurrentState == InitTags.InitState_DataAvailable && DesiredState == InitTags.InitState_DataInitialized) + { + // Transition to initialize if all features have their data available + return Manager->HaveAllFeaturesReachedInitState(Pawn, InitTags.InitState_DataAvailable); + } + else if (CurrentState == InitTags.InitState_DataInitialized && DesiredState == InitTags.InitState_GameplayReady) + { + return true; + } + + return false; +} + +void UALSPawnExtensionComponent::HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) +{ + if (DesiredState == FALSGameplayTags::Get().InitState_DataInitialized) + { + // This is currently all handled by other components listening to this state change + } +} + +void UALSPawnExtensionComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& Params) +{ + // If another feature is now in DataAvailable, see if we should transition to DataInitialized + if (Params.FeatureName != NAME_ActorFeatureName) + { + const FALSGameplayTags& InitTags = FALSGameplayTags::Get(); + if (Params.FeatureState == InitTags.InitState_DataAvailable) + { + CheckDefaultInitialization(); + } + } +} + + +void UALSPawnExtensionComponent::OnPawnReadyToInitialize_RegisterAndCall(FSimpleMulticastDelegate::FDelegate Delegate) +{ + if (!OnPawnReadyToInitialize.IsBoundToObject(Delegate.GetUObject())) + { + OnPawnReadyToInitialize.Add(Delegate); + } + + if (bPawnReadyToInitialize) + { + Delegate.Execute(); + } +} + +void UALSPawnExtensionComponent::OnAbilitySystemInitialized_RegisterAndCall(FSimpleMulticastDelegate::FDelegate Delegate) +{ + if (!OnAbilitySystemInitialized.IsBoundToObject(Delegate.GetUObject())) + { + OnAbilitySystemInitialized.Add(Delegate); + } + + if (AbilitySystemComponent) + { + Delegate.Execute(); + } +} + +void UALSPawnExtensionComponent::OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate Delegate) +{ + if (!OnAbilitySystemUninitialized.IsBoundToObject(Delegate.GetUObject())) + { + OnAbilitySystemUninitialized.Add(Delegate); + } +} + diff --git a/Source/ALSV4_CPP/Private/Character/ALSPlayerCameraManager.cpp b/Source/ALSV4_CPP/Private/Character/ALSPlayerCameraManager.cpp index ad7b3f1f..d0022649 100644 --- a/Source/ALSV4_CPP/Private/Character/ALSPlayerCameraManager.cpp +++ b/Source/ALSV4_CPP/Private/Character/ALSPlayerCameraManager.cpp @@ -56,7 +56,7 @@ void AALSPlayerCameraManager::OnPossess(AALSBaseCharacter* NewCharacter) } // Initial position - const FVector& TPSLoc = ControlledCharacter->GetThirdPersonPivotTarget().GetLocation(); + const FVector& TPSLoc = ControlledCharacter->GetCurrentPivotTarget().GetLocation(); SetActorLocation(TPSLoc); SmoothedPivotTarget.SetLocation(TPSLoc); @@ -127,8 +127,7 @@ bool AALSPlayerCameraManager::CustomCameraBehavior(float DeltaTime, FVector& Loc return false; } - // Step 1: Get Camera Parameters from CharacterBP via the Camera Interface - const FTransform& PivotTarget = ControlledCharacter->GetThirdPersonPivotTarget(); + const FTransform& PivotTarget = ControlledCharacter->GetCurrentPivotTarget(); const FVector& FPTarget = ControlledCharacter->GetFirstPersonCameraTarget(); float TPFOV = 90.0f; float FPFOV = 90.0f; @@ -137,7 +136,7 @@ bool AALSPlayerCameraManager::CustomCameraBehavior(float DeltaTime, FVector& Loc // Step 2: Calculate Target Camera Rotation. Use the Control Rotation and interpolate for smooth camera rotation. const FRotator& InterpResult = FMath::RInterpTo(GetCameraRotation(), - GetOwningPlayerController()->GetControlRotation(), DeltaTime, + ControlledCharacter->GetCurrentCameraControlRotation(), DeltaTime, GetCameraBehaviorParam(NAME_RotationLagSpeed)); TargetCameraRotation = UKismetMathLibrary::RLerp(InterpResult, DebugViewRotation, diff --git a/Source/ALSV4_CPP/Private/Character/ALSPlayerController.cpp b/Source/ALSV4_CPP/Private/Character/ALSPlayerController.cpp index ce68c74c..ad5ddae7 100644 --- a/Source/ALSV4_CPP/Private/Character/ALSPlayerController.cpp +++ b/Source/ALSV4_CPP/Private/Character/ALSPlayerController.cpp @@ -63,6 +63,7 @@ void AALSPlayerController::SetupInputComponent() EnhancedInputComponent->ClearDebugKeyBindings(); BindActions(DefaultInputMappingContext); + BindActions(GamepadInputMappingContext); BindActions(DebugInputMappingContext); } else @@ -102,6 +103,7 @@ void AALSPlayerController::SetupInputs() FModifyContextOptions Options; Options.bForceImmediately = 1; Subsystem->AddMappingContext(DefaultInputMappingContext, 1, Options); + Subsystem->AddMappingContext(GamepadInputMappingContext, 2, Options); UALSDebugComponent* DebugComp = Cast(PossessedCharacter->GetComponentByClass(UALSDebugComponent::StaticClass())); if (DebugComp) { diff --git a/Source/ALSV4_CPP/Private/Character/Animation/ALSCharacterAnimInstance.cpp b/Source/ALSV4_CPP/Private/Character/Animation/ALSCharacterAnimInstance.cpp index 11a44ff6..5992054d 100644 --- a/Source/ALSV4_CPP/Private/Character/Animation/ALSCharacterAnimInstance.cpp +++ b/Source/ALSV4_CPP/Private/Character/Animation/ALSCharacterAnimInstance.cpp @@ -51,6 +51,11 @@ void UALSCharacterAnimInstance::NativeInitializeAnimation() } } +void UALSCharacterAnimInstance::InitializeWithAbilitySystem(UAbilitySystemComponent* ASC) +{ + // TODO: Implement this +} + void UALSCharacterAnimInstance::NativeBeginPlay() { // it seems to be that the player pawn components are not really initialized @@ -196,13 +201,14 @@ bool UALSCharacterAnimInstance::ShouldMoveCheck() const bool UALSCharacterAnimInstance::CanRotateInPlace() const { return RotationMode.Aiming() || - CharacterInformation.ViewMode == EALSViewMode::FirstPerson; + CharacterInformation.ViewMode == EALSViewMode::FirstPerson || + CharacterInformation.ViewMode == EALSViewMode::TopDown; } bool UALSCharacterAnimInstance::CanTurnInPlace() const { return RotationMode.LookingDirection() && - CharacterInformation.ViewMode == EALSViewMode::ThirdPerson && + (CharacterInformation.ViewMode == EALSViewMode::ThirdPerson) && GetCurveValue(NAME_Enable_Transition) >= 0.99f; } diff --git a/Source/ALSV4_CPP/Private/Character/Animation/Notify/ALSAnimNotifyCameraShake.cpp b/Source/ALSV4_CPP/Private/Character/Animation/Notify/ALSAnimNotifyCameraShake.cpp index 9bfc50ba..e1713eb6 100644 --- a/Source/ALSV4_CPP/Private/Character/Animation/Notify/ALSAnimNotifyCameraShake.cpp +++ b/Source/ALSV4_CPP/Private/Character/Animation/Notify/ALSAnimNotifyCameraShake.cpp @@ -4,13 +4,15 @@ #include "Character/Animation/Notify/ALSAnimNotifyCameraShake.h" +#include "Character/ALSBaseCharacter.h" + void UALSAnimNotifyCameraShake::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) { Super::Notify(MeshComp, Animation, EventReference); - APawn* OwnerPawn = Cast(MeshComp->GetOwner()); - if (OwnerPawn) + AALSBaseCharacter* OwnerPawn = Cast(MeshComp->GetOwner()); + if (IsValid(OwnerPawn) && OwnerPawn->CanPlayCameraShake()) { APlayerController* OwnerController = Cast(OwnerPawn->GetController()); if (OwnerController) diff --git a/Source/ALSV4_CPP/Private/GameModes/ALSExperienceActionSet.cpp b/Source/ALSV4_CPP/Private/GameModes/ALSExperienceActionSet.cpp new file mode 100644 index 00000000..65dbdbd6 --- /dev/null +++ b/Source/ALSV4_CPP/Private/GameModes/ALSExperienceActionSet.cpp @@ -0,0 +1,57 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "GameModes/ALSExperienceActionSet.h" +#include "GameFeatureAction.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSExperienceActionSet) + +#define LOCTEXT_NAMESPACE "ALSSystem" + +UALSExperienceActionSet::UALSExperienceActionSet() +{ +} + +#if WITH_EDITOR +EDataValidationResult UALSExperienceActionSet::IsDataValid(TArray& ValidationErrors) +{ + EDataValidationResult Result = CombineDataValidationResults(Super::IsDataValid(ValidationErrors), EDataValidationResult::Valid); + + int32 EntryIndex = 0; + for (UGameFeatureAction* Action : Actions) + { + if (Action) + { + EDataValidationResult ChildResult = Action->IsDataValid(ValidationErrors); + Result = CombineDataValidationResults(Result, ChildResult); + } + else + { + Result = EDataValidationResult::Invalid; + ValidationErrors.Add(FText::Format(LOCTEXT("ActionEntryIsNull", "Null entry at index {0} in Actions"), FText::AsNumber(EntryIndex))); + } + + ++EntryIndex; + } + + return Result; +} +#endif + +#if WITH_EDITORONLY_DATA +void UALSExperienceActionSet::UpdateAssetBundleData() +{ + Super::UpdateAssetBundleData(); + + for (UGameFeatureAction* Action : Actions) + { + if (Action) + { + Action->AddAdditionalAssetBundleData(AssetBundleData); + } + } +} +#endif // WITH_EDITORONLY_DATA + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/ALSV4_CPP/Private/GameModes/ALSExperienceDefinition.cpp b/Source/ALSV4_CPP/Private/GameModes/ALSExperienceDefinition.cpp new file mode 100644 index 00000000..cfd08788 --- /dev/null +++ b/Source/ALSV4_CPP/Private/GameModes/ALSExperienceDefinition.cpp @@ -0,0 +1,80 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "GameModes/ALSExperienceDefinition.h" +#include "GameFeatureAction.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSExperienceDefinition) + +#define LOCTEXT_NAMESPACE "ALSSystem" + +UALSExperienceDefinition::UALSExperienceDefinition() +{ +} + +#if WITH_EDITOR +EDataValidationResult UALSExperienceDefinition::IsDataValid(TArray& ValidationErrors) +{ + EDataValidationResult Result = CombineDataValidationResults(Super::IsDataValid(ValidationErrors), EDataValidationResult::Valid); + + int32 EntryIndex = 0; + for (UGameFeatureAction* Action : Actions) + { + if (Action) + { + EDataValidationResult ChildResult = Action->IsDataValid(ValidationErrors); + Result = CombineDataValidationResults(Result, ChildResult); + } + else + { + Result = EDataValidationResult::Invalid; + ValidationErrors.Add(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()) + { + UClass* ParentClass = GetClass()->GetSuperClass(); + + // Find the native parent + UClass* FirstNativeParent = ParentClass; + while ((FirstNativeParent != nullptr) && !FirstNativeParent->IsNative()) + { + FirstNativeParent = FirstNativeParent->GetSuperClass(); + } + + if (FirstNativeParent != ParentClass) + { + ValidationErrors.Add(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 + +#if WITH_EDITORONLY_DATA +void UALSExperienceDefinition::UpdateAssetBundleData() +{ + Super::UpdateAssetBundleData(); + + for (UGameFeatureAction* Action : Actions) + { + if (Action) + { + Action->AddAdditionalAssetBundleData(AssetBundleData); + } + } +} +#endif // WITH_EDITORONLY_DATA + +#undef LOCTEXT_NAMESPACE + + diff --git a/Source/ALSV4_CPP/Private/GameModes/ALSExperienceManager.cpp b/Source/ALSV4_CPP/Private/GameModes/ALSExperienceManager.cpp new file mode 100644 index 00000000..48dc734b --- /dev/null +++ b/Source/ALSV4_CPP/Private/GameModes/ALSExperienceManager.cpp @@ -0,0 +1,57 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "GameModes/ALSExperienceManager.h" +#include "Engine/AssetManager.h" +#include "GameModes/ALSExperienceDefinition.h" +#include "GameModes/ALSExperienceManager.h" +#include "Engine/Engine.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSExperienceManager) + +#if WITH_EDITOR + +void UALSExperienceManager::OnPlayInEditorBegun() +{ + ensure(GameFeaturePluginRequestCountMap.IsEmpty()); + GameFeaturePluginRequestCountMap.Empty(); +} + +void UALSExperienceManager::NotifyOfPluginActivation(const FString PluginURL) +{ + if (GIsEditor) + { + UALSExperienceManager* ExperienceManagerSubsystem = GEngine->GetEngineSubsystem(); + 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 UALSExperienceManager::RequestToDeactivatePlugin(const FString PluginURL) +{ + if (GIsEditor) + { + UALSExperienceManager* ExperienceManagerSubsystem = GEngine->GetEngineSubsystem(); + 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 diff --git a/Source/ALSV4_CPP/Private/GameModes/ALSExperienceManagerComponent.cpp b/Source/ALSV4_CPP/Private/GameModes/ALSExperienceManagerComponent.cpp new file mode 100644 index 00000000..b9b6679f --- /dev/null +++ b/Source/ALSV4_CPP/Private/GameModes/ALSExperienceManagerComponent.cpp @@ -0,0 +1,453 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "GameModes/ALSExperienceManagerComponent.h" +#include "Net/UnrealNetwork.h" +#include "GameModes/ALSExperienceDefinition.h" +#include "GameModes/ALSExperienceActionSet.h" +#include "GameModes/ALSExperienceManager.h" +#include "GameFeaturesSubsystem.h" +#include "System/ALSAssetManager.h" +#include "GameFeatureAction.h" +#include "GameFeaturesSubsystemSettings.h" +#include "TimerManager.h" +#include "ALSLogChannels.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSExperienceManagerComponent) + +//@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 ALSConsoleVariables +{ + 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); + } +} + +UALSExperienceManagerComponent::UALSExperienceManagerComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SetIsReplicatedByDefault(true); +} + +#if WITH_SERVER_CODE +void UALSExperienceManagerComponent::ServerSetCurrentExperience(FPrimaryAssetId ExperienceId) +{ + UALSAssetManager& AssetManager = UALSAssetManager::Get(); + FSoftObjectPath AssetPath = AssetManager.GetPrimaryAssetPath(ExperienceId); + TSubclassOf AssetClass = Cast(AssetPath.TryLoad()); + check(AssetClass); + const UALSExperienceDefinition* Experience = GetDefault(AssetClass); + + check(Experience != nullptr); + check(CurrentExperience == nullptr); + CurrentExperience = Experience; + StartExperienceLoad(); +} +#endif + +void UALSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_HighPriority(FOnALSExperienceLoaded::FDelegate&& Delegate) +{ + if (IsExperienceLoaded()) + { + Delegate.Execute(CurrentExperience); + } + else + { + OnExperienceLoaded_HighPriority.Add(MoveTemp(Delegate)); + } +} + +void UALSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded(FOnALSExperienceLoaded::FDelegate&& Delegate) +{ + if (IsExperienceLoaded()) + { + Delegate.Execute(CurrentExperience); + } + else + { + OnExperienceLoaded.Add(MoveTemp(Delegate)); + } +} + +void UALSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_LowPriority(FOnALSExperienceLoaded::FDelegate&& Delegate) +{ + if (IsExperienceLoaded()) + { + Delegate.Execute(CurrentExperience); + } + else + { + OnExperienceLoaded_LowPriority.Add(MoveTemp(Delegate)); + } +} + +const UALSExperienceDefinition* UALSExperienceManagerComponent::GetCurrentExperienceChecked() const +{ + check(LoadState == EALSExperienceLoadState::Loaded); + check(CurrentExperience != nullptr); + return CurrentExperience; +} + +bool UALSExperienceManagerComponent::IsExperienceLoaded() const +{ + return (LoadState == EALSExperienceLoadState::Loaded) && (CurrentExperience != nullptr); +} + +void UALSExperienceManagerComponent::OnRep_CurrentExperience() +{ + StartExperienceLoad(); +} + +void UALSExperienceManagerComponent::StartExperienceLoad() +{ + check(CurrentExperience != nullptr); + check(LoadState == EALSExperienceLoadState::Unloaded); + + UE_LOG(LogALSExperience, Log, TEXT("EXPERIENCE: StartExperienceLoad(CurrentExperience = %s, %s)"), + *CurrentExperience->GetPrimaryAssetId().ToString(), + *GetClientServerContextString(this)); + + LoadState = EALSExperienceLoadState::Loading; + + UALSAssetManager& AssetManager = UALSAssetManager::Get(); + + TSet BundleAssetList; + TSet RawAssetList; + + BundleAssetList.Add(CurrentExperience->GetPrimaryAssetId()); + for (const TObjectPtr& ActionSet : CurrentExperience->ActionSets) + { + if (ActionSet != nullptr) + { + BundleAssetList.Add(ActionSet->GetPrimaryAssetId()); + } + } + + // Load assets associated with the experience + + TArray BundlesToLoad; + BundlesToLoad.Add(FALSBundles::Equipped); + + //@TODO: Centralize this client/server stuff into the ALSAssetManager + const ENetMode OwnerNetMode = GetOwner()->GetNetMode(); + const bool bLoadClient = GIsEditor || (OwnerNetMode != NM_DedicatedServer); + const bool bLoadServer = GIsEditor || (OwnerNetMode != NM_Client); + if (bLoadClient) + { + BundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateClient); + } + if (bLoadServer) + { + BundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateServer); + } + + const TSharedPtr BundleLoadHandle = AssetManager.ChangeBundleStateForPrimaryAssets(BundleAssetList.Array(), BundlesToLoad, {}, false, FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority); + const TSharedPtr RawLoadHandle = AssetManager.LoadAssetList(RawAssetList.Array(), FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority, TEXT("StartExperienceLoad()")); + + // If both async loads are running, combine them + TSharedPtr 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 PreloadAssetList; + //@TODO: Determine assets to preload (but not blocking-ly) + if (PreloadAssetList.Num() > 0) + { + AssetManager.ChangeBundleStateForPrimaryAssets(PreloadAssetList.Array(), BundlesToLoad, {}); + } +} + +void UALSExperienceManagerComponent::OnExperienceLoadComplete() +{ + check(LoadState == EALSExperienceLoadState::Loading); + check(CurrentExperience != nullptr); + + UE_LOG(LogALSExperience, 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& 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 + // if (!CurrentPlaylistData->GameFeaturePluginToActivateUntilDownloadedContentIsPresent.IsEmpty()) + // { + // FString PluginURL; + // if (UGameFeaturesSubsystem::Get().GetPluginURLByName(CurrentPlaylistData->GameFeaturePluginToActivateUntilDownloadedContentIsPresent, PluginURL)) + // { + // GameFeaturePluginURLs.AddUnique(PluginURL); + // } + // } + }; + + CollectGameFeaturePluginURLs(CurrentExperience, CurrentExperience->GameFeaturesToEnable); + for (const TObjectPtr& ActionSet : CurrentExperience->ActionSets) + { + if (ActionSet != nullptr) + { + CollectGameFeaturePluginURLs(ActionSet, ActionSet->GameFeaturesToEnable); + } + } + + // Load and activate the features + NumGameFeaturePluginsLoading = GameFeaturePluginURLs.Num(); + if (NumGameFeaturePluginsLoading > 0) + { + LoadState = EALSExperienceLoadState::LoadingGameFeatures; + for (const FString& PluginURL : GameFeaturePluginURLs) + { + UALSExperienceManager::NotifyOfPluginActivation(PluginURL); + UGameFeaturesSubsystem::Get().LoadAndActivateGameFeaturePlugin(PluginURL, FGameFeaturePluginLoadComplete::CreateUObject(this, &ThisClass::OnGameFeaturePluginLoadComplete)); + } + } + else + { + OnExperienceFullLoadCompleted(); + } +} + +void UALSExperienceManagerComponent::OnGameFeaturePluginLoadComplete(const UE::GameFeatures::FResult& Result) +{ + // decrement the number of plugins that are loading + NumGameFeaturePluginsLoading--; + + if (NumGameFeaturePluginsLoading == 0) + { + OnExperienceFullLoadCompleted(); + } +} + +void UALSExperienceManagerComponent::OnExperienceFullLoadCompleted() +{ + check(LoadState != EALSExperienceLoadState::Loaded); + + // Insert a random delay for testing (if configured) + if (LoadState != EALSExperienceLoadState::LoadingChaosTestingDelay) + { + const float DelaySecs = ALSConsoleVariables::GetExperienceLoadDelayDuration(); + if (DelaySecs > 0.0f) + { + FTimerHandle DummyHandle; + + LoadState = EALSExperienceLoadState::LoadingChaosTestingDelay; + GetWorld()->GetTimerManager().SetTimer(DummyHandle, this, &ThisClass::OnExperienceFullLoadCompleted, DelaySecs, /*bLooping=*/ false); + + return; + } + } + + LoadState = EALSExperienceLoadState::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& 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& ActionSet : CurrentExperience->ActionSets) + { + if (ActionSet != nullptr) + { + ActivateListOfActions(ActionSet->Actions); + } + } + + LoadState = EALSExperienceLoadState::Loaded; + + OnExperienceLoaded_HighPriority.Broadcast(CurrentExperience); + OnExperienceLoaded_HighPriority.Clear(); + + OnExperienceLoaded.Broadcast(CurrentExperience); + OnExperienceLoaded.Clear(); + + OnExperienceLoaded_LowPriority.Broadcast(CurrentExperience); + OnExperienceLoaded_LowPriority.Clear(); +} + +void UALSExperienceManagerComponent::OnActionDeactivationCompleted() +{ + check(IsInGameThread()); + ++NumObservedPausers; + + if (NumObservedPausers == NumExpectedPausers) + { + OnAllActionsDeactivated(); + } +} + +void UALSExperienceManagerComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ThisClass, CurrentExperience); +} + +void UALSExperienceManagerComponent::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 (UALSExperienceManager::RequestToDeactivatePlugin(PluginURL)) + { + UGameFeaturesSubsystem::Get().DeactivateGameFeaturePlugin(PluginURL); + } + } + + //@TODO: Ensure proper handling of a partially-loaded state too + if (LoadState == EALSExperienceLoadState::Loaded) + { + LoadState = EALSExperienceLoadState::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(FSimpleDelegate::CreateUObject(this, &ThisClass::OnActionDeactivationCompleted)); + + const FWorldContext* ExistingWorldContext = GEngine->GetWorldContextFromWorld(GetWorld()); + if (ExistingWorldContext) + { + Context.SetRequiredWorldContextHandle(ExistingWorldContext->ContextHandle); + } + + auto DeactivateListOfActions = [&Context](const TArray& ActionList) + { + for (UGameFeatureAction* Action : ActionList) + { + if (Action) + { + Action->OnGameFeatureDeactivating(Context); + Action->OnGameFeatureUnregistering(); + } + } + }; + + DeactivateListOfActions(CurrentExperience->Actions); + for (const TObjectPtr& ActionSet : CurrentExperience->ActionSets) + { + if (ActionSet != nullptr) + { + DeactivateListOfActions(ActionSet->Actions); + } + } + + NumExpectedPausers = Context.GetNumPausers(); + + if (NumExpectedPausers > 0) + { + UE_LOG(LogALSExperience, Error, TEXT("Actions that have asynchronous deactivation aren't fully supported yet in ALS experiences")); + } + + if (NumExpectedPausers == NumObservedPausers) + { + OnAllActionsDeactivated(); + } + } +} + +bool UALSExperienceManagerComponent::ShouldShowLoadingScreen(FString& OutReason) const +{ + if (LoadState != EALSExperienceLoadState::Loaded) + { + OutReason = TEXT("Experience still loading"); + return false; + } + else + { + return false; + } +} + +void UALSExperienceManagerComponent::OnAllActionsDeactivated() +{ + //@TODO: We actually only deactivated and didn't fully unload... + LoadState = EALSExperienceLoadState::Unloaded; + CurrentExperience = nullptr; + //@TODO: GEngine->ForceGarbageCollection(true); +} + diff --git a/Source/ALSV4_CPP/Private/GameModes/ALSGameMode.cpp b/Source/ALSV4_CPP/Private/GameModes/ALSGameMode.cpp new file mode 100644 index 00000000..a062ab9e --- /dev/null +++ b/Source/ALSV4_CPP/Private/GameModes/ALSGameMode.cpp @@ -0,0 +1,376 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "GameModes/ALSGameMode.h" + +#include "Character/ALSCharacter.h" +#include "Character/ALSPawnData.h" +#include "Character/ALSPawnExtensionComponent.h" +#include "Character/ALSPlayerController.h" +#include "GameModes/ALSExperienceManagerComponent.h" +#include "GameModes/ALSExperienceDefinition.h" +#include "Kismet/GameplayStatics.h" +#include "Player/ALSPlayerState.h" +#include "System/ALSAssetManager.h" +#include "ALSLogChannels.h" +#include "GameModes/ALSWorldSettings.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSGameMode) + +AALSGameMode::AALSGameMode(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + // Session And Game State + PlayerControllerClass = AALSPlayerController::StaticClass(); + PlayerStateClass = AALSPlayerState::StaticClass(); + DefaultPawnClass = AALSCharacter::StaticClass(); +} + +const UALSPawnData* AALSGameMode::GetPawnDataForController(const AController* InController) const +{ + // See if pawn data is already set on the player state + if (InController != nullptr) + { + if (const AALSPlayerState* ALSPS = InController->GetPlayerState()) + { + if (const UALSPawnData* PawnData = ALSPS->GetPawnData()) + { + return PawnData; + } + } + } + + // If not, fall back to the the default for the current experience + check(GameState); + UALSExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); + check(ExperienceComponent); + + if (ExperienceComponent->IsExperienceLoaded()) + { + const UALSExperienceDefinition* Experience = ExperienceComponent->GetCurrentExperienceChecked(); + if (Experience->DefaultPawnData != nullptr) + { + return Experience->DefaultPawnData; + } + + // Experience is loaded and there's still no pawn data, fall back to the default for now + return UALSAssetManager::Get().GetDefaultPawnData(); + } + + // Experience not loaded yet, so there is no pawn data to be had + return nullptr; +} + +void AALSGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) +{ + Super::InitGame(MapName, Options, ErrorMessage); + + //@TODO: Eventually only do this for PIE/auto + GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::HandleMatchAssignmentIfNotExpectingOne); +} + +void AALSGameMode::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 + // - Default experience + + UWorld* World = GetWorld(); + + if (!ExperienceId.IsValid() && UGameplayStatics::HasOption(OptionsString, TEXT("Experience"))) + { + const FString ExperienceFromOptions = UGameplayStatics::ParseOption(OptionsString, TEXT("Experience")); + ExperienceId = FPrimaryAssetId(FPrimaryAssetType(UALSExperienceDefinition::StaticClass()->GetFName()), FName(*ExperienceFromOptions)); + ExperienceIdSource = TEXT("OptionsString"); + } + + /*if (!ExperienceId.IsValid() && World->IsPlayInEditor()) + { + ExperienceId = GetDefault()->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); + ExperienceIdSource = TEXT("CommandLine"); + } + } + + // see if the world settings has a default experience + if (!ExperienceId.IsValid()) + { + if (AALSWorldSettings* TypedWorldSettings = Cast(GetWorldSettings())) + { + ExperienceId = TypedWorldSettings->GetDefaultGameplayExperience(); + ExperienceIdSource = TEXT("WorldSettings"); + } + } + + UALSAssetManager& AssetManager = UALSAssetManager::Get(); + FAssetData Dummy; + if (ExperienceId.IsValid() && !AssetManager.GetPrimaryAssetData(ExperienceId, /*out*/ Dummy)) + { + UE_LOG(LogALSExperience, 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()) + { + //@TODO: Pull this from a config setting or something + ExperienceId = FPrimaryAssetId(FPrimaryAssetType("ALSExperienceDefinition"), FName("BP_DungeonCrawler_DefaultExperience")); + ExperienceIdSource = TEXT("DungeonCrawlerCore"); + } + + UE_LOG(LogTemp, Warning, TEXT("ExperienceID: %s, ExperienceSource %s"), *ExperienceId.ToString(), *ExperienceIdSource); + OnMatchAssignmentGiven(ExperienceId, ExperienceIdSource); +} + +void AALSGameMode::OnMatchAssignmentGiven(FPrimaryAssetId ExperienceId, const FString& ExperienceIdSource) +{ +#if WITH_SERVER_CODE + if (ExperienceId.IsValid()) + { + UE_LOG(LogALSExperience, Log, TEXT("Identified experience %s (Source: %s)"), *ExperienceId.ToString(), *ExperienceIdSource); + + UALSExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); + check(ExperienceComponent); + ExperienceComponent->ServerSetCurrentExperience(ExperienceId); + } + else + { + UE_LOG(LogALSExperience, Error, TEXT("Failed to identify experience, loading screen will stay up forever")); + } +#endif +} + +void AALSGameMode::OnExperienceLoaded(const UALSExperienceDefinition* 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* PC = Cast(*Iterator); + if ((PC != nullptr) && (PC->GetPawn() == nullptr)) + { + if (PlayerCanRestart(PC)) + { + RestartPlayer(PC); + } + } + } +} + +bool AALSGameMode::IsExperienceLoaded() const +{ + check(GameState); + UALSExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); + check(ExperienceComponent); + + return ExperienceComponent->IsExperienceLoaded(); +} + +UClass* AALSGameMode::GetDefaultPawnClassForController_Implementation(AController* InController) +{ + if (const UALSPawnData* PawnData = GetPawnDataForController(InController)) + { + if (PawnData->PawnClass) + { + return PawnData->PawnClass; + } + } + + return Super::GetDefaultPawnClassForController_Implementation(InController); +} + +APawn* AALSGameMode::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(PawnClass, SpawnTransform, SpawnInfo)) + { + if (UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(SpawnedPawn)) + { + if (const UALSPawnData* PawnData = GetPawnDataForController(NewPlayer)) + { + PawnExtComp->SetPawnData(PawnData); + } + else + { + UE_LOG(LogALS, Error, TEXT("Game mode was unable to set PawnData on the spawned pawn [%s]."), *GetNameSafe(SpawnedPawn)); + } + } + + SpawnedPawn->FinishSpawning(SpawnTransform); + + return SpawnedPawn; + } + else + { + UE_LOG(LogALS, Error, TEXT("Game mode was unable to spawn Pawn of class [%s] at [%s]."), *GetNameSafe(PawnClass), *SpawnTransform.ToHumanReadableString()); + } + } + else + { + UE_LOG(LogALS, Error, TEXT("Game mode was unable to spawn Pawn due to NULL pawn class.")); + } + + return nullptr; +} + +bool AALSGameMode::ShouldSpawnAtStartSpot(AController* Player) +{ + // We never want to use the start spot, always use the spawn management component. + return false; +} + +void AALSGameMode::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* AALSGameMode::ChoosePlayerStart_Implementation(AController* Player) +{ + /*if (UALSPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass()) + { + return PlayerSpawningComponent->ChoosePlayerStart(Player); + }*/ + + return Super::ChoosePlayerStart_Implementation(Player); +} + +void AALSGameMode::FinishRestartPlayer(AController* NewPlayer, const FRotator& StartRotation) +{ + /*if (UALSPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass()) + { + PlayerSpawningComponent->FinishRestartPlayer(NewPlayer, StartRotation); + }*/ + + Super::FinishRestartPlayer(NewPlayer, StartRotation); +} + +bool AALSGameMode::PlayerCanRestart_Implementation(APlayerController* Player) +{ + return ControllerCanRestart(Player); +} + +bool AALSGameMode::ControllerCanRestart(AController* Controller) +{ + if (APlayerController* PC = Cast(Controller)) + { + if (!Super::PlayerCanRestart_Implementation(PC)) + { + return false; + } + } + else + { + // Bot version of Super::PlayerCanRestart_Implementation + if ((Controller == nullptr) || Controller->IsPendingKillPending()) + { + return false; + } + } + + /*if (UALSPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass()) + { + return PlayerSpawningComponent->ControllerCanRestart(Controller); + }*/ + + return true; +} + +void AALSGameMode::InitGameState() +{ + Super::InitGameState(); + + // Listen for the experience load to complete + UALSExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); + check(ExperienceComponent); + ExperienceComponent->CallOrRegister_OnExperienceLoaded(FOnALSExperienceLoaded::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded)); +} + +void AALSGameMode::OnPostLogin(AController* NewPlayer) +{ + Super::OnPostLogin(NewPlayer); + + OnGameModeCombinedPostLoginDelegate.Broadcast(this, NewPlayer); +} + +void AALSGameMode::RequestPlayerRestartNextFrame(AController* Controller, bool bForceReset) +{ + if (bForceReset && (Controller != nullptr)) + { + Controller->Reset(); + } + + if (APlayerController* PC = Cast(Controller)) + { + GetWorldTimerManager().SetTimerForNextTick(PC, &APlayerController::ServerRestartPlayer_Implementation); + } + /*else if (AALSPlayerBotController* BotController = Cast(Controller)) + { + GetWorldTimerManager().SetTimerForNextTick(BotController, &AALSPlayerBotController::ServerRestartController); + }*/ +} + +bool AALSGameMode::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 AALSGameMode::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(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 + { + UE_LOG(LogALSExperience, Verbose, TEXT("FailedToRestartPlayer(%s) and PlayerCanRestart returned false, so we're not going to try again."), *GetPathNameSafe(NewPlayer)); + } + } + else + { + RequestPlayerRestartNextFrame(NewPlayer, false); + } + } + else + { + UE_LOG(LogALS, Verbose, TEXT("FailedToRestartPlayer(%s) but there's no pawn class so giving up."), *GetPathNameSafe(NewPlayer)); + } +} diff --git a/Source/ALSV4_CPP/Private/GameModes/ALSGameState.cpp b/Source/ALSV4_CPP/Private/GameModes/ALSGameState.cpp new file mode 100644 index 00000000..ba718ea3 --- /dev/null +++ b/Source/ALSV4_CPP/Private/GameModes/ALSGameState.cpp @@ -0,0 +1,101 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GameModes/ALSGameState.h" + +#include "AbilitySystem/ALSAbilitySystemComponent.h" +#include "AbilitySystemComponent.h" +#include "Containers/Array.h" +#include "Engine/EngineBaseTypes.h" +#include "GameFramework/GameplayMessageSubsystem.h" +#include "GameModes/ALSExperienceManagerComponent.h" +#include "HAL/Platform.h" +#include "Misc/AssertionMacros.h" +#include "Net/UnrealNetwork.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSGameState) + +class APlayerState; +class FLifetimeProperty; + +extern ENGINE_API float GAverageFPS; + + +AALSGameState::AALSGameState(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = true; + + AbilitySystemComponent = ObjectInitializer.CreateDefaultSubobject(this, TEXT("AbilitySystemComponent")); + AbilitySystemComponent->SetIsReplicated(true); + AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed); + + ExperienceManagerComponent = CreateDefaultSubobject(TEXT("ExperienceManagerComponent")); + + ServerFPS = 0.0f; +} + +void AALSGameState::PreInitializeComponents() +{ + Super::PreInitializeComponents(); +} + +void AALSGameState::PostInitializeComponents() +{ + Super::PostInitializeComponents(); + + check(AbilitySystemComponent); + AbilitySystemComponent->InitAbilityActorInfo(/*Owner=*/ this, /*Avatar=*/ this); +} + +UAbilitySystemComponent* AALSGameState::GetAbilitySystemComponent() const +{ + return AbilitySystemComponent; +} + +void AALSGameState::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); +} + +void AALSGameState::AddPlayerState(APlayerState* PlayerState) +{ + Super::AddPlayerState(PlayerState); +} + +void AALSGameState::RemovePlayerState(APlayerState* PlayerState) +{ + //@TODO: This isn't getting called right now (only the 'rich' AGameMode uses it, not AGameModeBase) + // Need to at least comment the engine code, and possibly move things around + Super::RemovePlayerState(PlayerState); +} + +void AALSGameState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ThisClass, ServerFPS); +} + +void AALSGameState::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + if (GetLocalRole() == ROLE_Authority) + { + ServerFPS = GAverageFPS; + } +} + +void AALSGameState::MulticastMessageToClients_Implementation(const FALSVerbMessage Message) +{ + if (GetNetMode() == NM_Client) + { + UGameplayMessageSubsystem::Get(this).BroadcastMessage(Message.Verb, Message); + } +} + +void AALSGameState::MulticastReliableMessageToClients_Implementation(const FALSVerbMessage Message) +{ + MulticastMessageToClients_Implementation(Message); +} diff --git a/Source/ALSV4_CPP/Private/GameModes/ALSWorldSettings.cpp b/Source/ALSV4_CPP/Private/GameModes/ALSWorldSettings.cpp new file mode 100644 index 00000000..50f75503 --- /dev/null +++ b/Source/ALSV4_CPP/Private/GameModes/ALSWorldSettings.cpp @@ -0,0 +1,54 @@ +#include "GameModes/ALSWorldSettings.h" +#include "GameFramework/PlayerStart.h" +#include "EngineUtils.h" +#include "Misc/UObjectToken.h" +#include "Logging/TokenizedMessage.h" +#include "Logging/MessageLog.h" +#include "GameModes/ALSExperienceDefinition.h" +#include "ALSLogChannels.h" +#include "Engine/AssetManager.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSWorldSettings) + +AALSWorldSettings::AALSWorldSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +FPrimaryAssetId AALSWorldSettings::GetDefaultGameplayExperience() const +{ + FPrimaryAssetId Result; + if (!DefaultGameplayExperience.IsNull()) + { + Result = UAssetManager::Get().GetPrimaryAssetIdForPath(DefaultGameplayExperience.ToSoftObjectPath()); + + if (!Result.IsValid()) + { + UE_LOG(LogALS, Error, TEXT("%s.DefaultGameplayExperience is %s but that failed to resolve into an asset ID (you might need to add a path to the Asset Rules in your game feature plugin or project settings"), + *GetPathNameSafe(this), *DefaultGameplayExperience.ToString()); + } + } + return Result; +} + +#if WITH_EDITOR +void AALSWorldSettings::CheckForErrors() +{ + Super::CheckForErrors(); + + FMessageLog MapCheck("MapCheck"); + + for (TActorIterator PlayerStartIt(GetWorld()); PlayerStartIt; ++PlayerStartIt) + { + APlayerStart* PlayerStart = *PlayerStartIt; + if (IsValid(PlayerStart) && PlayerStart->GetClass() == APlayerStart::StaticClass()) + { + MapCheck.Warning() + ->AddToken(FUObjectToken::Create(PlayerStart)) + ->AddToken(FTextToken::Create(FText::FromString("is a normal APlayerStart, replace with AALSPlayerStart."))); + } + } + + //@TODO: Make sure the soft object path is something that can actually be turned into a primary asset ID (e.g., is not pointing to an experience in an unscanned directory) +} +#endif \ No newline at end of file diff --git a/Source/ALSV4_CPP/Private/Input/ALSInputComponent.cpp b/Source/ALSV4_CPP/Private/Input/ALSInputComponent.cpp new file mode 100644 index 00000000..0a657903 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Input/ALSInputComponent.cpp @@ -0,0 +1,104 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Input/ALSInputComponent.h" +#include "Player/ALSLocalPlayer.h" +#include "EnhancedInputSubsystems.h" +#include "Settings/ALSSettingsLocal.h" +#include "PlayerMappableInputConfig.h" + +UALSInputComponent::UALSInputComponent(const FObjectInitializer& ObjectInitializer) +{ +} + +void UALSInputComponent::AddInputMappings(const UALSInputConfig* InputConfig, UEnhancedInputLocalPlayerSubsystem* InputSubsystem) const +{ + check(InputConfig); + check(InputSubsystem); + + UALSLocalPlayer* LocalPlayer = InputSubsystem->GetLocalPlayer(); + check(LocalPlayer); + + // Add any registered input mappings from the settings! + if (UALSSettingsLocal* LocalSettings = UALSSettingsLocal::Get()) + { + // We don't want to ignore keys that were "Down" when we add the mapping context + // This allows you to die holding a movement key, keep holding while waiting for respawn, + // and have it be applied after you respawn immediately. Leaving bIgnoreAllPressedKeysUntilRelease + // to it's default "true" state would require the player to release the movement key, + // and press it again when they respawn + FModifyContextOptions Options = {}; + Options.bIgnoreAllPressedKeysUntilRelease = false; + + // Add all registered configs, which will add every input mapping context that is in it + const TArray& Configs = LocalSettings->GetAllRegisteredInputConfigs(); + for (const FLoadedMappableConfigPair& Pair : Configs) + { + if (Pair.bIsActive) + { + InputSubsystem->AddPlayerMappableConfig(Pair.Config, Options); + } + } + + // Tell enhanced input about any custom keymappings that we have set + for (const TPair& Pair : LocalSettings->GetCustomPlayerInputConfig()) + { + if (Pair.Key != NAME_None && Pair.Value.IsValid()) + { + InputSubsystem->AddPlayerMappedKey(Pair.Key, Pair.Value); + } + } + } +} + +void UALSInputComponent::RemoveInputMappings(const UALSInputConfig* InputConfig, UEnhancedInputLocalPlayerSubsystem* InputSubsystem) const +{ + check(InputConfig); + check(InputSubsystem); + + UALSLocalPlayer* LocalPlayer = InputSubsystem->GetLocalPlayer(); + check(LocalPlayer); + + if (UALSSettingsLocal* LocalSettings = UALSSettingsLocal::Get()) + { + // Remove any registered input contexts + const TArray& Configs = LocalSettings->GetAllRegisteredInputConfigs(); + for (const FLoadedMappableConfigPair& Pair : Configs) + { + InputSubsystem->RemovePlayerMappableConfig(Pair.Config); + } + + // Clear any player mapped keys from enhanced input + for (const TPair& Pair : LocalSettings->GetCustomPlayerInputConfig()) + { + InputSubsystem->RemovePlayerMappedKey(Pair.Key); + } + } +} + +void UALSInputComponent::RemoveBinds(TArray& BindHandles) +{ + for (uint32 Handle : BindHandles) + { + RemoveBindingByHandle(Handle); + } + BindHandles.Reset(); +} + +void UALSInputComponent::AddInputConfig(const FLoadedMappableConfigPair& ConfigPair, UEnhancedInputLocalPlayerSubsystem* InputSubsystem) +{ + check(InputSubsystem); + if (ensure(ConfigPair.bIsActive)) + { + InputSubsystem->AddPlayerMappableConfig(ConfigPair.Config); + } +} + +void UALSInputComponent::RemoveInputConfig(const FLoadedMappableConfigPair& ConfigPair, UEnhancedInputLocalPlayerSubsystem* InputSubsystem) +{ + check(InputSubsystem); + if (!ConfigPair.bIsActive) + { + InputSubsystem->AddPlayerMappableConfig(ConfigPair.Config); + } +} diff --git a/Source/ALSV4_CPP/Private/Input/ALSInputConfig.cpp b/Source/ALSV4_CPP/Private/Input/ALSInputConfig.cpp new file mode 100644 index 00000000..9b0b9b7e --- /dev/null +++ b/Source/ALSV4_CPP/Private/Input/ALSInputConfig.cpp @@ -0,0 +1,46 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Input/ALSInputConfig.h" + +#include "ALSLogChannels.h" + +UALSInputConfig::UALSInputConfig(const FObjectInitializer& ObjectInitializer) +{ +} + +const UInputAction* UALSInputConfig::FindNativeInputActionForTag(const FGameplayTag& InputTag, bool bLogNotFound) const +{ + for (const FALSInputAction& Action : NativeInputActions) + { + if (Action.InputAction && (Action.InputTag == InputTag)) + { + return Action.InputAction; + } + } + + if (bLogNotFound) + { + UE_LOG(LogALS, Error, TEXT("Can't find NativeInputAction for InputTag [%s] on InputConfig [%s]."), *InputTag.ToString(), *GetNameSafe(this)); + } + + return nullptr; +} + +const UInputAction* UALSInputConfig::FindAbilityInputActionForTag(const FGameplayTag& InputTag, bool bLogNotFound) const +{ + for (const FALSInputAction& Action : AbilityInputActions) + { + if (Action.InputAction && (Action.InputTag == InputTag)) + { + return Action.InputAction; + } + } + + if (bLogNotFound) + { + UE_LOG(LogALS, Error, TEXT("Can't find AbilityInputAction for InputTag [%s] on InputConfig [%s]."), *InputTag.ToString(), *GetNameSafe(this)); + } + + return nullptr; +} \ No newline at end of file diff --git a/Source/ALSV4_CPP/Private/Input/ALSMappableConfigPair.cpp b/Source/ALSV4_CPP/Private/Input/ALSMappableConfigPair.cpp new file mode 100644 index 00000000..7aefc7c2 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Input/ALSMappableConfigPair.cpp @@ -0,0 +1,90 @@ +#include "Input/ALSMappableConfigPair.h" + +#include "System/ALSAssetManager.h" +#include "Settings/ALSSettingsLocal.h" +#include "ICommonUIModule.h" +#include "CommonUISettings.h" +#include "PlayerMappableInputConfig.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSMappableConfigPair) + +bool FMappableConfigPair::CanBeActivated() const +{ + const FGameplayTagContainer& PlatformTraits = ICommonUIModule::GetSettings().GetPlatformTraits(); + + // If the current platform does NOT have all the dependent traits, then don't activate it + if (!DependentPlatformTraits.IsEmpty() && !PlatformTraits.HasAll(DependentPlatformTraits)) + { + return false; + } + + // If the platform has any of the excluded traits, then we shouldn't activate this config. + if (!ExcludedPlatformTraits.IsEmpty() && PlatformTraits.HasAny(ExcludedPlatformTraits)) + { + return false; + } + + return true; +} + +bool FMappableConfigPair::RegisterPair(const FMappableConfigPair& Pair) +{ + UALSAssetManager& AssetManager = UALSAssetManager::Get(); + + if (UALSSettingsLocal* Settings = UALSSettingsLocal::Get()) + { + // Register the pair with the settings, but do not activate it yet + if (const UPlayerMappableInputConfig* LoadedConfig = AssetManager.GetAsset(Pair.Config)) + { + Settings->RegisterInputConfig(Pair.Type, LoadedConfig, false); + return true; + } + } + + return false; +} + +bool FMappableConfigPair::ActivatePair(const FMappableConfigPair& Pair) +{ + UALSAssetManager& AssetManager = UALSAssetManager::Get(); + // Only activate a pair that has been successfully registered + if (FMappableConfigPair::RegisterPair(Pair) && Pair.CanBeActivated()) + { + if (UALSSettingsLocal* Settings = UALSSettingsLocal::Get()) + { + if (const UPlayerMappableInputConfig* LoadedConfig = AssetManager.GetAsset(Pair.Config)) + { + Settings->ActivateInputConfig(LoadedConfig); + return true; + } + } + } + return false; +} + +void FMappableConfigPair::DeactivatePair(const FMappableConfigPair& Pair) +{ + UALSAssetManager& AssetManager = UALSAssetManager::Get(); + + if (UALSSettingsLocal* Settings = UALSSettingsLocal::Get()) + { + if (const UPlayerMappableInputConfig* LoadedConfig = AssetManager.GetAsset(Pair.Config)) + { + Settings->DeactivateInputConfig(LoadedConfig); + } + } +} + +void FMappableConfigPair::UnregisterPair(const FMappableConfigPair& Pair) +{ + UALSAssetManager& AssetManager = UALSAssetManager::Get(); + + if (UALSSettingsLocal* Settings = UALSSettingsLocal::Get()) + { + if (const UPlayerMappableInputConfig* LoadedConfig = AssetManager.GetAsset(Pair.Config)) + { + Settings->UnregisterInputConfig(LoadedConfig); + } + } +} + diff --git a/Source/ALSV4_CPP/Private/Physics/PhysicalMaterialWithTags.cpp b/Source/ALSV4_CPP/Private/Physics/PhysicalMaterialWithTags.cpp new file mode 100644 index 00000000..3ddde234 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Physics/PhysicalMaterialWithTags.cpp @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Physics/PhysicalMaterialWithTags.h" + +UPhysicalMaterialWithTags::UPhysicalMaterialWithTags(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} diff --git a/Source/ALSV4_CPP/Private/Player/ALSLocalPlayer.cpp b/Source/ALSV4_CPP/Private/Player/ALSLocalPlayer.cpp new file mode 100644 index 00000000..f94234fd --- /dev/null +++ b/Source/ALSV4_CPP/Private/Player/ALSLocalPlayer.cpp @@ -0,0 +1,53 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Player/ALSLocalPlayer.h" +#include "InputMappingContext.h" +#include "AudioMixerBlueprintLibrary.h" +#include "GameFramework/PlayerController.h" + +UALSLocalPlayer::UALSLocalPlayer() +{ +} + +void UALSLocalPlayer::SwitchController(class APlayerController* PC) +{ + Super::SwitchController(PC); + + OnPlayerControllerChanged(PlayerController); +} + +bool UALSLocalPlayer::SpawnPlayActor(const FString& URL, FString& OutError, UWorld* InWorld) +{ + const bool bResult = Super::SpawnPlayActor(URL, OutError, InWorld); + + OnPlayerControllerChanged(PlayerController); + + return bResult; +} + +void UALSLocalPlayer::InitOnlineSession() +{ + OnPlayerControllerChanged(PlayerController); + + Super::InitOnlineSession(); +} + +void UALSLocalPlayer::OnAudioOutputDeviceChanged(const FString& InAudioOutputDeviceId) +{ + FOnCompletedDeviceSwap DevicesSwappedCallback; + DevicesSwappedCallback.BindUFunction(this, FName("OnCompletedAudioDeviceSwap")); + UAudioMixerBlueprintLibrary::SwapAudioOutputDevice(GetWorld(), InAudioOutputDeviceId, DevicesSwappedCallback); +} + +void UALSLocalPlayer::OnCompletedAudioDeviceSwap(const FSwapAudioOutputResult& SwapResult) +{ + if (SwapResult.Result == ESwapAudioOutputDeviceResultState::Failure) + { + } +} + +void UALSLocalPlayer::OnPlayerControllerChanged(APlayerController* NewController) +{ +} + diff --git a/Source/ALSV4_CPP/Private/Player/ALSPlayerController2.cpp b/Source/ALSV4_CPP/Private/Player/ALSPlayerController2.cpp new file mode 100644 index 00000000..c9deb5f3 --- /dev/null +++ b/Source/ALSV4_CPP/Private/Player/ALSPlayerController2.cpp @@ -0,0 +1,276 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Player/ALSPlayerController2.h" +#include "ALSLogChannels.h" +#include "Player/ALSPlayerState.h" +#include "Character/ALSPawnData.h" +#include "AbilitySystem/ALSAbilitySystemComponent.h" +#include "EngineUtils.h" +#include "ALSGameplayTags.h" +#include "GameFramework/Pawn.h" +#include "AbilitySystemGlobals.h" +#include "CommonInputSubsystem.h" +#include "Player/ALSLocalPlayer.h" + +AALSPlayerController2::AALSPlayerController2(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void AALSPlayerController2::PreInitializeComponents() +{ + Super::PreInitializeComponents(); +} + +void AALSPlayerController2::BeginPlay() +{ + Super::BeginPlay(); + SetActorHiddenInGame(false); +} + +void AALSPlayerController2::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); +} + +void AALSPlayerController2::ReceivedPlayer() +{ + Super::ReceivedPlayer(); +} + +void AALSPlayerController2::PlayerTick(float DeltaTime) +{ + Super::PlayerTick(DeltaTime); + + // If we are auto running then add some player input + if (GetIsAutoRunning()) + { + if (APawn* CurrentPawn = GetPawn()) + { + const FRotator MovementRotation(0.0f, GetControlRotation().Yaw, 0.0f); + const FVector MovementDirection = MovementRotation.RotateVector(FVector::ForwardVector); + CurrentPawn->AddMovementInput(MovementDirection, 1.0f); + } + } +} + +AALSPlayerState* AALSPlayerController2::GetALSPlayerState() const +{ + return CastChecked(PlayerState, ECastCheckedType::NullAllowed); +} + +UALSAbilitySystemComponent* AALSPlayerController2::GetALSAbilitySystemComponent() const +{ + const AALSPlayerState* ALSPS = GetALSPlayerState(); + return (ALSPS ? ALSPS->GetALSAbilitySystemComponent() : nullptr); +} + +void AALSPlayerController2::OnPlayerStateChangedTeam(UObject* TeamAgent, int32 OldTeam, int32 NewTeam) +{ +} + +void AALSPlayerController2::OnPlayerStateChanged() +{ + // Empty, place for derived classes to implement without having to hook all the other events +} + +void AALSPlayerController2::BroadcastOnPlayerStateChanged() +{ + OnPlayerStateChanged(); + + LastSeenPlayerState = PlayerState; +} + +void AALSPlayerController2::InitPlayerState() +{ + Super::InitPlayerState(); + BroadcastOnPlayerStateChanged(); +} + +void AALSPlayerController2::CleanupPlayerState() +{ + Super::CleanupPlayerState(); + BroadcastOnPlayerStateChanged(); +} + +void AALSPlayerController2::OnRep_PlayerState() +{ + Super::OnRep_PlayerState(); + BroadcastOnPlayerStateChanged(); +} + +void AALSPlayerController2::SetPlayer(UPlayer* InPlayer) +{ + Super::SetPlayer(InPlayer); +} + +void AALSPlayerController2::PreProcessInput(const float DeltaTime, const bool bGamePaused) +{ + Super::PreProcessInput(DeltaTime, bGamePaused); +} + +void AALSPlayerController2::PostProcessInput(const float DeltaTime, const bool bGamePaused) +{ + if (UALSAbilitySystemComponent* ALSASC = GetALSAbilitySystemComponent()) + { + ALSASC->ProcessAbilityInput(DeltaTime, bGamePaused); + } + + Super::PostProcessInput(DeltaTime, bGamePaused); +} + +// TODO: Fix this +/* +void AALSPlayerController2::OnCameraPenetratingTarget() +{ + bHideViewTargetPawnNextFrame = true; +}*/ + +void AALSPlayerController2::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + + SetIsAutoRunning(false); +} + +void AALSPlayerController2::SetIsAutoRunning(const bool bEnabled) +{ + const bool bIsAutoRunning = GetIsAutoRunning(); + if (bEnabled != bIsAutoRunning) + { + if (!bEnabled) + { + OnEndAutoRun(); + } + else + { + OnStartAutoRun(); + } + } +} + +bool AALSPlayerController2::GetIsAutoRunning() const +{ + bool bIsAutoRunning = false; + if (const UALSAbilitySystemComponent* ALSASC = GetALSAbilitySystemComponent()) + { + bIsAutoRunning = ALSASC->GetTagCount(FALSGameplayTags::Get().Status_AutoRunning) > 0; + } + return bIsAutoRunning; +} + +void AALSPlayerController2::OnStartAutoRun() +{ + if (UALSAbilitySystemComponent* ALSASC = GetALSAbilitySystemComponent()) + { + ALSASC->SetLooseGameplayTagCount(FALSGameplayTags::Get().Status_AutoRunning, 1); + K2_OnStartAutoRun(); + } +} + +void AALSPlayerController2::OnEndAutoRun() +{ + if (UALSAbilitySystemComponent* ALSASC = GetALSAbilitySystemComponent()) + { + ALSASC->SetLooseGameplayTagCount(FALSGameplayTags::Get().Status_AutoRunning, 0); + K2_OnEndAutoRun(); + } +} + +void AALSPlayerController2::UpdateForceFeedback(IInputInterface* InputInterface, const int32 ControllerId) +{ + if (bForceFeedbackEnabled) + { + if (const UCommonInputSubsystem* CommonInputSubsystem = UCommonInputSubsystem::Get(GetLocalPlayer())) + { + const ECommonInputType CurrentInputType = CommonInputSubsystem->GetCurrentInputType(); + if (CurrentInputType == ECommonInputType::Gamepad || CurrentInputType == ECommonInputType::Touch) + { + InputInterface->SetForceFeedbackChannelValues(ControllerId, ForceFeedbackValues); + return; + } + } + } + + InputInterface->SetForceFeedbackChannelValues(ControllerId, FForceFeedbackValues()); +} + +void AALSPlayerController2::UpdateHiddenComponents(const FVector& ViewLocation, TSet& OutHiddenComponents) +{ + Super::UpdateHiddenComponents(ViewLocation, OutHiddenComponents); + + if (bHideViewTargetPawnNextFrame) + { + AActor* const ViewTargetPawn = PlayerCameraManager ? Cast(PlayerCameraManager->GetViewTarget()) : nullptr; + if (ViewTargetPawn) + { + // internal helper func to hide all the components + auto AddToHiddenComponents = [&OutHiddenComponents](const TInlineComponentArray& InComponents) + { + // add every component and all attached children + for (UPrimitiveComponent* Comp : InComponents) + { + if (Comp->IsRegistered()) + { + OutHiddenComponents.Add(Comp->ComponentId); + + for (USceneComponent* AttachedChild : Comp->GetAttachChildren()) + { + static FName NAME_NoParentAutoHide(TEXT("NoParentAutoHide")); + UPrimitiveComponent* AttachChildPC = Cast(AttachedChild); + if (AttachChildPC && AttachChildPC->IsRegistered() && !AttachChildPC->ComponentTags.Contains(NAME_NoParentAutoHide)) + { + OutHiddenComponents.Add(AttachChildPC->ComponentId); + } + } + } + } + }; + + //TODO Solve with an interface. Gather hidden components or something. + //TODO Hiding isn't awesome, sometimes you want the effect of a fade out over a proximity, needs to bubble up to designers. + + // hide pawn's components + TInlineComponentArray PawnComponents; + ViewTargetPawn->GetComponents(PawnComponents); + AddToHiddenComponents(PawnComponents); + + //// hide weapon too + //if (ViewTargetPawn->CurrentWeapon) + //{ + // TInlineComponentArray WeaponComponents; + // ViewTargetPawn->CurrentWeapon->GetComponents(WeaponComponents); + // AddToHiddenComponents(WeaponComponents); + //} + } + + // we consumed it, reset for next frame + bHideViewTargetPawnNextFrame = false; + } +} + +void AALSPlayerController2::OnUnPossess() +{ + // Make sure the pawn that is being unpossessed doesn't remain our ASC's avatar actor + if (APawn* PawnBeingUnpossessed = GetPawn()) + { + if (UAbilitySystemComponent* ASC = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(PlayerState)) + { + if (ASC->GetAvatarActor() == PawnBeingUnpossessed) + { + ASC->SetAvatarActor(nullptr); + } + } + } + + Super::OnUnPossess(); +} + +////////////////////////////////////////////////////////////////////// +// AALSReplayPlayerController + +void AALSReplayPlayerController::SetPlayer(UPlayer* InPlayer) +{ + Super::SetPlayer(InPlayer); +} diff --git a/Source/ALSV4_CPP/Private/Player/ALSPlayerState.cpp b/Source/ALSV4_CPP/Private/Player/ALSPlayerState.cpp new file mode 100644 index 00000000..cc8999ed --- /dev/null +++ b/Source/ALSV4_CPP/Private/Player/ALSPlayerState.cpp @@ -0,0 +1,240 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "Player/ALSPlayerState.h" +#include "AbilitySystem/ALSAbilitySet.h" +#include "AbilitySystem/ALSAbilitySystemComponent.h" +#include "AbilitySystemComponent.h" +#include "Character/ALSPawnData.h" +#include "Character/ALSPawnExtensionComponent.h" +#include "Components/GameFrameworkComponentManager.h" +#include "Containers/Array.h" +#include "Containers/UnrealString.h" +#include "CoreTypes.h" +#include "Delegates/Delegate.h" +#include "Engine/EngineBaseTypes.h" +#include "Engine/EngineTypes.h" +#include "Engine/World.h" +#include "GameFramework/GameStateBase.h" +#include "GameFramework/GameplayMessageSubsystem.h" +#include "GameFramework/Pawn.h" +#include "GameModes/ALSExperienceManagerComponent.h" +//@TODO: Would like to isolate this a bit better to get the pawn data in here without this having to know about other stuff +#include "GameModes/ALSGameMode.h" +#include "GameplayTagContainer.h" +#include "Logging/LogCategory.h" +#include "Logging/LogMacros.h" +#include "ALSLogChannels.h" +#include "Player/ALSPlayerController2.h" +#include "Misc/AssertionMacros.h" +#include "Net/Core/PushModel/PushModel.h" +#include "Net/UnrealNetwork.h" +#include "Trace/Detail/Channel.h" +#include "UObject/NameTypes.h" +#include "UObject/UObjectBaseUtility.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSPlayerState) + +class AController; +class APlayerState; +class FLifetimeProperty; + +const FName AALSPlayerState::NAME_ALSAbilityReady("ALSAbilitiesReady"); + +AALSPlayerState::AALSPlayerState(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , MyPlayerConnectionType(EALSPlayerConnectionType::Player) +{ + AbilitySystemComponent = ObjectInitializer.CreateDefaultSubobject(this, TEXT("AbilitySystemComponent")); + AbilitySystemComponent->SetIsReplicated(true); + AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed); + + // AbilitySystemComponent needs to be updated at a high frequency. + NetUpdateFrequency = 100.0f; +} + +void AALSPlayerState::PreInitializeComponents() +{ + Super::PreInitializeComponents(); +} + +void AALSPlayerState::Reset() +{ + Super::Reset(); +} + +void AALSPlayerState::ClientInitialize(AController* C) +{ + Super::ClientInitialize(C); + + if (UALSPawnExtensionComponent* PawnExtComp = UALSPawnExtensionComponent::FindPawnExtensionComponent(GetPawn())) + { + PawnExtComp->CheckDefaultInitialization(); + } +} + +void AALSPlayerState::CopyProperties(APlayerState* PlayerState) +{ + Super::CopyProperties(PlayerState); + + //@TODO: Copy stats +} + +void AALSPlayerState::OnDeactivated() +{ + bool bDestroyDeactivatedPlayerState = false; + + switch (GetPlayerConnectionType()) + { + case EALSPlayerConnectionType::Player: + case EALSPlayerConnectionType::InactivePlayer: + //@TODO: Ask the experience if we should destroy disconnecting players immediately or leave them around + // (e.g., for long running servers where they might build up if lots of players cycle through) + bDestroyDeactivatedPlayerState = true; + break; + default: + bDestroyDeactivatedPlayerState = true; + break; + } + + SetPlayerConnectionType(EALSPlayerConnectionType::InactivePlayer); + + if (bDestroyDeactivatedPlayerState) + { + Destroy(); + } +} + +void AALSPlayerState::OnReactivated() +{ + if (GetPlayerConnectionType() == EALSPlayerConnectionType::InactivePlayer) + { + SetPlayerConnectionType(EALSPlayerConnectionType::Player); + } +} + +void AALSPlayerState::OnExperienceLoaded(const UALSExperienceDefinition* /*CurrentExperience*/) +{ + UE_LOG(LogTemp, Warning, TEXT("AALSPlayerState::OnExperienceLoaded()")) + if (AALSGameMode* ALSGameMode = GetWorld()->GetAuthGameMode()) + { + if (const UALSPawnData* NewPawnData = ALSGameMode->GetPawnDataForController(GetOwningController())) + { + SetPawnData(NewPawnData); + } + else + { + UE_LOG(LogALS, Error, TEXT("AALSPlayerState::OnExperienceLoaded(): Unable to find PawnData to initialize player state [%s]!"), *GetNameSafe(this)); + } + } +} + +void AALSPlayerState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + FDoRepLifetimeParams SharedParams; + SharedParams.bIsPushBased = true; + + DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, PawnData, SharedParams); + DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MyPlayerConnectionType, SharedParams) + + DOREPLIFETIME(ThisClass, StatTags); +} + +AALSPlayerController2* AALSPlayerState::GetALSPlayerController() const +{ + return Cast(GetOwner()); +} + +UAbilitySystemComponent* AALSPlayerState::GetAbilitySystemComponent() const +{ + return GetALSAbilitySystemComponent(); +} + +void AALSPlayerState::PostInitializeComponents() +{ + Super::PostInitializeComponents(); + + check(AbilitySystemComponent); + AbilitySystemComponent->InitAbilityActorInfo(this, GetPawn()); + + if (GetNetMode() != NM_Client) + { + AGameStateBase* GameState = GetWorld()->GetGameState(); + check(GameState); + UALSExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); + check(ExperienceComponent); + ExperienceComponent->CallOrRegister_OnExperienceLoaded(FOnALSExperienceLoaded::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded)); + } +} + +void AALSPlayerState::SetPawnData(const UALSPawnData* InPawnData) +{ + check(InPawnData); + + if (GetLocalRole() != ROLE_Authority) + { + return; + } + + if (PawnData) + { + UE_LOG(LogALS, Error, TEXT("Trying to set PawnData [%s] on player state [%s] that already has valid PawnData [%s]."), *GetNameSafe(InPawnData), *GetNameSafe(this), *GetNameSafe(PawnData)); + return; + } + + MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, PawnData, this); + PawnData = InPawnData; + + for (const UALSAbilitySet* AbilitySet : PawnData->AbilitySets) + { + if (AbilitySet) + { + AbilitySet->GiveToAbilitySystem(AbilitySystemComponent, nullptr); + } + } + + UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, NAME_ALSAbilityReady); + + ForceNetUpdate(); +} + +void AALSPlayerState::OnRep_PawnData() +{ +} + +void AALSPlayerState::SetPlayerConnectionType(EALSPlayerConnectionType NewType) +{ + MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, MyPlayerConnectionType, this); + MyPlayerConnectionType = NewType; +} + +void AALSPlayerState::AddStatTagStack(FGameplayTag Tag, int32 StackCount) +{ + StatTags.AddStack(Tag, StackCount); +} + +void AALSPlayerState::RemoveStatTagStack(FGameplayTag Tag, int32 StackCount) +{ + StatTags.RemoveStack(Tag, StackCount); +} + +int32 AALSPlayerState::GetStatTagStackCount(FGameplayTag Tag) const +{ + return StatTags.GetStackCount(Tag); +} + +bool AALSPlayerState::HasStatTag(FGameplayTag Tag) const +{ + return StatTags.ContainsTag(Tag); +} + +void AALSPlayerState::ClientBroadcastMessage_Implementation(const FALSVerbMessage Message) +{ + // This check is needed to prevent running the action when in standalone mode + if (GetNetMode() == NM_Client) + { + UGameplayMessageSubsystem::Get(this).BroadcastMessage(Message.Verb, Message); + } +} + diff --git a/Source/ALSV4_CPP/Private/Settings/ALSSettingsLocal.cpp b/Source/ALSV4_CPP/Private/Settings/ALSSettingsLocal.cpp new file mode 100644 index 00000000..b7f5aedc --- /dev/null +++ b/Source/ALSV4_CPP/Private/Settings/ALSSettingsLocal.cpp @@ -0,0 +1,175 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Settings/ALSSettingsLocal.h" +#include "CommonInputBaseTypes.h" +#include "CommonInputSubsystem.h" +#include "PlayerMappableInputConfig.h" +#include "EnhancedInputSubsystems.h" +#include "Player/ALSLocalPlayer.h" + +UALSSettingsLocal::UALSSettingsLocal() +{ +} + +UALSSettingsLocal* UALSSettingsLocal::Get() +{ + // HACK: Fix this + return GEngine ? CastChecked(GEngine->GetGameUserSettings()) : nullptr; +} + +void UALSSettingsLocal::BeginDestroy() +{ + Super::BeginDestroy(); +} + +void UALSSettingsLocal::SetControllerPlatform(const FName InControllerPlatform) +{ + if (ControllerPlatform != InControllerPlatform) + { + ControllerPlatform = InControllerPlatform; + + // Apply the change to the common input subsystem so that we refresh any input icons we're using. + if (UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(GetTypedOuter())) + { + InputSubsystem->SetGamepadInputType(ControllerPlatform); + } + } +} + +FName UALSSettingsLocal::GetControllerPlatform() const +{ + return ControllerPlatform; +} + +void UALSSettingsLocal::RegisterInputConfig(ECommonInputType Type, const UPlayerMappableInputConfig* NewConfig, const bool bIsActive) +{ + if (NewConfig) + { + const int32 ExistingConfigIdx = RegisteredInputConfigs.IndexOfByPredicate( [&NewConfig](const FLoadedMappableConfigPair& Pair) { return Pair.Config == NewConfig; } ); + if (ExistingConfigIdx == INDEX_NONE) + { + const int32 NumAdded = RegisteredInputConfigs.Add(FLoadedMappableConfigPair(NewConfig, Type, bIsActive)); + if (NumAdded != INDEX_NONE) + { + OnInputConfigRegistered.Broadcast(RegisteredInputConfigs[NumAdded]); + } + } + } +} + +int32 UALSSettingsLocal::UnregisterInputConfig(const UPlayerMappableInputConfig* ConfigToRemove) +{ + if (ConfigToRemove) + { + const int32 Index = RegisteredInputConfigs.IndexOfByPredicate( [&ConfigToRemove](const FLoadedMappableConfigPair& Pair) { return Pair.Config == ConfigToRemove; } ); + if (Index != INDEX_NONE) + { + RegisteredInputConfigs.RemoveAt(Index); + return 1; + } + + } + return INDEX_NONE; +} + +void UALSSettingsLocal::ActivateInputConfig(const UPlayerMappableInputConfig* Config) +{ + if (Config) + { + const int32 ExistingConfigIdx = RegisteredInputConfigs.IndexOfByPredicate( [&Config](const FLoadedMappableConfigPair& Pair) { return Pair.Config == Config; } ); + if (ExistingConfigIdx != INDEX_NONE) + { + RegisteredInputConfigs[ExistingConfigIdx].bIsActive = true; + OnInputConfigActivated.Broadcast(RegisteredInputConfigs[ExistingConfigIdx]); + } + } +} + +void UALSSettingsLocal::DeactivateInputConfig(const UPlayerMappableInputConfig* Config) +{ + if (Config) + { + const int32 ExistingConfigIdx = RegisteredInputConfigs.IndexOfByPredicate( [&Config](const FLoadedMappableConfigPair& Pair) { return Pair.Config == Config; } ); + if (ExistingConfigIdx != INDEX_NONE) + { + RegisteredInputConfigs[ExistingConfigIdx].bIsActive = false; + OnInputConfigDeactivated.Broadcast(RegisteredInputConfigs[ExistingConfigIdx]); + } + } +} + +const UPlayerMappableInputConfig* UALSSettingsLocal::GetInputConfigByName(FName ConfigName) const +{ + for (const FLoadedMappableConfigPair& Pair : RegisteredInputConfigs) + { + if (Pair.Config->GetConfigName() == ConfigName) + { + return Pair.Config; + } + } + return nullptr; +} + +void UALSSettingsLocal::GetRegisteredInputConfigsOfType(ECommonInputType Type, TArray& OutArray) const +{ + OutArray.Empty(); + + // If "Count" is passed in then + if (Type == ECommonInputType::Count) + { + OutArray = RegisteredInputConfigs; + return; + } + + for (const FLoadedMappableConfigPair& Pair : RegisteredInputConfigs) + { + if (Pair.Type == Type) + { + OutArray.Emplace(Pair); + } + } +} + +void UALSSettingsLocal::AddOrUpdateCustomKeyboardBindings(const FName MappingName, const FKey NewKey, UALSLocalPlayer* LocalPlayer) +{ + if (MappingName == NAME_None) + { + return; + } + + if (InputConfigName != TEXT("Custom")) + { + // Copy Presets. + if (const UPlayerMappableInputConfig* DefaultConfig = GetInputConfigByName(TEXT("Default"))) + { + for (const FEnhancedActionKeyMapping& Mapping : DefaultConfig->GetPlayerMappableKeys()) + { + // Make sure that the mapping has a valid name, its possible to have an empty name + // if someone has marked a mapping as "Player Mappabe" but deleted the default field value + if (Mapping.PlayerMappableOptions.Name != NAME_None) + { + CustomKeyboardConfig.Add(Mapping.PlayerMappableOptions.Name, Mapping.Key); + } + } + } + + InputConfigName = TEXT("Custom"); + } + + if (CustomKeyboardConfig.Find(MappingName)) + { + // Change the key to the new one + CustomKeyboardConfig[MappingName] = NewKey; + } + else + { + CustomKeyboardConfig.Add(MappingName, NewKey); + } + + // Tell the enhanced input subsystem for this local player that we should remap some input! Woo + if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem(LocalPlayer)) + { + Subsystem->AddPlayerMappedKey(MappingName, NewKey); + } +} \ No newline at end of file diff --git a/Source/ALSV4_CPP/Private/System/ALSAssetManager.cpp b/Source/ALSV4_CPP/Private/System/ALSAssetManager.cpp new file mode 100644 index 00000000..afa71a0f --- /dev/null +++ b/Source/ALSV4_CPP/Private/System/ALSAssetManager.cpp @@ -0,0 +1,285 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "System/ALSAssetManager.h" +#include "ALSLogChannels.h" +#include "ALSGameplayTags.h" +#include "System/ALSGameData.h" +#include "AbilitySystemGlobals.h" +#include "Character/ALSPawnData.h" +#include "Stats/StatsMisc.h" +#include "Engine/Engine.h" +// #include "AbilitySystem/ALSGameplayCueManager.h" +#include "SWarningOrErrorBox.h" +#include "Misc/ScopedSlowTask.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(ALSAssetManager) + +const FName FALSBundles::Equipped("Equipped"); + +////////////////////////////////////////////////////////////////////// + +static FAutoConsoleCommand CVarDumpLoadedAssets( + TEXT("ALS.DumpLoadedAssets"), + TEXT("Shows all assets that were loaded via the asset manager and are currently in memory."), + FConsoleCommandDelegate::CreateStatic(UALSAssetManager::DumpLoadedAssets) +); + +////////////////////////////////////////////////////////////////////// + +#define STARTUP_JOB_WEIGHTED(JobFunc, JobWeight) StartupJobs.Add(FALSAssetManagerStartupJob(#JobFunc, [this](const FALSAssetManagerStartupJob& StartupJob, TSharedPtr& LoadHandle){JobFunc;}, JobWeight)) +#define STARTUP_JOB(JobFunc) STARTUP_JOB_WEIGHTED(JobFunc, 1.f) + +////////////////////////////////////////////////////////////////////// + +UALSAssetManager::UALSAssetManager() +{ + DefaultPawnData = nullptr; +} + +UALSAssetManager& UALSAssetManager::Get() +{ + check(GEngine); + + if (UALSAssetManager* Singleton = Cast(GEngine->AssetManager)) + { + return *Singleton; + } + + UE_LOG(LogALS, Fatal, TEXT("Invalid AssetManagerClassName in DefaultEngine.ini. It must be set to ALSAssetManager!")); + + // Fatal error above prevents this from being called. + return *NewObject(); +} + +UObject* UALSAssetManager::SynchronousLoadAsset(const FSoftObjectPath& AssetPath) +{ + if (AssetPath.IsValid()) + { + TUniquePtr LogTimePtr; + + if (ShouldLogAssetLoads()) + { + LogTimePtr = MakeUnique(*FString::Printf(TEXT("Synchronously loaded asset [%s]"), *AssetPath.ToString()), nullptr, FScopeLogTime::ScopeLog_Seconds); + } + + if (UAssetManager::IsValid()) + { + return UAssetManager::GetStreamableManager().LoadSynchronous(AssetPath, false); + } + + // Use LoadObject if asset manager isn't ready yet. + return AssetPath.TryLoad(); + } + + return nullptr; +} + +bool UALSAssetManager::ShouldLogAssetLoads() +{ + static bool bLogAssetLoads = FParse::Param(FCommandLine::Get(), TEXT("LogAssetLoads")); + return bLogAssetLoads; +} + +void UALSAssetManager::AddLoadedAsset(const UObject* Asset) +{ + if (ensureAlways(Asset)) + { + FScopeLock LoadedAssetsLock(&LoadedAssetsCritical); + LoadedAssets.Add(Asset); + } +} + +void UALSAssetManager::DumpLoadedAssets() +{ + UE_LOG(LogALS, Log, TEXT("========== Start Dumping Loaded Assets ==========")); + + for (const UObject* LoadedAsset : Get().LoadedAssets) + { + UE_LOG(LogALS, Log, TEXT(" %s"), *GetNameSafe(LoadedAsset)); + } + + UE_LOG(LogALS, Log, TEXT("... %d assets in loaded pool"), Get().LoadedAssets.Num()); + UE_LOG(LogALS, Log, TEXT("========== Finish Dumping Loaded Assets ==========")); +} + +void UALSAssetManager::StartInitialLoading() +{ + UE_LOG(LogTemp, Warning, TEXT("UALSAssetManager::StartInitialLoading")) + + SCOPED_BOOT_TIMING("UALSAssetManager::StartInitialLoading"); + + // This does all of the scanning, need to do this now even if loads are deferred + Super::StartInitialLoading(); + + STARTUP_JOB(InitializeAbilitySystem()); + //STARTUP_JOB(InitializeGameplayCueManager()); + + { + // Load base game data asset + STARTUP_JOB_WEIGHTED(GetGameData(), 25.f); + } + + // Run all the queued up startup jobs + DoAllStartupJobs(); +} + +void UALSAssetManager::InitializeAbilitySystem() +{ + UE_LOG(LogTemp, Warning, TEXT("UALSAssetManager::InitializeAbilitySystem")) + SCOPED_BOOT_TIMING("UALSAssetManager::InitializeAbilitySystem"); + + FALSGameplayTags::InitializeNativeTags(); + + UAbilitySystemGlobals::Get().InitGlobalData(); +} + +void UALSAssetManager::InitializeGameplayCueManager() +{ + SCOPED_BOOT_TIMING("UALSAssetManager::InitializeGameplayCueManager"); + + /*UALSGameplayCueManager* GCM = UALSGameplayCueManager::Get(); + check(GCM); + GCM->LoadAlwaysLoadedCues();*/ +} + + +const UALSGameData& UALSAssetManager::GetGameData() +{ + return GetOrLoadTypedGameData(ALSGameDataPath); +} + +const UALSPawnData* UALSAssetManager::GetDefaultPawnData() const +{ + return GetAsset(DefaultPawnData); +} + +UPrimaryDataAsset* UALSAssetManager::LoadGameDataOfClass(TSubclassOf DataClass, const TSoftObjectPtr& 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("ALSEditor", "BeginLoadingGameDataTask", "Loading GameData {0}"), FText::FromName(DataClass->GetFName()))); + const bool bShowCancelButton = false; + const bool bAllowInPIE = true; + SlowTask.MakeDialog(bShowCancelButton, bAllowInPIE); +#endif + UE_LOG(LogALS, 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 Handle = LoadPrimaryAssetsWithType(PrimaryAssetType); + if (Handle.IsValid()) + { + Handle->WaitUntilComplete(0.0f, false); + + // This should always work + Asset = Cast(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. + UE_LOG(LogALS, 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 UALSAssetManager::DoAllStartupJobs() +{ + SCOPED_BOOT_TIMING("UALSAssetManager::DoAllStartupJobs"); + const double AllStartupJobsStartTime = FPlatformTime::Seconds(); + + if (IsRunningDedicatedServer()) + { + // No need for periodic progress updates, just run the jobs + for (const FALSAssetManagerStartupJob& StartupJob : StartupJobs) + { + StartupJob.DoJob(); + } + } + else + { + if (StartupJobs.Num() > 0) + { + float TotalJobValue = 0.0f; + for (const FALSAssetManagerStartupJob& StartupJob : StartupJobs) + { + TotalJobValue += StartupJob.JobWeight; + } + + float AccumulatedJobValue = 0.0f; + for (FALSAssetManagerStartupJob& 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(); + + UE_LOG(LogALS, Display, TEXT("All startup jobs took %.2f seconds to complete"), FPlatformTime::Seconds() - AllStartupJobsStartTime); +} + +void UALSAssetManager::UpdateInitialGameContentLoadPercent(float GameContentPercent) +{ + // Could route this to the early startup loading screen +} + +#if WITH_EDITOR +void UALSAssetManager::PreBeginPIE(bool bStartSimulate) +{ + Super::PreBeginPIE(bStartSimulate); + + { + FScopedSlowTask SlowTask(0, NSLOCTEXT("ALSEditor", "BeginLoadingPIEData", "Loading PIE Data")); + const bool bShowCancelButton = false; + const bool bAllowInPIE = true; + SlowTask.MakeDialog(bShowCancelButton, bAllowInPIE); + + //const UALSGameData& 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 diff --git a/Source/ALSV4_CPP/Private/System/ALSAssetManagerStartupJob.cpp b/Source/ALSV4_CPP/Private/System/ALSAssetManagerStartupJob.cpp new file mode 100644 index 00000000..af645532 --- /dev/null +++ b/Source/ALSV4_CPP/Private/System/ALSAssetManagerStartupJob.cpp @@ -0,0 +1,29 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "System/ALSAssetManagerStartupJob.h" + +#include "HAL/Platform.h" +#include "Logging/LogCategory.h" +#include "Logging/LogMacros.h" +#include "ALSLogChannels.h" +#include "Trace/Detail/Channel.h" + +TSharedPtr FALSAssetManagerStartupJob::DoJob() const +{ + const double JobStartTime = FPlatformTime::Seconds(); + + TSharedPtr Handle; + UE_LOG(LogALS, Display, TEXT("Startup job \"%s\" starting"), *JobName); + JobFunc(*this, Handle); + + if (Handle.IsValid()) + { + Handle->BindUpdateDelegate(FStreamableUpdateDelegate::CreateRaw(this, &FALSAssetManagerStartupJob::UpdateSubstepProgressFromStreamable)); + Handle->WaitUntilComplete(0.0f, false); + Handle->BindUpdateDelegate(FStreamableUpdateDelegate()); + } + + UE_LOG(LogALS, Display, TEXT("Startup job \"%s\" took %.2f seconds to complete"), *JobName, FPlatformTime::Seconds() - JobStartTime); + + return Handle; +} diff --git a/Source/ALSV4_CPP/Private/System/ALSGameData.cpp b/Source/ALSV4_CPP/Private/System/ALSGameData.cpp new file mode 100644 index 00000000..773fcc82 --- /dev/null +++ b/Source/ALSV4_CPP/Private/System/ALSGameData.cpp @@ -0,0 +1,14 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "System/ALSGameData.h" +#include "System/ALSAssetManager.h" + +UALSGameData::UALSGameData() +{ +} + +const UALSGameData& UALSGameData::UALSGameData::Get() +{ + return UALSAssetManager::Get().GetGameData(); +} \ No newline at end of file diff --git a/Source/ALSV4_CPP/Private/System/GameplayTagStack.cpp b/Source/ALSV4_CPP/Private/System/GameplayTagStack.cpp new file mode 100644 index 00000000..c25fed60 --- /dev/null +++ b/Source/ALSV4_CPP/Private/System/GameplayTagStack.cpp @@ -0,0 +1,109 @@ +#include "System/GameplayTagStack.h" + +#include "Logging/LogVerbosity.h" +#include "UObject/Stack.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(GameplayTagStack) + +////////////////////////////////////////////////////////////////////// +// FGameplayTagStack + +FString FGameplayTagStack::GetDebugString() const +{ + return FString::Printf(TEXT("%sx%d"), *Tag.ToString(), StackCount); +} + +////////////////////////////////////////////////////////////////////// +// FGameplayTagStackContainer + +void FGameplayTagStackContainer::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 (FGameplayTagStack& Stack : Stacks) + { + if (Stack.Tag == Tag) + { + const int32 NewCount = Stack.StackCount + StackCount; + Stack.StackCount = NewCount; + TagToCountMap[Tag] = NewCount; + MarkItemDirty(Stack); + return; + } + } + + FGameplayTagStack& NewStack = Stacks.Emplace_GetRef(Tag, StackCount); + MarkItemDirty(NewStack); + TagToCountMap.Add(Tag, StackCount); + } +} + +void FGameplayTagStackContainer::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) + { + FGameplayTagStack& 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 FGameplayTagStackContainer::PreReplicatedRemove(const TArrayView RemovedIndices, int32 FinalSize) +{ + for (int32 Index : RemovedIndices) + { + const FGameplayTag Tag = Stacks[Index].Tag; + TagToCountMap.Remove(Tag); + } +} + +void FGameplayTagStackContainer::PostReplicatedAdd(const TArrayView AddedIndices, int32 FinalSize) +{ + for (int32 Index : AddedIndices) + { + const FGameplayTagStack& Stack = Stacks[Index]; + TagToCountMap.Add(Stack.Tag, Stack.StackCount); + } +} + +void FGameplayTagStackContainer::PostReplicatedChange(const TArrayView ChangedIndices, int32 FinalSize) +{ + for (int32 Index : ChangedIndices) + { + const FGameplayTagStack& Stack = Stacks[Index]; + TagToCountMap[Stack.Tag] = Stack.StackCount; + } +} + + diff --git a/Source/ALSV4_CPP/Public/ALSGameplayTags.h b/Source/ALSV4_CPP/Public/ALSGameplayTags.h new file mode 100644 index 00000000..c034a75f --- /dev/null +++ b/Source/ALSV4_CPP/Public/ALSGameplayTags.h @@ -0,0 +1,91 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" + +class UGameplayTagsManager; + +/** + * FALSGameplayTags + * + * Singleton containing native gameplay tags. + */ +struct ALSV4_CPP_API FALSGameplayTags +{ +public: + + static const FALSGameplayTags& Get() { return GameplayTags; } + + static void InitializeNativeTags(); + + static FGameplayTag FindTagByString(FString TagString, bool bMatchPartialString = false); + +public: + + FGameplayTag Ability_ActivateFail_IsDead; + FGameplayTag Ability_ActivateFail_Cooldown; + FGameplayTag Ability_ActivateFail_Cost; + FGameplayTag Ability_ActivateFail_TagsBlocked; + FGameplayTag Ability_ActivateFail_TagsMissing; + FGameplayTag Ability_ActivateFail_Networking; + FGameplayTag Ability_ActivateFail_ActivationGroup; + + FGameplayTag Ability_Behavior_SurvivesDeath; + + FGameplayTag InputTag_Move; + FGameplayTag InputTag_Look_Mouse; + FGameplayTag InputTag_Look_Stick; + FGameplayTag InputTag_Crouch; + FGameplayTag InputTag_AutoRun; + + // Initialization states for the GameFrameworkComponentManager, these are registered in order by LyraGameInstance and some actors will skip right to GameplayReady + + /** Actor/component has initially spawned and can be extended */ + FGameplayTag InitState_Spawned; + + /** All required data has been loaded/replicated and is ready for initialization */ + FGameplayTag InitState_DataAvailable; + + /** The available data has been initialized for this actor/component, but it is not ready for full gameplay */ + FGameplayTag InitState_DataInitialized; + + /** The actor/component is fully ready for active gameplay */ + FGameplayTag InitState_GameplayReady; + + FGameplayTag GameplayEvent_Death; + FGameplayTag GameplayEvent_Reset; + FGameplayTag GameplayEvent_RequestReset; + + FGameplayTag SetByCaller_Damage; + FGameplayTag SetByCaller_Heal; + + FGameplayTag Cheat_GodMode; + FGameplayTag Cheat_UnlimitedHealth; + + FGameplayTag Status_Crouching; + FGameplayTag Status_AutoRunning; + FGameplayTag Status_Death; + FGameplayTag Status_Death_Dying; + FGameplayTag Status_Death_Dead; + + FGameplayTag Movement_Mode_Walking; + FGameplayTag Movement_Mode_NavWalking; + FGameplayTag Movement_Mode_Falling; + FGameplayTag Movement_Mode_Swimming; + FGameplayTag Movement_Mode_Flying; + FGameplayTag Movement_Mode_Custom; + + TMap MovementModeTagMap; + TMap CustomMovementModeTagMap; + +protected: + + void AddAllTags(UGameplayTagsManager& Manager); + void AddTag(FGameplayTag& OutTag, const ANSICHAR* TagName, const ANSICHAR* TagComment); + void AddMovementModeTag(FGameplayTag& OutTag, const ANSICHAR* TagName, uint8 MovementMode); + void AddCustomMovementModeTag(FGameplayTag& OutTag, const ANSICHAR* TagName, uint8 CustomMovementMode); + +private: + + static FALSGameplayTags GameplayTags; +}; \ No newline at end of file diff --git a/Source/ALSV4_CPP/Public/ALSLogChannels.h b/Source/ALSV4_CPP/Public/ALSLogChannels.h new file mode 100644 index 00000000..af6cec6a --- /dev/null +++ b/Source/ALSV4_CPP/Public/ALSLogChannels.h @@ -0,0 +1,10 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +ALSV4_CPP_API DECLARE_LOG_CATEGORY_EXTERN(LogALS, Log, All); +ALSV4_CPP_API DECLARE_LOG_CATEGORY_EXTERN(LogALSAbilitySystem, Log, All); +ALSV4_CPP_API DECLARE_LOG_CATEGORY_EXTERN(LogALSExperience, Log, All); + +ALSV4_CPP_API FString GetClientServerContextString(UObject* ContextObject = nullptr); diff --git a/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilitySet.h b/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilitySet.h new file mode 100644 index 00000000..fcaa4aef --- /dev/null +++ b/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilitySet.h @@ -0,0 +1,146 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GameplayAbilitySpec.h" +#include "GameplayEffectTypes.h" +#include "Engine/DataAsset.h" +#include "ALSAbilitySet.generated.h" + +class UALSAbilitySystemComponent; +class UALSGameplayAbility; +class UGameplayEffect; + + +/** + * FALSAbilitySet_GameplayAbility + * + * Data used by the ability set to grant gameplay abilities. + */ +USTRUCT(BlueprintType) +struct FALSAbilitySet_GameplayAbility +{ + GENERATED_BODY() + +public: + + // Gameplay ability to grant. + UPROPERTY(EditDefaultsOnly) + TSubclassOf Ability = nullptr; + + // Level of ability to grant. + UPROPERTY(EditDefaultsOnly) + int32 AbilityLevel = 1; + + // Tag used to process input for the ability. + UPROPERTY(EditDefaultsOnly, Meta = (Categories = "InputTag")) + FGameplayTag InputTag; +}; + + +/** + * FALSAbilitySet_GameplayEffect + * + * Data used by the ability set to grant gameplay effects. + */ +USTRUCT(BlueprintType) +struct FALSAbilitySet_GameplayEffect +{ + GENERATED_BODY() + +public: + + // Gameplay effect to grant. + UPROPERTY(EditDefaultsOnly) + TSubclassOf GameplayEffect = nullptr; + + // Level of gameplay effect to grant. + UPROPERTY(EditDefaultsOnly) + float EffectLevel = 1.0f; +}; + +/** + * FALSAbilitySet_AttributeSet + * + * Data used by the ability set to grant attribute sets. + */ +USTRUCT(BlueprintType) +struct FALSAbilitySet_AttributeSet +{ + GENERATED_BODY() + +public: + // Gameplay effect to grant. + UPROPERTY(EditDefaultsOnly) + TSubclassOf AttributeSet; + +}; + +/** + * FALSAbilitySet_GrantedHandles + * + * Data used to store handles to what has been granted by the ability set. + */ +USTRUCT(BlueprintType) +struct ALSV4_CPP_API FALSAbilitySet_GrantedHandles +{ + GENERATED_BODY() + +public: + + void AddAbilitySpecHandle(const FGameplayAbilitySpecHandle& Handle); + void AddGameplayEffectHandle(const FActiveGameplayEffectHandle& Handle); + void AddAttributeSet(UAttributeSet* Set); + + void TakeFromAbilitySystem(UALSAbilitySystemComponent* ALSASC); + +protected: + + // Handles to the granted abilities. + UPROPERTY() + TArray AbilitySpecHandles; + + // Handles to the granted gameplay effects. + UPROPERTY() + TArray GameplayEffectHandles; + + // Pointers to the granted attribute sets + UPROPERTY() + TArray> GrantedAttributeSets; +}; + + +/** + * UALSAbilitySet + * + * Non-mutable data asset used to grant gameplay abilities and gameplay effects. + */ +UCLASS(BlueprintType, Const) +class ALSV4_CPP_API UALSAbilitySet : public UPrimaryDataAsset +{ + GENERATED_BODY() + +public: + + UALSAbilitySet(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + // Grants the ability set to the specified ability system component. + // The returned handles can be used later to take away anything that was granted. + void GiveToAbilitySystem(UALSAbilitySystemComponent* ALSASC, FALSAbilitySet_GrantedHandles* OutGrantedHandles, UObject* SourceObject = nullptr) const; + +protected: + + // Gameplay abilities to grant when this ability set is granted. + UPROPERTY(EditDefaultsOnly, Category = "Gameplay Abilities", meta=(TitleProperty=Ability)) + TArray GrantedGameplayAbilities; + + // Gameplay effects to grant when this ability set is granted. + UPROPERTY(EditDefaultsOnly, Category = "Gameplay Effects", meta=(TitleProperty=GameplayEffect)) + TArray GrantedGameplayEffects; + + // Attribute sets to grant when this ability set is granted. + UPROPERTY(EditDefaultsOnly, Category = "Attribute Sets", meta=(TitleProperty=AttributeSet)) + TArray GrantedAttributes; +}; diff --git a/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilitySourceInterface.h b/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilitySourceInterface.h new file mode 100644 index 00000000..2cfbe81a --- /dev/null +++ b/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilitySourceInterface.h @@ -0,0 +1,40 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Interface.h" +#include "ALSAbilitySourceInterface.generated.h" + +class AActor; +struct FGameplayTagContainer; +class UPhysicalMaterial; + +// This class does not need to be modified. +UINTERFACE() +class UALSAbilitySourceInterface : public UInterface +{ + GENERATED_UINTERFACE_BODY() +}; + +/** + * + */ +class IALSAbilitySourceInterface +{ + GENERATED_IINTERFACE_BODY() + + /** + * Compute the multiplier for effect falloff with distance + * + * @param Distance Distance from source to target for ability calculations (distance bullet traveled for a gun, etc...) + * @param SourceTags Aggregated Tags from the source + * @param TargetTags Aggregated Tags currently on the target + * + * @return Multiplier to apply to the base attribute value due to distance + */ + virtual float GetDistanceAttenuation(float Distance, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr) const = 0; + + virtual float GetPhysicalMaterialAttenuation(const UPhysicalMaterial* PhysicalMaterial, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr) const = 0; +}; diff --git a/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilitySystemComponent.h b/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilitySystemComponent.h new file mode 100644 index 00000000..7d080a22 --- /dev/null +++ b/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilitySystemComponent.h @@ -0,0 +1,104 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "AbilitySystemComponent.h" +#include "NativeGameplayTags.h" +#include "Abilities/ALSGameplayAbility.h" +#include "ALSAbilitySystemComponent.generated.h" + +class UALSGameplayAbility; +class UALSAbilityTagRelationshipMapping; +struct FGameplayTag; +struct FGameplayAbilitySpec; + +ALSV4_CPP_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Gameplay_AbilityInputBlocked); + +/** + * UALSAbilitySystemComponent + * + * Base ability system component class used by this project. + */ +UCLASS() +class ALSV4_CPP_API UALSAbilitySystemComponent : public UAbilitySystemComponent +{ + GENERATED_BODY() + +public: + + UALSAbilitySystemComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + //~UActorComponent interface + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + //~End of UActorComponent interface + + virtual void InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor) override; + + typedef TFunctionRef TShouldCancelAbilityFunc; + void CancelAbilitiesByFunc(TShouldCancelAbilityFunc ShouldCancelFunc, bool bReplicateCancelAbility); + + void CancelInputActivatedAbilities(bool bReplicateCancelAbility); + + void AbilityInputTagPressed(const FGameplayTag& InputTag); + void AbilityInputTagReleased(const FGameplayTag& InputTag); + + void ProcessAbilityInput(float DeltaTime, bool bGamePaused); + void ClearAbilityInput(); + + bool IsActivationGroupBlocked(EALSAbilityActivationGroup Group) const; + void AddAbilityToActivationGroup(EALSAbilityActivationGroup Group, UALSGameplayAbility* ALSAbility); + void RemoveAbilityFromActivationGroup(EALSAbilityActivationGroup Group, UALSGameplayAbility* ALSAbility); + void CancelActivationGroupAbilities(EALSAbilityActivationGroup Group, UALSGameplayAbility* IgnoreALSAbility, bool bReplicateCancelAbility); + + // Uses a gameplay effect to add the specified dynamic granted tag. + void AddDynamicTagGameplayEffect(const FGameplayTag& Tag); + + // Removes all active instances of the gameplay effect that was used to add the specified dynamic granted tag. + void RemoveDynamicTagGameplayEffect(const FGameplayTag& Tag); + + /** Gets the ability target data associated with the given ability handle and activation info */ + void GetAbilityTargetData(const FGameplayAbilitySpecHandle AbilityHandle, FGameplayAbilityActivationInfo ActivationInfo, FGameplayAbilityTargetDataHandle& OutTargetDataHandle); + + /** Sets the current tag relationship mapping, if null it will clear it out */ + void SetTagRelationshipMapping(UALSAbilityTagRelationshipMapping* NewMapping); + + /** Looks at ability tags and gathers additional required and blocking tags */ + void GetAdditionalActivationTagRequirements(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer& OutActivationRequired, FGameplayTagContainer& OutActivationBlocked) const; + +protected: + + void TryActivateAbilitiesOnSpawn(); + + virtual void AbilitySpecInputPressed(FGameplayAbilitySpec& Spec) override; + virtual void AbilitySpecInputReleased(FGameplayAbilitySpec& Spec) override; + + virtual void NotifyAbilityActivated(const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability) override; + virtual void NotifyAbilityFailed(const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason) override; + virtual void NotifyAbilityEnded(FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability, bool bWasCancelled) override; + virtual void ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags, const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags, const FGameplayTagContainer& CancelTags) override; + virtual void HandleChangeAbilityCanBeCanceled(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bCanBeCanceled) override; + + /** Notify client that an ability failed to activate */ + UFUNCTION(Client, Unreliable) + void ClientNotifyAbilityFailed(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason); + + void HandleAbilityFailed(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason); +protected: + + // If set, this table is used to look up tag relationships for activate and cancel + UPROPERTY() + UALSAbilityTagRelationshipMapping* TagRelationshipMapping; + + // Handles to abilities that had their input pressed this frame. + TArray InputPressedSpecHandles; + + // Handles to abilities that had their input released this frame. + TArray InputReleasedSpecHandles; + + // Handles to abilities that have their input held. + TArray InputHeldSpecHandles; + + // Number of abilities running in each activation group. + int32 ActivationGroupCounts[(uint8)EALSAbilityActivationGroup::MAX]; +}; diff --git a/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilityTagRelationshipMapping.h b/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilityTagRelationshipMapping.h new file mode 100644 index 00000000..af6a0b07 --- /dev/null +++ b/Source/ALSV4_CPP/Public/AbilitySystem/ALSAbilityTagRelationshipMapping.h @@ -0,0 +1,60 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "GameplayTagContainer.h" +#include "Engine/DataAsset.h" + +#include "ALSAbilityTagRelationshipMapping.generated.h" + +/** Struct that defines the relationship between different ability tags */ +USTRUCT() +struct FALSAbilityTagRelationship +{ + GENERATED_BODY() + + /** The tag that this container relationship is about. Single tag, but abilities can have multiple of these */ + UPROPERTY(EditAnywhere, Category = Ability, meta = (Categories = "Gameplay.Action")) + FGameplayTag AbilityTag; + + /** The other ability tags that will be blocked by any ability using this tag */ + UPROPERTY(EditAnywhere, Category = Ability) + FGameplayTagContainer AbilityTagsToBlock; + + /** The other ability tags that will be canceled by any ability using this tag */ + UPROPERTY(EditAnywhere, Category = Ability) + FGameplayTagContainer AbilityTagsToCancel; + + /** If an ability has the tag, this is implicitly added to the activation required tags of the ability */ + UPROPERTY(EditAnywhere, Category = Ability) + FGameplayTagContainer ActivationRequiredTags; + + /** If an ability has the tag, this is implicitly added to the activation blocked tags of the ability */ + UPROPERTY(EditAnywhere, Category = Ability) + FGameplayTagContainer ActivationBlockedTags; +}; + + +/** Mapping of how ability tags block or cancel other abilities */ +UCLASS() +class UALSAbilityTagRelationshipMapping : public UDataAsset +{ + GENERATED_BODY() + +private: + /** The list of relationships between different gameplay tags (which ones block or cancel others) */ + UPROPERTY(EditAnywhere, Category = Ability, meta=(TitleProperty="AbilityTag")) + TArray AbilityTagRelationships; + +public: + /** Given a set of ability tags, parse the tag relationship and fill out tags to block and cancel */ + void GetAbilityTagsToBlockAndCancel(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutTagsToBlock, FGameplayTagContainer* OutTagsToCancel) const; + + /** Given a set of ability tags, add additional required and blocking tags */ + void GetRequiredAndBlockedActivationTags(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutActivationRequired, FGameplayTagContainer* OutActivationBlocked) const; + + /** Returns true if the specified ability tags are canceled by the passed in action tag */ + bool IsAbilityCancelledByTag(const FGameplayTagContainer& AbilityTags, const FGameplayTag& ActionTag) const; +}; diff --git a/Source/ALSV4_CPP/Public/AbilitySystem/ALSGameplayEffectContext.h b/Source/ALSV4_CPP/Public/AbilitySystem/ALSGameplayEffectContext.h new file mode 100644 index 00000000..0d89334d --- /dev/null +++ b/Source/ALSV4_CPP/Public/AbilitySystem/ALSGameplayEffectContext.h @@ -0,0 +1,76 @@ +#pragma once + +#include "GameplayEffectTypes.h" +#include "ALSGameplayEffectContext.generated.h" + +class UAbilitySystemComponent; +class IALSAbilitySourceInterface; +class UPhysicalMaterial; + +USTRUCT() +struct FALSGameplayEffectContext : public FGameplayEffectContext +{ + GENERATED_BODY() + + FALSGameplayEffectContext() + : FGameplayEffectContext() + { + } + + FALSGameplayEffectContext(AActor* InInstigator, AActor* InEffectCauser) + : FGameplayEffectContext(InInstigator, InEffectCauser) + { + } + + /** Returns the wrapped FALSGameplayEffectContext from the handle, or nullptr if it doesn't exist or is the wrong type */ + static ALSV4_CPP_API FALSGameplayEffectContext* ExtractEffectContext(struct FGameplayEffectContextHandle Handle); + + /** Sets the object used as the ability source */ + void SetAbilitySource(const IALSAbilitySourceInterface* InObject, float InSourceLevel); + + /** Returns the ability source interface associated with the source object. Only valid on the authority. */ + const IALSAbilitySourceInterface* GetAbilitySource() const; + + virtual FGameplayEffectContext* Duplicate() const override + { + FALSGameplayEffectContext* NewContext = new FALSGameplayEffectContext(); + *NewContext = *this; + if (GetHitResult()) + { + // Does a deep copy of the hit result + NewContext->AddHitResult(*GetHitResult(), true); + } + return NewContext; + } + + virtual UScriptStruct* GetScriptStruct() const override + { + return FALSGameplayEffectContext::StaticStruct(); + } + + /** Overridden to serialize new fields */ + virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) override; + + /** Returns the physical material from the hit result if there is one */ + const UPhysicalMaterial* GetPhysicalMaterial() const; + +public: + /** ID to allow the identification of multiple bullets that were part of the same cartridge */ + UPROPERTY() + int32 CartridgeID = -1; + +protected: + /** Ability Source object (should implement IALSAbilitySourceInterface). NOT replicated currently */ + UPROPERTY() + TWeakObjectPtr AbilitySourceObject; +}; + +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true, + WithCopy = true + }; +}; \ No newline at end of file diff --git a/Source/ALSV4_CPP/Public/AbilitySystem/ALSGlobalAbilitySystem.h b/Source/ALSV4_CPP/Public/AbilitySystem/ALSGlobalAbilitySystem.h new file mode 100644 index 00000000..79244820 --- /dev/null +++ b/Source/ALSV4_CPP/Public/AbilitySystem/ALSGlobalAbilitySystem.h @@ -0,0 +1,78 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/WorldSubsystem.h" +#include "AbilitySystemInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayAbilitySpec.h" +#include "GameplayEffectTypes.h" + +#include "ALSGlobalAbilitySystem.generated.h" + +class UALSAbilitySystemComponent; +class UAbilitySystemComponent; +class UGameplayEffect; +class UGameplayAbility; + +USTRUCT() +struct FGlobalAppliedAbilityList +{ + GENERATED_BODY() + + UPROPERTY() + TMap Handles; + + void AddToASC(TSubclassOf Ability, UALSAbilitySystemComponent* ASC); + void RemoveFromASC(UALSAbilitySystemComponent* ASC); + void RemoveFromAll(); +}; + +USTRUCT() +struct FGlobalAppliedEffectList +{ + GENERATED_BODY() + + UPROPERTY() + TMap Handles; + + void AddToASC(TSubclassOf Effect, UALSAbilitySystemComponent* ASC); + void RemoveFromASC(UALSAbilitySystemComponent* ASC); + void RemoveFromAll(); +}; + +UCLASS() +class UALSGlobalAbilitySystem : public UWorldSubsystem +{ + GENERATED_BODY() + +public: + UALSGlobalAbilitySystem(); + + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="ALS") + void ApplyAbilityToAll(TSubclassOf Ability); + + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="ALS") + void ApplyEffectToAll(TSubclassOf Effect); + + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "ALS") + void RemoveAbilityFromAll(TSubclassOf Ability); + + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "ALS") + void RemoveEffectFromAll(TSubclassOf Effect); + + /** Register an ASC with global system and apply any active global effects/abilities. */ + void RegisterASC(UALSAbilitySystemComponent* ASC); + + /** Removes an ASC from the global system, along with any active global effects/abilities. */ + void UnregisterASC(UALSAbilitySystemComponent* ASC); + +private: + UPROPERTY() + TMap, FGlobalAppliedAbilityList> AppliedAbilities; + + UPROPERTY() + TMap, FGlobalAppliedEffectList> AppliedEffects; + + UPROPERTY() + TArray RegisteredASCs; +}; diff --git a/Source/ALSV4_CPP/Public/AbilitySystem/Abilities/ALSAbilityCost.h b/Source/ALSV4_CPP/Public/AbilitySystem/Abilities/ALSAbilityCost.h new file mode 100644 index 00000000..2d283e0c --- /dev/null +++ b/Source/ALSV4_CPP/Public/AbilitySystem/Abilities/ALSAbilityCost.h @@ -0,0 +1,60 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayAbilitySpec.h" +#include "Abilities/GameplayAbility.h" +#include "ALSAbilityCost.generated.h" + +class UALSGameplayAbility; + +/** + * UALSAbilityCost + * + * Base class for costs that a ALSGameplayAbility has (e.g., ammo or charges) + */ +UCLASS(DefaultToInstanced, EditInlineNew, Abstract) +class UALSAbilityCost : public UObject +{ + GENERATED_BODY() + +public: + UALSAbilityCost() + { + } + + /** + * 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 UALSGameplayAbility* Ability, const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, FGameplayTagContainer* OptionalRelevantTags) const + { + return true; + } + + /** + * 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 UALSGameplayAbility* 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 { return bOnlyApplyCostOnHit; } + +protected: + /** If true, this cost should only be applied if this ability hits successfully */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Costs) + bool bOnlyApplyCostOnHit = false; +}; diff --git a/Source/ALSV4_CPP/Public/AbilitySystem/Abilities/ALSAbilitySimpleFailureMessage.h b/Source/ALSV4_CPP/Public/AbilitySystem/Abilities/ALSAbilitySimpleFailureMessage.h new file mode 100644 index 00000000..1859d04a --- /dev/null +++ b/Source/ALSV4_CPP/Public/AbilitySystem/Abilities/ALSAbilitySimpleFailureMessage.h @@ -0,0 +1,26 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "NativeGameplayTags.h" + +#include "ALSAbilitySimpleFailureMessage.generated.h" + +UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_ABILITY_SIMPLE_FAILURE_MESSAGE); + +USTRUCT(BlueprintType) +struct FALSAbilitySimpleFailureMessage +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadWrite) + APlayerController* PlayerController = nullptr; + + UPROPERTY(BlueprintReadWrite) + FGameplayTagContainer FailureTags; + + UPROPERTY(BlueprintReadWrite) + FText UserFacingReason; +}; + diff --git a/Source/ALSV4_CPP/Public/AbilitySystem/Abilities/ALSGameplayAbility.h b/Source/ALSV4_CPP/Public/AbilitySystem/Abilities/ALSGameplayAbility.h new file mode 100644 index 00000000..82938921 --- /dev/null +++ b/Source/ALSV4_CPP/Public/AbilitySystem/Abilities/ALSGameplayAbility.h @@ -0,0 +1,195 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Abilities/GameplayAbility.h" +#include "ALSGameplayAbility.generated.h" + +class UALSAbilitySystemComponent; +class AALSPlayerController2; +class AALSCharacter; +class UALSHeroComponent; +class UALSCameraMode; +class UALSAbilityCost; +class IALSAbilitySourceInterface; + +/** + * EALSAbilityActivationPolicy + * + * Defines how an ability is meant to activate. + */ +UENUM(BlueprintType) +enum class EALSAbilityActivationPolicy : 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 +}; + + +/** + * EALSAbilityActivationGroup + * + * Defines how an ability activates in relation to other abilities. + */ +UENUM(BlueprintType) +enum class EALSAbilityActivationGroup : 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 FALSAbilityMontageFailureMessage +{ + GENERATED_BODY() + +public: + + UPROPERTY(BlueprintReadWrite) + APlayerController* PlayerController = nullptr; + + // All the reasons why this ability has failed + UPROPERTY(BlueprintReadWrite) + FGameplayTagContainer FailureTags; + + UPROPERTY(BlueprintReadWrite) + UAnimMontage* FailureMontage = nullptr; +}; + +/** + * UALSGameplayAbility + * + * 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 ALSV4_CPP_API UALSGameplayAbility : public UGameplayAbility +{ + GENERATED_BODY() + friend class UALSAbilitySystemComponent; + +public: + + UALSGameplayAbility(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + UFUNCTION(BlueprintCallable, Category = "ALS|Ability") + UALSAbilitySystemComponent* GetALSAbilitySystemComponentFromActorInfo() const; + + UFUNCTION(BlueprintCallable, Category = "ALS|Ability") + AALSPlayerController2* GetALSPlayerControllerFromActorInfo() const; + + UFUNCTION(BlueprintCallable, Category = "ALS|Ability") + AController* GetControllerFromActorInfo() const; + + UFUNCTION(BlueprintCallable, Category = "ALS|Ability") + AALSCharacter* GetALSCharacterFromActorInfo() const; + + UFUNCTION(BlueprintCallable, Category = "ALS|Ability") + UALSHeroComponent* GetHeroComponentFromActorInfo() const; + + EALSAbilityActivationPolicy GetActivationPolicy() const { return ActivationPolicy; } + EALSAbilityActivationGroup GetActivationGroup() const { return ActivationGroup; } + + void TryActivateAbilityOnSpawn(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) const; + + // Returns true if the requested activation group is a valid transition. + UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = "ALS|Ability", Meta = (ExpandBoolAsExecs = "ReturnValue")) + bool CanChangeActivationGroup(EALSAbilityActivationGroup NewGroup) const; + + // Tries to change the activation group. Returns true if it successfully changed. + UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = "ALS|Ability", Meta = (ExpandBoolAsExecs = "ReturnValue")) + bool ChangeActivationGroup(EALSAbilityActivationGroup NewGroup); + + // Sets the ability's camera mode. + UFUNCTION(BlueprintCallable, Category = "ALS|Ability") + void SetCameraMode(TSubclassOf CameraMode); + + // Clears the ability's camera mode. Automatically called if needed when the ability ends. + UFUNCTION(BlueprintCallable, Category = "ALS|Ability") + void ClearCameraMode(); + + void OnAbilityFailedToActivate(const FGameplayTagContainer& FailedReason) const + { + NativeOnAbilityFailedToActivate(FailedReason); + ScriptOnAbilityFailedToActivate(FailedReason); + } + +protected: + + // Called when the ability fails to activate + virtual void NativeOnAbilityFailedToActivate(const FGameplayTagContainer& FailedReason) const; + + // Called when the ability fails to activate + UFUNCTION(BlueprintImplementableEvent) + void ScriptOnAbilityFailedToActivate(const FGameplayTagContainer& FailedReason) const; + + //~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 bCanBeCanceled) 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, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const override; + //~End of UGameplayAbility interface + + virtual void OnPawnAvatarSet(); + + virtual void GetAbilitySource(FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, float& OutSourceLevel, const IALSAbilitySourceInterface*& OutAbilitySource, AActor*& OutEffectCauser) 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 = "ALS|Ability Activation") + EALSAbilityActivationPolicy ActivationPolicy; + + // Defines the relationship between this ability activating and other abilities activating. + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "ALS|Ability Activation") + EALSAbilityActivationGroup ActivationGroup; + + // Additional costs that must be paid to activate this ability + UPROPERTY(EditDefaultsOnly, Instanced, Category = Costs) + TArray> AdditionalCosts; + + // Map of failure tags to simple error messages + UPROPERTY(EditDefaultsOnly, Category = "Advanced") + TMap FailureTagToUserFacingMessages; + + // Map of failure tags to anim montages that should be played with them + UPROPERTY(EditDefaultsOnly, Category = "Advanced") + TMap FailureTagToAnimMontage; + + // Current camera mode set by the ability. + TSubclassOf ActiveCameraMode; +}; diff --git a/Source/ALSV4_CPP/Public/Camera/ALSCameraComponent.h b/Source/ALSV4_CPP/Public/Camera/ALSCameraComponent.h new file mode 100644 index 00000000..2ff04834 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Camera/ALSCameraComponent.h @@ -0,0 +1,30 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Camera/CameraComponent.h" +#include "ALSCameraComponent.generated.h" + +class UALSCameraMode; + +DECLARE_DELEGATE_RetVal(TSubclassOf, FALSCameraModeDelegate); + +/** + * + */ +UCLASS() +class ALSV4_CPP_API UALSCameraComponent : public UCameraComponent +{ + GENERATED_BODY() + +public: + UALSCameraComponent(const FObjectInitializer& ObjectInitializer); + + // Returns the camera component if one exists on the specified actor. + UFUNCTION(BlueprintPure, Category = "ALS|Camera") + static UALSCameraComponent* FindCameraComponent(const AActor* Actor) { return (Actor ? Actor->FindComponentByClass() : nullptr); } + + // Delegate used to query for the best camera mode. + FALSCameraModeDelegate DetermineCameraModeDelegate; +}; diff --git a/Source/ALSV4_CPP/Public/Camera/ALSCameraMode.h b/Source/ALSV4_CPP/Public/Camera/ALSCameraMode.h new file mode 100644 index 00000000..7fa58c73 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Camera/ALSCameraMode.h @@ -0,0 +1,16 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "ALSCameraMode.generated.h" + +/** + * + */ +UCLASS() +class ALSV4_CPP_API UALSCameraMode : public UObject +{ + GENERATED_BODY() +}; diff --git a/Source/ALSV4_CPP/Public/Character/ALSBaseCharacter.h b/Source/ALSV4_CPP/Public/Character/ALSBaseCharacter.h index 372f146d..208b6acf 100644 --- a/Source/ALSV4_CPP/Public/Character/ALSBaseCharacter.h +++ b/Source/ALSV4_CPP/Public/Character/ALSBaseCharacter.h @@ -5,6 +5,7 @@ #pragma once #include "CoreMinimal.h" +#include "ModularCharacter.h" #include "Components/TimelineComponent.h" #include "Library/ALSCharacterEnumLibrary.h" #include "Library/ALSCharacterStructLibrary.h" @@ -27,7 +28,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRagdollStateChangedSignature, bool, * Base character class */ UCLASS(BlueprintType) -class ALSV4_CPP_API AALSBaseCharacter : public ACharacter +class ALSV4_CPP_API AALSBaseCharacter : public AModularCharacter { GENERATED_BODY() @@ -39,7 +40,7 @@ class ALSV4_CPP_API AALSBaseCharacter : public ACharacter { return MyCharacterMovementComponent; } - + virtual void Tick(float DeltaTime) override; virtual void BeginPlay() override; @@ -246,6 +247,9 @@ class ALSV4_CPP_API AALSBaseCharacter : public ACharacter UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "ALS|Movement System") UAnimMontage* GetRollAnimation(); + UFUNCTION(BlueprintCallable, Category = "ALS|Character States") + void RollStart(); + /** Utility */ UFUNCTION(BlueprintCallable, Category = "ALS|Utility") @@ -271,6 +275,13 @@ class ALSV4_CPP_API AALSBaseCharacter : public ACharacter UFUNCTION(BlueprintCallable, Category = "ALS|Camera System") virtual FTransform GetThirdPersonPivotTarget(); + UFUNCTION(BlueprintCallable, Category="ALS|Camera System") + virtual FTransform GetTopDownPivotTarget(); + + UFUNCTION(BlueprintCallable, Category = "ALS|Camera System") + virtual FTransform GetCurrentPivotTarget(); + FRotator GetCurrentCameraControlRotation() const; + UFUNCTION(BlueprintCallable, Category = "ALS|Camera System") virtual FVector GetFirstPersonCameraTarget(); @@ -280,6 +291,9 @@ class ALSV4_CPP_API AALSBaseCharacter : public ACharacter UFUNCTION(BlueprintCallable, Category = "ALS|Camera System") void SetCameraBehavior(UALSPlayerCameraBehavior* CamBeh) { CameraBehavior = CamBeh; } + UFUNCTION(BlueprintCallable, Category = "ALS|Camera System") + bool CanPlayCameraShake() const; + /** Essential Information Getters/Setters */ UFUNCTION(BlueprintGetter, Category = "ALS|Essential Information") @@ -456,6 +470,12 @@ class ALSV4_CPP_API AALSBaseCharacter : public ACharacter UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ALS|Camera System") float FirstPersonFOV = 90.0f; + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ALS|Camera System") + FVector TopDownPivotOffset = FVector::ZeroVector; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ALS|Camera System") + FRotator TopDownCameraRotation = FRotator::ZeroRotator; + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ALS|Camera System") bool bRightShoulder = false; @@ -609,6 +629,16 @@ class ALSV4_CPP_API AALSBaseCharacter : public ACharacter /** Last time the 'first' crouch/roll button is pressed */ float LastStanceInputTime = 0.0f; + /** Last forward input action pressed */ + float LastForwardInput = 0.0f; + + /** Last right input action pressed */ + float LastRightInput = 0.0f; + + float LastCameraUpInput = 0.0f; + + float LastCameraRightInput = 0.0f; + /* Timer to manage reset of braking friction factor after on landed event */ FTimerHandle OnLandedFrictionResetTimer; diff --git a/Source/ALSV4_CPP/Public/Character/ALSCharacter.h b/Source/ALSV4_CPP/Public/Character/ALSCharacter.h index 2483ec67..a2a5ce81 100644 --- a/Source/ALSV4_CPP/Public/Character/ALSCharacter.h +++ b/Source/ALSV4_CPP/Public/Character/ALSCharacter.h @@ -5,20 +5,29 @@ #pragma once #include "CoreMinimal.h" +#include "AbilitySystemInterface.h" #include "Character/ALSBaseCharacter.h" #include "ALSCharacter.generated.h" +class UALSAbilitySystemComponent; +class UALSPawnExtensionComponent; + /** * Specialized character class, with additional features like held object etc. */ UCLASS(Blueprintable, BlueprintType) -class ALSV4_CPP_API AALSCharacter : public AALSBaseCharacter +class ALSV4_CPP_API AALSCharacter : public AALSBaseCharacter, public IAbilitySystemInterface { GENERATED_BODY() public: AALSCharacter(const FObjectInitializer& ObjectInitializer); + + UFUNCTION(BlueprintCallable, Category = "ALS|Character") + UALSAbilitySystemComponent* GetALSAbilitySystemComponent() const; + virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override; + /** Implemented on BP to update held objects */ UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "ALS|HeldObject") void UpdateHeldObject(); @@ -38,15 +47,35 @@ class ALSV4_CPP_API AALSCharacter : public AALSBaseCharacter virtual FTransform GetThirdPersonPivotTarget() override; + virtual FTransform GetTopDownPivotTarget() override; + virtual FVector GetFirstPersonCameraTarget() override; protected: + virtual void PossessedBy(AController* NewController) override; + virtual void UnPossessed() override; + + virtual void OnRep_Controller() override; + virtual void OnRep_PlayerState() override; + + virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override; + + void InitializeGameplayTags(); + + virtual void OnAbilitySystemInitialized(); + virtual void OnAbilitySystemUninitialized(); + virtual void Tick(float DeltaTime) override; virtual void BeginPlay() override; virtual void OnOverlayStateChanged(EALSOverlayState PreviousState) override; + virtual void OnViewModeChanged(EALSViewMode PreviousViewMode) override; + + void EnableCursor(bool bEnable); + void UpdateAimMovement(float DeltaTime); + /** Implement on BP to update animation states of held objects */ UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "ALS|HeldObject") void UpdateHeldObjectAnimations(); @@ -63,4 +92,10 @@ class ALSV4_CPP_API AALSCharacter : public AALSBaseCharacter private: bool bNeedsColorReset = false; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "ALS|Character", Meta = (AllowPrivateAccess = "true")) + TObjectPtr PawnExtComponent; + + UPROPERTY() + APlayerController* PlayerController; }; diff --git a/Source/ALSV4_CPP/Public/Character/ALSHeroComponent.h b/Source/ALSV4_CPP/Public/Character/ALSHeroComponent.h new file mode 100644 index 00000000..22886669 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Character/ALSHeroComponent.h @@ -0,0 +1,102 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Character/ALSPawnComponent.h" +#include "GameplayTagContainer.h" +#include "GameplayAbilitySpec.h" +#include "Input/ALSMappableConfigPair.h" +#include "ALSHeroComponent.generated.h" + + +class AALSPlayerState; +class UInputComponent; +struct FInputActionValue; +class UALSInputConfig; +class UALSCameraMode; + +/** + * UALSHeroComponent + * + * A component used to create a player controlled pawns (characters, vehicles, etc..). + */ +UCLASS(Blueprintable, Meta=(BlueprintSpawnableComponent)) +class ALSV4_CPP_API UALSHeroComponent : public UALSPawnComponent +{ + GENERATED_BODY() + +public: + + UALSHeroComponent(const FObjectInitializer& ObjectInitializer); + + // Returns the hero component if one exists on the specified actor. + UFUNCTION(BlueprintPure, Category = "ALS|Hero") + static UALSHeroComponent* FindHeroComponent(const AActor* Actor) { return (Actor ? Actor->FindComponentByClass() : nullptr); } + + // TOOD: Create ability to define the camera behavior for each ability + void SetAbilityCameraMode(TSubclassOf CameraMode, const FGameplayAbilitySpecHandle& OwningSpecHandle); + void ClearAbilityCameraMode(const FGameplayAbilitySpecHandle& OwningSpecHandle); + + void AddAdditionalInputConfig(const UALSInputConfig* InputConfig); + void RemoveAdditionalInputConfig(const UALSInputConfig* InputConfig); + + /** True if this has completed OnPawnReadyToInitialize so is prepared for late-added features */ + bool HasPawnInitialized() const; + + /** True if this player has sent the BindInputsNow event and is prepared for bindings */ + bool IsReadyToBindInputs() const; + + static const FName NAME_BindInputsNow; + +protected: + + virtual void OnRegister() override; + + virtual bool IsPawnComponentReadyToInitialize() const override; + void OnPawnReadyToInitialize(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + virtual void InitializePlayerInput(UInputComponent* PlayerInputComponent); + + void Input_AbilityInputTagPressed(FGameplayTag InputTag); + void Input_AbilityInputTagReleased(FGameplayTag InputTag); + + void Input_Move(const FInputActionValue& InputActionValue); + void Input_LookMouse(const FInputActionValue& InputActionValue); + void Input_LookStick(const FInputActionValue& InputActionValue); + void Input_Crouch(const FInputActionValue& InputActionValue); + void Input_AutoRun(const FInputActionValue& InputActionValue); + + // TODO: Define the camera mode + TSubclassOf DetermineCameraMode() const; + + void OnInputConfigActivated(const FLoadedMappableConfigPair& ConfigPair); + void OnInputConfigDeactivated(const FLoadedMappableConfigPair& ConfigPair); + +protected: + + /** + * Input Configs that should be added to this player when initalizing the input. + * + * NOTE: You should only add to this if you do not have a game feature plugin accessible to you. + * If you do, then use the GameFeatureAction_AddInputConfig instead. + */ + UPROPERTY(EditAnywhere) + TArray DefaultInputConfigs; + + // Camera mode set by an ability. + // TODO: This should be a stack of camera modes, so that abilities can push and pop camera modes. + TSubclassOf AbilityCameraMode; + + // Spec handle for the last ability to set a camera mode. + FGameplayAbilitySpecHandle AbilityCameraModeOwningSpecHandle; + + // True when the pawn has fully finished initialization + bool bPawnHasInitialized; + + // True when player input bindings have been applyed, will never be true for non-players + bool bReadyToBindInputs; +}; diff --git a/Source/ALSV4_CPP/Public/Character/ALSPawn.h b/Source/ALSV4_CPP/Public/Character/ALSPawn.h new file mode 100644 index 00000000..fdd93954 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Character/ALSPawn.h @@ -0,0 +1,30 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "ModularPawn.h" +#include "ALSPawn.generated.h" + +/** + * AALSPawn + */ +UCLASS() +class ALSV4_CPP_API AALSPawn : public AModularPawn +{ + GENERATED_BODY() + +public: + + AALSPawn(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + //~AActor interface + virtual void PreInitializeComponents() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + //~End of AActor interface + + //~APawn interface + virtual void PossessedBy(AController* NewController) override; + virtual void UnPossessed() override; + //~End of APawn interface +}; diff --git a/Source/ALSV4_CPP/Public/Character/ALSPawnComponent.h b/Source/ALSV4_CPP/Public/Character/ALSPawnComponent.h new file mode 100644 index 00000000..52f3b8f1 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Character/ALSPawnComponent.h @@ -0,0 +1,41 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/PawnComponent.h" +#include "UObject/Interface.h" +#include "ALSPawnComponent.generated.h" + + +UINTERFACE(BlueprintType) +class ALSV4_CPP_API UALSReadyInterface : public UInterface +{ + GENERATED_BODY() +}; + +class IALSReadyInterface +{ + GENERATED_BODY() + +public: + virtual bool IsPawnComponentReadyToInitialize() const = 0; +}; + + +/** + * UALSPawnComponent + * + * An actor component that can be used for adding custom behavior to pawns. + */ +UCLASS(Blueprintable, Meta = (BlueprintSpawnableComponent)) +class ALSV4_CPP_API UALSPawnComponent : public UPawnComponent, public IALSReadyInterface +{ + GENERATED_BODY() + +public: + + UALSPawnComponent(const FObjectInitializer& ObjectInitializer); + + virtual bool IsPawnComponentReadyToInitialize() const override { return true; } +}; diff --git a/Source/ALSV4_CPP/Public/Character/ALSPawnData.h b/Source/ALSV4_CPP/Public/Character/ALSPawnData.h new file mode 100644 index 00000000..93ccc992 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Character/ALSPawnData.h @@ -0,0 +1,53 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "Templates/SubclassOf.h" +#include "UObject/UObjectGlobals.h" +#include "ALSPawnData.generated.h" + +class APawn; +class UALSAbilitySet; +class UALSInputConfig; +class UALSAbilityTagRelationshipMapping; +class UALSCameraMode; + + +/** + * UALSPawnData + * + * Non-mutable data asset that contains properties used to define a pawn. + */ +UCLASS(BlueprintType, Const, Meta = (DisplayName = "ALS Pawn Data", ShortTooltip = "Data asset used to define a Pawn.")) +class ALSV4_CPP_API UALSPawnData : public UPrimaryDataAsset +{ + GENERATED_BODY() + +public: + + UALSPawnData(const FObjectInitializer& ObjectInitializer); + +public: + + // Class to instantiate for this pawn (should usually derive from AALSPawn or AALSCharacter). + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "ALS|Pawn") + TSubclassOf PawnClass; + + // Ability sets to grant to this pawn's ability system. + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "ALS|Abilities") + TArray AbilitySets; + + // What mapping of ability tags to use for actions taking by this pawn + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "ALS|Abilities") + UALSAbilityTagRelationshipMapping* TagRelationshipMapping; + + // Input configuration used by player controlled pawns to create input mappings and bind input actions. + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "ALS|Input") + UALSInputConfig* InputConfig; + + // Default camera mode used by player controlled pawns. + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "ALS|Camera") + TSubclassOf DefaultCameraMode; +}; diff --git a/Source/ALSV4_CPP/Public/Character/ALSPawnExtensionComponent.h b/Source/ALSV4_CPP/Public/Character/ALSPawnExtensionComponent.h new file mode 100644 index 00000000..1268a911 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Character/ALSPawnExtensionComponent.h @@ -0,0 +1,115 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "ALSPawnComponent.h" +#include "Components/GameFrameworkInitStateInterface.h" +#include "ALSPawnExtensionComponent.generated.h" + + +class UALSPawnData; +class UALSAbilitySystemComponent; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FALSDynamicMulticastDelegate); + +/** + * UALSPawnExtensionComponent + * + * Component used to add functionality to all Pawn classes. + */ +UCLASS() +class ALSV4_CPP_API UALSPawnExtensionComponent : public UALSPawnComponent, public IGameFrameworkInitStateInterface +{ + GENERATED_BODY() + +public: + + UALSPawnExtensionComponent(const FObjectInitializer& ObjectInitializer); + + /** The name of this overall feature, this one depends on the other named component features */ + static const FName NAME_ActorFeatureName; + + //~ Begin IGameFrameworkInitStateInterface interface + virtual FName GetFeatureName() const override { return NAME_ActorFeatureName; } + virtual bool CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const override; + virtual void HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) override; + virtual void OnActorInitStateChanged(const FActorInitStateChangedParams& Params) override; + virtual void CheckDefaultInitialization() override; + //~ End IGameFrameworkInitStateInterface interface + + // Returns the pawn extension component if one exists on the specified actor. + UFUNCTION(BlueprintPure, Category = "ALS|Pawn") + static UALSPawnExtensionComponent* FindPawnExtensionComponent(const AActor* Actor) { return (Actor ? Actor->FindComponentByClass() : nullptr); } + + template + const T* GetPawnData() const { return Cast(PawnData); } + + void SetPawnData(const UALSPawnData* InPawnData); + + UFUNCTION(BlueprintPure, Category = "ALS|Pawn") + UALSAbilitySystemComponent* GetALSAbilitySystemComponent() const { return AbilitySystemComponent; } + + // Should be called by the owning pawn to become the avatar of the ability system. + void InitializeAbilitySystem(UALSAbilitySystemComponent* InASC, AActor* InOwnerActor); + + // Should be called by the owning pawn to remove itself as the avatar of the ability system. + void UninitializeAbilitySystem(); + + // Should be called by the owning pawn when the pawn's controller changes. + void HandleControllerChanged(); + + // Should be called by the owning pawn when the player state has been replicated. + void HandlePlayerStateReplicated(); + + // Should be called by the owning pawn when the input component is setup. + void SetupPlayerInputComponent(); + + // Call this anytime the pawn needs to check if it's ready to be initialized (pawn data assigned, possessed, etc..). + bool CheckPawnReadyToInitialize(); + + // Returns true if the pawn is ready to be initialized. + UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = "ALS|Pawn", Meta = (ExpandBoolAsExecs = "ReturnValue")) + bool IsPawnReadyToInitialize() const { return bPawnReadyToInitialize; } + + // Register with the OnPawnReadyToInitialize delegate and broadcast if condition is already met. + void OnPawnReadyToInitialize_RegisterAndCall(FSimpleMulticastDelegate::FDelegate Delegate); + + // Register with the OnAbilitySystemInitialized delegate and broadcast if condition is already met. + void OnAbilitySystemInitialized_RegisterAndCall(FSimpleMulticastDelegate::FDelegate Delegate); + + // Register with the OnAbilitySystemUninitialized delegate. + void OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate Delegate); + +protected: + + virtual void OnRegister() override; + + UFUNCTION() + void OnRep_PawnData(); + + // Delegate fired when pawn has everything needed for initialization. + FSimpleMulticastDelegate OnPawnReadyToInitialize; + + UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "On Pawn Ready To Initialize")) + FALSDynamicMulticastDelegate BP_OnPawnReadyToInitialize; + + // Delegate fired when our pawn becomes the ability system's avatar actor + FSimpleMulticastDelegate OnAbilitySystemInitialized; + + // Delegate fired when our pawn is removed as the ability system's avatar actor + FSimpleMulticastDelegate OnAbilitySystemUninitialized; + +protected: + + // Pawn data used to create the pawn. Specified from a spawn function or on a placed instance. + UPROPERTY(EditInstanceOnly, ReplicatedUsing = OnRep_PawnData, Category = "ALS|Pawn") + const UALSPawnData* PawnData; + + // Pointer to the ability system component that is cached for convenience. + UPROPERTY() + UALSAbilitySystemComponent* AbilitySystemComponent; + + // True when the pawn has everything needed for initialization. + int32 bPawnReadyToInitialize : 1; +}; diff --git a/Source/ALSV4_CPP/Public/Character/ALSPlayerController.h b/Source/ALSV4_CPP/Public/Character/ALSPlayerController.h index 1b80fc20..8f3d6264 100644 --- a/Source/ALSV4_CPP/Public/Character/ALSPlayerController.h +++ b/Source/ALSV4_CPP/Public/Character/ALSPlayerController.h @@ -117,6 +117,9 @@ class ALSV4_CPP_API AALSPlayerController : public APlayerController UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ALS|Input") TObjectPtr DefaultInputMappingContext = nullptr; + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ALS|Input") + TObjectPtr GamepadInputMappingContext = nullptr; + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ALS|Input") TObjectPtr DebugInputMappingContext = nullptr; }; diff --git a/Source/ALSV4_CPP/Public/Character/Animation/ALSCharacterAnimInstance.h b/Source/ALSV4_CPP/Public/Character/Animation/ALSCharacterAnimInstance.h index a4afa5ac..7bd15339 100644 --- a/Source/ALSV4_CPP/Public/Character/Animation/ALSCharacterAnimInstance.h +++ b/Source/ALSV4_CPP/Public/Character/Animation/ALSCharacterAnimInstance.h @@ -11,6 +11,7 @@ #include "ALSCharacterAnimInstance.generated.h" // forward declarations +class UAbilitySystemComponent; class UALSDebugComponent; class AALSBaseCharacter; class UCurveFloat; @@ -28,6 +29,8 @@ class ALSV4_CPP_API UALSCharacterAnimInstance : public UAnimInstance public: virtual void NativeInitializeAnimation() override; + virtual void InitializeWithAbilitySystem(UAbilitySystemComponent* ASC); + virtual void NativeBeginPlay() override; virtual void NativeUpdateAnimation(float DeltaSeconds) override; diff --git a/Source/ALSV4_CPP/Public/GameModes/ALSExperienceActionSet.h b/Source/ALSV4_CPP/Public/GameModes/ALSExperienceActionSet.h new file mode 100644 index 00000000..24f30653 --- /dev/null +++ b/Source/ALSV4_CPP/Public/GameModes/ALSExperienceActionSet.h @@ -0,0 +1,42 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "ALSExperienceActionSet.generated.h" + +class UGameFeatureAction; + +/** + * Definition of a set of actions to perform as part of entering an experience + */ +UCLASS(BlueprintType, NotBlueprintable) +class ALSV4_CPP_API UALSExperienceActionSet : public UPrimaryDataAsset +{ + GENERATED_BODY() + +public: + UALSExperienceActionSet(); + + //~UObject interface + #if WITH_EDITOR + virtual EDataValidationResult IsDataValid(TArray& ValidationErrors) override; +#endif + //~End of UObject interface + + //~UPrimaryDataAsset interface + #if WITH_EDITORONLY_DATA + virtual void UpdateAssetBundleData() override; +#endif + //~End of UPrimaryDataAsset interface + + public: + // List of actions to perform as this experience is loaded/activated/deactivated/unloaded + UPROPERTY(EditAnywhere, Instanced, Category="Actions to Perform") + TArray> Actions; + + // List of Game Feature Plugins this experience wants to have active + UPROPERTY(EditAnywhere, Category="Feature Dependencies") + TArray GameFeaturesToEnable; +}; diff --git a/Source/ALSV4_CPP/Public/GameModes/ALSExperienceDefinition.h b/Source/ALSV4_CPP/Public/GameModes/ALSExperienceDefinition.h new file mode 100644 index 00000000..cc77eead --- /dev/null +++ b/Source/ALSV4_CPP/Public/GameModes/ALSExperienceDefinition.h @@ -0,0 +1,53 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "ALSExperienceDefinition.generated.h" + +class UGameFeatureAction; +class UALSPawnData; +class UALSExperienceActionSet; + +/** + * Definition of an experience + */ +UCLASS(BlueprintType, Const) +class ALSV4_CPP_API UALSExperienceDefinition : public UPrimaryDataAsset +{ + GENERATED_BODY() + +public: + UALSExperienceDefinition(); + + //~UObject interface + #if WITH_EDITOR + virtual EDataValidationResult IsDataValid(TArray& ValidationErrors) override; +#endif + //~End of UObject interface + + //~UPrimaryDataAsset interface + #if WITH_EDITORONLY_DATA + virtual void UpdateAssetBundleData() override; +#endif + //~End of UPrimaryDataAsset interface + + public: + // List of Game Feature Plugins this experience wants to have active + UPROPERTY(EditDefaultsOnly, Category = Gameplay) + TArray GameFeaturesToEnable; + + /** The default pawn class to spawn for players */ + //@TODO: Make soft? + UPROPERTY(EditDefaultsOnly, Category=Gameplay) + TObjectPtr DefaultPawnData; + + // List of actions to perform as this experience is loaded/activated/deactivated/unloaded + UPROPERTY(EditDefaultsOnly, Instanced, Category="Actions") + TArray> Actions; + + // List of additional action sets to compose into this experience + UPROPERTY(EditDefaultsOnly, Category=Gameplay) + TArray> ActionSets; +}; diff --git a/Source/ALSV4_CPP/Public/GameModes/ALSExperienceManager.h b/Source/ALSV4_CPP/Public/GameModes/ALSExperienceManager.h new file mode 100644 index 00000000..b7aa139c --- /dev/null +++ b/Source/ALSV4_CPP/Public/GameModes/ALSExperienceManager.h @@ -0,0 +1,32 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/EngineSubsystem.h" +#include "ALSExperienceManager.generated.h" + +/** + * Manager for experiences - primarily for arbitration between multiple PIE sessions + */ +UCLASS(MinimalAPI) +class UALSExperienceManager : public UEngineSubsystem +{ + GENERATED_BODY() + +public: +#if WITH_EDITOR + ALSV4_CPP_API void OnPlayInEditorBegun(); + + static void NotifyOfPluginActivation(const FString PluginURL); + static bool RequestToDeactivatePlugin(const FString PluginURL); +#else + static void NotifyOfPluginActivation(const FString PluginURL) {} + static bool RequestToDeactivatePlugin(const FString PluginURL) { return true; } +#endif + +private: + // The map of requests to active count for a given game feature plugin + // (to allow first in, last out activation management during PIE) + TMap GameFeaturePluginRequestCountMap; +}; diff --git a/Source/ALSV4_CPP/Public/GameModes/ALSExperienceManagerComponent.h b/Source/ALSV4_CPP/Public/GameModes/ALSExperienceManagerComponent.h new file mode 100644 index 00000000..e21193fb --- /dev/null +++ b/Source/ALSV4_CPP/Public/GameModes/ALSExperienceManagerComponent.h @@ -0,0 +1,103 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/GameStateComponent.h" +#include "GameFeaturePluginOperationResult.h" +#include "LoadingProcessInterface.h" + +#include "ALSExperienceManagerComponent.generated.h" + +class UALSExperienceDefinition; + +DECLARE_MULTICAST_DELEGATE_OneParam(FOnALSExperienceLoaded, const UALSExperienceDefinition* /*Experience*/); + +enum class EALSExperienceLoadState +{ + Unloaded, + Loading, + LoadingGameFeatures, + LoadingChaosTestingDelay, + ExecutingActions, + Loaded, + Deactivating +}; + +UCLASS() +class UALSExperienceManagerComponent final : public UGameStateComponent, public ILoadingProcessInterface +{ + GENERATED_BODY() + +public: + + UALSExperienceManagerComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + //~UActorComponent interface + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + //~End of UActorComponent interface + + //~ILoadingProcessInterface interface + virtual bool ShouldShowLoadingScreen(FString& OutReason) const override; + //~End of ILoadingProcessInterface + +#if WITH_SERVER_CODE + void ServerSetCurrentExperience(FPrimaryAssetId ExperienceId); +#endif + + // Ensures the delegate is called once the experience has been loaded, + // before others are called. + // However, if the experience has already loaded, calls the delegate immediately. + void CallOrRegister_OnExperienceLoaded_HighPriority(FOnALSExperienceLoaded::FDelegate&& Delegate); + + // Ensures the delegate is called once the experience has been loaded + // If the experience has already loaded, calls the delegate immediately + void CallOrRegister_OnExperienceLoaded(FOnALSExperienceLoaded::FDelegate&& Delegate); + + // Ensures the delegate is called once the experience has been loaded + // If the experience has already loaded, calls the delegate immediately + void CallOrRegister_OnExperienceLoaded_LowPriority(FOnALSExperienceLoaded::FDelegate&& Delegate); + + // This returns the current experience if it is fully loaded, asserting otherwise + // (i.e., if you called it too soon) + const UALSExperienceDefinition* GetCurrentExperienceChecked() const; + + // Returns true if the experience is fully loaded + bool IsExperienceLoaded() const; + +private: + UFUNCTION() + void OnRep_CurrentExperience(); + + void StartExperienceLoad(); + void OnExperienceLoadComplete(); + void OnGameFeaturePluginLoadComplete(const UE::GameFeatures::FResult& Result); + void OnExperienceFullLoadCompleted(); + + void OnActionDeactivationCompleted(); + void OnAllActionsDeactivated(); + +private: + UPROPERTY(ReplicatedUsing=OnRep_CurrentExperience) + TObjectPtr CurrentExperience; + + EALSExperienceLoadState LoadState = EALSExperienceLoadState::Unloaded; + + int32 NumGameFeaturePluginsLoading = 0; + TArray GameFeaturePluginURLs; + + int32 NumObservedPausers = 0; + int32 NumExpectedPausers = 0; + + /** + * Delegate called when the experience has finished loading just before others + * (e.g., subsystems that set up for regular gameplay) + */ + FOnALSExperienceLoaded OnExperienceLoaded_HighPriority; + + /** Delegate called when the experience has finished loading */ + FOnALSExperienceLoaded OnExperienceLoaded; + + /** Delegate called when the experience has finished loading */ + FOnALSExperienceLoaded OnExperienceLoaded_LowPriority; +}; diff --git a/Source/ALSV4_CPP/Public/GameModes/ALSGameMode.h b/Source/ALSV4_CPP/Public/GameModes/ALSGameMode.h new file mode 100644 index 00000000..9f3c60b8 --- /dev/null +++ b/Source/ALSV4_CPP/Public/GameModes/ALSGameMode.h @@ -0,0 +1,87 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Delegates/Delegate.h" +#include "Math/Rotator.h" +#include "Math/Transform.h" +#include "ModularGameMode.h" +#include "UObject/UObjectGlobals.h" +#include "ALSGameMode.generated.h" + +class AActor; +class AController; +class AGameModeBase; +class APawn; +class APlayerController; +class UClass; +class UALSExperienceDefinition; +class UALSPawnData; +class UObject; +struct FFrame; +struct FPrimaryAssetId; + +/** + * Post login event, triggered when a player joins the game as well as after non-seamless ServerTravel + * + * This is called after the player has finished initialization + */ +DECLARE_MULTICAST_DELEGATE_TwoParams(FOnGameModeCombinedPostLogin, AGameModeBase* /*GameMode*/, AController* /*NewPlayer*/); + +/** + * AALSGameMode + * + * The base game mode class used by this project. + */ +UCLASS(Config = Game, Meta = (ShortTooltip = "The base game mode class used by this project.")) +class ALSV4_CPP_API AALSGameMode : public AModularGameModeBase +{ + GENERATED_BODY() + +public: + + AALSGameMode(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + UFUNCTION(BlueprintCallable, Category = "ALS|Pawn") + const UALSPawnData* GetPawnDataForController(const AController* InController) const; + + //~AGameModeBase interface + virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; + virtual UClass* GetDefaultPawnClassForController_Implementation(AController* InController) override; + virtual APawn* SpawnDefaultPawnAtTransform_Implementation(AController* NewPlayer, const FTransform& SpawnTransform) override; + virtual bool ShouldSpawnAtStartSpot(AController* Player) override; + virtual void HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) override; + virtual AActor* ChoosePlayerStart_Implementation(AController* Player) override; + virtual void FinishRestartPlayer(AController* NewPlayer, const FRotator& StartRotation) override; + virtual bool PlayerCanRestart_Implementation(APlayerController* Player) override; + virtual void InitGameState() override; + //~End of AGameModeBase interface + + FOnGameModeCombinedPostLogin& OnGameModeCombinedPostLogin() { return OnGameModeCombinedPostLoginDelegate; } + + // Restart (respawn) the specified player or bot next frame + // - If bForceReset is true, the controller will be reset this frame (abandoning the currently possessed pawn, if any) + UFUNCTION(BlueprintCallable) + void RequestPlayerRestartNextFrame(AController* Controller, bool bForceReset = false); + + // Agnostic version of PlayerCanRestart that can be used for both player bots and players + virtual bool ControllerCanRestart(AController* Controller); + +private: + FOnGameModeCombinedPostLogin OnGameModeCombinedPostLoginDelegate; + +protected: + //~AGameModeBase interface + virtual bool UpdatePlayerStartSpot(AController* Player, const FString& Portal, FString& OutErrorMessage) override; + virtual void OnPostLogin(AController* NewPlayer) override; + virtual void FailedToRestartPlayer(AController* NewPlayer) override; + //~End of AGameModeBase interface + + void OnExperienceLoaded(const UALSExperienceDefinition* CurrentExperience); + bool IsExperienceLoaded() const; + + void OnMatchAssignmentGiven(FPrimaryAssetId ExperienceId, const FString& ExperienceIdSource); + + void HandleMatchAssignmentIfNotExpectingOne(); +}; diff --git a/Source/ALSV4_CPP/Public/GameModes/ALSGameState.h b/Source/ALSV4_CPP/Public/GameModes/ALSGameState.h new file mode 100644 index 00000000..597a420a --- /dev/null +++ b/Source/ALSV4_CPP/Public/GameModes/ALSGameState.h @@ -0,0 +1,80 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "AbilitySystemInterface.h" +#include "Engine/EngineTypes.h" +#include "Messages/ALSVerbMessage.h" +#include "ModularGameState.h" +#include "UObject/UObjectGlobals.h" + +#include "ALSGameState.generated.h" + +class APlayerState; +class UAbilitySystemComponent; +class UALSAbilitySystemComponent; +class UALSExperienceManagerComponent; +class UObject; +struct FFrame; + +/** + * AALSGameState + * + * The base game state class used by this project. + */ +UCLASS(Config = Game) +class ALSV4_CPP_API AALSGameState : public AModularGameStateBase, public IAbilitySystemInterface +{ + GENERATED_BODY() + +public: + + AALSGameState(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + float GetServerFPS() const { return ServerFPS; } + + //~AActor interface + virtual void PreInitializeComponents() override; + virtual void PostInitializeComponents() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + //~End of AActor interface + + //~AGameStateBase interface + virtual void AddPlayerState(APlayerState* PlayerState) override; + virtual void RemovePlayerState(APlayerState* PlayerState) override; + //~End of AGameStateBase interface + + //~IAbilitySystemInterface + virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override; + //~End of IAbilitySystemInterface + + UFUNCTION(BlueprintCallable, Category = "ALS|GameState") + UALSAbilitySystemComponent* GetALSAbilitySystemComponent() const { return AbilitySystemComponent; } + + // Send a message that all clients will (probably) get + // (use only for client notifications like eliminations, server join messages, etc... that can handle being lost) + UFUNCTION(NetMulticast, Unreliable, BlueprintCallable, Category = "ALS|GameState") + void MulticastMessageToClients(const FALSVerbMessage Message); + + // Send a message that all clients will be guaranteed to get + // (use only for client notifications that cannot handle being lost) + UFUNCTION(NetMulticast, Reliable, BlueprintCallable, Category = "ALS|GameState") + void MulticastReliableMessageToClients(const FALSVerbMessage Message); + +private: + UPROPERTY() + TObjectPtr ExperienceManagerComponent; + + // The ability system component subobject for game-wide things (primarily gameplay cues) + UPROPERTY(VisibleAnywhere, Category = "ALS|GameState") + TObjectPtr AbilitySystemComponent; + + +protected: + + virtual void Tick(float DeltaSeconds) override; + +protected: + UPROPERTY(Replicated) + float ServerFPS; +}; diff --git a/Source/ALSV4_CPP/Public/GameModes/ALSWorldSettings.h b/Source/ALSV4_CPP/Public/GameModes/ALSWorldSettings.h new file mode 100644 index 00000000..e648cf2b --- /dev/null +++ b/Source/ALSV4_CPP/Public/GameModes/ALSWorldSettings.h @@ -0,0 +1,42 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/WorldSettings.h" +#include "ALSWorldSettings.generated.h" + +class UALSExperienceDefinition; + + +UCLASS() +class ALSV4_CPP_API AALSWorldSettings : public AWorldSettings +{ + GENERATED_BODY() + +public: + + AALSWorldSettings(const FObjectInitializer& ObjectInitializer); + +#if WITH_EDITOR + virtual void CheckForErrors() override; +#endif + +public: + // Returns the default experience to use when a server opens this map if it is not overridden by the user-facing experience + FPrimaryAssetId GetDefaultGameplayExperience() const; + +protected: + // The default experience to use when a server opens this map if it is not overridden by the user-facing experience + UPROPERTY(EditDefaultsOnly, Category=GameMode) + TSoftClassPtr DefaultGameplayExperience; + +public: + +#if WITH_EDITORONLY_DATA + // Is this level part of a front-end or other standalone experience? + // When set, the net mode will be forced to Standalone when you hit Play in the editor + UPROPERTY(EditDefaultsOnly, Category=PIE) + bool ForceStandaloneNetMode = false; +#endif +}; diff --git a/Source/ALSV4_CPP/Public/Input/ALSInputComponent.h b/Source/ALSV4_CPP/Public/Input/ALSInputComponent.h new file mode 100644 index 00000000..429601b7 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Input/ALSInputComponent.h @@ -0,0 +1,73 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" +#include "ALSInputConfig.h" +#include "GameplayTagContainer.h" +#include "Input/ALSMappableConfigPair.h" +#include "ALSInputComponent.generated.h" + +/** + * UALSInputComponent + * + * Component used to manage input mappings and bindings using an input config data asset. + */ +UCLASS(Config = Input) +class ALSV4_CPP_API UALSInputComponent : public UEnhancedInputComponent +{ + GENERATED_BODY() + +public: + + UALSInputComponent(const FObjectInitializer& ObjectInitializer); + + void AddInputMappings(const UALSInputConfig* InputConfig, UEnhancedInputLocalPlayerSubsystem* InputSubsystem) const; + void RemoveInputMappings(const UALSInputConfig* InputConfig, UEnhancedInputLocalPlayerSubsystem* InputSubsystem) const; + + template + void BindNativeAction(const UALSInputConfig* InputConfig, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent, UserClass* Object, FuncType Func, bool bLogIfNotFound); + + template + void BindAbilityActions(const UALSInputConfig* InputConfig, UserClass* Object, PressedFuncType PressedFunc, ReleasedFuncType ReleasedFunc, TArray& BindHandles); + + void RemoveBinds(TArray& BindHandles); + + void AddInputConfig(const FLoadedMappableConfigPair& ConfigPair, UEnhancedInputLocalPlayerSubsystem* InputSubsystem); + void RemoveInputConfig(const FLoadedMappableConfigPair& ConfigPair, UEnhancedInputLocalPlayerSubsystem* InputSubsystem); +}; + + +template +void UALSInputComponent::BindNativeAction(const UALSInputConfig* InputConfig, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent, UserClass* Object, FuncType Func, bool bLogIfNotFound) +{ + check(InputConfig); + if (const UInputAction* IA = InputConfig->FindNativeInputActionForTag(InputTag, bLogIfNotFound)) + { + BindAction(IA, TriggerEvent, Object, Func); + } +} + +template +void UALSInputComponent::BindAbilityActions(const UALSInputConfig* InputConfig, UserClass* Object, PressedFuncType PressedFunc, ReleasedFuncType ReleasedFunc, TArray& BindHandles) +{ + check(InputConfig); + + for (const FALSInputAction& Action : InputConfig->AbilityInputActions) + { + if (Action.InputAction && Action.InputTag.IsValid()) + { + if (PressedFunc) + { + BindHandles.Add(BindAction(Action.InputAction, ETriggerEvent::Triggered, Object, PressedFunc, Action.InputTag).GetHandle()); + } + + if (ReleasedFunc) + { + BindHandles.Add(BindAction(Action.InputAction, ETriggerEvent::Completed, Object, ReleasedFunc, Action.InputTag).GetHandle()); + } + } + } +} diff --git a/Source/ALSV4_CPP/Public/Input/ALSInputConfig.h b/Source/ALSV4_CPP/Public/Input/ALSInputConfig.h new file mode 100644 index 00000000..c161d60a --- /dev/null +++ b/Source/ALSV4_CPP/Public/Input/ALSInputConfig.h @@ -0,0 +1,56 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "GameplayTagContainer.h" +#include "ALSInputConfig.generated.h" + +class UInputAction; +class UInputMappingContext; +class UALSLocalPlayer; + +/** + * FALSInputAction + * + * Struct used to map a input action to a gameplay input tag. + */ +USTRUCT(BlueprintType) +struct FALSInputAction +{ + GENERATED_BODY() + +public: + + UPROPERTY(EditDefaultsOnly) + const UInputAction* InputAction = nullptr; + + UPROPERTY(EditDefaultsOnly, Meta = (Categories = "InputTag")) + FGameplayTag InputTag; +}; + + +/** + * + */ +UCLASS() +class ALSV4_CPP_API UALSInputConfig : public UDataAsset +{ + GENERATED_BODY() +public: + + UALSInputConfig(const FObjectInitializer& ObjectInitializer); + + const UInputAction* FindNativeInputActionForTag(const FGameplayTag& InputTag, bool bLogNotFound = true) const; + const UInputAction* FindAbilityInputActionForTag(const FGameplayTag& InputTag, bool bLogNotFound = true) const; + +public: + // List of input actions used by the owner. These input actions are mapped to a gameplay tag and must be manually bound. + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Meta = (TitleProperty = "InputAction")) + TArray NativeInputActions; + + // List of input actions used by the owner. These input actions are mapped to a gameplay tag and are automatically bound to abilities with matching input tags. + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Meta = (TitleProperty = "InputAction")) + TArray AbilityInputActions; +}; diff --git a/Source/ALSV4_CPP/Public/Input/ALSMappableConfigPair.h b/Source/ALSV4_CPP/Public/Input/ALSMappableConfigPair.h new file mode 100644 index 00000000..a178cf26 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Input/ALSMappableConfigPair.h @@ -0,0 +1,94 @@ +#pragma once + +#include "CommonInputBaseTypes.h" +#include "GameplayTagContainer.h" + +#include "ALSMappableConfigPair.generated.h" + +class UPlayerMappableInputConfig; + +/** A container to organize loaded player mappable configs to their CommonUI input type */ +USTRUCT(BlueprintType) +struct FLoadedMappableConfigPair +{ + GENERATED_BODY() + + FLoadedMappableConfigPair() = default; + FLoadedMappableConfigPair(const UPlayerMappableInputConfig* InConfig, ECommonInputType InType, const bool InIsActive) + : Config(InConfig) + , Type(InType) + , bIsActive(InIsActive) + {} + + /** The player mappable input config that should be applied to the Enhanced Input subsystem */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere) + const UPlayerMappableInputConfig* Config = nullptr; + + /** The type of device that this mapping config should be applied to */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere) + ECommonInputType Type = ECommonInputType::Count; + + /** If this config is currently active. A config is marked as active when it's owning GFA is active */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere) + bool bIsActive = false; +}; + +/** A container to organize potentially unloaded player mappable configs to their CommonUI input type */ +USTRUCT() +struct ALSV4_CPP_API FMappableConfigPair +{ + GENERATED_BODY() + +public: + FMappableConfigPair() = default; + + UPROPERTY(EditAnywhere) + TSoftObjectPtr Config; + + /** + * The type of config that this is. Useful for filtering out configs by the current input device + * for things like the settings screen, or if you only want to apply this config when a certain + * input type is being used. + */ + UPROPERTY(EditAnywhere) + ECommonInputType Type = ECommonInputType::Count; + + /** + * Container of platform traits that must be set in order for this input to be activated. + * + * If the platform does not have one of the traits specified it can still be registered, but cannot + * be activated. + */ + UPROPERTY(EditAnywhere) + FGameplayTagContainer DependentPlatformTraits; + + /** + * If the current platform has any of these traits, then this config will not be actived. + */ + UPROPERTY(EditAnywhere) + FGameplayTagContainer ExcludedPlatformTraits; + + /** If true, then this input config will be activated when it's associated Game Feature is activated. + * This is normally the desirable behavior + */ + UPROPERTY(EditAnywhere) + bool bShouldActivateAutomatically = true; + + /** Returns true if this config pair can be activated based on the current platform traits and settings. */ + bool CanBeActivated() const; + + /** + * Registers the given config mapping with the settings + */ + static bool RegisterPair(const FMappableConfigPair& Pair); + + /** + * Activates the given config mapping in the settings. This will also register the mapping + * if it hasn't been yet. + */ + static bool ActivatePair(const FMappableConfigPair& Pair); + + static void DeactivatePair(const FMappableConfigPair& Pair); + + static void UnregisterPair(const FMappableConfigPair& Pair); +}; \ No newline at end of file diff --git a/Source/ALSV4_CPP/Public/Library/ALSCharacterEnumLibrary.h b/Source/ALSV4_CPP/Public/Library/ALSCharacterEnumLibrary.h index 8e79ba2b..832e3332 100644 --- a/Source/ALSV4_CPP/Public/Library/ALSCharacterEnumLibrary.h +++ b/Source/ALSV4_CPP/Public/Library/ALSCharacterEnumLibrary.h @@ -107,7 +107,8 @@ UENUM(BlueprintType) enum class EALSViewMode : uint8 { ThirdPerson, - FirstPerson + FirstPerson, + TopDown }; UENUM(BlueprintType) diff --git a/Source/ALSV4_CPP/Public/Messages/ALSVerbMessage.cpp b/Source/ALSV4_CPP/Public/Messages/ALSVerbMessage.cpp new file mode 100644 index 00000000..20e5f909 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Messages/ALSVerbMessage.cpp @@ -0,0 +1 @@ +#include "ALSVerbMessage.h" diff --git a/Source/ALSV4_CPP/Public/Messages/ALSVerbMessage.h b/Source/ALSV4_CPP/Public/Messages/ALSVerbMessage.h new file mode 100644 index 00000000..a04eac02 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Messages/ALSVerbMessage.h @@ -0,0 +1,37 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" + +#include "ALSVerbMessage.generated.h" + +// Represents a generic message of the form Instigator Verb Target (in Context, with Magnitude) +USTRUCT(BlueprintType) +struct FALSVerbMessage +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category=Gameplay) + FGameplayTag Verb; + + UPROPERTY(BlueprintReadWrite, Category=Gameplay) + TObjectPtr Instigator = nullptr; + + UPROPERTY(BlueprintReadWrite, Category=Gameplay) + TObjectPtr Target = nullptr; + + UPROPERTY(BlueprintReadWrite, Category=Gameplay) + FGameplayTagContainer InstigatorTags; + + UPROPERTY(BlueprintReadWrite, Category=Gameplay) + FGameplayTagContainer TargetTags; + + UPROPERTY(BlueprintReadWrite, Category=Gameplay) + FGameplayTagContainer ContextTags; + + UPROPERTY(BlueprintReadWrite, Category=Gameplay) + double Magnitude = 1.0; + + // Returns a debug string representation of this message + ALSV4_CPP_API FString ToString() const; +}; \ No newline at end of file diff --git a/Source/ALSV4_CPP/Public/Physics/PhysicalMaterialWithTags.h b/Source/ALSV4_CPP/Public/Physics/PhysicalMaterialWithTags.h new file mode 100644 index 00000000..783645a2 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Physics/PhysicalMaterialWithTags.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "GameplayTagContainer.h" + +#include "PhysicalMaterialWithTags.generated.h" + +/** + * ULyraWeaponInstance + * + * A piece of equipment representing a weapon spawned and applied to a pawn + */ +UCLASS() +class UPhysicalMaterialWithTags : public UPhysicalMaterial +{ + GENERATED_BODY() + +public: + UPhysicalMaterialWithTags(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + // A container of gameplay tags that game code can use to reason about this physical material + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=PhysicalProperties) + FGameplayTagContainer Tags; +}; diff --git a/Source/ALSV4_CPP/Public/Player/ALSLocalPlayer.h b/Source/ALSV4_CPP/Public/Player/ALSLocalPlayer.h new file mode 100644 index 00000000..7eee8695 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Player/ALSLocalPlayer.h @@ -0,0 +1,47 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "CommonLocalPlayer.h" +#include "ALSLocalPlayer.generated.h" + +class UInputMappingContext; + +/** + * UALSLocalPlayer + */ +UCLASS() +class ALSV4_CPP_API UALSLocalPlayer : public UCommonLocalPlayer +{ + GENERATED_BODY() + +public: + + UALSLocalPlayer(); + + //~UPlayer interface + virtual void SwitchController(class APlayerController* PC) override; + //~End of UPlayer interface + + //~ULocalPlayer interface + virtual bool SpawnPlayActor(const FString& URL, FString& OutError, UWorld* InWorld) override; + virtual void InitOnlineSession() override; + //~End of ULocalPlayer interface + +protected: + void OnAudioOutputDeviceChanged(const FString& InAudioOutputDeviceId); + + UFUNCTION() + void OnCompletedAudioDeviceSwap(const FSwapAudioOutputResult& SwapResult); + + virtual void OnPlayerControllerChanged(APlayerController* NewController); + +protected: + UPROPERTY(Transient) + mutable const UInputMappingContext* InputMappingContext; + + UPROPERTY() + TWeakObjectPtr LastBoundPC; +}; + diff --git a/Source/ALSV4_CPP/Public/Player/ALSPlayerController2.h b/Source/ALSV4_CPP/Public/Player/ALSPlayerController2.h new file mode 100644 index 00000000..d1eb9fd1 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Player/ALSPlayerController2.h @@ -0,0 +1,117 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "CommonPlayerController.h" +#include "ALSPlayerController2.generated.h" + +class UALSSettingsShared; +class AALSPlayerState; +class UALSAbilitySystemComponent; +class APawn; + +/** + * AALSPlayerController2 + * + * The base player controller class used by this project. + */ +UCLASS(Config = Game, Meta = (ShortTooltip = "The base player controller class used by this project.")) +class ALSV4_CPP_API AALSPlayerController2 : public ACommonPlayerController +{ + GENERATED_BODY() + +public: + + AALSPlayerController2(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + UFUNCTION(BlueprintCallable, Category = "ALS|PlayerController") + AALSPlayerState* GetALSPlayerState() const; + + UFUNCTION(BlueprintCallable, Category = "ALS|PlayerController") + UALSAbilitySystemComponent* GetALSAbilitySystemComponent() const; + + //~AActor interface + virtual void PreInitializeComponents() override; + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + //~End of AActor interface + + //~AController interface + virtual void OnUnPossess() override; + //~End of AController interface + + //~APlayerController interface + virtual void ReceivedPlayer() override; + virtual void PlayerTick(float DeltaTime) override; + //~End of APlayerController interface + + //~IALSCameraAssistInterface interface + // TODO: Implement this + // virtual void OnCameraPenetratingTarget() override; + //~End of IALSCameraAssistInterface interface + + //~ACommonPlayerController interface + virtual void OnPossess(APawn* InPawn) override; + //~End of ACommonPlayerController interface + + UFUNCTION(BlueprintCallable, Category = "ALS|Character") + void SetIsAutoRunning(const bool bEnabled); + + UFUNCTION(BlueprintCallable, Category = "ALS|Character") + bool GetIsAutoRunning() const; + +private: + UPROPERTY() + APlayerState* LastSeenPlayerState; + +private: + UFUNCTION() + void OnPlayerStateChangedTeam(UObject* TeamAgent, int32 OldTeam, int32 NewTeam); + +protected: + // Called when the player state is set or cleared + virtual void OnPlayerStateChanged(); + +private: + void BroadcastOnPlayerStateChanged(); + +protected: + //~AController interface + virtual void InitPlayerState() override; + virtual void CleanupPlayerState() override; + virtual void OnRep_PlayerState() override; + //~End of AController interface + + //~APlayerController interface + virtual void SetPlayer(UPlayer* InPlayer) override; + + virtual void UpdateForceFeedback(IInputInterface* InputInterface, const int32 ControllerId) override; + virtual void UpdateHiddenComponents(const FVector& ViewLocation, TSet& OutHiddenComponents) override; + + virtual void PreProcessInput(const float DeltaTime, const bool bGamePaused) override; + virtual void PostProcessInput(const float DeltaTime, const bool bGamePaused) override; + //~End of APlayerController interface + + void OnStartAutoRun(); + void OnEndAutoRun(); + + UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName="OnStartAutoRun")) + void K2_OnStartAutoRun(); + + UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName="OnEndAutoRun")) + void K2_OnEndAutoRun(); + + bool bHideViewTargetPawnNextFrame = false; +}; + + +// A player controller used for replay capture and playback +UCLASS() +class AALSReplayPlayerController : public AALSPlayerController2 +{ + GENERATED_BODY() + + virtual void SetPlayer(UPlayer* InPlayer) override; +}; diff --git a/Source/ALSV4_CPP/Public/Player/ALSPlayerState.h b/Source/ALSV4_CPP/Public/Player/ALSPlayerState.h new file mode 100644 index 00000000..9860870b --- /dev/null +++ b/Source/ALSV4_CPP/Public/Player/ALSPlayerState.h @@ -0,0 +1,131 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "AbilitySystemInterface.h" +#include "HAL/Platform.h" +#include "Messages/ALSVerbMessage.h" +#include "ModularPlayerState.h" +#include "System/GameplayTagStack.h" +#include "Templates/Casts.h" +#include "UObject/UObjectGlobals.h" + +#include "ALSPlayerState.generated.h" + +class AController; +class AALSPlayerController2; +class APlayerState; +class FName; +class UAbilitySystemComponent; +class UALSAbilitySystemComponent; +class UALSExperienceDefinition; +class UALSPawnData; +class UObject; +struct FFrame; +struct FGameplayTag; + +/** Defines the types of client connected */ +UENUM() +enum class EALSPlayerConnectionType : uint8 +{ + // An active player + Player = 0, + + // Spectator connected to a running game + LiveSpectator, + + // Spectating a demo recording offline + ReplaySpectator, + + // A deactivated player (disconnected) + InactivePlayer +}; + +/** + * AALSPlayerState + * + * Base player state class used by this project. + */ +UCLASS(Config=Game) +class ALSV4_CPP_API AALSPlayerState : public AModularPlayerState, public IAbilitySystemInterface +{ + GENERATED_BODY() + +public: + AALSPlayerState(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + UFUNCTION(BlueprintCallable, Category = "ALS|PlayerState") + AALSPlayerController2* GetALSPlayerController() const; + + UFUNCTION(BlueprintCallable, Category = "ALS|PlayerState") + UALSAbilitySystemComponent* GetALSAbilitySystemComponent() const { return AbilitySystemComponent; } + virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override; + + template + const T* GetPawnData() const { return Cast(PawnData); } + + void SetPawnData(const UALSPawnData* InPawnData); + + //~AActor interface + virtual void PreInitializeComponents() override; + virtual void PostInitializeComponents() override; + //~End of AActor interface + + //~APlayerState interface + virtual void Reset() override; + virtual void ClientInitialize(AController* C) override; + virtual void CopyProperties(APlayerState* PlayerState) override; + virtual void OnDeactivated() override; + virtual void OnReactivated() override; + //~End of APlayerState interface + + static const FName NAME_ALSAbilityReady; + + void SetPlayerConnectionType(EALSPlayerConnectionType NewType); + EALSPlayerConnectionType GetPlayerConnectionType() const { return MyPlayerConnectionType; } + + // Adds a specified number of stacks to the tag (does nothing if StackCount is below 1) + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category=Teams) + void AddStatTagStack(FGameplayTag Tag, int32 StackCount); + + // Removes a specified number of stacks from the tag (does nothing if StackCount is below 1) + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category=Teams) + void RemoveStatTagStack(FGameplayTag Tag, int32 StackCount); + + // Returns the stack count of the specified tag (or 0 if the tag is not present) + UFUNCTION(BlueprintCallable, Category=Teams) + int32 GetStatTagStackCount(FGameplayTag Tag) const; + + // Returns true if there is at least one stack of the specified tag + UFUNCTION(BlueprintCallable, Category=Teams) + bool HasStatTag(FGameplayTag Tag) const; + + // Send a message to just this player + // (use only for client notifications like accolades, quest toasts, etc... that can handle being occasionally lost) + UFUNCTION(Client, Unreliable, BlueprintCallable, Category = "ALS|PlayerState") + void ClientBroadcastMessage(const FALSVerbMessage Message); + +private: + void OnExperienceLoaded(const UALSExperienceDefinition* CurrentExperience); + +protected: + UFUNCTION() + void OnRep_PawnData(); + +protected: + UPROPERTY(ReplicatedUsing = OnRep_PawnData) + TObjectPtr PawnData; + +private: + + // The ability system component sub-object used by player characters. + UPROPERTY(VisibleAnywhere, Category = "ALS|PlayerState") + TObjectPtr AbilitySystemComponent; + + UPROPERTY(Replicated) + EALSPlayerConnectionType MyPlayerConnectionType; + + UPROPERTY(Replicated) + FGameplayTagStackContainer StatTags; +}; diff --git a/Source/ALSV4_CPP/Public/Settings/ALSSettingsLocal.h b/Source/ALSV4_CPP/Public/Settings/ALSSettingsLocal.h new file mode 100644 index 00000000..8b8b23e3 --- /dev/null +++ b/Source/ALSV4_CPP/Public/Settings/ALSSettingsLocal.h @@ -0,0 +1,109 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameUserSettings.h" +#include "Input/ALSMappableConfigPair.h" +#include "ALSSettingsLocal.generated.h" + +class UPlayerMappableInputConfig; +class UALSLocalPlayer; + +/** + * UALSSettingsLocal + */ +UCLASS() +class ALSV4_CPP_API UALSSettingsLocal : public UGameUserSettings +{ + GENERATED_BODY() + +public: + UALSSettingsLocal(); + + static UALSSettingsLocal* Get(); + + //~UObject interface + virtual void BeginDestroy() override; + //~End of UObject interface + + // INPUT CONFIGS +public: + // Sets the controller representation to use, a single platform might support multiple kinds of controllers. For + // example, Win64 games could be played with both an XBox or Playstation controller. + UFUNCTION() + void SetControllerPlatform(const FName InControllerPlatform); + UFUNCTION() + FName GetControllerPlatform() const; + + DECLARE_EVENT_OneParam(UALSSettingsLocal, FInputConfigDelegate, const FLoadedMappableConfigPair& /*Config*/); + + /** Delegate called when a new input config has been registered */ + FInputConfigDelegate OnInputConfigRegistered; + + /** Delegate called when a registered input config has been activated */ + FInputConfigDelegate OnInputConfigActivated; + + /** Delegate called when a registered input config has been deactivate */ + FInputConfigDelegate OnInputConfigDeactivated; + + /** Register the given input config with the settings to make it available to the player. */ + void RegisterInputConfig(ECommonInputType Type, const UPlayerMappableInputConfig* NewConfig, const bool bIsActive); + + /** Unregister the given input config. Returns the number of configs removed. */ + int32 UnregisterInputConfig(const UPlayerMappableInputConfig* ConfigToRemove); + + /** Set a registered input config as active */ + void ActivateInputConfig(const UPlayerMappableInputConfig* Config); + + /** Deactivate a registered config */ + void DeactivateInputConfig(const UPlayerMappableInputConfig* Config); + + /** Get an input config with a certain name. If the config doesn't exist then nullptr will be returned. */ + UFUNCTION(BlueprintCallable) + const UPlayerMappableInputConfig* GetInputConfigByName(FName ConfigName) const; + + /** Get all currently registered input configs */ + const TArray& GetAllRegisteredInputConfigs() const { return RegisteredInputConfigs; } + + /** + * Get all registered input configs that match the input type. + * + * @param Type The type of config to get, ECommonInputType::Count will include all configs. + * @param OutArray Array to be populated with the current registered input configs that match the type + */ + void GetRegisteredInputConfigsOfType(ECommonInputType Type, OUT TArray& OutArray) const; + + /** + * Maps the given keyboard setting to the + * + * @param MappingName The name of the FPlayerMappableKeyOptions that you would like to change + * @param NewKey The new key to bind this option to + */ + void AddOrUpdateCustomKeyboardBindings(const FName MappingName, const FKey NewKey, UALSLocalPlayer* LocalPlayer); + + const TMap& GetCustomPlayerInputConfig() const { return CustomKeyboardConfig; } + +private: + UPROPERTY(Config) + FName ControllerPlatform; + + UPROPERTY(Config) + FName ControllerPreset = TEXT("Default"); + + /** The name of the current input config that the user has selected. */ + UPROPERTY(Config) + FName InputConfigName = TEXT("Default"); + + /** + * Array of currently registered input configs. This is populated by game feature plugins + * + * @see UGameFeatureAction_AddInputConfig + */ + UPROPERTY(VisibleAnywhere) + TArray RegisteredInputConfigs; + + /** Array of custom key mappings that have been set by the player. Empty by default. */ + UPROPERTY(Config) + TMap CustomKeyboardConfig; +}; diff --git a/Source/ALSV4_CPP/Public/System/ALSAssetManager.h b/Source/ALSV4_CPP/Public/System/ALSAssetManager.h new file mode 100644 index 00000000..a7c85382 --- /dev/null +++ b/Source/ALSV4_CPP/Public/System/ALSAssetManager.h @@ -0,0 +1,171 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/AssetManager.h" +#include "Engine/DataAsset.h" +#include "ALSAssetManagerStartupJob.h" +#include "ALSAssetManager.generated.h" + +class UALSGameData; +class UALSPawnData; + +struct FALSBundles +{ + static const FName Equipped; +}; + + +/** + * UALSAssetManager + * + * Game implementation of the asset manager that overrides functionality and stores game-specific types. + * It is expected that most games will want to override AssetManager as it provides a good place for game-specific loading logic. + * This class is used by setting 'AssetManagerClassName' in DefaultEngine.ini. + */ +UCLASS(Config = Game) +class ALSV4_CPP_API UALSAssetManager : public UAssetManager +{ + GENERATED_BODY() + +public: + + UALSAssetManager(); + + // Returns the AssetManager singleton object. + static UALSAssetManager& Get(); + + // Returns the asset referenced by a TSoftObjectPtr. This will synchronously load the asset if it's not already loaded. + template + static AssetType* GetAsset(const TSoftObjectPtr& AssetPointer, bool bKeepInMemory = true); + + // Returns the subclass referenced by a TSoftClassPtr. This will synchronously load the asset if it's not already loaded. + template + static TSubclassOf GetSubclass(const TSoftClassPtr& AssetPointer, bool bKeepInMemory = true); + + // Logs all assets currently loaded and tracked by the asset manager. + static void DumpLoadedAssets(); + + const UALSGameData& GetGameData(); + const UALSPawnData* GetDefaultPawnData() const; + +protected: + template + const GameDataClass& GetOrLoadTypedGameData(const TSoftObjectPtr& DataPath) + { + if (TObjectPtr const * pResult = GameDataMap.Find(GameDataClass::StaticClass())) + { + return *CastChecked(*pResult); + } + + // Does a blocking load if needed + return *CastChecked(LoadGameDataOfClass(GameDataClass::StaticClass(), DataPath, GameDataClass::StaticClass()->GetFName())); + } + + + static UObject* SynchronousLoadAsset(const FSoftObjectPath& AssetPath); + static bool ShouldLogAssetLoads(); + + // Thread safe way of adding a loaded asset to keep in memory. + void AddLoadedAsset(const UObject* Asset); + + //~UAssetManager interface + virtual void StartInitialLoading() override; +#if WITH_EDITOR + virtual void PreBeginPIE(bool bStartSimulate) override; +#endif + //~End of UAssetManager interface + + UPrimaryDataAsset* LoadGameDataOfClass(TSubclassOf DataClass, const TSoftObjectPtr& DataClassPath, FPrimaryAssetType PrimaryAssetType); + +protected: + + // Global game data asset to use. + UPROPERTY(Config) + TSoftObjectPtr ALSGameDataPath; + + // Loaded version of the game data + UPROPERTY(Transient) + TMap, TObjectPtr> GameDataMap; + + // Pawn data used when spawning player pawns if there isn't one set on the player state. + UPROPERTY(Config) + TSoftObjectPtr DefaultPawnData; + +private: + // Flushes the StartupJobs array. Processes all startup work. + void DoAllStartupJobs(); + + // Sets up the ability system + void InitializeAbilitySystem(); + void InitializeGameplayCueManager(); + + // Called periodically during loads, could be used to feed the status to a loading screen + void UpdateInitialGameContentLoadPercent(float GameContentPercent); + + // The list of tasks to execute on startup. Used to track startup progress. + TArray StartupJobs; + +private: + + // Assets loaded and tracked by the asset manager. + UPROPERTY() + TSet> LoadedAssets; + + // Used for a scope lock when modifying the list of load assets. + FCriticalSection LoadedAssetsCritical; +}; + + +template +AssetType* UALSAssetManager::GetAsset(const TSoftObjectPtr& AssetPointer, bool bKeepInMemory) +{ + AssetType* LoadedAsset = nullptr; + + const FSoftObjectPath& AssetPath = AssetPointer.ToSoftObjectPath(); + + if (AssetPath.IsValid()) + { + LoadedAsset = AssetPointer.Get(); + if (!LoadedAsset) + { + LoadedAsset = Cast(SynchronousLoadAsset(AssetPath)); + ensureAlwaysMsgf(LoadedAsset, TEXT("Failed to load asset [%s]"), *AssetPointer.ToString()); + } + + if (LoadedAsset && bKeepInMemory) + { + // Added to loaded asset list. + Get().AddLoadedAsset(Cast(LoadedAsset)); + } + } + + return LoadedAsset; +} + +template +TSubclassOf UALSAssetManager::GetSubclass(const TSoftClassPtr& AssetPointer, bool bKeepInMemory) +{ + TSubclassOf LoadedSubclass; + + const FSoftObjectPath& AssetPath = AssetPointer.ToSoftObjectPath(); + + if (AssetPath.IsValid()) + { + LoadedSubclass = AssetPointer.Get(); + if (!LoadedSubclass) + { + LoadedSubclass = Cast(SynchronousLoadAsset(AssetPath)); + ensureAlwaysMsgf(LoadedSubclass, TEXT("Failed to load asset class [%s]"), *AssetPointer.ToString()); + } + + if (LoadedSubclass && bKeepInMemory) + { + // Added to loaded asset list. + Get().AddLoadedAsset(Cast(LoadedSubclass)); + } + } + + return LoadedSubclass; +} diff --git a/Source/ALSV4_CPP/Public/System/ALSAssetManagerStartupJob.h b/Source/ALSV4_CPP/Public/System/ALSAssetManagerStartupJob.h new file mode 100644 index 00000000..4eab6746 --- /dev/null +++ b/Source/ALSV4_CPP/Public/System/ALSAssetManagerStartupJob.h @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/UnrealString.h" +#include "Delegates/Delegate.h" +#include "Engine/StreamableManager.h" +#include "HAL/PlatformTime.h" +#include "Templates/Function.h" +#include "Templates/SharedPointer.h" + +DECLARE_DELEGATE_OneParam(FALSAssetManagerStartupJobSubstepProgress, float /*NewProgress*/); + +/** Handles reporting progress from streamable handles */ +struct ALSV4_CPP_API FALSAssetManagerStartupJob +{ + FALSAssetManagerStartupJobSubstepProgress SubstepProgressDelegate; + TFunction&)> JobFunc; + FString JobName; + float JobWeight; + mutable double LastUpdate = 0; + + /** Simple job that is all synchronous */ + FALSAssetManagerStartupJob(const FString& InJobName, const TFunction&)>& InJobFunc, float InJobWeight) + : JobFunc(InJobFunc) + , JobName(InJobName) + , JobWeight(InJobWeight) + {} + + /** Perform actual loading, will return a handle if it created one */ + TSharedPtr DoJob() const; + + void UpdateSubstepProgress(float NewProgress) const + { + SubstepProgressDelegate.ExecuteIfBound(NewProgress); + } + + void UpdateSubstepProgressFromStreamable(TSharedRef 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; + } + } + } +}; diff --git a/Source/ALSV4_CPP/Public/System/ALSGameData.h b/Source/ALSV4_CPP/Public/System/ALSGameData.h new file mode 100644 index 00000000..1dcc6cd4 --- /dev/null +++ b/Source/ALSV4_CPP/Public/System/ALSGameData.h @@ -0,0 +1,41 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "ALSGameData.generated.h" + +class UGameplayEffect; + +/** + * UALSGameData + * + * Non-mutable data asset that contains global game data. + */ +UCLASS(BlueprintType, Const, Meta = (DisplayName = "ALS Game Data", ShortTooltip = "Data asset containing global game data.")) +class ALSV4_CPP_API UALSGameData : public UPrimaryDataAsset +{ + GENERATED_BODY() + +public: + + UALSGameData(); + + // Returns the loaded game data. + static const UALSGameData& Get(); + +public: + + // Gameplay effect used to apply damage. Uses SetByCaller for the damage magnitude. + UPROPERTY(EditDefaultsOnly, Category = "Default Gameplay Effects", meta = (DisplayName = "Damage Gameplay Effect (SetByCaller)")) + TSoftClassPtr DamageGameplayEffect_SetByCaller; + + // Gameplay effect used to apply healing. Uses SetByCaller for the healing magnitude. + UPROPERTY(EditDefaultsOnly, Category = "Default Gameplay Effects", meta = (DisplayName = "Heal Gameplay Effect (SetByCaller)")) + TSoftClassPtr HealGameplayEffect_SetByCaller; + + // Gameplay effect used to add and remove dynamic tags. + UPROPERTY(EditDefaultsOnly, Category = "Default Gameplay Effects") + TSoftClassPtr DynamicTagGameplayEffect; +}; diff --git a/Source/ALSV4_CPP/Public/System/GameplayTagStack.h b/Source/ALSV4_CPP/Public/System/GameplayTagStack.h new file mode 100644 index 00000000..cd1c3fe7 --- /dev/null +++ b/Source/ALSV4_CPP/Public/System/GameplayTagStack.h @@ -0,0 +1,107 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Containers/Array.h" +#include "Containers/ArrayView.h" +#include "Containers/Map.h" +#include "Containers/Set.h" +#include "Containers/SparseArray.h" +#include "Containers/UnrealString.h" +#include "GameplayTagContainer.h" +#include "HAL/Platform.h" +#include "Net/Serialization/FastArraySerializer.h" +#include "Templates/UnrealTemplate.h" +#include "UObject/Class.h" + +#include "GameplayTagStack.generated.h" + +struct FGameplayTagStackContainer; +struct FNetDeltaSerializeInfo; + +/** + * Represents one stack of a gameplay tag (tag + count) + */ +USTRUCT(BlueprintType) +struct FGameplayTagStack : public FFastArraySerializerItem +{ + GENERATED_BODY() + + FGameplayTagStack() + {} + + FGameplayTagStack(FGameplayTag InTag, int32 InStackCount) + : Tag(InTag) + , StackCount(InStackCount) + { + } + + FString GetDebugString() const; + +private: + friend FGameplayTagStackContainer; + + UPROPERTY() + FGameplayTag Tag; + + UPROPERTY() + int32 StackCount = 0; +}; + +/** Container of gameplay tag stacks */ +USTRUCT(BlueprintType) +struct FGameplayTagStackContainer : public FFastArraySerializer +{ + GENERATED_BODY() + + FGameplayTagStackContainer() + // : Owner(nullptr) + { + } + +public: + // Adds a specified number of stacks to the tag (does nothing if StackCount is below 1) + void AddStack(FGameplayTag Tag, int32 StackCount); + + // Removes a specified number of stacks from the tag (does nothing if StackCount is below 1) + void RemoveStack(FGameplayTag Tag, int32 StackCount); + + // Returns the stack count of the specified tag (or 0 if the tag is not present) + int32 GetStackCount(FGameplayTag Tag) const + { + return TagToCountMap.FindRef(Tag); + } + + // Returns true if there is at least one stack of the specified tag + bool ContainsTag(FGameplayTag Tag) const + { + return TagToCountMap.Contains(Tag); + } + + //~FFastArraySerializer contract + void PreReplicatedRemove(const TArrayView RemovedIndices, int32 FinalSize); + void PostReplicatedAdd(const TArrayView AddedIndices, int32 FinalSize); + void PostReplicatedChange(const TArrayView ChangedIndices, int32 FinalSize); + //~End of FFastArraySerializer contract + + bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms) + { + return FFastArraySerializer::FastArrayDeltaSerialize(Stacks, DeltaParms, *this); + } + +private: + // Replicated list of gameplay tag stacks + UPROPERTY() + TArray Stacks; + + // Accelerated list of tag stacks for queries + TMap TagToCountMap; +}; + +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetDeltaSerializer = true, + }; +};